diff --git a/docs/howto_src/_lti.py b/docs/howto_src/_lti.py index 8b777a2a..526079c9 100644 --- a/docs/howto_src/_lti.py +++ b/docs/howto_src/_lti.py @@ -1,11 +1,9 @@ -import numpy as np - import condor # pass get_settings in a list of configurable variables with defaults # it returns a dictionary with the the configured values -conf = condor.settings.get_settings(A=np.array([1.0]), B=None) -A, B = conf.values() +conf = condor.settings.get_settings(A=None, B=None, bounce=False) +A, B, bounce = conf.values() class LTI(condor.ODESystem): @@ -22,3 +20,11 @@ class LTI(condor.ODESystem): xdot += B @ u dot[x] = xdot + + +if bounce: + from condor.backend import operators as ops + + class Bounce(LTI.Event): + function = x[0] + update[x] = ops.concat([x[0], -x[1]]) diff --git a/docs/howto_src/configuration.py b/docs/howto_src/configuration.py index 2ea43d30..d7fcd902 100644 --- a/docs/howto_src/configuration.py +++ b/docs/howto_src/configuration.py @@ -3,10 +3,14 @@ Configuring Models ================== -At times, model templates need to be parametrized in a more of a programming sense than -a mathematical one. An example of this is a linear time invariant (LTI) ODE system, -where the size of the state vector and whether there is feedback control are dependent -on what the user passes in for the state and input matrices. +Logic within a model declaration can sometimes be handled by inputs/parameters and +:func:`~condor.backend.operators.if_else`, but sometimes models need to be templated +in a deeper way. A couple common examples are handling input arrays of arbitrary size, +and logic for whether or not certain models or submodels should be declared. + +This example walks through two different ways to handle these cases for a simple linear +time invariant (LTI) :class:`~condor.contrib.ODESystem` with templated dynamics and an +optional :class:`~condor.contrib.Event`. """ # %% @@ -59,17 +63,19 @@ class Sim(LTI_dblint.TrajectoryAnalysis): plt.plot(sim.t, sim.x[0].squeeze()) # %% -# We can also re-use the module with a different configuration: +# We can also re-import the module with a different configuration: -LTI_exp = condor.settings.get_module("_lti", A=np.array([[0, 1], [-2, -3]])).LTI +dlbint_mod = condor.settings.get_module("_lti", A=A, B=B, bounce=True) +LTI_bounce = dblint_mod.LTI -class Sim(LTI_exp.TrajectoryAnalysis): + +class Sim(LTI_bounce.TrajectoryAnalysis): tf = 10 initial[x] = [1.0, 0.5] -sim = Sim() +sim = Sim(K=sim.K) plt.figure() plt.plot(sim.t, sim.x[0].squeeze()) @@ -80,15 +86,20 @@ class Sim(LTI_exp.TrajectoryAnalysis): # ------------------------- # # An alternative approach is to programmatically generate the model using the -# metaprogramming machinery Condor uses internally. See -# :ref:`metaprogramming-walkthrough` for a more thorough overview. - +# metaprogramming machinery that Condor uses internally. See +# :ref:`metaprogramming-walkthrough` for an overview. +# +# The :class:`~condor.contrib.ODESystem` factory function declared below is essentially +# identical to the config-based example above except for the optional event, which we'll +# see later as a separate factory function: from condor.contrib import ModelTemplateType, ODESystem -def make_LTI(A, B=None, name="LTISystem"): - attrs = ModelTemplateType.__prepare__(name, (ODESystem,)) +def make_LTI(A, B=None): + name = "LTI" + bases = (ODESystem,) + attrs = ModelTemplateType.__prepare__(name, bases) attrs["A"] = A @@ -110,25 +121,9 @@ def make_LTI(A, B=None, name="LTISystem"): attrs["dot"][x] = xdot - plant = ModelTemplateType(name, (ODESystem,), attrs) - - return plant + LTI = ModelTemplateType(name, bases, attrs) - # OR for primary models: - ode_attrs = condor.ODESystem.__prepare__(ode_name, (condor.ODESystem,)) - ode_model = condor.ODESystem.__class__(ode_name, (condor.ODESystem,), ode_attrs) - - # but submodels must be ~ the way shown above: - event_meta_args = ( - event_name, - (ode_model.Event, condor.models.Submodel), - ) - event_attrs = condor.contrib.Event.__prepare__(*event_meta_args) - condor.contrib.EventType.__new__( - condor.contrib.EventType, - *event_meta_args, - attrs=event_attrs, - ) + return LTI # %% @@ -148,16 +143,36 @@ class Sim(LTI_dblint.TrajectoryAnalysis): plt.plot(sim.t, sim.x[0].squeeze()) # %% +# To define a submodel of a primary system, like an event, the construction looks like +# this: + +from condor.backend import operators as ops + + +def add_bounce_event(odesys_cls): + name = "Bounce" + bases = (odesys_cls.Event, condor.models.Submodel) + attrs = condor.contrib.Event.__prepare__(name, bases) + + x = odesys_cls.x.backend_repr + attrs["function"] = x[0] + attrs["update"][x] = ops.concat([x[0], -x[1]]) + + condor.contrib.EventType.__new__(condor.contrib.EventType, name, bases, attrs=attrs) + + +# %% +# Add the event to our ODESystem and simulate again: -LTI_exp = make_LTI(A=np.array([[0, 1], [-2, -3]])) +add_bounce_event(LTI_dblint) -class Sim(LTI_exp.TrajectoryAnalysis): +class Sim(LTI_dblint.TrajectoryAnalysis): tf = 20 - initial[x] = [1.0, 0.5] + initial[x] = [1.0, 0.1] -sim = Sim() +sim = Sim(K=sim.K) plt.figure() plt.plot(sim.t, sim.x[0].squeeze())