from scipy.optimize import minimize
import numpy as np
[docs]def minimization_history(x, history, functional=None):
"""
Saves the minimization history in the given iterable (history).
Entries are dictionary's linking indices of interior cells to the value after the minimizing steps.
Parameters
----------
x: numpy.ndarray
Flattened output of scipy.minimize.
history: list
List to store the results after each minimization step.
Entries are dictionary's (see above).
functional : ddg.optimize.functional.HalfEdgeFunctional
Half-edge functional of minimization process.
Returns
-------
None
"""
d = {}
multi_dim_result = result_decorator_unflatten(x, functional)
for cell in functional.interior_cells:
val = multi_dim_result[cell.interior_cell_index][0] if functional.attr_dimension == 1 \
else multi_dim_result[cell.interior_cell_index]
d[cell.index] = val
history.append(d)
[docs]def function_decorator_flatten(function, functional):
"""
Returns a function that wraps the given function to be able to handle flat inputs.
Parameters
----------
function:
Function of functional that handles multidimensional input.
functional : ddg.optimize.functional.HalfEdgeFunctional
Half-edge functional of minimization process.
Returns
-------
function
"""
def wrapper_flatten(flat_input):
multi_dim_input = result_decorator_unflatten(flat_input, functional)
return function(multi_dim_input)
return wrapper_flatten
[docs]def result_decorator_unflatten(x, functional):
"""
Reshapes the given (flat) array x to the multidimensional shape that was given by the functional.
Parameters
----------
x: numpy.ndarray of shape (n, )
Flat input array.
functional : ddg.optimize.functional.HalfEdgeFunctional
Functional of minimization process.
Returns
-------
function
"""
dim = functional.attr_dimension
l = len(x)
if l / dim - l // dim != 0:
raise ValueError(f"The number of elements in the flat input is not {l // dim} * {dim} (n*dim), "
f"where n is the numer of cells and dim the dimension of the attribute. "
f"Instead the length of the flat array is {l}.")
return np.reshape(x, (l // dim, dim))
[docs]def write_to_attribute(multi_dim_result, functional):
"""
Takes the multidimensional result of the minimization process (i.e. given by result_decorator_unflatten) and
writes it to the corresponding cells given by the functional.
Parameters
----------
multi_dim_result: np.ndarray of shape(n, dim)
Where n is the number of (interior-) cells in the minimization process and dim the dimension of the
minimization attribute.
functional : ddg.optimize.functional.HalfEdgeFunctional
Functional of minimization process.
Returns
-------
None
"""
for cell in functional.interior_cells:
val = multi_dim_result[cell.interior_cell_index][0] if functional.attr_dimension == 1 else multi_dim_result[
cell.interior_cell_index]
setattr(cell, functional.attr_name, val)
#TODO make history a part of the fucntional
#TODO add keyword argument allowing other minimization functions
[docs]def minimizer(functional, return_history=False, **kwargs):
"""
Minimizer for a given functional.
Minimizes value of interior cells where these cells are being computed as the complement
of fucntional.boundary_cells.
The values must be stored in a cell attribute, referenced by functional.attr_name.
The scipy.optimize.minimize function only accepts functionals with 1-dimensional input. For functionals that have multidimensional
input, the minimizer acts as a decorator and wraps the functional to convert the input to a multidimensional one.
The current values are being used as starting values and the methods functional.gradient and functional.hessian
are being used in optimization if they are implemented.
The history keyword allows to store the result of the minimization process after each step.
Parameters
----------
functional : ddg.optimize.functional.HalfEdgeFunctional
functional to minimize
history: bool (default=False)
\*\*kwargs: keyword arguments for scipy.optimize.minimize
By default jac=functional.gradient and hess=functional.hessian if existent
Returns
-------
scipy.optimize.OptimizeResult
and updates the desired values of the surface
Warnings
--------
If history argument is True, a callback function will be initialized to store the step wise results of the minimization.
This will overwrite any callback function that might be given by the \*\*kwargs input.
See Also
--------
ddg.optimize.minimize.minimization_history
"""
x0 = np.array([getattr(v, functional.attr_name) for v in functional.interior_cells])
flat_x0 = x0.flatten()
kwargs.update({"x0": flat_x0})
if hasattr(functional, "gradient"):
kwargs.update({"jac": function_decorator_flatten(functional.gradient, functional)})
if hasattr(functional, "hessian"):
kwargs.update({"hess": function_decorator_flatten(functional.hessian, functional)})
if return_history:
history = []
kwargs['callback'] = lambda x: minimization_history(x, history, functional)
result = minimize(function_decorator_flatten(functional.evaluate, functional), **kwargs)
if result.success:
multi_dim_result = result_decorator_unflatten(result.x, functional)
write_to_attribute(multi_dim_result, functional)
if return_history:
return result, history
else:
return result