Search code examples
pythonsympyexponential

Solving exponential functions in Python with SymPy


I have a small web app that takes 3 points and calculates either a parabola function or an exponential function. Heres the code for the parabola:

@app.route("/calculate/parabola", methods=["POST"])
def calculate_parabola():
    content = request.get_json()
    p1_X = content["p1_X"]
    p1_Y = content["p1_Y"]
    p2_X = content["p2_X"]
    p2_Y = content["p2_Y"]
    p3_X = content["p3_X"]
    p3_Y = content["p3_Y"]
    
    a, b, c = symbols("a,b,c")
    eq1 = Eq((a*(p1_X**2)+b*p1_X+c),p1_Y)
    eq2 = Eq((a*(p2_X**2)+b*p2_X+c), p2_Y)
    eq3 = Eq((a*(p3_X**2)+b*p3_X+c), p3_Y)

    eq_result = solve((eq1, eq2, eq3), (a,b,c))
    print(eq_result)

    returnJSON = {
        "function": f"{eq_result[a]}*x^2+{eq_result[b]}*x+{eq_result[c]}",
        "success": 1
    }
    return returnJSON

This works just fine. But here's the problem:

@app.route("/calculate/exponential-function", methods=["POST"])
def calculate_exponential_function():
    print("calculating...")
    content = request.get_json()
    p1_X = content["p1_X"]
    p1_Y = content["p1_Y"]
    p2_X = content["p2_X"]
    p2_Y = content["p2_Y"]
    p3_X = content["p3_X"]
    p3_Y = content["p3_Y"]
    
    a, b, c = symbols("a,b,c", real=True)
    # eq1 = Eq((a*(b**p1_X)+c), p1_Y)
    # eq2 = Eq((a*(b**p2_X)+c), p2_Y)
    # eq3 = Eq((a*(b**p3_X)+c), p3_Y)

    eq1 = Eq((a*(b**p1_X)+c), p1_Y)
    eq2 = Eq((a*(b**p2_X)+c), p2_Y)
    eq3 = Eq((a*(b**p3_X)+c), p3_Y)


    # eq_result = solve((eq1, eq2, eq3), (a,b,c))
    eq_result = solve((eq1, eq2, eq3), (a,b,c))

    print(eq_result)

    returnJSON = {}

    if(eq_result == []):
        returnJSON = {
        "success": 0
        }
    else:
        returnJSON = {
        "function": f"{eq_result[a]}*({eq_result[b]}**x)+{eq_result[c]}",
        #"function": f"{eq_result[a]}*x+{eq_result[c]}",
        "success": 1
        }

    return returnJSON

If everything works fine, "eq_result" should be something like this: {a: 5/6, b: -1/6, c: 1} but when calculate_exponential_function() is executed, "eq_result" outputs (for example) this: [(-27/5, -2/3, 32/5), (1, 2, 0)] Did I do anything wrong? Let me know if you need more information.


Solution

  • solve() specifically is pretty weird in what it returns, but fairly friendly for that strangeness (if anything, returning different, incompatible objects other than None is wholly unpythonic and surprising, though it largely returns a list of results..)

    From the docs https://docs.sympy.org/latest/explanation/solve_output.html

    The output of the solve() function can seem very unwieldy since it may appear to arbitrarily return one of six different types of output (in addition to raising errors). The reasons for this are historical and are biased toward human interaction rather than programmatic use. The type of output will depend on the type of equation(s) (and how they are entered) and the number of symbols that are provided (and how they are provided).

    You can make this behave more nicely by setting dict=True (though the output is still a list with zero or one entry (or perhaps multiple entries, but I'm not sure it's possible for this case))

    >>> x, y = symbols("x y")
    >>> solve(x + y, {x, y})
    [{x: -y}]
    >>> solve([x + y], {x, y})  # ???
    {x: -y}
    >>> solve([x + y], {x, y}, dict=True)  # always list of dict(s)
    [{x: -y}]
    

    From solve()

    If you pass symbols for which solutions are sought, the output will vary depending on the number of symbols you passed, whether you are passing a list of expressions or not, and whether a linear system was solved. Uniform output is attained by using dict=True or set=True.

    Further confusingly, solve() with dict=True (or set=True) won't return values that are themselves because they're not "interesting", so in my brief experimenting they need to be put into the results dict if you want the full collection

    >>> x, y = symbols("x y")
    >>> solve(x + y, {x, y}, dict=True)  # doesn't bother with y=y or y=-x
    [{x: -y}]
    >>> solve(x + y, {y}, dict=True)     # force solving for y
    [{y: -x}]
    

    All together!

    def calculate_exponential_function():
        content = request.get_json()
        p1_X = content["p1_X"]
        p1_Y = content["p1_Y"]
        p2_X = content["p2_X"]
        p2_Y = content["p2_Y"]
        p3_X = content["p3_X"]
        p3_Y = content["p3_Y"]
        
        a, b, c = symbols("a b c", real=True)
    
        eq1 = Eq((a*(b**p1_X)+c), p1_Y)
        eq2 = Eq((a*(b**p2_X)+c), p2_Y)
        eq3 = Eq((a*(b**p3_X)+c), p3_Y)
    
        eq_result = solve((eq1, eq2, eq3), {a,b,c}, dict=True)
    
        if not eq_result:  # no solution: empty list
            return {
                "success": 0,
            }
    
        eq_result = eq_result[0]  # TODO consider case of multiple results
    
        # add missing keys (alt: dict of values and .update())
        for key in (a, b, c):
            if key not in eq_result:
                eq_result[key] = key
    
        return {
            "function": f"{eq_result[a]}*({eq_result[b]}**x)+{eq_result[c]}",
            "success": 1,
        }