exerpy.schema

Formal schema and validation of the JSON model that ExerPy’s analyses read.

The JSON document fed into exerpy.ExergyAnalysis.from_json() (and produced by the TESPy, Aspen and EBSILON parsers) is described here as msgspec structs.

The schema covers only the fields ExerPy reads. The keys of each component type are declared next to the code that reads them, as the Input struct nested in the component class (see exerpy.components.component.Component); this module collects them from the component registry, so custom components are covered too. The exports also carry what the simulators report besides (efficiencies, units, fluid ids, results of an earlier analysis, …); ExerPy ignores those, and so does the validation. Each field below notes where ExerPy uses it.

Validation happens in two steps, which can also be run on their own:

  1. Structure (check_structure()), on the input as it is written. While decoding, msgspec checks the required keys, the types and the kind tag of each connection. On the decoded structs follow the rules that span several fields or sections, e.g. that each component type is registered, that no two streams enter the same inlet of a component, that split_physical_exergy has the e_T/e_M values it needs, or that each unit given ("T_unit": "degC") is known and of the right kind. A value without a unit is in SI.

  2. Values (check_values()): each value lies in its reasonable range, e.g. positive temperatures and pressures or mass fractions between 0 and 1. The ranges are the Limit of the types and hold in SI units.

The validation does not convert units. Before an analysis, ExerPy converts the values with exerpy.units.to_SI() between the two steps.

The problems of a step are collected and reported together. The values are checked once the structure is valid.

Example

>>> from exerpy.schema import validate
>>> model = validate(open("example.json", "rb").read())
>>> model.connections["1"].m
637.86...

The JSON Schema of the format is returned by json_schema().

class exerpy.schema.AmbientConditions(*, Tamb: Annotated[float, msgspec.Meta(description='Absolute temperature, in K unless a unit is given'), Limit(gt=0, ge=None, le=None)] | UnsetType = UNSET, pamb: Annotated[float, msgspec.Meta(description='Absolute pressure, in Pa unless a unit is given'), Limit(gt=0, ge=None, le=None)] | UnsetType = UNSET)[source]

Bases: Struct

Dead state of the analysis. Values may instead be passed to from_json.

class exerpy.schema.ExerpyInput(*, components: dict[str, dict[str, ~exerpy.components.component.Component.Input]], connections: dict[str, ~exerpy.schema.MaterialConnection | ~exerpy.schema.PowerConnection | ~exerpy.schema.HeatConnection | ~exerpy.schema.OtherConnection], ambient_conditions: ~exerpy.schema.AmbientConditions, settings: ~exerpy.schema.Settings = <factory>)[source]

Bases: Struct

The complete input document of an exergy analysis.

class exerpy.schema.HeatConnection(energy_flow: float | None | UnsetType = UNSET, *, source_component: str | None, target_component: str | None, source_connector: Annotated[int, msgspec.Meta(ge=0, description='0-based port index on the component')] | None = None, target_connector: Annotated[int, msgspec.Meta(ge=0, description='0-based port index on the component')] | None = None, source_component_type: int | str | None | UnsetType = UNSET)[source]

Bases: _ConnectionBase

Heat, in W. Its exergy is derived from the attached component.

exception exerpy.schema.InputValidationError(issues: list[Issue])[source]

Bases: ValueError

Raised when the input fails validation. issues holds every finding.

class exerpy.schema.Issue(severity: Literal['error', 'warning'], path: str, message: str)[source]

Bases: Struct

One validation finding, located by a JSONPath-like path.

class exerpy.schema.MaterialConnection(m: Annotated[float, msgspec.Meta(description='Mass flow, in kg/s unless a unit is given'), Limit(gt=None, ge=0, le=None)], T: Annotated[float, msgspec.Meta(description='Absolute temperature, in K unless a unit is given'), Limit(gt=0, ge=None, le=None)], e_PH: Annotated[float, msgspec.Meta(description='Specific exergy, in J/kg unless a unit is given')], h: Annotated[float, msgspec.Meta(description='Specific enthalpy, in J/kg unless a unit is given')], p: Annotated[float, msgspec.Meta(description='Absolute pressure, in Pa unless a unit is given'), Limit(gt=0, ge=None, le=None)], e_T: Annotated[float, msgspec.Meta(description='Specific exergy, in J/kg unless a unit is given')] | None | UnsetType = UNSET, e_M: Annotated[float, msgspec.Meta(description='Specific exergy, in J/kg unless a unit is given')] | None | UnsetType = UNSET, e_CH: Annotated[float, msgspec.Meta(description='Specific exergy, in J/kg unless a unit is given')] | None | UnsetType = UNSET, mass_composition: dict[str, Annotated[float, msgspec.Meta(description='Mass or molar fraction'), Limit(gt=None, ge=0, le=1)]] | UnsetType = UNSET, molar_composition: dict[str, Annotated[float, msgspec.Meta(description='Mass or molar fraction'), Limit(gt=None, ge=0, le=1)]] | UnsetType = UNSET, x: float | None | UnsetType = UNSET, v: float | None | UnsetType = UNSET, VM: float | None | UnsetType = UNSET, *, source_component: str | None, target_component: str | None, source_connector: Annotated[int, msgspec.Meta(ge=0, description='0-based port index on the component')] | None = None, target_connector: Annotated[int, msgspec.Meta(ge=0, description='0-based port index on the component')] | None = None, source_component_type: int | str | None | UnsetType = UNSET)[source]

Bases: _ConnectionBase

A stream of matter.

class exerpy.schema.OtherConnection(*, source_component: str | None, target_component: str | None, source_connector: Annotated[int, msgspec.Meta(ge=0, description='0-based port index on the component')] | None = None, target_connector: Annotated[int, msgspec.Meta(ge=0, description='0-based port index on the component')] | None = None, source_component_type: int | str | None | UnsetType = UNSET)[source]

Bases: _ConnectionBase

A logical connection (e.g. EBSILON control lines), carried but not analysed.

class exerpy.schema.PowerConnection(energy_flow: Annotated[float, msgspec.Meta(description='Power, in W unless a unit is given')], *, source_component: str | None, target_component: str | None, source_connector: Annotated[int, msgspec.Meta(ge=0, description='0-based port index on the component')] | None = None, target_connector: Annotated[int, msgspec.Meta(ge=0, description='0-based port index on the component')] | None = None, source_component_type: int | str | None | UnsetType = UNSET)[source]

Bases: _ConnectionBase

Mechanical or electrical power; its exergy equals energy_flow.

class exerpy.schema.Settings(*, split_physical_exergy: bool | None = None, chemExLib: Literal['Ahrendts'] | None = None)[source]

Bases: Struct

Analysis options. Arguments to from_json take precedence over these.

exerpy.schema.check(data: bytes | str | dict, *, Tamb: float | None = None, pamb: float | None = None, chemExLib: str | None = None, split_physical_exergy: bool | None = None) → tuple[ExerpyInput | None, list[Issue]][source]

Check the structure and then the values without raising, see validate().

Returns the model (None if invalid) and all issues.

exerpy.schema.check_structure(data: bytes | str | dict, *, Tamb: float | None = None, pamb: float | None = None, chemExLib: str | None = None, split_physical_exergy: bool | None = None) → tuple[ExerpyInput | None, list[Issue]][source]

Check the structure of the input without raising, see validate_structure().

Returns the model (None if invalid) and all issues.

exerpy.schema.check_values(data: bytes | str | dict, *, Tamb: float | None = None, pamb: float | None = None) → tuple[ExerpyInput | None, list[Issue]][source]

Check the values of the input without raising, see validate_values().

Returns the model (None if invalid) and all issues.

exerpy.schema.input_struct(component_type: str) → type[Input][source]

The Input struct of a component type, as found in the component registry.

Deprecated types resolve to the type they are read as; an unknown type, or a registered class that declares no Input, gets the base Component.Input.

exerpy.schema.json_schema() → dict[source]

JSON Schema (draft 2020-12) of the input, for editors and other tools.

components lists each component type registered when this is called, with the fields of its Input struct.

exerpy.schema.validate(data: bytes | str | dict, *, Tamb: float | None = None, pamb: float | None = None, chemExLib: str | None = None, split_physical_exergy: bool | None = None, log_warnings: bool = True) → ExerpyInput[source]

Validate an ExerPy input document and return it as typed structs.

Runs validate_structure() and, if the structure is valid, validate_values().

Parameters:
  • data (bytes, str or dict) – The JSON document, or the dictionary decoded from it (as the parsers build).

  • Tamb, pamb, chemExLib, split_physical_exergy (optional) – The overrides that will be passed to the analysis; they take precedence over the JSON, as in exerpy.ExergyAnalysis.from_json().

  • log_warnings (bool) – Log warnings through the ExerPy logger.

Returns:

ExerpyInput – The validated document. Unknown keys are not part of it; the analysis keeps working on the original dictionary.

Raises:

InputValidationError – A subclass of ValueError. issues lists every finding, warnings included.

exerpy.schema.validate_structure(data: bytes | str | dict, *, Tamb: float | None = None, pamb: float | None = None, chemExLib: str | None = None, split_physical_exergy: bool | None = None, log_warnings: bool = True) → ExerpyInput[source]

Validate the structure of an ExerPy input document, as it is written.

Checks the required keys, the types, the kind of each connection, the component types and names, the ports, the values the settings need, and the units. The values themselves are checked by validate_values(). Parameters, return value and errors are those of validate().

exerpy.schema.validate_values(data: bytes | str | dict, *, Tamb: float | None = None, pamb: float | None = None, log_warnings: bool = True) → ExerpyInput[source]

Validate that each value of an ExerPy input document lies in its reasonable range.

The ranges are the Limit of the types and hold in SI units, e.g. positive temperatures and pressures or mass fractions between 0 and 1. Mass fractions that do not add up to one give a warning. Parameters, return value and errors are those of validate(); the document has to have a valid structure.