Source code for TransportMaps.L2.L2_distance

#
# This file is part of TransportMaps.
#
# TransportMaps is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# TransportMaps is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with TransportMaps.  If not, see <http://www.gnu.org/licenses/>.
#
# Transport Maps Library
# Copyright (C) 2015-2018 Massachusetts Institute of Technology
# Uncertainty Quantification group
# Department of Aeronautics and Astronautics
#
# Authors: Transport Map Team
# Website: transportmaps.mit.edu
# Support: transportmaps.mit.edu/qa/
#

import numpy as np

from ..MPI import mpi_map, ExpectationReduce

__all__ = [
    # L2 minimization
    'misfit_squared', 'grad_a_misfit_squared',
    'hess_a_misfit_squared',
    'L2_misfit',
    'L2squared_misfit', 'grad_a_L2squared_misfit',
    'hess_a_L2squared_misfit',
    'storage_hess_a_L2squared_misfit',
    'action_stored_hess_a_L2squared_misfit',
]

nax = np.newaxis


[docs]def misfit_squared(f1, f2, x, params1=None, params2=None, idxs_slice=None): r""" Compute :math:`\vert f_1 - f_2 \vert^2` Args: f1 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_1` or its functions values f2 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_2` or its functions values x (:class:`ndarray<numpy.ndarray>` [:math:`m,d`]): quadrature points used for the approximation of :math:`\mathbb{E}_{\pi}` params1 (dict): parameters for function :math:`f_1` params2 (dict): parameters for function :math:`f_2` idxs_slice (:class:`slice<slice>`): slice of points to be Returns: (:class:`ndarray<numpy.ndarray>`) -- misfit :math:`\vert f_1 - f_2 \vert^2` """ if idxs_slice is None: idxs_slice = slice(None) if isinstance(f1, np.ndarray): F1 = f1[idxs_slice] else: F1 = f1.evaluate(x, precomp=params1, idxs_slice=idxs_slice)[:, 0] if isinstance(f2, np.ndarray): F2 = f2[idxs_slice] else: F2 = f2.evaluate(x, precomp=params2, idxs_slice=idxs_slice)[:, 0] if F1.ndim == 2: mf = np.sum((F1 - F2) ** 2, axis=1) else: mf = (F1 - F2) ** 2. return mf
[docs]def grad_a_misfit_squared(f1, f2, x, params1=None, params2=None, idxs_slice=None): r""" Compute :math:`\nabla_{\bf a}\vert f_{1,{\bf a}} - f_2 \vert^2` Args: f1 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_1` or its functions values f2 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_2` or its functions values x (:class:`ndarray<numpy.ndarray>` [:math:`m,d`]): quadrature points used for the approximation of :math:`\mathbb{E}_{\pi}` params1 (dict): parameters for function :math:`f_1` params2 (dict): parameters for function :math:`f_2` idxs_slice (:class:`slice<slice>`): slice of points to be Returns: (:class:`ndarray<numpy.ndarray>`) -- misfit :math:`\nabla_{\bf a}\vert f_{1,{\bf a}} - f_2 \vert^2` """ if idxs_slice is None: idxs_slice = slice(None) # Evaluate f2 if isinstance(f2, np.ndarray): F2 = f2[idxs_slice] else: F2 = f2.evaluate(x, precomp=params2, idxs_slice=idxs_slice)[:, 0] # Evaluate f1 and grad_a f1 F1 = f1.evaluate(x, precomp=params1, idxs_slice=idxs_slice)[:, 0] ga_F1 = f1.grad_a(x, precomp=params1, idxs_slice=idxs_slice)[:, 0, :] mf2 = F1 - F2 ga_mf2 = 2. * mf2[:, np.newaxis] * ga_F1 return ga_mf2
[docs]def hess_a_misfit_squared(f1, f2, x, params1=None, params2=None, idxs_slice=None): r""" Compute :math:`\nabla^2_{\bf a}\vert f_{1,{\bf a}} - f_2 \vert^2` Args: f1 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_1` or its functions values f2 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_2` or its functions values x (:class:`ndarray<numpy.ndarray>` [:math:`m,d`]): quadrature points used for the approximation of :math:`\mathbb{E}_{\pi}` params1 (dict): parameters for function :math:`f_1` params2 (dict): parameters for function :math:`f_2` idxs_slice (:class:`slice<slice>`): slice of points to be Returns: (:class:`ndarray<numpy.ndarray>`) -- misfit :math:`\nabla^2_{\bf a}\vert f_{1,{\bf a}} - f_2 \vert^2` """ if idxs_slice is None: idxs_slice = slice(None) # Evaluate f2 if isinstance(f2, np.ndarray): F2 = f2[idxs_slice] else: F2 = f2.evaluate(x, precomp=params2, idxs_slice=idxs_slice)[:, 0] # Evaluate f1, grad_a f1 and hess_a f1 F1 = f1.evaluate(x, precomp=params1, idxs_slice=idxs_slice)[:, 0] ga_F1 = f1.grad_a(x, precomp=params1, idxs_slice=idxs_slice)[:, 0, :] ha_F1 = f1.hess_a(x, precomp=params1, idxs_slice=idxs_slice)[:, 0, :, :] mf2 = F1 - F2 ha_mf2 = 2. * (mf2[:, np.newaxis, np.newaxis] * ha_F1 + \ ga_F1[:, :, np.newaxis] * ga_F1[:, np.newaxis, :]) return ha_mf2
[docs]def L2_misfit(*args, **kwargs): r""" Compute :math:`\Vert f_1 - f_2 \Vert_{L^2_\pi}` Args: f1 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_1` or its functions values f2 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_2` or its functions values d (Distribution): distribution :math:`\pi` params1 (dict): parameters for function :math:`f_1` params2 (dict): parameters for function :math:`f_2` qtype (int): quadrature type to be used for the approximation of :math:`\mathbb{E}_{\pi}` qparams (object): parameters necessary for the construction of the quadrature x (:class:`ndarray<numpy.ndarray>` [:math:`m,d`]): quadrature points used for the approximation of :math:`\mathbb{E}_{\pi}` w (:class:`ndarray<numpy.ndarray>` [:math:`m`]): quadrature weights used for the approximation of :math:`\mathbb{E}_{\pi}` batch_size (int): this defines whether to evaluate in batches or not. A size ``1`` correspond to a completely non-vectorized evaluation. A size ``None`` correspond to a completely vectorized one. (Note: if ``nprocs > 1``, then the batch size defines the size of the batch for each process) mpi_pool (:class:`mpi_map.MPI_Pool<mpi_map.MPI_Pool>`): pool of processes to be used for the evaluation of ``f1`` and ``f2` Returns: (:class:`float<float>`) -- misfit :math:`\Vert f_1 - f_2 \Vert_{L^2_\pi}` .. note:: The parameters ``(qtype,qparams)`` and ``(x,w)`` are mutually exclusive, but one pair of them is necessary. """ l2squared = L2squared_misfit(*args, **kwargs) return np.sqrt(l2squared)
[docs]def L2squared_misfit(f1, f2, d=None, params1=None, params2=None, qtype=None, qparams=None, x=None, w=None, batch_size=None, mpi_pool=None): r""" Compute :math:`\Vert f_1 - f_2 \Vert^2_{L^2_\pi}` Args: f1 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_1` or its functions values f2 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_2` or its functions values d (Distribution): distribution :math:`\pi` params1 (dict): parameters for function :math:`f_1` params2 (dict): parameters for function :math:`f_2` qtype (int): quadrature type to be used for the approximation of :math:`\mathbb{E}_{\pi}` qparams (object): parameters necessary for the construction of the quadrature x (:class:`ndarray<numpy.ndarray>` [:math:`m,d`]): quadrature points used for the approximation of :math:`\mathbb{E}_{\pi}` w (:class:`ndarray<numpy.ndarray>` [:math:`m`]): quadrature weights used for the approximation of :math:`\mathbb{E}_{\pi}` batch_size (int): this defines whether to evaluate in batches or not. A size ``1`` correspond to a completely non-vectorized evaluation. A size ``None`` correspond to a completely vectorized one. (Note: if ``nprocs > 1``, then the batch size defines the size of the batch for each process) mpi_pool (:class:`mpi_map.MPI_Pool<mpi_map.MPI_Pool>`): pool of processes to be used for the evaluation of ``f1`` and ``f2` Returns: (:class:`float<float>`) -- misfit :math:`\Vert f_1 - f_2 \Vert^2_{L^2_\pi}` .. note:: The parameters ``(qtype,qparams)`` and ``(x,w)`` are mutually exclusive, but one pair of them is necessary. """ if (isinstance(f1, np.ndarray) and isinstance(f2, np.ndarray) and isinstance(w, np.ndarray)): F1 = f1 F2 = f2 err = np.abs(F1 - F2) L2err = np.dot(err ** 2., w) else: if (x is None) and (w is None): (x, w) = d.quadrature(qtype, qparams) reduce_obj = ExpectationReduce() if batch_size is None: scatter_tuple = (['x'], [x]) reduce_tuple = (['w'], [w]) bcast_tuple = (['f1', 'f2'], [f1, f2]) dmem_key_in_list = ['params1', 'params2'] dmem_arg_in_list = ['params1', 'params2'] dmem_val_in_list = [params1, params2] L2err = mpi_map(misfit_squared, scatter_tuple=scatter_tuple, bcast_tuple=bcast_tuple, reduce_obj=reduce_obj, reduce_tuple=reduce_tuple, dmem_key_in_list=dmem_key_in_list, dmem_arg_in_list=dmem_arg_in_list, dmem_val_in_list=dmem_val_in_list, mpi_pool=mpi_pool) else: # Batching L2err = 0. # Split data and get maximum length of chunk if mpi_pool is None: x_list, ns = ([x], [0, len(x)]) w_list = [w] else: split_dict = mpi_pool.split_data([x, w], ['x', 'w']) x_list = [sd['x'] for sd in split_dict] w_list = [sd['w'] for sd in split_dict] ns = [0] + [len(xi) for xi in x_list] ns = list(np.cumsum(ns)) max_len = x_list[0].shape[0] # Compute the number of iterations necessary for batching niter = max_len // batch_size + (1 if max_len % batch_size > 0 else 0) # Iterate idx0_list = [0] * len(x_list) for it in range(niter): # Prepare batch-slicing for each chunk idxs_slice_list = [] for i, (xs, idx0) in enumerate(zip(x_list, idx0_list)): incr = min(batch_size, xs.shape[0] - idx0) idxs_slice_list.append(slice(idx0, idx0 + incr, None)) idx0_list[i] += incr # Prepare input x and w x_in = [xs[idxs_slice, :] for xs, idxs_slice in zip(x_list, idxs_slice_list)] w_in = [ws[idxs_slice] for ws, idxs_slice in zip(w_list, idxs_slice_list)] # Evaluate scatter_tuple = (['x', 'idxs_slice'], [x_in, idxs_slice_list]) reduce_tuple = (['w'], [w_in]) bcast_tuple = (['f1', 'f2'], [f1, f2]) dmem_key_in_list = ['params1', 'params2'] dmem_arg_in_list = ['params1', 'params2'] dmem_val_in_list = [params1, params2] L2err += mpi_map(misfit_squared, scatter_tuple=scatter_tuple, bcast_tuple=bcast_tuple, dmem_key_in_list=dmem_key_in_list, dmem_arg_in_list=dmem_arg_in_list, dmem_val_in_list=dmem_val_in_list, reduce_obj=reduce_obj, reduce_tuple=reduce_tuple, mpi_pool=mpi_pool, splitted=True) return L2err
[docs]def grad_a_L2squared_misfit(f1, f2, d=None, params1=None, params2=None, qtype=None, qparams=None, x=None, w=None, batch_size=None, mpi_pool=None): r""" Compute :math:`\nabla_{\bf a}\Vert f_{1,{\bf a}} - f_2 \Vert^2_{L^2_\pi}` Args: f1 (:class:`ParametricFunctionApproximation`): function :math:`f_1` f2 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_2` or its functions values d (Distribution): distribution :math:`\pi` params1 (dict): parameters for function :math:`f_1` params2 (dict): parameters for function :math:`f_2` qtype (int): quadrature type to be used for the approximation of :math:`\mathbb{E}_{\pi}` qparams (object): parameters necessary for the construction of the quadrature x (:class:`ndarray<numpy.ndarray>` [:math:`m,d`]): quadrature points used for the approximation of :math:`\mathbb{E}_{\pi}` w (:class:`ndarray<numpy.ndarray>` [:math:`m`]): quadrature weights used for the approximation of :math:`\mathbb{E}_{\pi}` batch_size (int): this defines whether to evaluate in batches or not. A size ``1`` correspond to a completely non-vectorized evaluation. A size ``None`` correspond to a completely vectorized one. (Note: if ``nprocs > 1``, then the batch size defines the size of the batch for each process) mpi_pool (:class:`mpi_map.MPI_Pool<mpi_map.MPI_Pool>`): pool of processes to be used for the evaluation of ``f1`` and ``f2` Returns: (:class:`ndarray<numpy.ndarray>` [:math:`N`]) -- misfit gradient :math:`\nabla_{\bf a}\Vert f_1 - f_2 \Vert_{L^2_\pi}` .. note:: The parameters ``(qtype,qparams)`` and ``(x,w)`` are mutually exclusive, but one pair of them is necessary. """ if (x is None) and (w is None): (x, w) = d.quadrature(qtype, qparams) reduce_obj = ExpectationReduce() if batch_size is None: scatter_tuple = (['x'], [x]) reduce_tuple = (['w'], [w]) bcast_tuple = (['f1', 'f2'], [f1, f2]) dmem_key_in_list = ['params1', 'params2'] dmem_arg_in_list = ['params1', 'params2'] dmem_val_in_list = [params1, params2] ga_L2err = mpi_map(grad_a_misfit_squared, scatter_tuple=scatter_tuple, bcast_tuple=bcast_tuple, reduce_obj=reduce_obj, reduce_tuple=reduce_tuple, dmem_key_in_list=dmem_key_in_list, dmem_arg_in_list=dmem_arg_in_list, dmem_val_in_list=dmem_val_in_list, mpi_pool=mpi_pool) else: # Batching ga_L2err = np.zeros(f1.n_coeffs) # Split data and get maximum length of chunk if mpi_pool is None: x_list, ns = ([x], [0, len(x)]) w_list = [w] else: split_dict = mpi_pool.split_data([x, w], ['x', 'w']) x_list = [sd['x'] for sd in split_dict] w_list = [sd['w'] for sd in split_dict] ns = [0] + [len(xi) for xi in x_list] ns = list(np.cumsum(ns)) max_len = x_list[0].shape[0] # Compute the number of iterations necessary for batching niter = max_len // batch_size + (1 if max_len % batch_size > 0 else 0) # Iterate idx0_list = [0] * len(x_list) for it in range(niter): # Prepare batch-slicing for each chunk idxs_slice_list = [] for i, (xs, idx0) in enumerate(zip(x_list, idx0_list)): incr = min(batch_size, xs.shape[0] - idx0) idxs_slice_list.append(slice(idx0, idx0 + incr, None)) idx0_list[i] += incr # Prepare input x and w x_in = [xs[idxs_slice, :] for xs, idxs_slice in zip(x_list, idxs_slice_list)] w_in = [ws[idxs_slice] for ws, idxs_slice in zip(w_list, idxs_slice_list)] # Evaluate scatter_tuple = (['x', 'idxs_slice'], [x_in, idxs_slice_list]) reduce_tuple = (['w'], [w_in]) bcast_tuple = (['f1', 'f2'], [f1, f2]) dmem_key_in_list = ['params1', 'params2'] dmem_arg_in_list = ['params1', 'params2'] dmem_val_in_list = [params1, params2] ga_L2err += mpi_map(grad_a_misfit_squared, scatter_tuple=scatter_tuple, bcast_tuple=bcast_tuple, dmem_key_in_list=dmem_key_in_list, dmem_arg_in_list=dmem_arg_in_list, dmem_val_in_list=dmem_val_in_list, reduce_obj=reduce_obj, reduce_tuple=reduce_tuple, mpi_pool=mpi_pool, splitted=True) return ga_L2err
[docs]def hess_a_L2squared_misfit(f1, f2, d=None, params1=None, params2=None, qtype=None, qparams=None, x=None, w=None, batch_size=None, mpi_pool=None): r""" Compute :math:`\nabla^2_{\bf a}\Vert f_{1,{\bf a}} - f_2 \Vert^2_{L^2_\pi}` Args: f1 (:class:`ParametricFunctionApproximation`): function :math:`f_1` f2 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_2` or its functions values d (Distribution): distribution :math:`\pi` params1 (dict): parameters for function :math:`f_1` params2 (dict): parameters for function :math:`f_2` qtype (int): quadrature type to be used for the approximation of :math:`\mathbb{E}_{\pi}` qparams (object): parameters necessary for the construction of the quadrature x (:class:`ndarray<numpy.ndarray>` [:math:`m,d`]): quadrature points used for the approximation of :math:`\mathbb{E}_{\pi}` w (:class:`ndarray<numpy.ndarray>` [:math:`m`]): quadrature weights used for the approximation of :math:`\mathbb{E}_{\pi}` batch_size (int): this defines whether to evaluate in batches or not. A size ``1`` correspond to a completely non-vectorized evaluation. A size ``None`` correspond to a completely vectorized one. (Note: if ``nprocs > 1``, then the batch size defines the size of the batch for each process) mpi_pool (:class:`mpi_map.MPI_Pool<mpi_map.MPI_Pool>`): pool of processes to be used for the evaluation of ``f1`` and ``f2` Returns: (:class:`ndarray<numpy.ndarray>` [:math:`N,N`]) -- misfit Hessian :math:`\nabla^2_{\bf a}\Vert f_1 - f_2 \Vert_{L^2_\pi}` .. note:: The parameters ``(qtype,qparams)`` and ``(x,w)`` are mutually exclusive, but one pair of them is necessary. """ if (x is None) and (w is None): (x, w) = d.quadrature(qtype, qparams) reduce_obj = ExpectationReduce() if batch_size is None: scatter_tuple = (['x'], [x]) reduce_tuple = (['w'], [w]) bcast_tuple = (['f1', 'f2'], [f1, f2]) dmem_key_in_list = ['params1', 'params2'] dmem_arg_in_list = ['params1', 'params2'] dmem_val_in_list = [params1, params2] ha_L2err = mpi_map(hess_a_misfit_squared, scatter_tuple=scatter_tuple, bcast_tuple=bcast_tuple, reduce_obj=reduce_obj, reduce_tuple=reduce_tuple, dmem_key_in_list=dmem_key_in_list, dmem_arg_in_list=dmem_arg_in_list, dmem_val_in_list=dmem_val_in_list, mpi_pool=mpi_pool) else: # Batching nc = f1.n_coeffs ha_L2err = np.zeros((nc, nc)) # Split data and get maximum length of chunk if mpi_pool is None: x_list, ns = ([x], [0, len(x)]) w_list = [w] else: split_dict = mpi_pool.split_data([x, w], ['x', 'w']) x_list = [sd['x'] for sd in split_dict] w_list = [sd['w'] for sd in split_dict] ns = [0] + [len(xi) for xi in x_list] ns = list(np.cumsum(ns)) max_len = x_list[0].shape[0] # Compute the number of iterations necessary for batching niter = max_len // batch_size + (1 if max_len % batch_size > 0 else 0) # Iterate idx0_list = [0] * len(x_list) for it in range(niter): # Prepare batch-slicing for each chunk idxs_slice_list = [] for i, (xs, idx0) in enumerate(zip(x_list, idx0_list)): incr = min(batch_size, xs.shape[0] - idx0) idxs_slice_list.append(slice(idx0, idx0 + incr, None)) idx0_list[i] += incr # Prepare input x and w x_in = [xs[idxs_slice, :] for xs, idxs_slice in zip(x_list, idxs_slice_list)] w_in = [ws[idxs_slice] for ws, idxs_slice in zip(w_list, idxs_slice_list)] # Evaluate scatter_tuple = (['x', 'idxs_slice'], [x_in, idxs_slice_list]) reduce_tuple = (['w'], [w_in]) bcast_tuple = (['f1', 'f2'], [f1, f2]) dmem_key_in_list = ['params1', 'params2'] dmem_arg_in_list = ['params1', 'params2'] dmem_val_in_list = [params1, params2] ha_L2err += mpi_map(hess_a_misfit_squared, scatter_tuple=scatter_tuple, bcast_tuple=bcast_tuple, dmem_key_in_list=dmem_key_in_list, dmem_arg_in_list=dmem_arg_in_list, dmem_val_in_list=dmem_val_in_list, reduce_obj=reduce_obj, reduce_tuple=reduce_tuple, mpi_pool=mpi_pool, splitted=True) return ha_L2err
[docs]def storage_hess_a_L2squared_misfit(f1, f2, d=None, params1=None, params2=None, qtype=None, qparams=None, x=None, w=None, batch_size=None, mpi_pool=None): r""" Assemble :math:`\nabla^2_{\bf a}\Vert f_{1,{\bf a}} - f_2 \Vert^2_{L^2_\pi}`. Args: f1 (:class:`ParametricFunctionApproximation`): function :math:`f_1` f2 (:class:`Function` or :class:`ndarray<numpy.ndarray>` [:math:`m`]): function :math:`f_2` or its functions values d (Distribution): distribution :math:`\pi` params1 (dict): parameters for function :math:`f_1` params2 (dict): parameters for function :math:`f_2` qtype (int): quadrature type to be used for the approximation of :math:`\mathbb{E}_{\pi}` qparams (object): parameters necessary for the construction of the quadrature x (:class:`ndarray<numpy.ndarray>` [:math:`m,d`]): quadrature points used for the approximation of :math:`\mathbb{E}_{\pi}` w (:class:`ndarray<numpy.ndarray>` [:math:`m`]): quadrature weights used for the approximation of :math:`\mathbb{E}_{\pi}` batch_size (int): this defines whether to evaluate in batches or not. A size ``1`` correspond to a completely non-vectorized evaluation. A size ``None`` correspond to a completely vectorized one. (Note: if ``nprocs > 1``, then the batch size defines the size of the batch for each process) mpi_pool (:class:`mpi_map.MPI_Pool<mpi_map.MPI_Pool>`): pool of processes to be used for the evaluation of ``f1`` and ``f2` Returns: (None) -- the result is stored in ``params1['hess_a_L2_misfit']`` .. note:: The parameters ``(qtype,qparams)`` and ``(x,w)`` are mutually exclusive, but one pair of them is necessary. .. note:: the dictionary ``params1`` must be provided """ H = hess_a_L2squared_misfit( f1, f2, d=d, params1=params1, params2=params2, qtype=qtype, qparams=qparams, x=x, w=w, batch_size=batch_size, mpi_pool=mpi_pool) return (H,)
[docs]def action_stored_hess_a_L2squared_misfit(H, v): r""" Evaluate the action of :math:`\nabla^2_{\bf a}\Vert f_{1,{\bf a}} - f_2 \Vert^2_{L^2_\pi}` on :math:`v`. Args: v (:class:`ndarray<numpy.ndarray>` [:math:`N`]): vector :math:`v` v (:class:`ndarray<numpy.ndarray>` [:math:`N,N`]): Hessian :math:`\nabla^2_{\bf a}\Vert f_{1,{\bf a}} - f_2 \Vert^2_{L^2_\pi}` Returns: (:class:`ndarray<numpy.ndarray>` [:math:`N`]) -- :math:`\langle\nabla^2_{\bf a}\Vert f_{1,{\bf a}} - f_2 \Vert^2_{L^2_\pi},v\rangle` """ return np.dot(H, v)