# SciML to solve compound interest equation

**URL:** <https://discourse.julialang.org/t/sciml-to-solve-compound-interest-equation/106387>\
**Category:** Modelling & Simulations\
**Tags:** sciml, modelingtoolkit, nonlinearsolve\
**Created:** [November 17, 2023, 8:32pm UTC](https://discourse.julialang.org/t/sciml-to-solve-compound-interest-equation/106387 "2023-11-17T20:32:32Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [November 17, 2023, 8:32pm UTC](https://discourse.julialang.org/t/sciml-to-solve-compound-interest-equation/106387/1 "2023-11-17T20:32:32Z")

</div>

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**
>
> ```julia
> 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[1]
> M = p[2]
> A = p[3]
> r = p[4]
> F = p[5]
> 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](https://docs.sciml.ai/NonlinearSolve/stable/basics/NonlinearProblem/).
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**
>
> ```julia
> 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, [1], [F=>15000, P=>5000, A=>100, r=>8, n=>12])
> sol = round(only(solve(prob).u), digits=2)
> 
> ```

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [November 21, 2023, 7:50pm UTC](https://discourse.julialang.org/t/sciml-to-solve-compound-interest-equation/106387/2 "2023-11-21T19:50:41Z")

</div>

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.

```julia
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-repl
julia> eqsolve(eq, guess1, params1)
t => 5.09

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

```

---

<div class="post-metadata">

**Author:** ![tbeason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tbeason/32/15898_2.png) [@tbeason](https://discourse.julialang.org/u/tbeason)\
**Post date:** [November 21, 2023, 8:39pm UTC](https://discourse.julialang.org/t/sciml-to-solve-compound-interest-equation/106387/3 "2023-11-21T20:39:26Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [November 22, 2023, 2:11pm UTC](https://discourse.julialang.org/t/sciml-to-solve-compound-interest-equation/106387/4 "2023-11-22T14:11:26Z")

</div>

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).

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [May 22, 2024, 2:10pm UTC](https://discourse.julialang.org/t/sciml-to-solve-compound-interest-equation/106387/5 "2024-05-22T14:10:28Z")

</div>

I have now turned my compound interest calculator into a Pluto notebook, where the reactivity makes it really easy to explore input changes. The novelty is that you can quickly solve for any variable in the equation. Most online calculators I found will only solve for `F`.

> **[GitHub: fonsp/pluto-on-binder/v0.19.36](https://mybinder.org/v2/gh/fonsp/pluto-on-binder/v0.19.36?urlpath=pluto%2Fopen?url=https%253A%252F%252Fgist.githubusercontent.com%252Fnathanrboyer%252F5d4bb34e6c7d3ed19d70bbbb78370afd%252Fraw%252Fd1bf3beb18424ce674a4c94be35543065e4d2942%252FCompound%252520Interest%252520Calculator.jl)**
>
> Click to run this interactive environment. From the Binder Project: Reproducible, sharable, interactive computing environments.

You will probably want to download and run it locally because I cannot get it to precompile on Binder. 🫤

I’m still brand new to SciML, so tips are welcome. Enjoy!

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [April 7, 2025, 5:15pm UTC](https://discourse.julialang.org/t/sciml-to-solve-compound-interest-equation/106387/6 "2025-04-07T17:15:17Z")

</div>

Just FYI. I moved this notebook to a normal repository with Pluto’s [static export template](https://github.com/JuliaPluto/static-export-template). The new link is here:

[nathanrboyer/CompoundInterestCalculator.jl: Solves for any variable in the compound interest equation.](https://github.com/nathanrboyer/CompoundInterestCalculator.jl)
