"""
This module contains all classes and functions related to the approximation of distributed control laws
as well as their implementation for simulation purposes.
"""
import numpy as np
from itertools import chain
from .registry import get_base
from .core import real, get_weight_transformation, get_transformation_info
from .simulation import SimulationInput, parse_weak_formulation
__all__ = ["Controller", "LawEvaluator"]
[docs]class Controller(SimulationInput):
"""
Wrapper class for all controllers that have to interact with the simulation
environment.
Args:
control_law (:py:class:`.WeakFormulation`): Function handle that
calculates the control output if provided with correct weights.
"""
def __init__(self, control_law):
SimulationInput.__init__(self, name=control_law.name)
ce = parse_weak_formulation(control_law, finalize=False)
self._evaluator = LawEvaluator(ce, self._value_storage)
def _calc_output(self, **kwargs):
"""
Calculates the controller output based on the current_weights.
Keyword Args:
weights: Current weights of the simulations system approximation.
weights_lbl (str): Corresponding label of :code:`weights`.
Return:
dict: Controller output :math:`u`.
"""
return self._evaluator(kwargs["weights"], kwargs["weight_lbl"])
[docs]class LawEvaluator(object):
"""
Object that evaluates the control law approximation given by a
:py:class:`.CanonicalEquations` object.
Args:
cfs (:py:class:`.CanonicalEquation`): evaluation handle
"""
def __init__(self, cfs, storage=None):
self._cfs = cfs
self._transformations = {}
self._eval_vectors = {}
self._storage = storage
@staticmethod
def _build_eval_vector(terms):
"""
Build a set of vectors that will compute the output by multiplication with the corresponding
power of the weight vector.
Args:
terms (dict): coefficient vectors
Return:
dict: evaluation vector
"""
orders = set(terms["E"].keys())
powers = set(chain.from_iterable([list(mat) for mat in terms["E"].values()]))
dim = next(iter(terms["E"][max(orders)].values())).shape
vectors = {}
for power in powers:
vector = np.hstack([terms["E"].get(order, {}).get(power, np.zeros(dim))[0, :]
for order in range(max(orders) + 1)])
vectors.update({power: vector})
return vectors
def __call__(self, weights, weight_label):
"""
Evaluation function for approximated control law.
Args:
weights (numpy.ndarray): 1d ndarray of approximation weights.
weight_label (string): Label of functions the weights correspond to.
Return:
dict: control output :math:`u`
"""
res = {}
output = 0 + 0j
# add dynamic part
for lbl, law in self._cfs.get_dynamic_terms().items():
dst_weights = [0]
if "E" in law:
# build eval vector
if lbl not in self._eval_vectors.keys():
self._eval_vectors[lbl] = self._build_eval_vector(law)
# collect information
info = get_transformation_info(weight_label,
lbl,
int(weights.size / get_base(weight_label).fractions.size) - 1,
int(next(iter(self._eval_vectors[lbl].values())).size
/ get_base(lbl).fractions.size) - 1)
# look up transformation
if info not in self._transformations.keys():
# fetch handle
handle = get_weight_transformation(info)
self._transformations[info] = handle
# transform weights
dst_weights = self._transformations[info](weights)
# evaluate
vectors = self._eval_vectors[lbl]
for p, vec in vectors.items():
output += np.dot(vec, np.power(dst_weights, p))
res[lbl] = dst_weights
# add constant term
static_terms = self._cfs.get_static_terms()
if "f" in static_terms:
output = output + static_terms["f"]
res["output"] = real(output)
return res