首页 文章

Sympy似乎随着数字的增加而崩溃

提问于
浏览
3

因为我的财务课变得有点沉闷,我一直在玩弄同情并决定做一个任意的方程求解器 . 我写了一个基本框架,并开始玩一些例子,但有些工作,有些不是出于某种原因 .

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

这是我到目前为止的代码 . 我想如果需要的话我可以把它剥离成一个基本的例子,但我也想知道是否有更好的方法来实现这种系统 . 在我完成此操作后,我希望能够添加任意方程并在输入除一个参数之外的所有方程后解决它们 .

例如,如果我输入(对于等式0),FV = 1000,PV = 500,i = .02,n为空,我得到35.0027887811465,这是正确的答案 . 如果我重做它并将FV更改为4000,它将返回一个空列表作为答案 .

另一个例子,当我输入FV,PV和n时,程序似乎挂了 . 当我输入小数字时,我得到RootOf()答案而不是简单的小数 .

谁能帮我?

旁注:我正在使用SymPy 0.7.6和Python 3.5.1,我很确定它是最新的

2 回答

  • 2

    这是一个浮点精度问题 . solve 默认情况下将解决方案插入到原始等式中并对其进行评估(使用浮点算法)以便对错误解决方案进行排序 . 您可以通过设置 check=False 来禁用此功能 . 例如,对于Hugh Bothwell的代码

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

    这使

    1870: [66.6116466112007]
    1871: [66.6386438584579]
    1872: [66.6656266802551]
    1873: [66.6925950919998]
    1874: [66.7195491090752]
    
  • 1

    我没有答案,但我确实有一个更简单的演示案例;-)

    import sympy as sp
    
    FV, n = sp.symbols("FV n")
    eq = sp.Eq(FV, sp.S("500 * 1.02 ** n"))
    
    # see where it breaks
    for fv in range(1870, 1875, 1):
        sols = sp.solve(eq.subs({FV:fv}))
        print("{}: {}".format(fv, sols))
    

    哪个产生

    1870: [66.6116466112007]
    1871: [66.6386438584579]
    1872: []
    1873: []
    1874: []
    

    猜测这是准确度下降到足以找不到 n 的可验证解决方案的地方?


    此外,虽然我在这里做了一个相当广泛的重写,你可能会觉得有用 . 它与您的代码几乎完全相同,但是以更加松散耦合的方式 .

    import sympy as sp
    
    class Equation:
        def __init__(self, label, equality_str, eq="=="):
            self.label = label
            # parse the equality
            lhs, rhs = equality_str.split(eq)
            self.equality = sp.Eq(sp.sympify(lhs), sp.sympify(rhs))
            # index free variables by name
            self.vars = {var.name: var for var in self.equality.free_symbols}
    
        def prompt_for_values(self):
            # show variables to be entered
            var_names = sorted(self.vars, key=str.lower)
            print("\nFree variables are: " + ", ".join(var_names))
            print("Enter a value for all but one (press Enter to skip):")
            # prompt for values by name
            var_values = {}
            for name in var_names:
                value = input("Value of {}: ".format(name)).strip()
                if value:
                    var_values[name] = sp.sympify(value)
            # convert names to Sympy variable references
            return {self.vars[name]:value for name,value in var_values.items()}
    
        def solve(self):
            values = self.prompt_for_values()
            solutions = sp.solve(self.equality.subs(values))
            # remove complex answers
            solutions = [sol.evalf() for sol in solutions if sol.is_real]
            return solutions
    
        def __str__(self):
            return str(self.equality)
    
    # Define some equations!
    equations = [
        Equation("Time value of money (discrete)",   "FV == PV * (1 + i) ** n"),
        Equation("Time value of money (continuous)", "FV == PV * exp(i * n)"  )
    ]
    
    # Create menu
    menu_lo = 1
    menu_hi = len(equations) + 1
    menu_prompt = "\n".join(
        [""]
        + ["{}: {}".format(i, eq.label) for i, eq in enumerate(equations, 1)]
        + ["{}: Exit".format(menu_hi)]
        + ["? "]
    )
    
    def get_int(prompt, lo=None, hi=None):
        while True:
            try:
                value = int(input(prompt))
                if (lo is None or lo <= value) and (hi is None or value <= hi):
                    return value
            except ValueError:
                pass
    
    def main():
        while True:
            choice = get_int(menu_prompt, menu_lo, menu_hi)
            if choice == menu_hi:
                print("Goodbye!")
                break
            else:
                solutions = equations[choice - 1].solve()
                num = len(solutions)
                if num == 0:
                    print("No solutions found")
                elif num == 1:
                    print("1 solution found: " + str(solutions[0]))
                else:
                    print("{} solutions found:".format(num))
                    for sol in solutions:
                        print(sol)
    
    if __name__ == "__main__":
        main()
    

相关问题