Define and apply property sets#
A property set, or schema, is a reusable group of quantities: each field has a QUDT quantity kind, a unit, a medium, and optional bounds and default. Define one in Python for a single program, or use one compiled from an ontology. Attach quantities covers single values.
from plumb import Model, connect
from plumb.qudt import QuantityAmount as Q, quantity_kinds, units
from plumb.watr import Pump, enums
from plumb.watr.drinking_water import CoagulationStage
values = enums.values
model = Model("urn:example:property-sets#")
with model:
intake = Pump("intake-pump")
coagulation = CoagulationStage("coagulation")
raw_water = connect(intake, coagulation.inlet, name="raw-water")
Define a schema in Python#
Subclass PropertySchema and declare each field with quantity_field():
from plumb import PropertySchema, quantity_field
class InfluentQuality(PropertySchema):
"""Raw surface-water quality at the plant intake."""
flow_rate: float | Q = quantity_field(
kind=quantity_kinds.VolumeFlowRate,
unit=units.M3_PER_DAY,
label="Raw-water flow",
of_medium=values.Water_Freshwater,
aspects=(values.Aspect_Nominal,),
minimum=0,
)
turbidity: float | Q = quantity_field(
kind=quantity_kinds.Turbidity,
unit=units.NTU,
label="Raw-water turbidity",
of_medium=values.Water_Freshwater,
aspects=(values.Aspect_Nominal,),
minimum=0,
)
temperature: float | Q = quantity_field(
kind=quantity_kinds.Temperature,
unit=units.DEG_C,
label="Raw-water temperature",
of_medium=values.Water_Freshwater,
aspects=(values.Aspect_Nominal,),
default=12.0,
)
print(InfluentQuality.describe())
Raw surface-water quality at the plant intake.
flow_rate: Volume Flow Rate [Cubic Meter per Day; http://qudt.org/vocab/unit/M3-PER-DAY] required — __main__.InfluentQuality.flow_rate
medium=Water-Freshwater; aspects=Nominal
turbidity: Turbidity [Nephelometry Turbidity Unit; http://qudt.org/vocab/unit/NTU] required — __main__.InfluentQuality.turbidity
medium=Water-Freshwater; aspects=Nominal
temperature: Temperature [Degree Celsius; http://qudt.org/vocab/unit/DEG_C] default=12.0 — __main__.InfluentQuality.temperature
medium=Water-Freshwater; aspects=Nominal
The class is a frozen, keyword-only dataclass. A bare number uses the
field’s unit. A QuantityAmount keeps its own compatible unit; nothing is
converted. Bounds and units are checked on creation:
try:
InfluentQuality(flow_rate=-5, turbidity=18.0)
except ValueError as error:
print(error)
flow_rate: value must be >= 0
Apply a schema#
apply_properties() attaches one property per field and returns them by
field name:
influent = raw_water.apply_properties(
InfluentQuality(flow_rate=40_000, turbidity=Q(18.0, units.NTU))
)
dict(influent)
{'flow_rate': <QuantifiableProperty 'raw-water-flow-rate'>,
'turbidity': <QuantifiableProperty 'raw-water-turbidity'>,
'temperature': <QuantifiableProperty 'raw-water-temperature'>}
Properties are named <subject>-<field>; prefix= changes the stem. If a
name is taken, nothing is applied.
Use a schema from an ontology#
Domain packages compile property schemas from SHACL. ASM2d defines an aerobic reactor’s design values:
from plumb.watr.asm2d import AerobicDesign, AerobicStage
print(AerobicDesign.describe())
Aerobic basin volume in m³ (default 1333) and oxygen transfer coefficient KLa in h⁻¹.
volume: Volume [Cubic Meter; http://qudt.org/vocab/unit/M3] default=1333.0 — https://watermetadata.org/plumb/asm2d#AerobicVolumeShape
aspects=Nominal; property_class=QuantifiableProperty
oxygen_transfer_coefficient: frequency [Reciprocal Hour; http://qudt.org/vocab/unit/PER-HR] required — https://watermetadata.org/plumb/asm2d#OxygenTransferCoefficientShape
aspects=Nominal; property_class=QuantifiableProperty
with model:
reactor = AerobicStage("R5")
dict(reactor.apply_properties(AerobicDesign(oxygen_transfer_coefficient=10.0)))
{'volume': <QuantifiableProperty 'R5-volume'>,
'oxygen_transfer_coefficient': <QuantifiableProperty 'R5-oxygen-transfer-coefficient'>}
Start a schema in Python. Move it to SHACL when other models or tools need it; see Domain extensions.