Custom System

This tutorial describes how to create and use a custom system in a parent package.

  1. Define the system. This example defines some custom attributes to illustrate serialization and de-serialization behaviors.

from typing import Any

from infrasys import System

class CustomSystem(System):
    """Custom System"""

    def __init__(self, my_attribute=5, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.data_format_version = "1.1.0"
        self.my_attribute = my_attribute

    def serialize_system_attributes(self) -> dict[str, Any]:
        return {"my_attribute": self.my_attribute}

    def deserialize_system_attributes(self, data: dict[str, Any]) -> None:
        self.my_attribute = data["my_attribute"]

    def handle_data_format_upgrade(self, data: dict[str, Any], from_version, to_version)) -> None:
        ...

Notes:

  • The system’s custom attribute my_attribute will be serialized and de-serialized automatically.

  • infrasys will call handle_data_format_upgrade during de-serialization so that this package can handle format changes that might occur in the future.

  1. Define some component classes.

from uuid import UUID
from infrasys.component import  Component
from infrasys.location import Location

class Bus(Component):
    """Represents a bus."""

    voltage: float
    coordinates: Location | None = None

    @classmethod
    def example(cls) -> "Bus":
        return Bus(
            name="my-bus",
            voltage=1.1,
            coordinates=Location(x=0.0, y=0.0),
        )

class Generator(Component):
    """Represents a generator."""

    available: bool
    bus: Bus
    active_power: float
    rating: float

    @classmethod
    def example(cls) -> "Generator":
        return Generator(
            name="simple-gen",
            available=True,
            bus=Bus.example(),
            active_power=1.0,
            rating=0.0,
        )

Notes:

  • Each component defines the example method. This is highly recommended so that users can see what a component might look like in the REPL.

  • The Bus class implements a custom check when it is added to the system. It raises an exception if its Location object is not already attached to the system. The same could be done for generators and buses.

  1. Build a system.

import random
from datetime import datetime, timedelta

from infrasys.location import Location
from infrasys.time_series_models import SingleTimeSeries

system = CustomSystem(name="my_system", my_attribute=10)
location = Location(x=0.0, y=0.0)
bus = Bus(name="bus1", voltage=1.1, coordinates=location)
gen = Generator(name="gen1", available=True, bus=bus, active_power=1.2, rating=1.1)
system.add_components(location, bus, gen)
time_series = SingleTimeSeries.from_array(
    data=[random.random() for x in range(24)],
    variable_name="active_power",
    initial_time=datetime(year=2030, month=1, day=1),
    resolution=timedelta(hours=1),
)
system.add_time_series(time_series, gen)
  1. Serialize and de-serialize the system.

system.to_json("system.json")
system2 = CustomSystem.from_json("system.json")
assert system.get_component(Generator, "gen1").active_power == \
    system2.get_component(Generator, "gen1").active_power