Sympy 似乎因更高的数字而崩溃

Sympy seems to break down with higher numbers

我一直在玩弄 sympy 并决定制作一个任意方程求解器,因为我的财务 class 变得有点沉闷。我编写了一个基本框架并开始尝试一些示例,但由于某些原因,有些可以,有些则不能。

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,它 returns 一个空列表作为答案。

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

谁能帮帮我?

旁注:我使用的是 SymPy 0.7.6 和 Python 3.5.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?

的可验证解决方案的地方

此外,在研究这个问题时,我进行了相当广泛的重写,您可能会发现它很有用。它的作用与您的代码几乎相同,但更 loosely-coupled 时尚。

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

这是一个浮点精度问题。 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]