System

The System class provides a data store for components and time series data.

Refer to the System API for complete information.

Items to consider for parent packages

Composition vs Inheritance

Parent packages must choose one of the following:

  1. Derive a custom System class that inherits from infrasys.System. Re-implement methods as desired. Add custom attributes to the System that will be serialized to JSON.

    • Reimplement System.add_components in order to perform custom validation or custom behavior. This is only needed for validation that needs information from both the system and the component. Note that the System constructor provides the keyword argument auto_add_composed_components that dictates how to handle the condition where a component contains another component which is not already attached to the system.

    • Reimplement System.serialize_system_attributes and System.deserialize_system_attributes. infrasys will call those methods during to_json and from_json and serialize/de-serialize the contents.

    • Reimplement System.data_format_version and System.handle_data_format_upgrade. infrasys will call the upgrade function if it detects a version change during de-serialization.

  2. Implement an independent System class and compose the infrasys.System. This can be beneficial if you want to make the underlying system opaque to users.

    • This pattern requires that you call System.to_json() with the keyword argument data set to a dictionary containing your system’s attributes. infrasys will add its contents to a field called system inside that dictionary.

  3. Use infrasys.System directly. This is probably not what most packages want because they will not be able to serialize custom attributes or implement specialized behavior as discussed above.

Units

infrasys uses the pint library to help manage units. Package developers should consider storing fields that are quantities as subtypes of Base.Quantity. Pint performs unit conversion automatically when performing arithmetic.

If you want to be able to generate JSON schema for a model that contains a Pint quantity, you must add an annotation as shown below. Otherwise, Pydantic will raise an exception.

from pydantic import WithJsonSchema
from infrasys import Component

class ComponentWithPintQuantity(Component):

    distance: Annotated[Distance, WithJsonSchema({"type": "string"})]

Component.model_json_schema()

Notes:

  • infrasys includes some basic quantities in infrasys.quantities.

  • Pint will automatically convert a list or list of lists of values into a numpy.ndarray. infrasys will handle serialization/de-serialization of these types.

Component Associations

The system tracks associations between components in order to optimize lookups.

For example, suppose a Generator class has a field for a Bus. It is trivial to find a generator’s bus. However, if you need to find all generators connected to specific bus, you would have to traverse all generators in the system and check their bus values.

Every time you add a component to a system, infrasys inspects the component type for composed components. It checks for directly connected components, such as Generator.bus, and lists of components. (It does not inspect other composite data structures like dictionaries.)

infrasys stores these component associations in a SQLite table and so lookups are fast.

Here is how to complete this example:

generators = system.list_parent_components(bus)

If you only want to find specific types, you can pass that type as well.

generators = system.list_parent_components(bus, component_type=Generator)

Warning: There is one potentially problematic case.

Suppose that you have a system with generators and buses and then reassign the buses, as in

gen1.bus = other_bus

infrasys cannot detect such reassignments and so the component associations will be incorrect. You must inform infrasys to rebuild its internal table.

system.rebuild_component_associations()