@@ -2052,6 +2052,83 @@ def hessian(cost, wrt, consider_constant=None, disconnected_inputs="raise"):
20522052 return as_list_or_tuple (using_list , using_tuple , hessians )
20532053
20542054
2055+ def hessian_vector_product (cost , wrt , p , ** grad_kwargs ):
2056+ """Return the expression of the Hessian times a vector p.
2057+
2058+ Notes
2059+ -----
2060+ This function uses backward autodiff twice to obtain the desired expression.
2061+ You may want to manually build the equivalent expression by combining backward
2062+ followed by forward (if all Ops support it) autodiff.
2063+ See {ref}`docs/_tutcomputinggrads#Hessian-times-a-Vector` for how to do this.
2064+
2065+ Parameters
2066+ ----------
2067+ cost: Scalar (0-dimensional) variable.
2068+ wrt: Vector (1-dimensional tensor) 'Variable' or list of Vectors
2069+ p: Vector (1-dimensional tensor) 'Variable' or list of Vectors
2070+ Each vector will be used for the hessp wirt to exach input variable
2071+ **grad_kwargs:
2072+ Keyword arguments passed to `grad` function.
2073+
2074+ Returns
2075+ -------
2076+ :class:` Vector or list of Vectors
2077+ The Hessian times p of the `cost` with respect to (elements of) `wrt`.
2078+
2079+ Examples
2080+ --------
2081+
2082+ >>> import numpy as np
2083+ >>> from scipy.optimize import minimize
2084+ >>> from pytensor import function
2085+ >>> from pytensor.tensor import vector
2086+ >>> from pytensor.gradient import grad, hessian_vector_product
2087+ >>>
2088+ >>> x = vector('x')
2089+ >>> p = vector('p')
2090+ >>>
2091+ >>> rosen = (100 * (x[1:] - x[:-1] ** 2) ** 2 + (1 - x[:-1]) ** 2).sum()
2092+ >>> rosen_jac = grad(rosen, x)
2093+ >>> rosen_hessp = hessian_vector_product(rosen, x, p)
2094+ >>>
2095+ >>> rosen_fn = function([x], rosen)
2096+ >>> rosen_jac_fn = function([x], rosen_jac)
2097+ >>> rosen_hessp_fn = function([x, p], rosen_hessp)
2098+ >>> x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
2099+ >>> res = minimize(
2100+ ... rosen_fn,
2101+ ... x0,
2102+ ... method="Newton-CG",
2103+ ... jac=rosen_jac_fn,
2104+ ... hessp=rosen_hessp_fn,
2105+ ... options={"xtol": 1e-8, "disp": True},
2106+ ... )
2107+ Optimization terminated successfully.
2108+ Current function value: 0.000000
2109+ Iterations: 24
2110+ Function evaluations: 33
2111+ Gradient evaluations: 33
2112+ Hessian evaluations: 66
2113+ >>> res.x
2114+ array([1. , 1. , 1. , 0.99999999, 0.99999999])
2115+ """
2116+ wrt_list = wrt if isinstance (wrt , Sequence ) else [wrt ]
2117+ p_list = p if isinstance (p , Sequence ) else [p ]
2118+ grad_wrt_list = grad (cost , wrt = wrt_list , ** grad_kwargs )
2119+ hessian_cost = pytensor .tensor .add (
2120+ * [
2121+ (grad_wrt * p ).sum ()
2122+ for grad_wrt , p in zip (grad_wrt_list , p_list , strict = True )
2123+ ]
2124+ )
2125+ Hp_list = grad (hessian_cost , wrt = wrt_list , ** grad_kwargs )
2126+
2127+ if isinstance (wrt , Variable ):
2128+ return Hp_list [0 ]
2129+ return Hp_list
2130+
2131+
20552132def _is_zero (x ):
20562133 """
20572134 Returns 'yes', 'no', or 'maybe' indicating whether x
0 commit comments