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.