Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions docs/howto_src/_lti.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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]])
85 changes: 50 additions & 35 deletions docs/howto_src/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
"""

# %%
Expand Down Expand Up @@ -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())
Expand All @@ -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

Expand All @@ -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,))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the example just use this instead?

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


# %%
Expand All @@ -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())
Expand Down