Programming Skills — Solution Formulation, Correctness, Clarity & Optimisation

Next Topic(s):

Created:
29th of August 2025
12:35:27 AM
Modified:
30th of August 2025
08:21:37 AM

Programming Skills — Solution Formulation, Correctness, Clarity & Optimisation

Learning Objectives

  • Formulate an equivalence problem in code: given two models f and g, define the crossing where f(x) = g(x), identify inputs/outputs and constraints, and state clearly which parameter is the unknown.
  • Translate mathematics to a computable pipeline: use exact expressions, recognise and eliminate constants that cancel, choose control variables, and set safe numeric ranges for search.
  • Implement bisection with while loops only (no imports/functions in Option 1): maintain a valid bracket [lo, hi], update it via the midpoint test, and stop when hi - lo < eps.
  • Build a single-scenario comparator: evaluate both models at fixed parameters, decide which outcome is larger, and report the absolute difference.
  • Follow a strict output contract: keep full precision internally, round only for display, apply consistent number formatting (decimals and separators), and print exactly the required lines in the specified template.
  • Justify existence and uniqueness: use monotonicity/continuity to guarantee a single root, track loop invariants, and reason about convergence and iteration counts.
  • Generalise the pattern: vary constants, switch which parameter you solve for, and (in a modular version) refactor to small reusable functions while preserving the flat-script variant.
🧭

Tip: Keep full precision inside loops; round only for display to avoid logic errors.

Breakeven: Simple vs Compound Interest

Breakeven analysis — what it is and why it matters

Breakeven analysis identifies the point where two return formulas deliver the same maturity. Here we compare a simple-interest offer (linear growth with time) against a compound-interest offer (exponential growth with compounding). At the crossing, an investor is indifferent; on one side of the crossing simple interest pays more, and on the other side compound interest does.

Breakeven condition is given here for practice; in tests, derive it.

Engineers are often faced with financial decisions, each of which hold good under specific conditions. This problem should introduce you to a scenario where you will be required to have different constraints, and you ease one while keeping the other constraints constant. Ofcourse, with improved computing power, scenarios with more than one constraint being modified at the same time is possible, however we will use such scenarios for later.

Phrasing of the question.

This question was phrased thus.

Assignment — Option 1 (while-loop & bisection only)

Use the formulas and bisection idea explained above. Do not define functions and do not import any libraries. Work only with variables and while loops plus simple comparisons.

Given (fixed for Part A)

  • Principal P = ₹1,50,000 (store as 150000.0 in code).
  • Compound offer: nominal rate R_c = 8.4 (% p.a.), compounding m = 4 times per year.
  • Reference tenure for Tasks 1 & 3: N0 = 6.75 (years).

Programming constraints

  • No function definitions; keep the file as a straight script.
  • Use bisection inside while loops with a bracket that encloses the solution.
  • Use eps = 1e-12 (or tighter) as the stopping tolerance.
  • Rate-search bracket: lo = 0.0, hi = 50.0 (% p.a.).
  • Tenure-search bracket: loN = 0.0, hiN = 50.0 (years).

Part A — Core tasks (use the exact constants above)

  1. Task 1 — Solve for the matching simple rate R_s* at N = N0.
    1. Compute the compound growth factor at N0: target = (1 + R_c/(100*m))**(m*N0) (this equals A_CI/P at N0).
    2. Bisect on R_s in [0, 50] until hi - lo < eps, comparing lhs = 1 + N0*mid/100 with target.
    3. Report R_s* to two decimals and the common maturity A* = P*(1 + N0*R_s*/100) rounded to the nearest rupee.
  2. Task 2 — Solve for the matching tenure N* at fixed simple rate R_s = 11.2.
    1. Bisect on N in [0, 50] until hiN - loN < eps, comparing lhs = 1 + N*R_s/100 with rhs = (1 + R_c/(100*m))**(m*N).
    2. Report N* to two decimals and the common maturity A* = P*(1 + R_s*N*/100) rounded to the nearest rupee.
  3. Task 3 — One-shot comparison at the quoted rate.
    1. Take R_s = 10.5 and N = N0.
    2. Compute A_SI = P*(1 + R_s*N0/100) and A_CI = P*((1 + R_c/(100*m))**(m*N0)).
    3. Print which option is higher and by how many rupees (absolute difference, rounded to nearest rupee).

Printing & rounding rules (match exactly)

  • Round R_s* and N* to two decimals for display only (keep full precision inside loops).
  • Round rupee amounts with round() and print with comma separators, e.g. ₹{round(value):,}.
  • Produce exactly three lines, one per task, in this format (values will vary):
Task 1: Rs* ≈ XX.XX% ; A* ≈ ₹Y,YYY
Task 2: N* ≈ ZZ.ZZ years ; A* ≈ ₹W,WWW
Task 3: <SI higher by|CI higher by> ₹V,VVV

Sanity checks (build them into your thinking; optional asserts allowed)

  • If N = 0, both maturities equal ₹P.
  • The bisection invariant holds: after each iteration the true solution remains within the current bracket.
  • At the reported R_s* or N*, the two sides match to within your tolerance.

What to submit

  • A single file named si_ci_breakeven_while.py that runs without input and prints the three lines above.
  • No extra prompts, no additional output, no function definitions, no imports.

Part B — Short variations (practice, same code pattern; do not submit unless asked)

  1. Find R_s* & A*: P = ₹2,25,000, N = 5.80; CI R_c = 9.0, m = 12.
  2. Find N* & A*: P = ₹3,00,000, R_s = 10.1; CI R_c = 7.4, m = 2.
  3. Find R_s* & A*: P = ₹4,00,000, cap N = 6.25; CI R_c = 8.6, m = 4.
  4. Report each N*: P = ₹1,20,000, R_s = 10.8; CI R_c = 8.4 with m ∈ {1,4,12}.
  5. Find minimal tenure: P = ₹2,75,000, R_s = 13.0; CI R_c = 9.2, m = 12 — compute the smallest N (to two decimals) for which CI ≥ SI.

It is now our responsibility to get a solution.

Scenario

You are responsible for negotiating the best return for some extra money that you have lying with you from your business. You wish to have the information on what rates or duration that you should keep as the levels below which you should not go below when negotiationg with different financial institutions.

Principal: ₹1,50,000. Two offers:

  • Option A — Simple Interest (SI): rate R_s is negotiable.
  • Option B — Compound Interest (CI): R_c = 8.4% p.a., compounded quarterly (m = 4).

Formulas

$$ \text{SI}=\frac{P\,N\,R_s}{100}, \qquad A_{\mathrm{SI}}=P+\text{SI}=P\!\left(1+\frac{N\,R_s}{100}\right) $$ $$ A_{\mathrm{CI}}=P\!\left(1+\frac{R_c}{100\,m}\right)^{mN} $$ $$ \textbf{Breakeven:}\quad P\!\left(1+\frac{N\,R_s}{100}\right)=P\!\left(1+\frac{R_c}{100\,m}\right)^{mN} $$

What to find

  1. Unknown rate. With N = 6.75 years, find R_s* (% p.a., two decimals) and the common maturity A*.
  2. Unknown years. With R_s = 11.2%, find N* (years, two decimals) and A*.
  3. Quick check. With N = 6.75 and R_s = 10.5%, which option is higher and by how many rupees?

As an exercise, we are restricting to fixed values of input. However, try entering different values to find the ideal solution for the scenario that you are envisioning. In practice, these values become constraints.

Option 1: Python (Using the while-loop iteration only)

We use the while loop to still work out a solution, however the algorithm uses a bisection method which will aim to achieve covergence with fewer iterations.


# si_ci_breakeven_while.py
# Breakeven (SI vs CI) using only while loops (no function defs)

# Inputs
P  = 150000.0   # rupees
Rc = 8.4        # % p.a. (compound)
m  = 4          # compounding per year
N0 = 6.75       # years for Task 1 and Task 3

# ---------- Task 1: find Rs* for fixed N0 (bisection) ----------
target = (1.0 + Rc/(100.0*m))**(m*N0)   # = A_CI/P at N0
lo, hi = 0.0, 50.0                      # search Rs in [0%, 50%]
eps = 1e-12
while (hi - lo) > eps:
    mid = 0.5*(lo + hi)
    lhs = 1.0 + N0*mid/100.0            # = A_SI/P at N0
    if lhs >= target:
        hi = mid
    else:
        lo = mid
Rs_star = 0.5*(lo + hi)
A_star1 = P*(1.0 + N0*Rs_star/100.0)

print(f"Task 1: Rs* ≈ {Rs_star:.2f}% ; A* ≈ ₹{round(A_star1):,}")

# ---------- Task 2: find N* for fixed Rs (bisection) ----------
Rs_fixed = 11.2                          # % p.a. (simple)
loN, hiN = 0.0, 50.0                     # years bracket
while (hiN - loN) > eps:
    midN = 0.5*(loN + hiN)
    lhs  = 1.0 + midN*Rs_fixed/100.0
    rhs  = (1.0 + Rc/(100.0*m))**(m*midN)
    if lhs >= rhs:
        hiN = midN
    else:
        loN = midN
N_star  = 0.5*(loN + hiN)
A_star2 = P*(1.0 + Rs_fixed*N_star/100.0)

print(f"Task 2: N* ≈ {N_star:.2f} years ; A* ≈ ₹{round(A_star2):,}")

# ---------- Task 3: single-horizon comparison ----------
Rs_offer = 10.5
A_si = P*(1.0 + Rs_offer*N0/100.0)
A_ci = P*((1.0 + Rc/(100.0*m))**(m*N0))
diff = A_si - A_ci
print(f"Task 3: {'SI higher by' if diff>=0 else 'CI higher by'} ₹{abs(round(diff)):,}")
    

Option 2: Python (Defining functions to create more reusable code)

A similar approach, but functions are defined to achieve modularity.

# Inputs
P  = 150000.0   # rupees
Rc = 8.4        # % p.a. (compound)
m  = 4          # compounding per year
N0 = 6.75       # years for Task 1 and Task 3

import math

def comp_factor(N, Rc, m):
    # This function returns the Maturity Amount at the
    # end of the period of investment for a given rate
    # of interest, using Compound Interest.
    return (1.0 + Rc/(100.0*m))**(m*N)

def A_SI(P, N, Rs):
    # This function returns the Maturity Amount at the
    # end of the period of investment for a given rate
    # of interest, using Simple Interest.
    return P*(1.0 + N*Rs/100.0)   # uses PNR/100

def A_CI(P, N, Rc, m):
    return P*comp_factor(N, Rc, m)

# ---------- Task 1: solve for Rs* with bisection (while loop) ----------
target = comp_factor(N0, Rc, m)     # equals A_CI/P at N0
lo, hi = 0.0, 50.0                  # search Rs in [0%, 50%]
eps = 1e-10
while hi - lo > 1e-10:
    mid = 0.5*(lo + hi)
    lhs = 1.0 + N0*mid/100.0        # equals A_SI/P at N0
    if lhs >= target:
        hi = mid
    else:
        lo = mid
Rs_star = 0.5*(lo + hi)
A_star1 = A_SI(P, N0, Rs_star)      # common maturity

print(f"Task 1: Rs* ≈ {Rs_star:.2f}% ; A* ≈ ₹{round(A_star1):,}")

# ---------- Task 2: solve for N* with bisection (while loop) ----------
Rs_fixed = 11.2
loN, hiN = 0.0, 50.0                 # years bracket
while hiN - loN > 1e-10:
    midN = 0.5*(loN + hiN)
    lhs  = 1.0 + midN*Rs_fixed/100.0
    rhs  = comp_factor(midN, Rc, m)
    if lhs >= rhs:
        hiN = midN
    else:
        loN = midN
N_star  = 0.5*(loN + hiN)
A_star2 = A_SI(P, N_star, Rs_fixed)

print(f"Task 2: N* ≈ {N_star:.2f} years ; A* ≈ ₹{round(A_star2):,}")

# ---------- Task 3: quick comparison ----------
Rs_offer = 10.5
diff = A_SI(P, N0, Rs_offer) - A_CI(P, N0, Rc, m)
who  = "SI higher by" if diff >= 0 else "CI higher by"
print(f"Task 3: {who} ₹{abs(round(diff)):,}")

Five short practice items

  1. Given P = ₹2,25,000, N = 5.80, CI R_c = 9.0% monthly (m=12): find R_s* and A*.
  2. P = ₹3,00,000, R_s = 10.1%, CI R_c = 7.4% semi-annual (m=2): find N* and A*.
  3. P = ₹4,00,000, cap N = 6.25, CI R_c = 8.6% quarterly (m=4): find R_s* and A*.
  4. P = ₹1,20,000, R_s = 10.8%, CI R_c = 8.4% with m = 1, 4, 12: report each N*.
  5. P = ₹2,75,000, R_s = 13.0%, CI R_c = 9.2% monthly (m=12): find minimal

Some

With principal ₹1,50,000, years N, simple rate R_s% p.a., compound rate R_c% p.a., and compounding frequency m per year, the formulas are:

$$ \text{SI}=\frac{P\,N\,R_s}{100}, \qquad A_{\mathrm{SI}}=P+\text{SI}=P\!\left(1+\frac{N\,R_s}{100}\right) $$ $$ A_{\mathrm{CI}}=P\!\left(1+\frac{R_c}{100\,m}\right)^{mN} $$

Breakeven (given here for practice):

$$ P\!\left(1+\frac{N\,R_s}{100}\right) = P\!\left(1+\frac{R_c}{100\,m}\right)^{mN} $$

The principal P cancels from the breakeven equation, so the crossing depends on rates, compounding frequency, and time. This gives two natural “negotiation levers”: (i) fix the tenure N and solve for the matching rate R_s^*; or (ii) fix the quoted simple rate R_s and solve for the matching tenure N^*.

Monotonicity guarantees (existence and uniqueness at a glance)

  • Unknown rate, time fixed. For fixed N>0, the left-hand side \(1+\frac{N\,R_s}{100}\) increases strictly and smoothly as R_s increases, whereas the right-hand side is a positive constant. There is exactly one solution, and it has the closed form $$ R_s^{*}(N)=\frac{100}{N}\left[\left(1+\frac{R_c}{100\,m}\right)^{mN}-1\right]. $$
  • Unknown time, rate fixed. With R_s fixed, \(g(N)=1+\frac{N\,R_s}{100}\) and \(h(N)=\left(1+\frac{R_c}{100\,m}\right)^{mN}\) are both continuous and strictly increasing in N. For small N the linear term can exceed the compound term; for large N the exponential dominates. Continuity plus opposing behaviours ensures a single crossing \(N^*>0\). Convexity of the exponential rules out multiple positive roots.

How the Python while-loop code works

The script computes three items using only while loops and simple comparisons: (1) the matching simple rate R_s^* for a fixed tenure, (2) the matching tenure N^* for a fixed simple rate, and (3) a one-shot comparison at a given rate and tenure. The common technique is bisection: keep a lower and upper bound that straddle the solution, test the midpoint, and halve the bracket until it is sufficiently tight.

  1. Task 1 — find R_s^* for N=N0. Compute the compound growth factor at the given tenure: $$ \text{target}=\left(1+\frac{R_c}{100\,m}\right)^{mN_0}. $$ Start with a safe rate bracket, e.g. [0, 50] (% p.a.). At each step set mid=(lo+hi)/2, form the linear left side \(1+\frac{N_0\,\text{mid}}{100}\), and compare it to target. If the left side is too large, move the upper bound down; otherwise move the lower bound up. Stop once hi − lo is below a tiny tolerance. The midpoint is R_s^*. The common maturity is \(A^*=P\!\left(1+\frac{N_0\,R_s^*}{100}\right)\).
  2. Task 2 — find N^* for fixed R_s. Use the same bracket-and-halve logic on years, e.g. [0, 50]. Compare the linear left side \(1+\frac{N\,R_s}{100}\) with the exponential right side \(\left(1+\frac{R_c}{100\,m}\right)^{mN}\). Move the bound that keeps the (unique) root inside the bracket and stop when the interval is tight. Report \(N^*\) and \(A^*=P\!\left(1+\frac{N^*\,R_s}{100}\right)\).
  3. Task 3 — straight comparison. Evaluate \(A_{\mathrm{SI}}=P\!\left(1+\frac{N_0\,R_s}{100}\right)\) and \(A_{\mathrm{CI}}=P\!\left(1+\frac{R_c}{100\,m}\right)^{mN_0}\), then print which is larger and by how many rupees.

Why this works smoothly: the quantities you are solving for are monotone in their unknowns. That guarantees a single solution in any bracket that encloses it, and bisection will reach it with steady, predictable convergence—no derivatives, no fragile steps.

Key Takeaways — Principles to work on

You now have a practical way to compare a simple-interest quote with a quarterly-compounded quote. The breakeven condition tells you exactly when both offers pay the same maturity; on either side of that point, you can state which offer is better and why. Because the principal cancels, the decision rests only on rates, compounding frequency, and time—the three levers you can negotiate.

How to read your results in plain language

  • Fix tenure N and solve for R_s^*: if a bank’s simple rate R_s is above R_s^*, simple interest wins at that tenure; if it is below, compound interest wins.
  • Fix simple rate R_s and solve for N^*: for tenures shorter than N^*, simple interest pays more; for tenures longer than N^*, compound interest overtakes.
  • Single-horizon check: computing both maturities at your chosen N and R_s gives a clear rupee difference to justify your choice.

What good programming looks like here

  • Correctness by design: use a bracket that safely contains the answer and keep the invariant “the solution lies between lo and hi”.
  • Predictable convergence: stop when hi − lo is below a sensible tolerance (match it to the number of decimals you will report).
  • Clean separation: compute in full precision; round only for display (₹ with commas).
  • Small, named functions: keep A_SI, comp_factor, and A_CI focused and reusable.

Sanity checks before you submit

  • If N = 0, both maturities equal ₹P.
  • For fixed N, your computed R_s^* makes \(A_{\mathrm{SI}}=A_{\mathrm{CI}}\) to within your tolerance.
  • For fixed R_s, your N^* makes the linear and exponential sides match and gives a single crossing (not multiple roots).
  • Changing m from 1 → 4 → 12 should not decrease the compound maturity at fixed nominal R_c and N.

Common pitfalls to avoid

  • Letting rounding drive logic: never compare rounded values inside the loop.
  • Seed Values that do not enclose the solution: choose wide, realistic bounds (e.g., rates/years in [0, 50]) before tightening. Seed values are the initial values chosen to start an iterative process.
  • Confusing nominal and effective rates: remember the code uses nominal R_c with frequency m.

Reflect and extend (for curious minds)

  • Vary m across 1, 2, 4, 12 and note how N^* shifts. Why do brochures emphasise compounding frequency?
  • Hold N fixed and sweep R_s to sketch where SI beats CI. What pattern do you see?
  • Rewrite your script so the tolerance is an input, then measure how many iterations bisection needs as you tighten it.

In short: you have modelled a real negotiation, coded a robust method to find the crossing, and learnt to justify a choice with numbers. That blend—clear question, careful code, and defensible decision—is the habit to carry into larger finance and engineering problems.