Skip to content

rebase-energy/EnergyDataModel

Repository files navigation

EnergyDataModel

πŸ”‹ Represent energy systems as Python data classes for improved modularity and readability

License: MIT PyPI version Join us on Slack All Contributors GitHub Repo stars

EnergyDataModel provides an open-source, Python-based data model that enables energy data scientists and modellers to write more modular and readable code. EnergyDataModel lets you:

  • 🧱 Modularity - Represent energy assets, energy systems and other relevant concepts as object-oriented building blocks;
  • πŸ—οΈ Relationships - Structure your energy assets in graphs and hierarchies representing energy systems that can be serialized to files (e.g. .csv, .json, and .geojson files);
  • πŸ€“ Readability - Write more explicit Python code through human-readable expressions and built-in convenience methods;
  • 🧩 Interoperability - Convert data format to other energy-relevant data models and ontologies; and
  • πŸ’¬ Communicate - Communicate effectively in teams with a common energy system data vocabulary.

⬇️ Installation  |  πŸ“– Documentation  |  πŸš€ Try out now in Colab  |  πŸ‘‹ Join Slack Community

Class hierarchy

Everything in EnergyDataModel inherits from a single root, Element, which carries identity (id: UUID, name), a list of attached TimeSeries (typically metadata-only β€” declarations of the series this element exposes), an optional shapely geometry, and an extra dict for ad-hoc JSON-scalar fields. Identity is a UUID7 generated at construction time β€” stable across renames, round-trips through JSON, and (when paired with EnergyDB) sits as the row primary key in PostgreSQL.

Three sibling subtrees specialize Element, plus an Asset mixin:

  • Node β€” graph vertices (equipment, areas, sensors, grid topology points). Adds members and tz.
  • Edge β€” relationships between two nodes (lines, transformers, interconnectors). Adds from_element, to_element, directed. Endpoints are Reference[T] objects holding the target's UUID.
  • Collection β€” logical groupings (Portfolio, Site, ...). Adds members and tz. Not a graph vertex β€” isinstance(portfolio, Node) is False.
  • Asset β€” cross-cutting mixin marking physical equipment. Adds commissioning_date. Mixed into Node via NodeAsset and into Edge via EdgeAsset; never used as a leaf type.

System-structural classes (containers, areas, networks, bases, utilities) live flat at edm.X. Technology-specific equipment lives under sub-namespaces β€” edm.solar.PVSystem, edm.wind.WindTurbine, edm.grid.Line, etc.

Namespace Data Classes
🧱 edm (core) Element, Node, Edge
🏷️ edm (bases) Asset, NodeAsset, GridNode, Sensor
β˜€οΈΒ edm.solar FixedMount, SingleAxisTrackerMount, PVArray, PVSystem, SolarPowerArea
🌬️ edm.wind WindTurbine, WindFarm, WindPowerArea
πŸ”‹Β edm.battery Battery
πŸ’¦Β edm.hydro Reservoir, HydroTurbine, HydroPowerPlant
♻️ edm.heatpump HeatPump
🏠 edm.building Building, House
🌑️ edm.weather TemperatureSensor, WindSpeedSensor, RadiationSensor, RainSensor, HumiditySensor
⚑ edm.grid nodes: JunctionPoint, Meter, DeliveryPoint, Transformer
edges: Line, Link, Pipe, Interconnection, EdgeAsset
other: Carrier, SubNetwork, Network
πŸ—ΊοΈΒ edm (area) Area, BiddingZone, Country, ControlArea, WeatherCell, SynchronousArea
πŸ“¦Β edm (containers) Collection, Portfolio, Site, MultiSite, Region, EnergyCommunity, VirtualPowerPlant
πŸ“ˆΒ edm (constructors) electricity_supply, electricity_demand, electricity_supply_area, electricity_demand_area, spot_price, cross_border_flow, grid_frequency, temperature, gas_supply, gas_demand, heating_demand

Explore the data model visually here.
Read the full documentation here.

Purpose and Philosphy

The aim of EnergyDataModel is to provide the energy data and modelling community with a Python-based open-source tool to enable improvement of software engineering aspects like code quality, maintainability, modularity, reusability and interoperability. We believe that bringing more rigorous software engineering practices to the energy data community has the potential to radically improve productivity, collaboration and usefulness of software tools, utimately leading to better energy decisions.

Our philosophy is aligned on usefulness and practicality over maximizing execution performance or some kind of esoteric theoretical rigor. A well-know quote by Abelson & Sussman comes to mind:

"Programs [software] are meant to be read by humans and only incidentally for computers to execute"

Making code explicit, readable and intuitive counts.

If you are interested in joining our mission to build open-source tools that improve productiveness and workflow of energy modellers worldwide - then join our Slack!

Basic usage

Create an energy system made up of two sites with co-located solar, wind and batteries and save as a JSON-file.

import energydatamodel as edm
from shapely.geometry import Point

pvsystem_1 = edm.solar.PVSystem(name="PV-1", capacity=2400, surface_azimuth=180, surface_tilt=25)
windturbine_1 = edm.wind.WindTurbine(name="WT-1", capacity=3200, hub_height=120, rotor_diameter=100)
battery_1 = edm.battery.Battery(name="B-1", storage_capacity=1000, min_soc=150, max_charge=500, max_discharge=500)

site_1 = edm.Site(name="Site-1",
                  geometry=Point(64, 46),  # (lon, lat)
                  members=[pvsystem_1, windturbine_1, battery_1])

pvsystem_2 = edm.solar.PVSystem(name="PV-2", capacity=2400, surface_azimuth=180, surface_tilt=25)
windturbine_2 = edm.wind.WindTurbine(name="WT-2", capacity=3200, hub_height=120, rotor_diameter=100)
battery_2 = edm.battery.Battery(name="B-2", storage_capacity=1000, min_soc=150, max_charge=500, max_discharge=500)

site_2 = edm.Site(name="Site-2",
                  geometry=Point(58, 51),
                  members=[pvsystem_2, windturbine_2, battery_2])

portfolio = edm.Portfolio(name="My Portfolio", members=[site_1, site_2])

portfolio.to_json()

Modeling a synchronous area with grid frequency

import energydatamodel as edm

nsa = edm.SynchronousArea(
    name="NSA",
    nominal_frequency=50.0,
    members=[
        edm.BiddingZone(name="SE-SE1"),
        edm.BiddingZone(name="SE-SE2"),
        edm.BiddingZone(name="NO1"),
        edm.BiddingZone(name="FI"),
    ],
    timeseries=[edm.grid_frequency()],
)

For more examples on usage and applications of EnergyDataModel see the documentation examples page here.

Extending EnergyDataModel

EDM is built to be extended. To add your own asset type, just inherit from the closest base β€” usually edm.NodeAsset for physical equipment, edm.grid.EdgeAsset for things connecting two nodes, or edm.Element for anything else. Use @dataclass(repr=False, kw_only=True) to match the rest of the model.

from dataclasses import dataclass
import energydatamodel as edm

@dataclass(repr=False, kw_only=True)
class Electrolyzer(edm.NodeAsset):
    capacity_kw: float | None = None
    efficiency: float | None = None

Your class is automatically registered for JSON round-trips β€” no decorator, no extra setup:

e = Electrolyzer(name="EL-1", capacity_kw=5000, efficiency=0.65)
site = edm.Site(name="H2 Site", members=[e])

raw = site.to_json()
restored = edm.Element.from_json(raw)
assert isinstance(restored.members[0], Electrolyzer)

The only requirement is that the module defining your class is imported before from_json is called. EDM looks up types by name in a process-wide registry, so the class must be defined for the loader to find it. In practice this means a single import my_assets (or from my_assets import Electrolyzer) at the top of any script that loads saved models β€” the same pattern used by every Python registry library. If the loader can't find a type, it raises ValueError: Unknown Element type 'Electrolyzer'.

For one-off custom fields that don't justify a new class, every Element carries an extra: dict[str, JSONScalar] you can populate freely:

pv = edm.solar.PVSystem(name="PV-1", capacity=2400, extra={"owner": "Acme", "asset_id_legacy": 17})

Values in extra are restricted to JSON-native scalars (str, int, float, bool, None) plus nested dict / list of the same. EDM types, enums, and shapely geometries are rejected β€” for typed values, define a typed subclass instead. This restriction is validated at to_json time and surfaces a clear TypeError when violated.

Cross-tree references

Edges and other cross-cutting links use Reference[T] to point at another Element by its UUID. References are valid the moment they're constructed and resolve lazily against an Index:

se4 = edm.BiddingZone(name="SE4")
dk2 = edm.BiddingZone(name="DK2")
icx = edm.grid.Interconnection(
    name="SE4-DK2",
    from_element=edm.Reference(se4),  # captures se4.id
    to_element=edm.Reference(dk2),
    capacity_forward=1700,
    capacity_backward=1300,
)
portfolio = edm.Portfolio(name="Nordic", members=[se4, dk2, icx])

# Resolve a Reference against the tree root (or pre-built Index).
icx.from_element.resolve(portfolio)
icx.from_element.get().name  # "SE4"

A bare Element passed as from_element / to_element on an Edge is auto-wrapped in a Reference at construction. JSON round-trip emits refs as {"__ref__": "<uuid>"} β€” single-pass deserialize, no two-pass walk.

Installation

We recommend installing using a virtual environment like venv, poetry or uv.

Install the stable release:

pip install energydatamodel

Install the latest release:

pip install git+https://github.com/rebase-energy/EnergyDataModel.git

Install in editable mode for development:

git clone https://github.com/rebase-energy/EnergyDataModel.git
cd EnergyDataModel
pip install -e .[dev] 

Ways to Contribute

We welcome contributions from anyone interested in this project! Here are some ways to contribute to EnergyDataModel:

  • Add a new energy system assets and concepts;
  • Propose updates to existing energy assets and concepts;
  • Create a converter to new data format; or
  • Create a converter to another energy data model.

If you are interested in contributing, then feel free to join our Slack Community so that we can discuss it. πŸ‘‹

Contributors

This project uses allcontributors.org to recognize all contributors, including those that don't push code.

Sebastian Haglund
Sebastian Haglund

πŸ’»
Nelson
Nelson

πŸ€”

Licence

This project uses the MIT Licence.

Acknowledgement

The authors of this project would like to thank the Swedish Energy Agency for their financial support under the E2B2 program (project number P2022-00903)

Packages

 
 
 

Contributors

Languages