Composing Models

Now that we have learned how to specify composition patterns and primitive models, we can learn how to combine them into a composite model.

using SyntacticModels

using SyntacticModels.AMR
using SyntacticModels.ASKEMDecapodes
using SyntacticModels.ASKEMUWDs
using SyntacticModels.Composites

using MLStyle
import SyntacticModels.ASKEMDecapodes.Decapodes as Decapodes
using Catlab
using Catlab.RelationalPrograms
using Catlab.WiringDiagrams
using DiagrammaticEquations
using Test
using JSON3

draw(uwd) = to_graphviz(uwd, box_labels=:name, junction_labels=:variable)
draw (generic function with 1 method)

Specifying the Composition Pattern

We can build an Undirected Wiring Diagram to represent the pattern of composition. In this model we have 3 variables X,V,Q, which are all the same type.

x = Typed(:X, :Form0)
v = Typed(:V, :Form0)
Q = Typed(:Q, :Form0)
variables = [x,v,Q]
3-element Vector{SyntacticModels.ASKEMUWDs.Typed}:
 SyntacticModels.ASKEMUWDs.Typed(:X, :Form0)
 SyntacticModels.ASKEMUWDs.Typed(:V, :Form0)
 SyntacticModels.ASKEMUWDs.Typed(:Q, :Form0)

Our system will expose two variables namely x and Q to the outside world. These variables could be used as a basis for further composition, or measured by an observer. The system also hase two subsystems an oscillator that couples X and V and a heating element that couples V and Q.

c = [x, Q]
s = [Statement(:oscillator, [x,v]),
  Statement(:heating, [v,Q])]
u = ASKEMUWDs.UWDExpr(c, s)
{ oscillator(X:Form0, V:Form0)
  heating(V:Form0, Q:Form0) } where {X:Form0, Q:Form0}

This UWDExpr can be interpreted with Catlab as a set of tables.

u_tables = ASKEMUWDs.construct(RelationDiagram, u)
Catlab.Programs.RelationalPrograms.TypedUnnamedRelationDiagram{Symbol, Symbol, Symbol} {Box:2, Port:4, OuterPort:2, Junction:3, Type:0, Name:0, VarName:0}
Box name
1 oscillator
2 heating
Port box junction port_type
1 1 1 Form0
2 1 3 Form0
3 2 3 Form0
4 2 2 Form0
OuterPort outer_junction outer_port_type
1 1 X
2 2 Q
Junction junction_type variable
1 Form0 X
2 Form0 Q
3 Form0 V

And visualized with graphviz as a UWD drawing.

draw(u_tables)

Specifying the Component Systems

A key component of using these serialized syntactic representations is that they need to be self-describing in files. This is where the Header blocks come in.

h = AMR.Header("harmonic_oscillator",
  "modelreps.io/DecaExpr",
  "A Simple Harmonic Oscillator as a Diagrammatic Equation",
  "DecaExpr",
  "v1.0")
SyntacticModels.AMR.Header("harmonic_oscillator", "modelreps.io/DecaExpr", "A Simple Harmonic Oscillator as a Diagrammatic Equation", "DecaExpr", "v1.0")

The easiest way to write down a DecaExpr is in our DSL and calling the parser.

dexpr = DiagrammaticEquations.parse_decapode(quote
  X::Form0{Point}
  V::Form0{Point}

  k::Constant{Point}

  ∂ₜ(X) == V
  ∂ₜ(V) == -1*k*(X)
end
)
DiagrammaticEquations.decapodes.DecaExpr(DiagrammaticEquations.decapodes.Judgement[DiagrammaticEquations.decapodes.Judgement(:X, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:V, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:k, :Constant, :Point)], DiagrammaticEquations.decapodes.Equation[DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Tan(DiagrammaticEquations.decapodes.Var(:X)), DiagrammaticEquations.decapodes.Var(:V)), DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Tan(DiagrammaticEquations.decapodes.Var(:V)), DiagrammaticEquations.decapodes.Mult(DiagrammaticEquations.decapodes.Term[DiagrammaticEquations.decapodes.Lit(Symbol("-1")), DiagrammaticEquations.decapodes.Var(:k), DiagrammaticEquations.decapodes.Var(:X)]))])

That gave us the first model

d1 = ASKEMDecaExpr(h, dexpr)
SyntacticModels.ASKEMDecapodes.ASKEMDecaExpr(SyntacticModels.AMR.Header("harmonic_oscillator", "modelreps.io/DecaExpr", "A Simple Harmonic Oscillator as a Diagrammatic Equation", "DecaExpr", "v1.0"), DiagrammaticEquations.decapodes.DecaExpr(DiagrammaticEquations.decapodes.Judgement[DiagrammaticEquations.decapodes.Judgement(:X, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:V, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:k, :Constant, :Point)], DiagrammaticEquations.decapodes.Equation[DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Tan(DiagrammaticEquations.decapodes.Var(:X)), DiagrammaticEquations.decapodes.Var(:V)), DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Tan(DiagrammaticEquations.decapodes.Var(:V)), DiagrammaticEquations.decapodes.Mult(DiagrammaticEquations.decapodes.Term[DiagrammaticEquations.decapodes.Lit(Symbol("-1")), DiagrammaticEquations.decapodes.Var(:k), DiagrammaticEquations.decapodes.Var(:X)]))]), SyntacticModels.AMR.Annotation{Symbol, Symbol}[])

The second model is:

d2 = ASKEMDecaExpr(
  AMR.Header("fricative_heating",
   "modelreps.io/SummationDecapode",
   "Velocity makes it get hot, but you dissipate heat away from Q₀",
   "SummationDecapode", "v1.0"),
    DiagrammaticEquations.parse_decapode(quote
      V::Form0{Point}
      Q::Form0{Point}
      κ::Constant{Point}
      λ::Constant{Point}
      Q₀::Parameter{Point}

      ∂ₜ(Q) == κ*V + λ(Q - Q₀)
    end)
)
SyntacticModels.ASKEMDecapodes.ASKEMDecaExpr(SyntacticModels.AMR.Header("fricative_heating", "modelreps.io/SummationDecapode", "Velocity makes it get hot, but you dissipate heat away from Q₀", "SummationDecapode", "v1.0"), DiagrammaticEquations.decapodes.DecaExpr(DiagrammaticEquations.decapodes.Judgement[DiagrammaticEquations.decapodes.Judgement(:V, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:Q, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:κ, :Constant, :Point), DiagrammaticEquations.decapodes.Judgement(:λ, :Constant, :Point), DiagrammaticEquations.decapodes.Judgement(:Q₀, :Parameter, :Point)], DiagrammaticEquations.decapodes.Equation[DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Tan(DiagrammaticEquations.decapodes.Var(:Q)), DiagrammaticEquations.decapodes.Plus(DiagrammaticEquations.decapodes.Term[DiagrammaticEquations.decapodes.App2(:*, DiagrammaticEquations.decapodes.Var(:κ), DiagrammaticEquations.decapodes.Var(:V)), DiagrammaticEquations.decapodes.App1(:λ, DiagrammaticEquations.decapodes.App2(:-, DiagrammaticEquations.decapodes.Var(:Q), DiagrammaticEquations.decapodes.Var(:Q₀)))]))]), SyntacticModels.AMR.Annotation{Symbol, Symbol}[])

Now we can assemble this bad boi:

h = AMR.Header("composite_physics", "modelreps.io/Composite", "A composite model", "CompositeModelExpr", "v0.0")
m = CompositeModelExpr(h, u, [OpenModel(d1, [:X, :V]), OpenModel(d2, [:V, :Q])])
@test interface(m) == [:X, :Q]
Test Passed

The CompositeModelExpr is a tree that stores the composition pattern and the models that are to be composed. You can see from this little model (just two coupled odes) that the json output will not be human writeable. This is why we need a library for Syntactic Model representations.

JSON3.pretty(m, JSON3.AlignmentContext(indent=2))
{
  "components": [
    {
      "interface": [
        "X",
        "V"
      ],
      "_type": "OpenModel",
      "model": {
        "annotations": [
        ],
        "header": {
          "description": "A Simple Harmonic Oscillator as a Diagrammatic Equation",
          "name": "harmonic_oscillator",
          "_type": "Header",
          "model_version": "v1.0",
          "schema": "modelreps.io/DecaExpr",
          "schema_name": "DecaExpr"
        },
        "_type": "ASKEMDecaExpr",
        "model": {
          "context": [
            {
              "var": "X",
              "dim": "Form0",
              "space": "Point"
            },
            {
              "var": "V",
              "dim": "Form0",
              "space": "Point"
            },
            {
              "var": "k",
              "dim": "Constant",
              "space": "Point"
            }
          ],
          "_type": "DecaExpr",
          "equations": [
            {
              "rhs": {
                "name": "V",
                "_type": "Var"
              },
              "lhs": {
                "var": {
                  "name": "X",
                  "_type": "Var"
                },
                "_type": "Tan"
              },
              "_type": "Eq"
            },
            {
              "rhs": {
                "args": [
                  {
                    "name": "-1",
                    "_type": "Lit"
                  },
                  {
                    "name": "k",
                    "_type": "Var"
                  },
                  {
                    "name": "X",
                    "_type": "Var"
                  }
                ],
                "_type": "Mult"
              },
              "lhs": {
                "var": {
                  "name": "V",
                  "_type": "Var"
                },
                "_type": "Tan"
              },
              "_type": "Eq"
            }
          ]
        }
      }
    },
    {
      "interface": [
        "V",
        "Q"
      ],
      "_type": "OpenModel",
      "model": {
        "annotations": [
        ],
        "header": {
          "description": "Velocity makes it get hot, but you dissipate heat away from Q₀",
          "name": "fricative_heating",
          "_type": "Header",
          "model_version": "v1.0",
          "schema": "modelreps.io/SummationDecapode",
          "schema_name": "SummationDecapode"
        },
        "_type": "ASKEMDecaExpr",
        "model": {
          "context": [
            {
              "var": "V",
              "dim": "Form0",
              "space": "Point"
            },
            {
              "var": "Q",
              "dim": "Form0",
              "space": "Point"
            },
            {
              "var": "κ",
              "dim": "Constant",
              "space": "Point"
            },
            {
              "var": "λ",
              "dim": "Constant",
              "space": "Point"
            },
            {
              "var": "Q₀",
              "dim": "Parameter",
              "space": "Point"
            }
          ],
          "_type": "DecaExpr",
          "equations": [
            {
              "rhs": {
                "args": [
                  {
                    "f": "*",
                    "arg1": {
                      "name": "κ",
                      "_type": "Var"
                    },
                    "_type": "App2",
                    "arg2": {
                      "name": "V",
                      "_type": "Var"
                    }
                  },
                  {
                    "f": "λ",
                    "arg": {
                      "f": "-",
                      "arg1": {
                        "name": "Q",
                        "_type": "Var"
                      },
                      "_type": "App2",
                      "arg2": {
                        "name": "Q₀",
                        "_type": "Var"
                      }
                    },
                    "_type": "App1"
                  }
                ],
                "_type": "Plus"
              },
              "lhs": {
                "var": {
                  "name": "Q",
                  "_type": "Var"
                },
                "_type": "Tan"
              },
              "_type": "Eq"
            }
          ]
        }
      }
    }
  ],
  "composition_pattern": {
    "statements": [
      {
        "variables": [
          {
            "var": "X",
            "type": "Form0",
            "_type": "Typed"
          },
          {
            "var": "V",
            "type": "Form0",
            "_type": "Typed"
          }
        ],
        "_type": "Statement",
        "relation": "oscillator"
      },
      {
        "variables": [
          {
            "var": "V",
            "type": "Form0",
            "_type": "Typed"
          },
          {
            "var": "Q",
            "type": "Form0",
            "_type": "Typed"
          }
        ],
        "_type": "Statement",
        "relation": "heating"
      }
    ],
    "context": [
      {
        "var": "X",
        "type": "Form0",
        "_type": "Typed"
      },
      {
        "var": "Q",
        "type": "Form0",
        "_type": "Typed"
      }
    ],
    "_type": "UWDExpr"
  },
  "header": {
    "description": "A composite model",
    "name": "composite_physics",
    "_type": "Header",
    "model_version": "v0.0",
    "schema": "modelreps.io/Composite",
    "schema_name": "CompositeModelExpr"
  },
  "_type": "CompositeModelExpr"
}

We can interpret this big data structure to execute a composition! Notice how the variables in the composite model are namespaced with the subsystem they came from. The coupled variables get their names from the UWD and thus live in the top level namespace.

composite = oapply(m)
display(apex(composite))
to_graphviz(apex(composite))
Example block output

Important: because the oapply algorithm operates on the compute graph representation of the equations, it does not produce syntactic equations. Calls to oapply produce instances of OpenDecapode and not DecaExpr. Software that expects to consume decapodes should plan to interact with both forms.

Nested Composition

In this section we will build a model that is a composite of composites of models. This demonstrates that the Decapodes system can recursively compose multiphysics models.

Q₊ = Untyped(:Q₊)
Q₋ = Untyped(:Q₋)
Q̇ = Untyped(:Q̇)
uwdʰ = UWDExpr([v, Q], [Statement(:drag, [v, Q₊]), Statement(:cooling, [Q₋, Q]), Statement(:superposition, [Q₊, Q₋, Q̇])])
{ drag(V:Form0, Q₊)
  cooling(Q₋, Q:Form0)
  superposition(Q₊, Q₋, Q̇) } where {V:Form0, Q:Form0}

Our new model has 3 subsystems, drag, cooling, and superposition. A key innovation of decapodes is to realize that even simple systems like drag are actually multiphysical, they have some term that represents a force, and some term that determines how that force changes the state of the system. In order to write models compositionally, you must first break them down into their atomic subsystems, which are smaller than you think.

Our three primitive subsystems are each composed of one equation. Of course at this scale of complexity, you don't need to do compositional specification, you can just compose them in your head and write down the composite. But this is a tutorial, so we are building a very simple model as a composite of atomic models (one equation each).

drag = ASKEMDecaExpr(
  AMR.Header("DragHeat", "modelreps.io/SummationDecapode", "velocity makes it get hot", "SummationDecapode", "v1.0"),
  DiagrammaticEquations.parse_decapode(quote
    V::Form0{Point}
    Q₊::Form0{Point}
    κ::Constant{Point}

    Q₊ == κ*V
  end)
)

cooling = ASKEMDecaExpr(
  AMR.Header("NetwonCooling", "modelreps.io/SummationDecapode", "heat dissipates to the enviornment", "SummationDecapode", "v1.0"),
  DiagrammaticEquations.parse_decapode(quote
    Q₋::Form0{Point}
    Q₀::Parameter{Point}
    Q::Form0{Point}
    λ::Constant{Point}

    Q₋ == λ(Q-Q₀)
  end)
)

superposition = ASKEMDecaExpr(
  AMR.Header("LinearSuperpositon", "modelreps.io/SummationDecapode", "variables be addin", "SummationDecapode", "v1.0"),
  DiagrammaticEquations.parse_decapode(quote
    X::Form0{Point}
    Y::Form0{Point}
    T::Form0{Point}

    T == X + Y
  end)
)
SyntacticModels.ASKEMDecapodes.ASKEMDecaExpr(SyntacticModels.AMR.Header("LinearSuperpositon", "modelreps.io/SummationDecapode", "variables be addin", "SummationDecapode", "v1.0"), DiagrammaticEquations.decapodes.DecaExpr(DiagrammaticEquations.decapodes.Judgement[DiagrammaticEquations.decapodes.Judgement(:X, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:Y, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:T, :Form0, :Point)], DiagrammaticEquations.decapodes.Equation[DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Var(:T), DiagrammaticEquations.decapodes.Plus(DiagrammaticEquations.decapodes.Term[DiagrammaticEquations.decapodes.Var(:X), DiagrammaticEquations.decapodes.Var(:Y)]))]), SyntacticModels.AMR.Annotation{Symbol, Symbol}[])

The CompositeModelExpr type can store recursive model descriptions in terms of compositions of composite models.

h = AMR.Header("hierarchical_composite", "modelreps.io/Composite", "A hierarchical composite model of frictional heating", "CompositeModelExpr", "v0.1")
m = CompositeModelExpr(h,u, [OpenModel(d1, [:X, :V]),
      CompositeModelExpr(AMR.Header("heating_dynamics", "modelreps.io/Composite", "A formula for heating - cooling", "CompositeModelExpr", "v0.1"),
        uwdʰ, [OpenModel(drag, [:V, :Q₊]), OpenModel(cooling, [:Q₋, :Q]), OpenModel(superposition, [:X, :Y, :T])])
])
SyntacticModels.Composites.CompositeModelExpr(SyntacticModels.AMR.Header("hierarchical_composite", "modelreps.io/Composite", "A hierarchical composite model of frictional heating", "CompositeModelExpr", "v0.1"), { oscillator(X:Form0, V:Form0)
  heating(V:Form0, Q:Form0) } where {X:Form0, Q:Form0}, SyntacticModels.Composites.CompositeModel[SyntacticModels.Composites.OpenModel(SyntacticModels.ASKEMDecapodes.ASKEMDecaExpr(SyntacticModels.AMR.Header("harmonic_oscillator", "modelreps.io/DecaExpr", "A Simple Harmonic Oscillator as a Diagrammatic Equation", "DecaExpr", "v1.0"), DiagrammaticEquations.decapodes.DecaExpr(DiagrammaticEquations.decapodes.Judgement[DiagrammaticEquations.decapodes.Judgement(:X, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:V, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:k, :Constant, :Point)], DiagrammaticEquations.decapodes.Equation[DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Tan(DiagrammaticEquations.decapodes.Var(:X)), DiagrammaticEquations.decapodes.Var(:V)), DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Tan(DiagrammaticEquations.decapodes.Var(:V)), DiagrammaticEquations.decapodes.Mult(DiagrammaticEquations.decapodes.Term[DiagrammaticEquations.decapodes.Lit(Symbol("-1")), DiagrammaticEquations.decapodes.Var(:k), DiagrammaticEquations.decapodes.Var(:X)]))]), SyntacticModels.AMR.Annotation{Symbol, Symbol}[]), [:X, :V]), SyntacticModels.Composites.CompositeModelExpr(SyntacticModels.AMR.Header("heating_dynamics", "modelreps.io/Composite", "A formula for heating - cooling", "CompositeModelExpr", "v0.1"), { drag(V:Form0, Q₊)
  cooling(Q₋, Q:Form0)
  superposition(Q₊, Q₋, Q̇) } where {V:Form0, Q:Form0}, SyntacticModels.Composites.CompositeModel[SyntacticModels.Composites.OpenModel(SyntacticModels.ASKEMDecapodes.ASKEMDecaExpr(SyntacticModels.AMR.Header("DragHeat", "modelreps.io/SummationDecapode", "velocity makes it get hot", "SummationDecapode", "v1.0"), DiagrammaticEquations.decapodes.DecaExpr(DiagrammaticEquations.decapodes.Judgement[DiagrammaticEquations.decapodes.Judgement(:V, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:Q₊, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:κ, :Constant, :Point)], DiagrammaticEquations.decapodes.Equation[DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Var(:Q₊), DiagrammaticEquations.decapodes.App2(:*, DiagrammaticEquations.decapodes.Var(:κ), DiagrammaticEquations.decapodes.Var(:V)))]), SyntacticModels.AMR.Annotation{Symbol, Symbol}[]), [:V, :Q₊]), SyntacticModels.Composites.OpenModel(SyntacticModels.ASKEMDecapodes.ASKEMDecaExpr(SyntacticModels.AMR.Header("NetwonCooling", "modelreps.io/SummationDecapode", "heat dissipates to the enviornment", "SummationDecapode", "v1.0"), DiagrammaticEquations.decapodes.DecaExpr(DiagrammaticEquations.decapodes.Judgement[DiagrammaticEquations.decapodes.Judgement(:Q₋, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:Q₀, :Parameter, :Point), DiagrammaticEquations.decapodes.Judgement(:Q, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:λ, :Constant, :Point)], DiagrammaticEquations.decapodes.Equation[DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Var(:Q₋), DiagrammaticEquations.decapodes.App1(:λ, DiagrammaticEquations.decapodes.App2(:-, DiagrammaticEquations.decapodes.Var(:Q), DiagrammaticEquations.decapodes.Var(:Q₀))))]), SyntacticModels.AMR.Annotation{Symbol, Symbol}[]), [:Q₋, :Q]), SyntacticModels.Composites.OpenModel(SyntacticModels.ASKEMDecapodes.ASKEMDecaExpr(SyntacticModels.AMR.Header("LinearSuperpositon", "modelreps.io/SummationDecapode", "variables be addin", "SummationDecapode", "v1.0"), DiagrammaticEquations.decapodes.DecaExpr(DiagrammaticEquations.decapodes.Judgement[DiagrammaticEquations.decapodes.Judgement(:X, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:Y, :Form0, :Point), DiagrammaticEquations.decapodes.Judgement(:T, :Form0, :Point)], DiagrammaticEquations.decapodes.Equation[DiagrammaticEquations.decapodes.Eq(DiagrammaticEquations.decapodes.Var(:T), DiagrammaticEquations.decapodes.Plus(DiagrammaticEquations.decapodes.Term[DiagrammaticEquations.decapodes.Var(:X), DiagrammaticEquations.decapodes.Var(:Y)]))]), SyntacticModels.AMR.Annotation{Symbol, Symbol}[]), [:X, :Y, :T])])])

The oapply function will recursively descend the tree to assemble a flat model with hierarchical namespacing.

dh = apex(oapply(m))
DiagrammaticEquations.decapodeacset.SummationDecapode{Any, Any, Symbol} {Var:14, TVar:2, Op1:3, Op2:4, Σ:1, Summand:2, Type:0, Operator:0, Name:0}
Var type name
1 Form0 X
2 Form0 V
3 Constant oscillator_k
4 infer oscillator_mult_1
5 infer oscillator_V̇
6 Literal -1
7 Form0 heating_Q₊
8 Constant heating_drag_κ
9 Form0 heating_Q₋
10 Parameter heating_cooling_Q₀
11 Form0 Q
12 Constant heating_cooling_λ
13 infer heating_cooling_•1
14 Form0 heating_Q̇
TVar incl
1 2
2 5
Op1 src tgt op1
1 1 2 ∂ₜ
2 2 5 ∂ₜ
3 13 9 λ
Op2 proj1 proj2 res op2
1 6 3 4 *
2 4 1 5 *
3 8 2 7 *
4 11 10 13 -
Σ sum
1 14
Summand summand summation
1 7 1
2 9 1

This model can also be drawn as a decapode.

to_graphviz(dh)
Example block output

The new model description needs to be written by hand for the new model header. Some annotation in the description can't be avoided, yet.

composite = OpenDecapode(m)
hf = composite.model.header
ASKEMDecapode(Header("flattened_composite", hf.schema, "A flattened version of the composite_physics model.", hf.schema_name, hf.model_version), composite.model.model)
SyntacticModels.ASKEMDecapodes.ASKEMDecapode(SyntacticModels.AMR.Header("flattened_composite", "modelreps.io/Composite", "A flattened version of the composite_physics model.", "CompositeModelExpr", "v0.1"), DiagrammaticEquations.decapodeacset.SummationDecapode{Any, Any, Symbol}:
  Var = 1:14
  TVar = 1:2
  Op1 = 1:3
  Op2 = 1:4
  Σ = 1:1
  Summand = 1:2
  Type = 1:0
  Operator = 1:0
  Name = 1:0
  src : Op1 → Var = [1, 2, 13]
  tgt : Op1 → Var = [2, 5, 9]
  proj1 : Op2 → Var = [6, 4, 8, 11]
  proj2 : Op2 → Var = [3, 1, 2, 10]
  res : Op2 → Var = [4, 5, 7, 13]
  incl : TVar → Var = [2, 5]
  summand : Summand → Var = [7, 9]
  summation : Summand → Σ = [1, 1]
  sum : Σ → Var = [14]
  op1 : Op1 → Operator = [:∂ₜ, :∂ₜ, :λ]
  op2 : Op2 → Operator = [:*, :*, :*, :-]
  type : Var → Type = [:Form0, :Form0, :Constant, :infer, :infer, :Literal, :Form0, :Constant, :Form0, :Parameter, :Form0, :Constant, :infer, :Form0]
  name : Var → Name = [:X, :V, :oscillator_k, :oscillator_mult_1, :oscillator_V̇, Symbol("-1"), :heating_Q₊, :heating_drag_κ, :heating_Q₋, :heating_cooling_Q₀, :Q, :heating_cooling_λ, Symbol("heating_cooling_•1"), :heating_Q̇], SyntacticModels.AMR.Annotation{Symbol, Symbol}[])