Skip to content

Commit

Permalink
Merge pull request #1818 from gridsingularity/feature/GSYE-810
Browse files Browse the repository at this point in the history
Feature/gsye 810
  • Loading branch information
hannesdiedrich authored Dec 17, 2024
2 parents 8e4024a + 63444ac commit 296caf0
Show file tree
Hide file tree
Showing 63 changed files with 4,807 additions and 2,721 deletions.
7 changes: 0 additions & 7 deletions src/gsy_e/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@
# pylint: disable=unused-import
import os

from gsy_framework.constants_limits import DATE_TIME_FORMAT, DATE_TIME_UI_FORMAT, TIME_ZONE # NOQA
from gsy_framework.constants_limits import TIME_FORMAT, DATE_FORMAT, GlobalConfig # NOQA

# In order to cover conversion and reverse-conversion to 5 decimal points, the tolerance has to be
# 0.00002. That way off-by-one consecutive rounding errors would not be treated as errors, e.g.
# when recalculating the original energy rate in trade chains.
FLOATING_POINT_TOLERANCE = 0.00002
ROUND_TOLERANCE = 5

# Percentual standard deviation relative to the forecast energy, used to compute the (simulated)
Expand Down
216 changes: 155 additions & 61 deletions src/gsy_e/gsy_e_core/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import logging
import multiprocessing
import platform
Expand All @@ -23,25 +24,40 @@
from click.types import Choice
from click_default_group import DefaultGroup
from colorlog.colorlog import ColoredFormatter
from gsy_framework.constants_limits import ConstSettings
from gsy_framework.constants_limits import ConstSettings, DATE_FORMAT, TIME_FORMAT, TIME_ZONE
from gsy_framework.exceptions import GSyException
from gsy_framework.settings_validators import validate_global_settings
from pendulum import today

import gsy_e.constants
from gsy_e.gsy_e_core.simulation import run_simulation
from gsy_e.gsy_e_core.util import (
DateType, IntervalType, available_simulation_scenarios, convert_str_to_pause_after_interval,
read_settings_from_file, update_advanced_settings)
DateType,
IntervalType,
available_simulation_scenarios,
convert_str_to_pause_after_interval,
read_settings_from_file,
update_advanced_settings,
)
from gsy_e.models.config import SimulationConfig

log = logging.getLogger(__name__)


@click.group(name="gsy-e", cls=DefaultGroup, default="run", default_if_no_args=True,
context_settings={"max_content_width": 120})
@click.option("-l", "--log-level", type=Choice(logging._nameToLevel.keys()), default="INFO",
show_default=True, help="Log level")
@click.group(
name="gsy-e",
cls=DefaultGroup,
default="run",
default_if_no_args=True,
context_settings={"max_content_width": 120},
)
@click.option(
"-l",
"--log-level",
type=Choice(logging._nameToLevel.keys()),
default="INFO",
show_default=True,
help="Log level",
)
def main(log_level):
"""Entrypoint for command-line interface interaction."""
handler = logging.StreamHandler()
Expand All @@ -50,7 +66,7 @@ def main(log_level):
ColoredFormatter(
"%(log_color)s%(asctime)s.%(msecs)03d %(levelname)-8s (%(lineno)4d) %(name)-30s: "
"%(message)s%(reset)s",
datefmt="%H:%M:%S"
datefmt="%H:%M:%S",
)
)
root_logger = logging.getLogger()
Expand All @@ -62,53 +78,124 @@ def main(log_level):


@main.command()
@click.option("-d", "--duration", type=IntervalType("D:H"), default="1d", show_default=True,
help="Duration of simulation")
@click.option("-t", "--tick-length", type=IntervalType("M:S"), default="1s", show_default=True,
help="Length of a tick")
@click.option("-s", "--slot-length", type=IntervalType("M:S"), default="15m", show_default=True,
help="Length of a market slot")
@click.option("--slot-length-realtime", type=IntervalType("M:S"), default="0m",
show_default=True, help="Desired duration of slot in realtime")
@click.option("--setup", "setup_module_name", default="default_2a",
help=("Simulation setup module use. "
f"Available modules: [{', '.join(_setup_modules)}]"))
@click.option("-g", "--settings-file", default=None,
help="Settings file path")
@click.option(
"-d",
"--duration",
type=IntervalType("D:H"),
default="1d",
show_default=True,
help="Duration of simulation",
)
@click.option(
"-t",
"--tick-length",
type=IntervalType("M:S"),
default="1s",
show_default=True,
help="Length of a tick",
)
@click.option(
"-s",
"--slot-length",
type=IntervalType("M:S"),
default="15m",
show_default=True,
help="Length of a market slot",
)
@click.option(
"--slot-length-realtime",
type=IntervalType("M:S"),
default="0m",
show_default=True,
help="Desired duration of slot in realtime",
)
@click.option(
"--setup",
"setup_module_name",
default="default_2a",
help=("Simulation setup module use. " f"Available modules: [{', '.join(_setup_modules)}]"),
)
@click.option("-g", "--settings-file", default=None, help="Settings file path")
@click.option("--seed", help="Manually specify random seed")
@click.option("--paused", is_flag=True, default=False, show_default=True,
help="Start simulation in paused state")
@click.option("--pause-at", type=str, default=None,
help="Automatically pause at a certain time. "
f"Accepted Input formats: ({gsy_e.constants.DATE_FORMAT}, "
f"{gsy_e.constants.TIME_FORMAT}) [default: disabled]")
@click.option("--incremental", is_flag=True, default=False, show_default=True,
help="Pause the simulation at the end of each time slot.")
@click.option("--repl/--no-repl", default=False, show_default=True,
help="Start REPL after simulation run.")
@click.option(
"--paused",
is_flag=True,
default=False,
show_default=True,
help="Start simulation in paused state",
)
@click.option(
"--pause-at",
type=str,
default=None,
help="Automatically pause at a certain time. "
f"Accepted Input formats: ({DATE_FORMAT}, "
f"{TIME_FORMAT}) [default: disabled]",
)
@click.option(
"--incremental",
is_flag=True,
default=False,
show_default=True,
help="Pause the simulation at the end of each time slot.",
)
@click.option(
"--repl/--no-repl", default=False, show_default=True, help="Start REPL after simulation run."
)
@click.option("--no-export", is_flag=True, default=False, help="Skip export of simulation data")
@click.option("--export-path", type=str, default=None, show_default=False,
help="Specify a path for the csv export files (default: ~/gsy-e-simulation)")
@click.option(
"--export-path",
type=str,
default=None,
show_default=False,
help="Specify a path for the csv export files (default: ~/gsy-e-simulation)",
)
@click.option("--enable-bc", is_flag=True, default=False, help="Run simulation on Blockchain")
@click.option("--enable-external-connection", is_flag=True, default=False,
help="External Agents interaction to simulation during runtime")
@click.option("--start-date", type=DateType(gsy_e.constants.DATE_FORMAT),
default=today(tz=gsy_e.constants.TIME_ZONE).format(gsy_e.constants.DATE_FORMAT),
show_default=True,
help=f"Start date of the Simulation ({gsy_e.constants.DATE_FORMAT})")
@click.option("--enable-dof/--disable-dof",
is_flag=True, default=True,
help=(
"Enable or disable Degrees of Freedom "
"(orders can't contain attributes/requirements)."))
@click.option("-m", "--market-type", type=int,
default=ConstSettings.MASettings.MARKET_TYPE, show_default=True,
help="Market type. 1 for one-sided market, 2 for two-sided market, "
"3 for coefficient-based trading.")
def run(setup_module_name, settings_file, duration, slot_length, tick_length,
enable_external_connection, start_date,
pause_at, incremental, slot_length_realtime, enable_dof: bool,
market_type: int, **kwargs):
@click.option(
"--enable-external-connection",
is_flag=True,
default=False,
help="External Agents interaction to simulation during runtime",
)
@click.option(
"--start-date",
type=DateType(DATE_FORMAT),
default=today(tz=TIME_ZONE).format(DATE_FORMAT),
show_default=True,
help=f"Start date of the Simulation ({DATE_FORMAT})",
)
@click.option(
"--enable-dof/--disable-dof",
is_flag=True,
default=True,
help=(
"Enable or disable Degrees of Freedom " "(orders can't contain attributes/requirements)."
),
)
@click.option(
"-m",
"--market-type",
type=int,
default=ConstSettings.MASettings.MARKET_TYPE,
show_default=True,
help="Market type. 1 for one-sided market, 2 for two-sided market, "
"3 for coefficient-based trading.",
)
def run(
setup_module_name,
settings_file,
duration,
slot_length,
tick_length,
enable_external_connection,
start_date,
pause_at,
incremental,
slot_length_realtime,
enable_dof: bool,
market_type: int,
**kwargs,
):
"""Configure settings and run a simulation."""
# Force the multiprocessing start method to be 'fork' on macOS.
if platform.system() == "Darwin":
Expand All @@ -124,24 +211,31 @@ def run(setup_module_name, settings_file, duration, slot_length, tick_length,
else:
assert 1 <= market_type <= 3, "Market type should be an integer between 1 and 3."
ConstSettings.MASettings.MARKET_TYPE = market_type
global_settings = {"sim_duration": duration,
"slot_length": slot_length,
"tick_length": tick_length,
"enable_degrees_of_freedom": enable_dof}
global_settings = {
"sim_duration": duration,
"slot_length": slot_length,
"tick_length": tick_length,
"enable_degrees_of_freedom": enable_dof,
}

validate_global_settings(global_settings)
simulation_config = SimulationConfig(
duration, slot_length, tick_length, start_date=start_date,
duration,
slot_length,
tick_length,
start_date=start_date,
external_connection_enabled=enable_external_connection,
enable_degrees_of_freedom=enable_dof)
enable_degrees_of_freedom=enable_dof,
)

if incremental:
kwargs["incremental"] = incremental

if pause_at is not None:
kwargs["pause_after"] = convert_str_to_pause_after_interval(start_date, pause_at)
run_simulation(setup_module_name, simulation_config, None, None, None,
slot_length_realtime, kwargs)
run_simulation(
setup_module_name, simulation_config, None, None, None, slot_length_realtime, kwargs
)

except GSyException as ex:
log.exception(ex)
Expand Down
10 changes: 4 additions & 6 deletions src/gsy_e/gsy_e_core/rq_job_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from datetime import datetime, date
from typing import Dict, Optional

from gsy_framework.constants_limits import GlobalConfig, ConstSettings
from gsy_framework.constants_limits import GlobalConfig, ConstSettings, TIME_ZONE
from gsy_framework.enums import ConfigurationType, SpotMarketTypeEnum, CoefficientAlgorithm
from gsy_framework.settings_validators import validate_global_settings
from pendulum import duration, instance, now
Expand Down Expand Up @@ -94,7 +94,7 @@ def launch_simulation_from_rq_job(

if settings.get("type") == ConfigurationType.CANARY_NETWORK.value:
config.start_date = instance(
datetime.combine(date.today(), datetime.min.time()), tz=gsy_e.constants.TIME_ZONE
datetime.combine(date.today(), datetime.min.time()), tz=TIME_ZONE
)

if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.COEFFICIENTS.value:
Expand Down Expand Up @@ -220,7 +220,7 @@ def _create_config_settings_object(
"start_date": (
instance(
datetime.combine(settings.get("start_date"), datetime.min.time()),
tz=gsy_e.constants.TIME_ZONE,
tz=TIME_ZONE,
)
if "start_date" in settings
else GlobalConfig.start_date
Expand Down Expand Up @@ -292,9 +292,7 @@ def _handle_scm_past_slots_simulation_run(
# Adding 4 hours of extra time to the SCM past slots simulation duration, in order to
# compensate for the runtime of the SCM past slots simulation and to not have any results gaps
# after this simulation run and the following Canary Network launch.
config.end_date = (
now(tz=gsy_e.constants.TIME_ZONE).subtract(hours=config.hours_of_delay).add(hours=4)
)
config.end_date = now(tz=TIME_ZONE).subtract(hours=config.hours_of_delay).add(hours=4)
config.sim_duration = config.end_date - config.start_date
GlobalConfig.sim_duration = config.sim_duration
gsy_e.constants.RUN_IN_REALTIME = False
Expand Down
27 changes: 14 additions & 13 deletions src/gsy_e/gsy_e_core/sim_results/file_export_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

# pylint: disable=too-many-return-statements, broad-exception-raised
from abc import ABC, abstractmethod
from statistics import mean
from typing import TYPE_CHECKING, Dict, List
Expand Down Expand Up @@ -179,8 +180,6 @@ def _specific_labels(self):
return [
"unmatched demand [kWh]",
"storage temperature C",
"temp decrease K",
"temp increase K",
"COP",
"heat demand J",
]
Expand All @@ -189,8 +188,6 @@ def _specific_labels(self):
return [
"unmatched demand [kWh]",
"storage temperature C",
"temp decrease K",
"temp increase K",
"COP",
"heat demand J",
"condenser temperature C",
Expand Down Expand Up @@ -251,20 +248,24 @@ def _specific_row(self, slot, market):
# pylint: disable=unidiomatic-typecheck
if type(self.area.strategy) == HeatPumpStrategy:
return [
round(self.area.strategy.state.get_unmatched_demand_kWh(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_storage_temp_C(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_temp_decrease_K(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_temp_increase_K(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_cop(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_heat_demand(slot), ROUND_TOLERANCE),
round(
self.area.strategy.state.tanks.get_unmatched_demand_kWh(slot),
ROUND_TOLERANCE,
),
round(
self.area.strategy.state.tanks.get_average_tank_temperature(slot),
ROUND_TOLERANCE,
),
round(self.area.strategy.state.heatpump.get_cop(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.heatpump.get_heat_demand(slot), ROUND_TOLERANCE),
]
# pylint: disable=unidiomatic-typecheck
if type(self.area.strategy) == VirtualHeatpumpStrategy:
return [
round(self.area.strategy.state.get_unmatched_demand_kWh(slot), ROUND_TOLERANCE),
round(
self.area.strategy.state.tanks.get_unmatched_demand_kWh(slot), ROUND_TOLERANCE
),
round(self.area.strategy.state.get_storage_temp_C(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_temp_decrease_K(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_temp_increase_K(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_cop(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_heat_demand(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_condenser_temp(slot), ROUND_TOLERANCE),
Expand Down
Loading

0 comments on commit 296caf0

Please sign in to comment.