# SciML to solve compound interest equation

I just went through the SciML tutorials and now am trying to apply them by building a calculator for solving the compound interest equation. I have two working implementations, but neither seems very convenient. What is the best way to implement this model so that I can plug in all but one variable and solve for whichever one is missing?

F = P(1+\frac{r}{n})^{nt}+A\frac{(1+\frac{r}{n})^{nt}-1}{\frac{r}{n}}

# First Try

I initially used just NonlinearSolve.jl directly. I wrote a function that steps sequentially through time. I then wrote a wrapper function to isolate the variable I wanted to solve for. Then I constructed an IntervalNonlinearProblem and solved.

The issues here are that …

1. The results overshot in whole year steps because of the steps in my for loop.
2. I have to write a new wrapper function and IntervalNonlinearProblem for every variable. (Not implemented.)
Code for First Implementation
using NonlinearSolve

"""
finalvalue(; P=0, M=0, A=0, r=8, t=1) -> F

Calculate the final value of an investment assuming interest is compounded monthly.

# Arguments
- F: final value (after t years)
- P: principal value (before interest)
- M: regular monthly contibution
- A: regular annual contibution
- r: annual interest rate (%)
- t: total number of years
"""
function finalvalue(; P=0, M=0, A=0, r=8, t=1)
r /= 100               # convert percent to decimal
F = P                  # starting value
for _ in 1:t
for _ in 1:12
F *= 1 + r/12  # apply monthly interest
F += M         # add monthly contribution
end
F += A             # add annual contribution
end
return round(F, digits=2)
end

function findt(t, p)
P = p
M = p
A = p
r = p
F = p
return finalvalue(; P, M, A, r, t) - F
end

p = [5000, 100, 0, 8, 14900]
tspan = (0.0, 10.0)
prob = IntervalNonlinearProblem(findt, tspan, p)
t = round(solve(prob).u, digits=2)


# Second Try

Next I found and modeled the equation with ModelingToolkit.This resulted in better accuracy and smaller code. However,

1. The only type that seemed applicable is a NonlinearSystem, so I have to wrap everything in vectors.
2. I could not figure out how to create an IntervalNonlinearProblem from a NonlinearSystem, so I am using a NonlinearProblem instead. This seems like the wrong type of problem based on what I read in the docs.
3. I don’t know how to switch which variables are @variables and which are @parameters without copying and pasting the entire code over and over and tweaking it. I know of the remake function, but that seems to only be able to change values not variables.
Code for Second Implementation
using ModelingToolkit, NonlinearSolve

@variables t
@parameters F P A r n

eq = [F ~ P * (1 + r/100/n)^(n*t) +
A * ((1 + r/100/n)^(n*t) - 1) / (r/100/n)]

@named ns = NonlinearSystem(eq, [t], [F, P, A, r, n])
prob = NonlinearProblem(ns, , [F=>15000, P=>5000, A=>100, r=>8, n=>12])
sol = round(only(solve(prob).u), digits=2)


I have now implemented a function to choose which variable to solve for. (Apparently @variables can be known or unknown, so now I’m not sure what @parameters are.) It works fairly well for this simple problem, but I doubt it is an efficient/intended solution. Still wondering if there is a way to avoid creating a new NonlinearSystem every time and if there is a way to formulate this into an IntervalNonlinearProblem instead.

using ModelingToolkit, NonlinearSolve, Chain

@variables F P A r n t

eq = F ~ P * (1 + r/100/n)^(n*t) +
A * ((1 + r/100/n)^(n*t) - 1) / (r/100/n)

function eqsolve(
eq::Equation,
guess::Pair{Num, <:Real},
params::Dict{Num, <:Real},
)
@named ns = NonlinearSystem([eq], [guess.first], keys(params))
prob = NonlinearProblem(ns, [guess.second], params)
sol = @chain prob begin
solve
getfield(:u)
only
round(digits=2)
end
return guess.first => sol
end

guess1 = t => 1

params1 = Dict(
F => 15000,
P => 5000,
A => 100,
r => 8,
n => 12,
)

guess2 = r => 1

params2 = Dict(
F => 15000,
P => 5000,
A => 100,
t => 5,
n => 12,
)

julia> eqsolve(eq, guess1, params1)
t => 5.09

julia> eqsolve(eq, guess2, params2)
r => 8.36


That is a pretty cool solution. Honestly I would have coded up different functions and done some sort of dispatch to the correct one. Finding F, P, and A are easy for example. Probably t as well.

1 Like

For problems like this where I am coding up a book equation, I try to avoid manipulating or retyping the equation in multiple places because I am likely to introduce bugs that way. This is more of a proof of concept anyway to try to get a method under my belt for nonlinear equation solving. I would like to use Julia to replace some of our large Excel lookup tables.

I was hoping someone from SciML could tell me if I am on the right track (and clarify my confusion about the macros and Problem types).