From e933b764689228e210a4af1a5360a0a6ef02a24c Mon Sep 17 00:00:00 2001 From: Kenneth Lyons Date: Fri, 7 Nov 2025 11:04:14 -0800 Subject: [PATCH 1/4] Show more explicitly how to programmatically construct a submodel, mirror in config example --- docs/howto_src/_lti.py | 12 ++++- docs/howto_src/configuration.py | 77 ++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/docs/howto_src/_lti.py b/docs/howto_src/_lti.py index 8b777a2a..dcc044d6 100644 --- a/docs/howto_src/_lti.py +++ b/docs/howto_src/_lti.py @@ -4,8 +4,8 @@ # 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=np.array([1.0]), B=None, bounce=False) +A, B, bounce = conf.values() class LTI(condor.ODESystem): @@ -22,3 +22,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..373baf24 100644 --- a/docs/howto_src/configuration.py +++ b/docs/howto_src/configuration.py @@ -3,10 +3,15 @@ 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 they need to be parametrized +in more of a programmatic sense than a mathematical one. 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`. """ # %% @@ -61,15 +66,16 @@ class Sim(LTI_dblint.TrajectoryAnalysis): # %% # We can also re-use 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,8 +86,8 @@ 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. from condor.contrib import ModelTemplateType, ODESystem @@ -114,22 +120,6 @@ def make_LTI(A, B=None, name="LTISystem"): return plant - # 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, - ) - # %% # Use of the model factory function looks similar to using ``get_module``: @@ -148,16 +138,45 @@ 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): + event_meta_args = ( + "bounce", # name + (odesys_cls.Event, condor.models.Submodel), # bases + ) + event_attrs = condor.contrib.Event.__prepare__(*event_meta_args) -LTI_exp = make_LTI(A=np.array([[0, 1], [-2, -3]])) + # extract elements to operate on + x = odesys_cls.x.backend_repr + # define the event + event_attrs["function"] = x[0] + event_attrs["update"][x] = ops.concat((x[0], -x[1])) -class Sim(LTI_exp.TrajectoryAnalysis): + condor.contrib.EventType.__new__( + condor.contrib.EventType, + *event_meta_args, + attrs=event_attrs, + ) + + +# %% +# Add the event to our ODESystem and simulate again: + +add_bounce_event(LTI_dblint) + + +class Sim(LTI_dblint.TrajectoryAnalysis): tf = 20 - initial[x] = [1.0, 0.5] + initial[x] = [1.0, 0.1] -sim = Sim() +sim = Sim(K=[1.0, 0.1]) plt.figure() plt.plot(sim.t, sim.x[0].squeeze()) From 07c8437cd6ae54fc433f602f8d7cc3f999167adc Mon Sep 17 00:00:00 2001 From: Kenneth Lyons Date: Fri, 7 Nov 2025 11:52:17 -0800 Subject: [PATCH 2/4] Some touchups for clarity --- docs/howto_src/_lti.py | 4 +--- docs/howto_src/configuration.py | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/howto_src/_lti.py b/docs/howto_src/_lti.py index dcc044d6..526079c9 100644 --- a/docs/howto_src/_lti.py +++ b/docs/howto_src/_lti.py @@ -1,10 +1,8 @@ -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, bounce=False) +conf = condor.settings.get_settings(A=None, B=None, bounce=False) A, B, bounce = conf.values() diff --git a/docs/howto_src/configuration.py b/docs/howto_src/configuration.py index 373baf24..f22451b2 100644 --- a/docs/howto_src/configuration.py +++ b/docs/howto_src/configuration.py @@ -4,10 +4,9 @@ ================== Logic within a model declaration can sometimes be handled by inputs/parameters and -:func:`~condor.backend.operators.if_else`, but sometimes they need to be parametrized -in more of a programmatic sense than a mathematical one. Common examples are handling -input arrays of arbitrary size and logic for whether or not certain models or submodels -should be declared. +: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 @@ -64,7 +63,8 @@ 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: + dlbint_mod = condor.settings.get_module("_lti", A=A, B=B, bounce=True) LTI_bounce = dblint_mod.LTI @@ -88,12 +88,15 @@ class Sim(LTI_bounce.TrajectoryAnalysis): # An alternative approach is to programmatically generate the model using the # 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"): +def make_LTI(A, B=None, name="LTI"): attrs = ModelTemplateType.__prepare__(name, (ODESystem,)) attrs["A"] = A @@ -146,7 +149,7 @@ class Sim(LTI_dblint.TrajectoryAnalysis): def add_bounce_event(odesys_cls): event_meta_args = ( - "bounce", # name + "Bounce", # name (odesys_cls.Event, condor.models.Submodel), # bases ) event_attrs = condor.contrib.Event.__prepare__(*event_meta_args) @@ -156,7 +159,7 @@ def add_bounce_event(odesys_cls): # define the event event_attrs["function"] = x[0] - event_attrs["update"][x] = ops.concat((x[0], -x[1])) + event_attrs["update"][x] = ops.concat([x[0], -x[1]]) condor.contrib.EventType.__new__( condor.contrib.EventType, @@ -176,7 +179,7 @@ class Sim(LTI_dblint.TrajectoryAnalysis): initial[x] = [1.0, 0.1] -sim = Sim(K=[1.0, 0.1]) +sim = Sim(K=sim.K) plt.figure() plt.plot(sim.t, sim.x[0].squeeze()) From 00105639f1131bb274acba56a0699439d432a7f1 Mon Sep 17 00:00:00 2001 From: Kenneth Lyons Date: Fri, 7 Nov 2025 13:46:22 -0800 Subject: [PATCH 3/4] Don't quote re-import --- docs/howto_src/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto_src/configuration.py b/docs/howto_src/configuration.py index f22451b2..af84b5b4 100644 --- a/docs/howto_src/configuration.py +++ b/docs/howto_src/configuration.py @@ -63,7 +63,7 @@ class Sim(LTI_dblint.TrajectoryAnalysis): plt.plot(sim.t, sim.x[0].squeeze()) # %% -# We can also "re-import" the module with a different configuration: +# We can also re-import the module with a different configuration: dlbint_mod = condor.settings.get_module("_lti", A=A, B=B, bounce=True) From beb9b8b9be87c10d0eb8f5b6d4f0cf77e949b0cd Mon Sep 17 00:00:00 2001 From: Kenneth Lyons Date: Fri, 7 Nov 2025 13:59:51 -0800 Subject: [PATCH 4/4] More detailed matching between examples --- docs/howto_src/configuration.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/docs/howto_src/configuration.py b/docs/howto_src/configuration.py index af84b5b4..d7fcd902 100644 --- a/docs/howto_src/configuration.py +++ b/docs/howto_src/configuration.py @@ -96,8 +96,10 @@ class Sim(LTI_bounce.TrajectoryAnalysis): from condor.contrib import ModelTemplateType, ODESystem -def make_LTI(A, B=None, name="LTI"): - attrs = ModelTemplateType.__prepare__(name, (ODESystem,)) +def make_LTI(A, B=None): + name = "LTI" + bases = (ODESystem,) + attrs = ModelTemplateType.__prepare__(name, bases) attrs["A"] = A @@ -119,9 +121,9 @@ def make_LTI(A, B=None, name="LTI"): attrs["dot"][x] = xdot - plant = ModelTemplateType(name, (ODESystem,), attrs) + LTI = ModelTemplateType(name, bases, attrs) - return plant + return LTI # %% @@ -148,24 +150,15 @@ class Sim(LTI_dblint.TrajectoryAnalysis): def add_bounce_event(odesys_cls): - event_meta_args = ( - "Bounce", # name - (odesys_cls.Event, condor.models.Submodel), # bases - ) - event_attrs = condor.contrib.Event.__prepare__(*event_meta_args) + name = "Bounce" + bases = (odesys_cls.Event, condor.models.Submodel) + attrs = condor.contrib.Event.__prepare__(name, bases) - # extract elements to operate on x = odesys_cls.x.backend_repr + attrs["function"] = x[0] + attrs["update"][x] = ops.concat([x[0], -x[1]]) - # define the event - event_attrs["function"] = x[0] - event_attrs["update"][x] = ops.concat([x[0], -x[1]]) - - condor.contrib.EventType.__new__( - condor.contrib.EventType, - *event_meta_args, - attrs=event_attrs, - ) + condor.contrib.EventType.__new__(condor.contrib.EventType, name, bases, attrs=attrs) # %%