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
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). Addsmembersandtz.Edgeβ relationships between two nodes (lines, transformers, interconnectors). Addsfrom_element,to_element,directed. Endpoints areReference[T]objects holding the target's UUID.Collectionβ logical groupings (Portfolio, Site, ...). Addsmembersandtz. Not a graph vertex βisinstance(portfolio, Node)is False.Assetβ cross-cutting mixin marking physical equipment. Addscommissioning_date. Mixed into Node viaNodeAssetand into Edge viaEdgeAsset; 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, Transformeredges: Line, Link, Pipe, Interconnection, EdgeAssetother: 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.
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!
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()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.
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 = NoneYour 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.
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.
We recommend installing using a virtual environment like venv, poetry or uv.
Install the stable release:
pip install energydatamodelInstall the latest release:
pip install git+https://github.com/rebase-energy/EnergyDataModel.gitInstall in editable mode for development:
git clone https://github.com/rebase-energy/EnergyDataModel.git
cd EnergyDataModel
pip install -e .[dev] 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. π
This project uses allcontributors.org to recognize all contributors, including those that don't push code.
Sebastian Haglund π» |
Nelson π€ |
This project uses the MIT Licence.
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)
