I'm currently working through some exercises on multivariable function calculus and thought I would have a go at making my own function to determine gradient and hessian at a defined point for any function. I'm currently having issues when attempting to substitute the resulting matrices with coordinate values for an arbitrary function. I've already managed to solve specific examples, but my attempt to make a function to solve a user defined function isn't working correctly.
def multivariable_function(function, variables, substitute=(0,0)):
"""Determines Gradient and Hessian vectors for multivariable function.
Args:
function: Enter the multivariable function
variables: Enter list of variable names
substitute: Default = (0,0)
Returns:
gradient/hessian matrices for given coordinate
To do:
Include sympy symbol() generation within function
"""
#derive_by_array returns a gradient matrix for multivariable function
Gradient = simplify(derive_by_array(function, variables))
#derive_by_array returns a Hessian matrix for multivariable function
Hessian = simplify(derive_by_array(derive_by_array(function, variables), variables))
#Line currently isn't doing anything
Gradient.subs(zip(variables, substitute))
return Gradient, Hessian
This is the basic function so far in operation.
multivariable_function((x**2)*(y**3) + exp(2*x + x*y - 1) - (x**3 + 3*y**2)**2, (x,y))`
which yields the following result, I am however aiming to substitute the desired values into the gradient and hessian matrices to achieve the following desired result. I managed to achieved the desired result using the following.
from sympy import *
x, y, z, K, T, r, σ, h, a, f, μ, c, t, m, x1, x2, x3 = symbols('x, y, z, K, T, r, σ, h, a, f, μ, c, t, m, x1, x2, x3') # Variables used must be defined in sympy.
init_printing(use_unicode=False) #Print the answers in unicode characters
function = (x**2)*(y**3) + exp(2*x + x*y - 1) - (x**3 + 3*y**2)**2
Gradient_1 = simplify(derive_by_array(function, (x, y)))
Hessian_1 = simplify(derive_by_array(derive_by_array(function, (x, y)), (x, y)))
Gradient_1.subs(x, 0).subs(y,0), Hessian_1.subs(x,0).subs(y,0)
After viewing the issue raised here, it seems zipping the two lists should enable the subs() function to work, but it currently isn't for me. I attempted to loop through 'variables and 'substitute' to sequentially apply .subs(), however I'm finding the function only works if the method is chained for all replacement variables, as in the example above.
Does anyone know how I can apply the .subs() n times for a given coordinate to yield the relevant gradient/hessian matrices?
The variable Gradient
is of type
sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray
Like almost all SymPy objects, with exception of mutable matrices, it is immutable. The method subs
does not modify it in place; it returns a new object, which needs to be assigned.
Gradient = Gradient.subs(zip(variables, substitute))
Hessian = Hessian.subs(zip(variables, substitute))
Then the function works as expected, returning
([2*exp(-1), 0], [[4*exp(-1), exp(-1)], [exp(-1), 0]])
But I suggest not passing generators to subs
; there are outstanding issues involving that. Convert to a list or a dict first, to be safe. (There is also a difference there: should substitutions be consecutive or simultaneous, although this does not matter when substituting numbers for symbols.)
subs_dict = dict(zip(variables, substitute))
Gradient = Gradient.subs(subs_dict)
Hessian = Hessian.subs(subs_dict)