How connections are resolved#
In S223 and WaTr, one connection is several resources: a connection point on
each side, a Connection such as a Pipe, their media, and inverse links.
connect(a, b) records only the intention. Resolution builds the rest with
the Z3 solver, using constraints compiled from the SHACL shapes.
Points come from the shapes#
A class’s shapes say which points it needs: how many, which direction, and which media. The compiler turns them into a port contract:
from plumb.watr import Clarifier
print(Clarifier.describe_ports())
Clarifier port requirements
Unconditional ports:
- direction="out" -> OutletConnectionPoint (count 2..any); medium roots: Mix-Fluid
Shape constraints:
- all of:
- direction="out" -> OutletConnectionPoint (count 1..any); medium roots: Fluid-Sludge
- direction="out" -> OutletConnectionPoint (count 1..any); medium roots: Fluid-Water
Points with a fixed medium are created with the equipment. The rest wait for resolution.
Requirements are minimums. A
Clarifierwhose shapes list only outlets still gets an inlet when something connects into it. A maximum on the total still applies.The allowed media on a side form a domain. Every S223
Faninlet carries air, soconnect(damper, fan)needs nomedium=.A port declared with
.port(..., medium=...)overrides the domain.sh:and,sh:or, andsh:xonestay as alternatives for the solver to choose between.
What the solver decides#
Each group of connected equipment is solved separately and cached until the model changes. Z3 chooses which points exist, their classes, which point each connection uses, each connection’s medium, and the connection class, subject to:
the shapes’ minimum and maximum counts, and their
and/or/xonestructure;an outlet connects to an inlet (or a bidirectional point), and a point holds one connection;
each point’s medium is within its allowed domain, and a connection’s medium matches both of its points;
a property’s
of_mediumis compatible with the medium of the pipe or point it is on (223P’s “Incompatible Medium” rule);anything you stated:
medium=, a named point or port,connection=, or a paired port.
Media form a hierarchy: Water_ChilledWater satisfies a Fluid_Water
domain.
Among valid answers, Z3 prefers fewer medium changes: two ports on one device prefer the same medium when their domains overlap. This is a preference, not a claim that equipment preserves its medium.
A property without of_medium takes the resolved medium of its host. A
property with one only has to be compatible: the same medium, an ancestor or
descendant, or a mixture sharing a constituent. Water and sludge share H₂O,
so this rarely separates them.
The medium picks the connection class: water, gas, and oil give a Pipe,
and air a Duct, unless connection= says otherwise.
Ambiguity is an error#
If more than one answer remains, resolution raises AmbiguousModelError
rather than choose. A lone pump-to-pump connection is the simplest case:
nothing says what flows. A model may be undecided while you author it, but
RDF is only written for one interpretation. Contradictions raise
UnsatisfiableModelError, listing the conflicting facts.
explain() shows each decision:
from plumb import Model, connect
from plumb.watr import Pump
from plumb.watr.drinking_water import CoagulationStage
with Model("urn:example:resolution#") as model:
pump = Pump("intake-pump")
coagulation = CoagulationStage("coagulation")
raw_water = connect(pump, coagulation, name="raw-water")
resolved = model.resolve()
resolved.explain(raw_water)
raw-water: intake-pump-out -> coagulation-inlet
medium: Fluid-Water (explicit port)
- selected OutletConnectionPoint intake-pump-out
- selected InletConnectionPoint coagulation-inlet
- coagulation-inlet.medium = Fluid-Water
No medium was given. The coagulation inlet has a fixed water medium, and the pump accepts water. The pipe in the resolved RDF:
from rdflib import URIRef
graph = resolved.graph()
pipe = resolved.connection(raw_water)
pipe_rdf = graph.cbd(URIRef(pipe.meta.instance_iri))
pipe_rdf.namespace_manager = graph.namespace_manager
print(pipe_rdf.serialize(format="turtle"))
@prefix : <urn:example:resolution#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix s223: <http://data.ashrae.org/standard223#> .
:raw-water a s223:Pipe ;
rdfs:label "raw-water" ;
s223:connectsAt :coagulation-inlet,
:intake-pump-out ;
s223:hasMedium s223:Fluid-Water .
Required properties that require each other#
A SHACL minCount 1 usually becomes a required constructor argument, with
two exceptions:
A requirement the class itself fixes, such as a sedimentation tank’s settling process, is filled in.
A cycle is left to validation. An S223
Junctionrequires a connection point, and a point requires its owner, soJunction("j", medium=...)works and its points are added during resolution.
Where validation fits#
The solver fixes topology and media before RDF exists. Validation then checks the RDF with shifty. A resolved model has no violations from its connection pattern. See Model lifecycle.