def __call__(self, weights, weight_label): """ evaluation function for approximated control law :param weights: 1d ndarray of approximation weights :param weight_label: string, label of functions the weights correspond to. :return: control output u """ output = 0 + 0j # add dynamic part for lbl, law in self._cfs.get_dynamic_terms().iteritems(): dst_weights = [0] if law[0] is not None: # build eval vector if lbl not in self._eval_vectors.keys(): self._eval_vectors[lbl] = self._build_eval_vector(law) # collect information info = TransformationInfo() info.src_lbl = weight_label info.dst_lbl = lbl info.src_base = get_base(weight_label, 0) info.dst_base = get_base(lbl, 0) info.src_order = int(weights.size / info.src_base.size) - 1 info.dst_order = int(self._eval_vectors[lbl].size / info.dst_base.size) - 1 if info not in self._transformations.keys(): # fetch handle handle = get_weight_transformation(info) self._transformations[info] = handle dst_weights = self._transformations[info](weights) output += np.dot(self._eval_vectors[lbl], dst_weights) if self._storage is not None: entry = self._storage.get(info.dst_lbl, []) entry.append(dst_weights) self._storage[info.dst_lbl] = entry # add constant term static_terms = self._cfs.get_static_terms() if static_terms[1] is not None: output += static_terms[1][0] # TODO: replace with the one from utils if abs(np.imag(output)) > np.finfo(np.complex128).eps * 100: print("Warning: Imaginary part of output is nonzero! out = {0}".format(output)) out = np.real_if_close(output, tol=10000000) if np.imag(out) != 0: raise sim.SimulationException( "calculated complex control output u={0}," " check for errors in control law!".format(out) ) return out
def evaluate_approximation(base_label, weights, temp_domain, spat_domain, spat_order=0, name=""): """ evaluate an approximation given by weights and functions at the points given in spatial and temporal steps :param weights: 2d np.ndarray where axis 1 is the weight index and axis 0 the temporal index :param base_label: functions to use for back-projection :param temp_domain: steps to evaluate at :param spat_domain: sim.Domain to evaluate at (or in) :param spat_order: spatial derivative order to use :param name: name to use :return: EvalData """ funcs = get_base(base_label, spat_order) if weights.shape[1] != funcs.shape[0]: raise ValueError("weights (len={0}) have to fit provided functions (len={1})!".format(weights.shape[1], funcs.size)) # evaluate shape functions at given points if isinstance(base_label[0], LagrangeFirstOrder): # shortcut for fem approximations shape_vals = [func.top for func in funcs] else: shape_vals = [func(spat_domain) for func in funcs] shape_vals = np.atleast_2d(shape_vals) def eval_spatially(weight_vector): return np.real_if_close(np.dot(weight_vector, shape_vals), 1000) data = np.apply_along_axis(eval_spatially, 1, weights) return vis.EvalData([temp_domain, spat_domain], data, name=name)
def process_sim_data(weight_lbl, q, temp_domain, spat_domain, temp_order, spat_order, name=""): """ create handles and evaluate at given points :param weight_lbl: label of Basis for reconstruction :param temp_order: order or temporal derivatives to evaluate additionally :param spat_order: order or spatial derivatives to evaluate additionally :param q: weights :param spat_domain: sim.Domain object providing values for spatial evaluation :param temp_domain: timesteps on which rows of q are given :param name: name of the WeakForm, used to generate the dataset """ data = [] # temporal ini_funcs = get_base(weight_lbl, 0) for der_idx in range(temp_order+1): name = "{0}{1}".format(name, "_" + "".join(["d" for x in range(der_idx)] + ["t"]) if der_idx > 0 else "") data.append(evaluate_approximation(weight_lbl, q[:, der_idx * ini_funcs.size:(der_idx + 1) * ini_funcs.size], temp_domain, spat_domain, name=name)) # spatial (0th derivative is skipped since this is already handled above) for der_idx in range(1, spat_order+1): name = "{0}{1}".format(name, "_" + "".join(["d" for x in range(der_idx)] + ["z"]) if der_idx > 0 else "") data.append( evaluate_approximation(weight_lbl, q[:, :ini_funcs.size], temp_domain, spat_domain, der_idx, name=name)) return data
def _parse_control_law(law): """ parses the given control law by approximating given terms :param law: list of equation terms :return: evaluation handle """ # check terms for term in law.terms: if not isinstance(term, EquationTerm): raise TypeError("only EquationTerm(s) accepted.") cfs = sim.CanonicalForms(law.name) for term in law.terms: placeholders = dict( [ ("field_variables", term.arg.get_arg_by_class(FieldVariable)), ("scalars", term.arg.get_arg_by_class(Scalars)), ] ) if placeholders["field_variables"]: field_var = placeholders["field_variables"][0] temp_order = field_var.order[0] func_lbl = field_var.data["func_lbl"] weight_lbl = field_var.data["weight_lbl"] init_funcs = get_base(func_lbl, field_var.order[1]) factors = np.atleast_2d( [integrate_function(func, domain_intersection(term.limits, func.nonzero))[0] for func in init_funcs] ) if placeholders["scalars"]: scales = placeholders["scalars"][0] res = np.prod(np.array([factors, scales]), axis=0) else: res = factors cfs.add_to(weight_lbl, ("E", temp_order), res * term.scale) elif placeholders["scalars"]: # TODO make sure that all have the same target form! scalars = placeholders["scalars"] if len(scalars) > 1: # TODO if one of 'em is just a scalar and no array an error occurs res = np.prod(np.array([scalars[0].data, scalars[1].data]), axis=0) else: res = scalars[0].data cfs.add_to(scalars[0].target_form, get_scalar_target(scalars), res * term.scale) else: raise NotImplementedError return cfs
def get_weight_transformation(info): """ somehow calculates a handle that will transform weights from src into weights for dst with the given derivative orders. :param info: transformation info :return: handle """ # trivial case if info.src_lbl == info.dst_lbl: mat = calculate_expanded_base_transformation_matrix(info.src_base, None, info.src_order, info.dst_order, True) def identity(weights): return np.dot(mat, weights) return identity # try to get help from the destination base handle, hint = info.dst_base[0].transformation_hint(info, True) # if handle is None: # # try source instead # handle, hint = info.src_base[0].transformation_hint(info, False) if handle is None: raise TypeError("no transformation between given bases possible!") # check termination criterion if hint is None: # direct transformation possible return handle kwargs = {} new_handle = None if hasattr(hint, "extras"): # try to gain transformations that will satisfy the extra terms for dep_lbl, dep_order in hint.extras.iteritems(): new_info = copy(info) new_info.dst_lbl = dep_lbl new_info.dst_base = get_base(dep_lbl, 0) new_info.dst_order = dep_order dep_handle = get_weight_transformation(new_info) kwargs[dep_lbl] = dep_handle if hint.src_lbl is not None: # transformation to assistant system required new_handle = get_weight_transformation(hint) def last_handle(weights): if new_handle: return handle(new_handle(weights), **kwargs) else: return handle(weights, **kwargs) return last_handle
def evaluate_placeholder_function(placeholder, input_values): """ evaluate a given placeholder object, that contains functions :param placeholder: instance of ref:py:class: FieldVariable or ref:py:class TestFunction ref:py:class ScalarFunction :return: results as np.ndarray """ if not isinstance(placeholder, (FieldVariable, TestFunction)): raise TypeError("Input Object not supported!") funcs = get_base(placeholder.data["func_lbl"], placeholder.order[1]) return np.array([func(input_values) for func in funcs])
def _simplify_product(a, b): # try to simplify expression containing ScalarFunctions scalar_func = None other_func = None for obj1, obj2 in [(a, b), (b, a)]: if isinstance(obj1, ScalarFunction): scalar_func = obj1 if isinstance(obj2, (FieldVariable, TestFunction, ScalarFunction)): other_func = obj2 break if scalar_func and other_func: s_func = get_base(scalar_func.data["func_lbl"], scalar_func.order[1]) o_func = get_base(other_func.data["func_lbl"], other_func.order[1]) if s_func.shape != o_func.shape: if s_func.shape[0] == 1: # only one function provided, use it for all others s_func = s_func[[0 for i in range(o_func.shape[0])]] else: raise ValueError("Cannot simplify Product due to dimension mismatch!") new_func = np.asarray([func.scale(scale_func) for func, scale_func in zip(o_func, s_func)]) new_name = new_func.tostring() register_base(new_name, new_func) if isinstance(other_func, (ScalarFunction, TestFunction)): a = other_func.__class__(function_label=new_name, order=other_func.order[1], location=other_func.location) elif isinstance(other_func, FieldVariable): # overwrite spatial derivative order, since derivation has been performed a = FieldVariable(function_label=new_name, weight_label=other_func.data["weight_lbl"], order=(other_func.order[0], 0), location=other_func.location) b = None return a, b
def simulate_system(weak_form, initial_states, temporal_domain, spatial_domain, der_orders=(0, 0)): """ convenience wrapper that encapsulates the whole simulation process :param weak_form: :param initial_states: np.array of core.Functions for :math:`x(t=0, z), \\dot{x}(t=0, z), \\dotsc, x^{(n)}(t=0, z)` :param temporal_domain: sim.Domain object holding information for time evaluation :param spatial_domain: sim.Domain object holding information for spatial evaluation :param der_orders: tuple of derivative orders (time, spat) that shall be evaluated additionally :return: list of EvalData object, holding the results for the FieldVariable and asked derivatives """ print("simulating system: {0}".format(weak_form.name)) if not isinstance(weak_form, WeakFormulation): raise TypeError("only WeakFormulation accepted.") initial_states = np.atleast_1d(initial_states) if not isinstance(initial_states[0], Function): raise TypeError("only core.Function accepted as initial state") if not isinstance(temporal_domain, Domain) or not isinstance(spatial_domain, Domain): raise TypeError("domains must be given as Domain object") # parse input and create state space system print(">>> parsing formulation") canonical_form = parse_weak_formulation(weak_form) print(">>> creating state space system") state_space_form = canonical_form.convert_to_state_space() # calculate initial state print(">>> deriving initial conditions") q0 = np.array([project_on_base(initial_state, get_base( canonical_form.weights, 0)) for initial_state in initial_states]).flatten() # simulate print(">>> performing time step integration") sim_domain, q = simulate_state_space(state_space_form, canonical_form.input_function, q0, temporal_domain) # evaluate print(">>> performing postprocessing") temporal_order = min(initial_states.size-1, der_orders[0]) data = process_sim_data(canonical_form.weights, q, sim_domain, spatial_domain, temporal_order, der_orders[1], name=canonical_form.name) print("finished simulation.") return data
def _evaluate_placeholder(placeholder): """ evaluates a placeholder object and returns a Scalars object :param placeholder: :return: """ if not isinstance(placeholder, Placeholder): raise TypeError("only placeholders supported") if isinstance(placeholder, (Scalars, Input)): raise TypeError("provided type cannot be evaluated") functions = get_base(placeholder.data['func_lbl'], placeholder.order[1]) location = placeholder.location values = np.atleast_2d([func(location) for func in functions]) if isinstance(placeholder, FieldVariable): return Scalars(values, target_term=("E", placeholder.order[0]), target_form=placeholder.data["weight_lbl"]) elif isinstance(placeholder, TestFunction): # target form does not matter, since the f vector is added independently return Scalars(values.T, target_term=("f", 0)) else: raise NotImplementedError
def parse_weak_formulation(weak_form): """ creates an ode system for the weights x_i based on the weak formulation. :return: simulation.ODESystem """ if not isinstance(weak_form, WeakFormulation): raise TypeError("only able to parse WeakFormulation") cf = CanonicalForm(weak_form.name) # handle each term for term in weak_form.terms: # extract Placeholders placeholders = dict(scalars=term.arg.get_arg_by_class(Scalars), functions=term.arg.get_arg_by_class(TestFunction), field_variables=term.arg.get_arg_by_class(FieldVariable), inputs=term.arg.get_arg_by_class(Input)) # field variable terms, sort into E_n, E_n-1, ..., E_0 if placeholders["field_variables"]: if len(placeholders["field_variables"]) != 1: raise NotImplementedError field_var = placeholders["field_variables"][0] temp_order = field_var.order[0] init_funcs = get_base(field_var.data["func_lbl"], field_var.order[1]) if placeholders["inputs"]: # TODO think about this case, is it relevant? raise NotImplementedError # is the integrand a product? if placeholders["functions"]: if len(placeholders["functions"]) != 1: raise NotImplementedError func = placeholders["functions"][0] test_funcs = get_base(func.data["func_lbl"], func.order[1]) result = calculate_scalar_product_matrix(dot_product_l2, test_funcs, init_funcs) else: # pull constant term out and compute integral a = Scalars(np.atleast_2d([integrate_function(func, func.nonzero)[0] for func in init_funcs])) if placeholders["scalars"]: b = placeholders["scalars"][0] else: b = Scalars(np.ones_like(a.data.T)) result = _compute_product_of_scalars([a, b]) cf.weights = field_var.data["weight_lbl"] cf.add_to(("E", temp_order), result*term.scale) continue # TestFunction Terms, those will end up in f if placeholders["functions"]: if not 1 <= len(placeholders["functions"]) <= 2: raise NotImplementedError func = placeholders["functions"][0] test_funcs = get_base(func.data["func_lbl"], func.order[1]) if len(placeholders["functions"]) == 2: # TODO this computation is nonesense. Result must be a vektor conataining int of (tf1*tf2) raise NotImplementedError func2 = placeholders["functions"][1] test_funcs2 = get_base(func2.data["func_lbl"], func2.order[2]) result = calculate_scalar_product_matrix(dot_product_l2, test_funcs, test_funcs2) cf.add_to(("f", 0), result*term.scale) continue if placeholders["scalars"]: a = placeholders["scalars"][0] b = Scalars(np.vstack([integrate_function(func, func.nonzero)[0] for func in test_funcs])) result = _compute_product_of_scalars([a, b]) cf.add_to(get_scalar_target(placeholders["scalars"]), result*term.scale) continue if placeholders["inputs"]: if len(placeholders["inputs"]) != 1: raise NotImplementedError input_var = placeholders["inputs"][0] input_func = input_var.data input_order = input_var.order result = np.array([integrate_function(func, func.nonzero)[0] for func in init_funcs]) cf.add_to(("g", 0), result*term.scale) cf.input_function = input_func continue # pure scalar terms, sort into corresponding matrices if placeholders["scalars"]: result = _compute_product_of_scalars(placeholders["scalars"]) target = get_scalar_target(placeholders["scalars"]) if placeholders["inputs"]: input_var = placeholders["inputs"][0] input_func = input_var.data input_order = input_var.order[0] # this would mean that the input term should appear in a matrix like E1 or E2 if target[0] == "E": raise NotImplementedError cf.add_to(("g", 0), result*term.scale) # TODO think of a more modular concept for input handling cf.input_function = input_func continue cf.add_to(target, result*term.scale) continue return cf