Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sympy seems to break down with higher numbers

Tags:

python

sympy

I've been playing around with sympy and decided to make an arbitrary equations solver since my finance class was getting a little dreary. I wrote a basic framework and started playing with some examples, but some work and some don't for some reason.

from sympy import *
import sympy.mpmath as const

OUT_OF_BOUNDS = "Integer out of bounds."
INVALID_INTEGER = "Invalid Integer."
INVALID_FLOAT = "Invalid Float."
CANT_SOLVE_VARIABLES = "Unable to Solve for More than One Variable."
CANT_SOLVE_DONE = "Already Solved. Nothing to do."

# time value of money equation: FV = PV(1 + i)**n
# FV = future value
# PV = present value
# i = growth rate per perioid
# n = number of periods
FV, PV, i, n = symbols('FV PV i n')
time_value_money_discrete = Eq(FV, PV*(1+i)**n)
time_value_money_continuous = Eq(FV, PV*const.e**(i*n))

def get_sym_num(prompt, fail_prompt):
    while(True):
        try:
            s = input(prompt)
            if s == "":
                return None
            f = sympify(s)
            return f
        except:
            print(fail_prompt)
            continue

equations_supported = [['Time Value of Money (discrete)', [FV, PV, i, n], time_value_money_discrete], 
                       ['Time Value of Money (continuous)',[FV, PV, i, n], time_value_money_continuous]]
EQUATION_NAME = 0
EQUATION_PARAMS = 1
EQUATION_EXPR = 2

if __name__ == "__main__":
    while(True):
        print()
        for i, v in enumerate(equations_supported):
            print("{}: {}".format(i, v[EQUATION_NAME]))
        try:
            process = input("What equation do you want to solve?  ")
            if process == "" or process == "exit":
                break
            process = int(process)
        except:
            print(INVALID_INTEGER)
            continue
        if process < 0 or process >= len(equations_supported):
            print(OUT_OF_BOUNDS)
            continue
        params = [None]*len(equations_supported[process][EQUATION_PARAMS])
        for i, p in enumerate(equations_supported[process][EQUATION_PARAMS]):
            params[i] = get_sym_num("What is {}? ".format(p), INVALID_FLOAT)
        if params.count(None) > 1:
            print(CANT_SOLVE_VARIABLES)
            continue
        if params.count(None) == 0:
            print(CANT_SOLVE_DONE)
            continue
        curr_expr = equations_supported[process][EQUATION_EXPR]
        for i, p in enumerate(params):
            if p != None:
                curr_expr = curr_expr.subs(equations_supported[process][EQUATION_PARAMS][i], params[i])
        print(solve(curr_expr,  equations_supported[process][EQUATION_PARAMS][params.index(None)]))

This is the code I have so far. I guess I can strip it down to a basic example if need be, but I was also wondering if there was a better way to implement this sort of system. After I have this down, I want to be able to add arbitrary equations and solve them after inputting all but one parameter.

For example, if I put in (for equation 0), FV = 1000, PV = 500, i = .02, n is empty I get 35.0027887811465 which is the correct answer. If I redo it and change FV to 4000, it returns an empty list as the answer.

Another example, when I input an FV, PV, and an n, the program seems to hang. When I input small numbers, I got RootOf() answers instead of a simple decimal.

Can anyone help me?

Side note: I'm using SymPy 0.7.6 and Python 3.5.1 which I'm pretty sure are the latest

like image 935
Billy Won Avatar asked Feb 03 '26 21:02

Billy Won


1 Answers

This is a floating point accuracy issue. solve by default plugs solutions into the original equation and evaluates them (using floating point arithmetic) in order to sort out false solutions. You can disable this by setting check=False. For example, for Hugh Bothwell's code

for fv in range(1870, 1875, 1):
    sols = sp.solve(eq.subs({FV:fv}), check=False)
    print("{}: {}".format(fv, sols))

which gives

1870: [66.6116466112007]
1871: [66.6386438584579]
1872: [66.6656266802551]
1873: [66.6925950919998]
1874: [66.7195491090752]
like image 66
asmeurer Avatar answered Feb 06 '26 11:02

asmeurer