import numpy as np
from iwopy.utils import RegularDiscretizationGrid
from .local_fd import LocalFD
from iwopy.core import Memory, ProblemDefaultFunc
[docs]
class DiscretizeRegGrid(LocalFD):
"""
A wrapper that provides finite distance
differentiation on a regular grid for
selected or all problem float variables.
Attributes
----------
grid: iwopy.tools.RegularDiscretizationGrid
The discretization grid
order: dict
Finite difference order. Key: variable name
str, value: 1 = forward, -1 = backward, 2 = centre
orderb: dict or int
Finite difference order of boundary points.
Key: variable name str, value: order int
:group: wrappers
"""
[docs]
def __init__(
self,
base_problem,
deltas,
fd_order=1,
fd_bounds_order=1,
mem_size=1000,
name=None,
**dpars,
):
"""
Constructor
Parameters
----------
base_problem: iwopy.Problem
The underlying concrete problem
deltas: dict
The step sizes. Key: variable name str,
Value: step size. Will be adjusted to the
variable bounds if necessary.
fd_order: dict or int
Finite difference order. Either a dict with
key: variable name str, value: order int, or
a global integer order for all variables.
1 = forward, -1 = backward, 2 = centre
fd_bounds_order: dict or int
Finite difference order of boundary points.
Either a dict with key: variable name str,
value: order int, or a global integer order
for all variables. Default is same as fd_order
mem_size: int, optional
The memory size, default no memory
name: str, optional
The problem name
dpars: dict, optional
Additional parameters for `RegularDiscretizationGrid`
"""
name = base_problem.name + "_grid" if name is None else name
super().__init__(base_problem, deltas, fd_order, fd_bounds_order, name)
self.grid = None
self._msize = mem_size
self._dpars = dpars
[docs]
def initialize(self, verbosity=1):
"""
Initialize the problem.
Parameters
----------
verbosity: int
The verbosity level, 0 = silent
"""
super().initialize(verbosity)
if verbosity > 1:
print(f" Finite difference grid:")
origin = []
deltas = []
nsteps = []
vnms = super().var_names_float()
vmins = np.full(super().n_vars_float, np.nan, dtype=np.float64)
vmins[:] = super().min_values_float()
vmaxs = np.full(super().n_vars_float, np.nan, dtype=np.float64)
vmaxs[:] = super().max_values_float()
for vi in self._vinds:
vnam = vnms[vi]
vmin = vmins[vi]
vmax = vmaxs[vi]
d = self._deltas[vnam]
if np.isinf(vmin) and np.isinf(vmax):
origin.append(0.0)
deltas.append(d)
nsteps.append(None)
elif np.isinf(vmin):
origin.append(vmax)
deltas.append(d)
nsteps.append(None)
elif np.isinf(vmax):
origin.append(vmin)
deltas.append(d)
nsteps.append(None)
else:
origin.append(vmin)
nsteps.append(int((vmax - vmin) / d))
deltas.append((vmax - vmin) / nsteps[-1])
self.grid = RegularDiscretizationGrid(origin, deltas, nsteps, **self._dpars)
if verbosity > 1:
self.grid.print_info(4)
print(self._hline)
if self._msize is not None:
def keyf(varsi, varsf):
gpts = np.atleast_2d(varsf[self._vinds])
li = varsi.tolist() if len(varsi) else []
tf = tuple(tuple(v.tolist()) for v in self.grid.gpts2inds(gpts))
return (tuple(li), tf)
self.memory = Memory(self._msize, keyf)
def _grad_coeffs(self, varsf, gvars, order, orderb):
"""
Helper function that provides gradient coeffs
"""
gpts, coeffs = self.grid.grad_coeffs(varsf[None, :], gvars, order, orderb)
return gpts, coeffs[0]
[docs]
def apply_individual(self, vars_int, vars_float):
"""
Apply new variables to the problem.
Parameters
----------
vars_int: np.array
The integer variable values, shape: (n_vars_int,)
vars_float: np.array
The float variable values, shape: (n_vars_float,)
Returns
-------
problem_results: Any
The results of the variable application
to the problem
"""
if self.grid.is_gridpoint(vars_float[self._vinds]):
return super().apply_individual(vars_int, vars_float)
else:
raise NotImplementedError(
f"Problem '{self.name}' cannot apply non-grid point {vars_float} to problem"
)
[docs]
def apply_population(self, vars_int, vars_float):
"""
Apply new variables to the problem,
for a whole population.
Parameters
----------
vars_int: np.array
The integer variable values, shape: (n_pop, n_vars_int)
vars_float: np.array
The float variable values, shape: (n_pop, n_vars_float)
Returns
-------
problem_results: Any
The results of the variable application
to the problem
"""
if self.grid.all_gridpoints(vars_float[:, self._vinds]):
return super().apply_population(vars_int, vars_float)
else:
raise NotImplemented(
f"Problem '{self.name}' cannot apply non-grid points to problem"
)
[docs]
def evaluate_individual(self, vars_int, vars_float, ret_prob_res=False):
"""
Evaluate a single individual of the problem.
Parameters
----------
vars_int: np.array
The integer variable values, shape: (n_vars_int,)
vars_float: np.array
The float variable values, shape: (n_vars_float,)
ret_prob_res: bool
Flag for additionally returning of problem results
Returns
-------
objs: np.array
The objective function values, shape: (n_objectives,)
con: np.array
The constraints values, shape: (n_constraints,)
prob_res: object, optional
The problem results
"""
varsf = vars_float[self._vinds]
if self.grid.is_gridpoint(varsf):
return super().evaluate_individual(vars_int, vars_float, ret_prob_res)
else:
gpts, coeffs = self.grid.interpolation_coeffs_point(varsf)
n_gpts = len(gpts)
objs = np.zeros((n_gpts, self.n_objectives), dtype=np.float64)
cons = np.zeros((n_gpts, self.n_constraints), dtype=np.float64)
res = [None for __ in range(n_gpts)]
for gi, gp in enumerate(gpts):
varsf = vars_float.copy()
varsf[self._vinds] = gp
if ret_prob_res:
objs[gi], cons[gi], res[gi] = super().evaluate_individual(
vars_int, varsf, ret_prob_res
)
else:
objs[gi], cons[gi] = super().evaluate_individual(
vars_int, varsf, ret_prob_res
)
if ret_prob_res:
return (
np.einsum("go,g->o", objs, coeffs),
np.einsum("gc,g->c", cons, coeffs),
self.prob_res_einsum_individual(res, coeffs),
)
else:
return (
np.einsum("go,g->o", objs, coeffs),
np.einsum("gc,g->c", cons, coeffs),
)
[docs]
def evaluate_population(self, vars_int, vars_float, ret_prob_res=False):
"""
Evaluate all individuals of a population.
Parameters
----------
vars_int: np.array
The integer variable values, shape: (n_pop, n_vars_int)
vars_float: np.array
The float variable values, shape: (n_pop, n_vars_float)
ret_prob_res: bool
Flag for additionally returning of problem results
Returns
-------
objs: np.array
The objective function values, shape: (n_pop, n_objectives)
cons: np.array
The constraints values, shape: (n_pop, n_constraints)
prob_res: object, optional
The problem results
"""
varsf = vars_float[:, self._vinds]
# case all points on grid:
if self.grid.all_gridpoints(varsf):
return super().evaluate_population(vars_int, vars_float, ret_prob_res)
# case all vars are grid vars:
elif self.n_vars_int == 0 and len(self._vinds) == self.n_vars_float:
gpts, coeffs = self.grid.interpolation_coeffs_points(varsf)
n_gpts = len(gpts)
varsi = np.zeros((n_gpts, self.n_vars_int), dtype=np.int32)
varsi[:] = vars_int[0, None, :]
varsf = np.zeros((n_gpts, self.n_vars_float), dtype=np.float64)
varsf[:] = vars_float[0, None, :]
varsf[:, self._vinds] = gpts
if ret_prob_res:
objs, cons, res = self.evaluate_population(varsi, varsf, ret_prob_res)
return (
np.einsum("go,pg->po", objs, coeffs),
np.einsum("gc,pg->pc", cons, coeffs),
self.prob_res_einsum_population(res, coeffs),
)
else:
objs, cons = self.evaluate_population(varsi, varsf, ret_prob_res)
return (
np.einsum("go,pg->po", objs, coeffs),
np.einsum("gc,pg->pc", cons, coeffs),
)
# mixed case:
else:
gpts, coeffs, gmap = self.grid.interpolation_coeffs_points(
varsf, ret_pmap=True
)
# each pop has n_gp grid points, this yields pop2:
n_pop = len(vars_float)
n_gp = gmap.shape[1]
n_pop2 = n_pop * n_gp
n_int = self.n_vars_int
n_v = n_int + self.n_vars_float
vinds = n_int + np.array(self._vinds)
# create variables of pop2:
apts = np.zeros((n_pop, n_gp, n_v), dtype=np.float64)
apts[:, :, :n_int] = vars_int.astype(np.float64)[:, None, :]
apts[:, :, n_int:] = vars_float[:, None, :]
apts = apts.reshape(n_pop2, n_v)
apts[:, vinds] = np.take_along_axis(gpts, gmap.reshape(n_pop2), axis=0)
# the uniques of pop2 form pop3:
upts, umap = np.unique(apts, axis=0, return_inverse=True)
varsi = upts[:, :n_int].astype(np.int32)
varsf = upts[:, n_int:]
del apts, upts
# calculate results for pop3:
if ret_prob_res:
objs, cons, res = self.evaluate_population(varsi, varsf, ret_prob_res)
else:
objs, cons = self.evaluate_population(varsi, varsf, ret_prob_res)
del varsi, varsf
# reconstruct results for pop2:
objs = np.take_along_axis(objs, umap, axis=0)
cons = np.take_along_axis(cons, umap, axis=0)
# calculate results for pop, by applying coeffs:
objs = objs.reshape(n_pop, n_gp, self.n_objectives)
cons = cons.reshape(n_pop, n_gp, self.n_objectives)
coeffs = np.take_along_axis(coeffs, gmap, axis=1)
if ret_prob_res:
return (
np.einsum("pgo,pg->po", objs, coeffs),
np.einsum("pgc,pg->pc", cons, coeffs),
self.prob_res_einsum_population(res, coeffs),
)
else:
return (
np.einsum("pgo,pg->po", objs, coeffs),
np.einsum("pgc,pg->pc", cons, coeffs),
)
[docs]
def finalize_individual(self, vars_int, vars_float, verbosity=1):
"""
Finalization, given the champion data.
Parameters
----------
vars_int: np.array
The optimal integer variable values, shape: (n_vars_int,)
vars_float: np.array
The optimal float variable values, shape: (n_vars_float,)
verbosity: int
The verbosity level, 0 = silent
Returns
-------
problem_results: Any
The results of the variable application
to the problem
objs: np.array
The objective function values, shape: (n_objectives,)
cons: np.array
The constraints values, shape: (n_constraints,)
"""
if self._msize is not None:
self.memory.clear()
return self.base_problem.finalize_individual(vars_int, vars_float, verbosity)
[docs]
def finalize_population(self, vars_int, vars_float, verbosity=0):
"""
Finalization, given the final population data.
Parameters
----------
vars_int: np.array
The integer variable values of the final
generation, shape: (n_pop, n_vars_int)
vars_float: np.array
The float variable values of the final
generation, shape: (n_pop, n_vars_float)
verbosity: int
The verbosity level, 0 = silent
Returns
-------
problem_results: Any
The results of the variable application
to the problem
objs: np.array
The final objective function values, shape: (n_pop, n_components)
cons: np.array
The final constraint values, shape: (n_pop, n_constraints)
"""
if self._msize is not None:
self.memory.clear()
return self.base_problem.finalize_population(vars_int, vars_float, verbosity)