Skip to content

Commit

Permalink
add amiris scenario loader
Browse files Browse the repository at this point in the history
This allows loading scenarios which were originally written for AMIRIS by DLR
  • Loading branch information
maurerle committed Oct 30, 2023
1 parent d092ee9 commit 32afae0
Showing 1 changed file with 287 additions and 0 deletions.
287 changes: 287 additions & 0 deletions assume/common/scenario_loader_amiris.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import logging
from datetime import datetime, timedelta

Check warning on line 2 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L1-L2

Added lines #L1 - L2 were not covered by tests

import dateutil.rrule as rr
import numpy as np
import pandas as pd
import yaml
from tqdm import tqdm

Check warning on line 8 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L4-L8

Added lines #L4 - L8 were not covered by tests

from assume.common.base import LearningConfig
from assume.common.forecasts import CsvForecaster, Forecaster, NaiveForecast
from assume.common.market_objects import MarketConfig, MarketProduct
from assume.world import World

Check warning on line 13 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L10-L13

Added lines #L10 - L13 were not covered by tests

logger = logging.getLogger(__name__)
import yaml
from yamlinclude import YamlIncludeConstructor

Check warning on line 17 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L15-L17

Added lines #L15 - L17 were not covered by tests

translate_clearing = {

Check warning on line 19 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L19

Added line #L19 was not covered by tests
"SAME_SHARES": "pay_as_clear",
"FIRST_COME_FIRST_SERVE": "pay_as_bid",
"RANDOMIZE": "not_implemented_yet",
}
translate_fuel_type = {

Check warning on line 24 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L24

Added line #L24 was not covered by tests
"NUCLEAR": "nuclear",
"LIGNITE": "lignite",
"HARD_COAL": "hard coal",
"NATURAL_GAS": "natural gas",
"OIL": "oil",
"HYDROGEN": "hydrogen",
"Biogas": "biomass",
"PV": "solar",
"WindOn": "wind_onshore",
"WindOff": "wind_offshore",
"RunOfRiver": "hydro",
"Other": "hydro,",
}


def read_csv(base_path, filename):
return pd.read_csv(

Check warning on line 41 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L40-L41

Added lines #L40 - L41 were not covered by tests
base_path + "/" + filename,
date_format="%Y-%m-%d_%H:%M:%S",
sep=";",
header=None,
names=["time", "load"],
index_col="time",
)["load"]


def get_send_receive_msgs_per_id(agent_id, contracts_config: list):
sends = []
receives = []
for contracts in contracts_config:
for contract in contracts["Contracts"]:

Check warning on line 55 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L51-L55

Added lines #L51 - L55 were not covered by tests
# sends
if isinstance(contract["SenderId"], list):
if agent_id in contract["SenderId"]:
sends.append(contract)
elif agent_id == contract["SenderId"]:
sends.append(contract)

Check warning on line 61 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L57-L61

Added lines #L57 - L61 were not covered by tests
# receives
if isinstance(contract["ReceiverId"], list):
if agent_id in contract["ReceiverId"]:
receives.append(contract)
elif agent_id == contract["ReceiverId"]:
receives.append(contract)
return sends, receives

Check warning on line 68 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L63-L68

Added lines #L63 - L68 were not covered by tests


def get_matching_send_one_or_multi(agent_id: int, contract: dict):
assert isinstance(contract["SenderId"], list)

Check warning on line 72 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L71-L72

Added lines #L71 - L72 were not covered by tests
# if the receiver is only one - use it
if not isinstance(contract["ReceiverId"], list):
return contract["ReceiverId"]

Check warning on line 75 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L74-L75

Added lines #L74 - L75 were not covered by tests

# else we need to find the matching index
idx = contract["SenderId"].index(agent_id)
return contract["ReceiverId"][idx]

Check warning on line 79 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L78-L79

Added lines #L78 - L79 were not covered by tests


def add_agent_to_world(

Check warning on line 82 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L82

Added line #L82 was not covered by tests
agent: dict, world: World, prices: dict, contracts: list, base_path: str
):
match agent["Type"]:
case "EnergyExchange":
market_config = MarketConfig(

Check warning on line 87 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L85-L87

Added lines #L85 - L87 were not covered by tests
f"Market_{agent['Id']}",
rr.rrule(rr.HOURLY, interval=1, dtstart=world.start, until=world.end),
timedelta(hours=1),
translate_clearing[agent["Attributes"]["DistributionMethod"]],
[MarketProduct(timedelta(hours=1), 1, timedelta(hours=1))],
maximum_bid_volume=99999,
)
world.add_market_operator(f"Market_{agent['Id']}")
world.add_market(f"Market_{agent['Id']}", market_config)
case "CarbonMarket":
co2_price = agent["Attributes"]["Co2Prices"]
if isinstance(co2_price, str):
price_series = read_csv(base_path, co2_price)
co2_price = price_series.reindex(world.index).ffill().fillna(0)
prices["co2"] = co2_price
case "FuelsMarket":

Check warning on line 103 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L95-L103

Added lines #L95 - L103 were not covered by tests
# fill prices for forecaster
for fuel in agent["Attributes"]["FuelPrices"]:
fuel_type = translate_fuel_type[fuel["FuelType"]]
price = fuel["Price"]
if isinstance(fuel["Price"], str):
price_series = read_csv(base_path, fuel["Price"])
price_series.index = price_series.index.round("h")
if not price_series.index.is_unique:
price_series = price_series.groupby(level=0).last()
price = price_series.reindex(world.index).ffill()
prices[fuel_type] = price * fuel["ConversionFactor"]
case "DemandTrader":
world.add_unit_operator(agent["Id"])

Check warning on line 116 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L105-L116

Added lines #L105 - L116 were not covered by tests

for i, load in enumerate(agent["Attributes"]["Loads"]):
demand_series = -read_csv(base_path, load["DemandSeries"])
world.add_unit(

Check warning on line 120 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L118-L120

Added lines #L118 - L120 were not covered by tests
f"demand_{agent['Id']}_{i}",
"demand",
agent["Id"],
{
"min_power": 0,
"max_power": 100000,
"bidding_strategies": {"energy": "naive"},
"technology": "demand",
"price": load["ValueOfLostLoad"],
},
NaiveForecast(world.index, demand=demand_series),
)

case "StorageTrader":
world.add_unit_operator(f"Operator_{agent['Id']}")
case "RenewableTrader":
world.add_unit_operator(f"Operator_{agent['Id']}")

Check warning on line 137 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L134-L137

Added lines #L134 - L137 were not covered by tests
# send, receive = get_send_receive_msgs_per_id(agent["Id"], contracts)
case "NoSupportTrader":

Check warning on line 139 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L139

Added line #L139 was not covered by tests
# does not get support - just trades renewables
# has a ShareOfRevenues (how much of the profit he keeps)
world.add_unit_operator(f"Operator_{agent['Id']}")
case "SystemOperatorTrader":
world.add_unit_operator(f"Operator_{agent['Id']}")

Check warning on line 144 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L142-L144

Added lines #L142 - L144 were not covered by tests

case "ConventionalPlantOperator" | "ConventionalTrader":

Check warning on line 146 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L146

Added line #L146 was not covered by tests
# this can be left out for now - we only use the actual plant
# TODO the conventional Trader sets markup for the according plantbuilder - we should respect that too
world.add_unit_operator(f"Operator_{agent['Id']}")
pass
case "PredefinedPlantBuilder":

Check warning on line 151 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L149-L151

Added lines #L149 - L151 were not covered by tests
# this is the actual powerplant
prototype = agent["Attributes"]["Prototype"]
attr = agent["Attributes"]
send, receive = get_send_receive_msgs_per_id(agent["Id"], contracts)
operator_id = get_matching_send_one_or_multi(agent["Id"], send[0])
operator_id = f"Operator_{operator_id}"
fuel_price = prices.get(translate_fuel_type[prototype["FuelType"]], 0)
fuel_price += prototype.get("OpexVarInEURperMWH", 0)

Check warning on line 159 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L153-L159

Added lines #L153 - L159 were not covered by tests
# TODO CyclingCostInEURperMW
forecast = NaiveForecast(

Check warning on line 161 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L161

Added line #L161 was not covered by tests
world.index,
availability=prototype["PlannedAvailability"],
fuel_price=fuel_price,
co2_price=prices.get("co2", 2),
)
# TODO UnplannedAvailabilityFactor is not respected
world.add_unit(

Check warning on line 168 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L168

Added line #L168 was not covered by tests
f"PredefinedPlantBuilder_{agent['Id']}",
"power_plant",
operator_id,
{
# I think AMIRIS plans per block - so minimum is 1 block
"min_power": attr["BlockSizeInMW"],
"max_power": attr["InstalledPowerInMW"],
"bidding_strategies": {"energy": "naive"},
"technology": translate_fuel_type[prototype["FuelType"]],
"fuel_type": translate_fuel_type[prototype["FuelType"]],
"emission_factor": prototype["SpecificCo2EmissionsInTperMWH"],
"efficiency": sum(attr["Efficiency"].values()) / 2,
},
forecast,
)
case "VariableRenewableOperator" | "Biogas":
send, receive = get_send_receive_msgs_per_id(agent["Id"], contracts)
operator_id = get_matching_send_one_or_multi(agent["Id"], send[0])
operator_id = f"Operator_{operator_id}"
attr = agent["Attributes"]
availability = attr.get("YieldProfile", attr.get("DispatchTimeSeries"))
if isinstance(availability, str):
dispatch_profile = read_csv(base_path, availability)
availability = dispatch_profile.reindex(world.index).ffill().fillna(0)
fuel_price = prices.get(translate_fuel_type[attr["EnergyCarrier"]], 0)
fuel_price += attr.get("OpexVarInEURperMWH", 0)
forecast = NaiveForecast(

Check warning on line 195 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L184-L195

Added lines #L184 - L195 were not covered by tests
world.index,
availability=availability,
fuel_price=fuel_price,
co2_price=prices.get("co2", 0),
)
# TODO attr["SupportInstrument"] and
world.add_unit(

Check warning on line 202 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L202

Added line #L202 was not covered by tests
f"VariableRenewableOperator_{agent['Id']}",
"power_plant",
operator_id,
{
"min_power": 0,
"max_power": attr["InstalledPowerInMW"],
"bidding_strategies": {"energy": "naive"},
"technology": translate_fuel_type[attr["EnergyCarrier"]],
"fuel_type": translate_fuel_type[attr["EnergyCarrier"]],
"emission_factor": 0,
"efficiency": 1,
},
forecast,
)


def read_amiris_yaml(base_path):
YamlIncludeConstructor.add_to_loader_class(

Check warning on line 220 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L219-L220

Added lines #L219 - L220 were not covered by tests
loader_class=yaml.FullLoader, base_dir=base_path
)

with open(base_path + "/scenario.yaml", "rb") as f:
amiris_scenario = yaml.load(f, Loader=yaml.FullLoader)
return amiris_scenario

Check warning on line 226 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L224-L226

Added lines #L224 - L226 were not covered by tests


async def load_amiris_async(

Check warning on line 229 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L229

Added line #L229 was not covered by tests
world: World,
scenario: str,
study_case: str,
base_path: str,
):
# In practice - this seems fixed in AMIRIS
DeliveryIntervalInSteps = 3600

Check warning on line 236 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L236

Added line #L236 was not covered by tests

amiris_scenario = read_amiris_yaml(base_path)

Check warning on line 238 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L238

Added line #L238 was not covered by tests

start = amiris_scenario["GeneralProperties"]["Simulation"]["StartTime"]
start = pd.to_datetime(start, format="%Y-%m-%d_%H:%M:%S")
end = amiris_scenario["GeneralProperties"]["Simulation"]["StopTime"]
end = pd.to_datetime(end, format="%Y-%m-%d_%H:%M:%S")

Check warning on line 243 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L240-L243

Added lines #L240 - L243 were not covered by tests
# AMIRIS caveat: start and end is always two minutes before actual start
start += timedelta(minutes=2)
sim_id = f"{scenario}_{study_case}"
save_interval = amiris_scenario["GeneralProperties"]["Output"]["Interval"]
prices = {}
index = pd.date_range(start=start, end=end, freq="1h", inclusive="left")
await world.setup(

Check warning on line 250 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L245-L250

Added lines #L245 - L250 were not covered by tests
start=start,
end=end,
save_frequency_hours=save_interval,
simulation_id=sim_id,
index=index,
)
for agent in amiris_scenario["Agents"]:
add_agent_to_world(

Check warning on line 258 in assume/common/scenario_loader_amiris.py

View check run for this annotation

Codecov / codecov/patch

assume/common/scenario_loader_amiris.py#L257-L258

Added lines #L257 - L258 were not covered by tests
agent, world, prices, amiris_scenario["Contracts"], base_path
)


if __name__ == "__main__":
# To use this with amiris run:
# git clone https://gitlab.com/dlr-ve/esy/amiris/examples.git amiris-examples
# next to the assume folder
scenario = "Germany2019" # Germany2019 or Austria2019 or Simple
base_path = f"../amiris-examples/{scenario}/"
amiris_scenario = read_amiris_yaml(base_path)
sends, receives = get_send_receive_msgs_per_id(1000, amiris_scenario["Contracts"])

demand_agent = amiris_scenario["Agents"][3]
demand_series = read_csv(
base_path, demand_agent["Attributes"]["Loads"][0]["DemandSeries"]
)

db_uri = "postgresql://assume:assume@localhost:5432/assume"
world = World(database_uri=db_uri)
world.loop.run_until_complete(
load_amiris_async(
world,
"amiris",
scenario,
base_path,
)
)
world.run()

0 comments on commit 32afae0

Please sign in to comment.