Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
attach_region_load_time_series,
attach_reserve_time_series,
attach_time_series_to_generators,
attach_time_series_to_purchasers,
convert_pumped_storage_generators,
ensure_generator_node_memberships,
ensure_region_node_memberships,
Expand All @@ -32,6 +33,7 @@
"attach_region_load_time_series",
"attach_reserve_time_series",
"attach_emissions_to_generators",
"attach_time_series_to_purchasers",
"convert_pumped_storage_generators",
"ensure_generator_node_memberships",
"ensure_region_node_memberships",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,19 @@
"ramp_rate_down": 100.0,
"ramp_rate_up": 100.0
},
"electrolyzer": {
"capacity_MW": 50.0,
"forced_outage_rate": 0.0,
"maintenance_rate": 0.0,
"max_capacity_MW": 500.0,
"max_ramp_up_percentage": 1.0,
"mean_time_to_repair": 0.0,
"min_capacity_MW": 0.0,
"min_down_time": 0.0,
"min_stable_level_percentage": 0.0,
"min_up_time": 0.0,
"start_cost_per_MW": 0.0
},
Comment on lines +404 to +416
"egs_nearfield": {
"capacity_MW": 50.0,
"forced_outage_rate": 3.09,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
},
{
"defaults": {
"category": "electrolyzer",
"category": "consuming-tech",
"fuel_price": 0.0,
"heat_rate": 0.0,
"vom_charge": 0.0,
Expand Down Expand Up @@ -147,10 +147,57 @@
"mean_time_to_repair": "mean_time_to_repair_hours",
"start_cost": "gen_startup_cost"
},
"filter": {
"casefold": true,
"field": "technology",
"op": "not_in",
"values": ["electrolyzer", "data-center"]
},
"source_type": "ReEDSConsumingTechnology",
"target_type": "PLEXOSGenerator",
"version": 2
},
{
"defaults": {
"category": "electrolyzer",
"max_load": 0.0
},
"field_map": {
"category": "technology",
"name": "name",
"max_load": "capacity",
"ext": "ext"
},
"getters": {
"units": "get_component_units"
},
"filter": {
"casefold": true,
"field": "technology",
"op": "eq",
"values": ["electrolyzer"]
},
"source_type": "ReEDSElectrolyzerDemand",
"target_type": "PLEXOSPurchaser",
"version": 1
},
{
"defaults": {
"category": "data-center",
"max_load": 0.0
},
"field_map": {
"category": "technology",
"name": "name",
"ext": "ext"
},
"getters": {
"units": "get_component_units"
},
"source_type": "ReEDSDataCenterDemand",
"target_type": "PLEXOSPurchaser",
"version": 2
},
{
"field_map": {
"category": "technology",
Expand Down Expand Up @@ -483,6 +530,18 @@
"target_type": "PLEXOSMembership",
"version": 4
},
{
"name": "purchaser_node_membership",
"system": "target",
"getters": {
"parent_object": "reeds_membership_parent_component",
"child_object": "reeds_membership_component_child_node",
"collection": "reeds_membership_collection_nodes"
},
"source_type": "PLEXOSPurchaser",
"target_type": "PLEXOSMembership",
"version": 5
},
{
"name": "line_from_node_membership",
"system": "target",
Expand Down
14 changes: 9 additions & 5 deletions packages/r2x-reeds-to-plexos/src/r2x_reeds_to_plexos/getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,25 @@ def _lookup_target_node(context: PluginContext, region_name: str) -> Result[Any,


def _lookup_source_generator(context: PluginContext, name: str) -> Any | None:
"""Find a ReEDS generator-like component by name."""
from r2x_reeds.models import ReEDSConsumingTechnology, ReEDSGenerator, ReEDSStorage
"""Find a ReEDS component that maps to node memberships by name."""
from r2x_reeds import models as reeds_models

reeds_consuming_technology = reeds_models.ReEDSConsumingTechnology
reeds_generator = reeds_models.ReEDSGenerator
reeds_storage = reeds_models.ReEDSStorage

if context.source_system is None:
return None

for gen in context.source_system.get_components(ReEDSGenerator):
for gen in context.source_system.get_components(reeds_generator):
if gen.name == name:
return gen

for consuming_tech in context.source_system.get_components(ReEDSConsumingTechnology):
for consuming_tech in context.source_system.get_components(reeds_consuming_technology):
if consuming_tech.name == name:
return consuming_tech

for storage in context.source_system.get_components(ReEDSStorage):
for storage in context.source_system.get_components(reeds_storage):
if storage.name == name:
return storage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@
from r2x_core import PluginContext, System


def _get_reeds_purchaser_source_types() -> tuple[type[Any], ...]:
"""Return consuming-demand source model types available in the installed ReEDS package."""
from r2x_reeds import models as reeds_models

type_names = ("ReEDSElectrolyzerDemand", "ReEDSDataCenterDemand", "ReEDSConsumingTechnology")
resolved_types: list[type[Any]] = []
for type_name in type_names:
model_type = getattr(reeds_models, type_name, None)
if isinstance(model_type, type) and model_type not in resolved_types:
resolved_types.append(model_type)
return tuple(resolved_types)


def _get_plexos_purchaser_type() -> type[Any]:
"""Return purchaser model type, falling back to generator when purchaser is unavailable."""
from r2x_plexos import models as plexos_models

purchaser_type = getattr(plexos_models, "PLEXOSPurchaser", None)
if isinstance(purchaser_type, type):
return purchaser_type
return PLEXOSGenerator


def attach_region_load_time_series(context: PluginContext) -> None:
"""Attach demand load and time series from ReEDSDemand to the translated PLEXOSRegion."""
from r2x_plexos.models import PLEXOSRegion
Expand Down Expand Up @@ -112,6 +135,53 @@ def attach_time_series_to_generators(context: PluginContext) -> None:
target_gen, name=ts.name, time_series_type=type(ts)
):
context.target_system.add_time_series(deepcopy(ts), target_gen)


def attach_time_series_to_purchasers(context: PluginContext) -> None:
"""Transfer electrolyzer and data center demand time series to translated PLEXOS purchasers."""
if context.source_system is None or context.target_system is None:
return

source_demands: dict[str, Any] = {}
for demand_type in _get_reeds_purchaser_source_types():
for demand in context.source_system.get_components(demand_type):
source_demands[demand.name] = demand
purchaser_type = _get_plexos_purchaser_type()
target_purchasers = {
purchaser.name: purchaser for purchaser in context.target_system.get_components(purchaser_type)
}

for demand_name, source_demand in source_demands.items():
target_purchaser = target_purchasers.get(demand_name)
if target_purchaser is None:
continue

for metadata in context.source_system.time_series.list_time_series_metadata(source_demand):
ts_list = context.source_system.list_time_series(
source_demand, name=metadata.name, **metadata.features
)
if not ts_list:
logger.warning(
"Missing purchaser demand time series {} for {}",
metadata.name,
source_demand.name,
)
continue

ts = deepcopy(ts_list[0])
ts_type = ts.__class__
if not context.target_system.has_time_series(
target_purchaser,
name=ts.name,
time_series_type=ts_type,
**metadata.features,
):
context.target_system.add_time_series(ts, target_purchaser, **metadata.features)
logger.debug(
"Attached purchaser time series {} to purchaser {}",
ts.name,
target_purchaser.name,
)
continue


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
attach_region_load_time_series,
attach_reserve_time_series,
attach_time_series_to_generators,
attach_time_series_to_purchasers,
)


Expand Down Expand Up @@ -51,5 +52,6 @@ def reeds_to_plexos(system: System, config: ReedsToPlexosConfig) -> System:
attach_reserve_time_series(context)
attach_time_series_to_generators(context)
attach_region_load_time_series(context)
attach_time_series_to_purchasers(context)

return context.target_system
38 changes: 38 additions & 0 deletions packages/r2x-reeds-to-plexos/tests/test_getters_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
PLEXOSLine,
PLEXOSMembership,
PLEXOSNode,
PLEXOSPurchaser,
PLEXOSRegion,
PLEXOSStorage,
)
Expand Down Expand Up @@ -37,6 +38,43 @@ def test_attach_time_series_to_generators(context):
getters_utils.attach_time_series_to_generators(context)


def test_attach_time_series_to_purchasers(context, monkeypatch):
from r2x_reeds.models import ReEDSRegion
from r2x_reeds.models.components import ReEDSElectrolyzerDemand

region = ReEDSRegion(name="R1", transmission_region="Z1")
demand = ReEDSElectrolyzerDemand(
name="R1_electrolyzer_demand",
region=region,
technology="electrolyzer",
capacity=30.0,
electricity_efficiency=1.0,
)
purchaser = PLEXOSPurchaser(name="R1_electrolyzer_demand")

context.source_system.add_component(region)
context.source_system.add_component(demand)
context.target_system.add_component(purchaser)

monkeypatch.setattr(
context.source_system.time_series,
"list_time_series_metadata",
lambda _x: [types.SimpleNamespace(name="max_active_power", features={})],
)
monkeypatch.setattr(
context.source_system,
"list_time_series",
lambda _component, **_kwargs: [types.SimpleNamespace(name="max_active_power", __class__=object)],
)

added = []
context.target_system.has_time_series = lambda *_args, **_kwargs: False
context.target_system.add_time_series = lambda *args, **kwargs: added.append((args, kwargs))

getters_utils.attach_time_series_to_purchasers(context)
assert len(added) == 1


def test_ensure_region_node_memberships(context):
# Add region and node with same name
region = PLEXOSRegion(name="R1")
Expand Down
22 changes: 22 additions & 0 deletions packages/r2x-reeds-to-plexos/tests/test_rule_loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,28 @@ def test_has_storage_rule() -> None:
), "Missing ReEDSStorage -> PLEXOSBattery or PLEXOSGenerator rule"


def test_has_electrolyzer_purchaser_rule() -> None:
"""Verify ReEDSElectrolyzerDemand maps to PLEXOSPurchaser."""
rules_path = files("r2x_reeds_to_plexos.config") / "rules.json"
rules_data = json.loads(rules_path.read_text())

assert any(
rule.get("source_type") == "ReEDSElectrolyzerDemand" and rule.get("target_type") == "PLEXOSPurchaser"
for rule in rules_data
), "Missing ReEDSElectrolyzerDemand -> PLEXOSPurchaser rule"


def test_has_data_center_purchaser_rule() -> None:
"""Verify ReEDSDataCenterDemand maps to PLEXOSPurchaser."""
rules_path = files("r2x_reeds_to_plexos.config") / "rules.json"
rules_data = json.loads(rules_path.read_text())

assert any(
rule.get("source_type") == "ReEDSDataCenterDemand" and rule.get("target_type") == "PLEXOSPurchaser"
for rule in rules_data
), "Missing ReEDSDataCenterDemand -> PLEXOSPurchaser rule"


def test_has_interface_rule() -> None:
"""Verify ReEDSInterface maps to PLEXOSInterface."""
rules_path = files("r2x_reeds_to_plexos.config") / "rules.json"
Expand Down
Loading
Loading