diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3c8498f..fc0943b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,4 +21,4 @@ jobs: CREATE_PAGES_ON_FAILURE: true EXECUTE_TESTS: true EXECUTE_COVERAGE_TEST: true - + EXTRA_REQUIREMENTS: '["ml"]' diff --git a/agentlib_flexquant/data_structures/flex_kpis.py b/agentlib_flexquant/data_structures/flex_kpis.py index 2ddb344d..6057baa6 100644 --- a/agentlib_flexquant/data_structures/flex_kpis.py +++ b/agentlib_flexquant/data_structures/flex_kpis.py @@ -134,6 +134,10 @@ class FlexibilityKPIs(pydantic.BaseModel): default=KPISeries(name="power_flex_offer", unit="kW", integration_method=LINEAR), description="Power flexibility", ) + power_flex_offer_prepared: KPISeries = pydantic.Field( + default=KPISeries(name="power_flex_offer_prepared", unit="kW", integration_method=LINEAR), + description="Power flexibility Series prepared for integration", + ) power_flex_offer_max: KPI = pydantic.Field( default=KPI(name="power_flex_offer_max", unit="kW"), description="Maximum power flexibility", @@ -202,7 +206,7 @@ def calculate( enable_energy_costs_correction: bool, calculate_flex_cost: bool, integration_method: INTEGRATION_METHOD, - collocation_time_grid: list = None, + time_grid_info: dict = None, ): """Calculate the KPIs based on the power and electricity price input profiles. @@ -219,7 +223,8 @@ def calculate( enable_energy_costs_correction: whether the energy costs should be corrected calculate_flex_cost: whether the cost of the flexibility should be calculated integration_method: method used for integration of KPISeries e.g. linear, constant - collocation_time_grid: Time grid of the mpc output with collocation discretization + time_grid_info: Dictionary with 'type' ('collocation', 'multiple_shooting', 'none') + and 'grid' (list of time points) keys """ @@ -231,10 +236,10 @@ def calculate( integration_method=integration_method, ) self._calculate_power_flex_stats( - mpc_time_grid=mpc_time_grid, collocation_time_grid=collocation_time_grid + mpc_time_grid=mpc_time_grid, time_grid_info=time_grid_info ) self._calculate_energy_flex( - mpc_time_grid=mpc_time_grid, collocation_time_grid=collocation_time_grid + mpc_time_grid=mpc_time_grid, time_grid_info=time_grid_info ) # Costs KPIs @@ -258,7 +263,7 @@ def calculate( eta_thermal_base_avg=eta_thermal_base_avg, integration_method=integration_method, mpc_time_grid=mpc_time_grid, - collocation_time_grid=collocation_time_grid, + time_grid_info=time_grid_info, ) self._calculate_costs_rel() @@ -312,28 +317,38 @@ def _calculate_power_flex( self.power_flex_offer.integration_method = integration_method def _calculate_power_flex_stats( - self, mpc_time_grid: np.array, collocation_time_grid: list = None + self, mpc_time_grid: np.array, time_grid_info: dict = None ): - """Calculate the characteristic values of the power flexibility for the offer.""" + """Calculate the characteristic values of the power flexibility for the offer. + + Args: + mpc_time_grid: the MPC time grid over the horizon + time_grid_info: Dictionary with 'type' and 'grid' keys for discretization info + """ if self.power_flex_offer.value is None: raise ValueError("Power flexibility value is empty.") # Calculate characteristic values # max and min of power flex offer - power_flex_offer = self.power_flex_offer.value.iloc[:-1].drop( - collocation_time_grid, errors="ignore" - ) + + self.power_flex_offer_prepared = self.power_flex_offer.__deepcopy__() + + # Only drop collocation points if using collocation method + if time_grid_info and time_grid_info.get("type") == "collocation": + self.power_flex_offer_prepared.value = self.power_flex_offer_prepared.value.drop( + time_grid_info["grid"], errors="ignore" + ) + power_flex_offer = self.power_flex_offer_prepared.value.iloc[:-1] + power_flex_offer_max = power_flex_offer.max() power_flex_offer_min = power_flex_offer.min() + # Average of the power flex offer # Get the series for integration before calculating average power_flex_offer_integration = self._get_series_for_integration( - series=self.power_flex_offer, mpc_time_grid=mpc_time_grid + series=self.power_flex_offer_prepared, mpc_time_grid=mpc_time_grid ) - power_flex_offer_integration.value = power_flex_offer_integration.value.drop( - collocation_time_grid, errors="ignore" - ) - # Calculate the average and stores the original value + power_flex_offer_avg = power_flex_offer_integration.avg() # Set values @@ -342,7 +357,7 @@ def _calculate_power_flex_stats( self.power_flex_offer_avg.value = power_flex_offer_avg def _get_series_for_integration( - self, series: KPISeries, mpc_time_grid: np.ndarray + self, series: Union[pd.Series, KPISeries], mpc_time_grid: np.ndarray ) -> KPISeries: """Return the KPISeries value sampled on the MPC time grid when the integration method is constant. @@ -361,20 +376,23 @@ def _get_series_for_integration( else: return series.__deepcopy__() - def _calculate_energy_flex(self, mpc_time_grid, collocation_time_grid: list = None): + def _calculate_energy_flex(self, mpc_time_grid, time_grid_info: dict = None): """Calculate the energy flexibility by integrating the power flexibility - of the offer window.""" + of the offer window. + + Args: + mpc_time_grid: the MPC time grid over the horizon + time_grid_info: Dictionary with 'type' and 'grid' keys for discretization info + """ if self.power_flex_offer.value is None: raise ValueError("Power flexibility value of the offer is empty.") # Calculate flexibility # Get the series for integration before calculating average power_flex_offer_integration = self._get_series_for_integration( - series=self.power_flex_offer, mpc_time_grid=mpc_time_grid - ) - power_flex_offer_integration.value = power_flex_offer_integration.value.drop( - collocation_time_grid, errors="ignore" + series=self.power_flex_offer_prepared, mpc_time_grid=mpc_time_grid ) + # Calculate the energy flex and stores the original value energy_flex = power_flex_offer_integration.integrate(time_unit="hours") @@ -391,7 +409,7 @@ def _calculate_costs( eta_thermal_base_avg: float, integration_method: INTEGRATION_METHOD, mpc_time_grid: np.ndarray, - collocation_time_grid: list = None, + time_grid_info: dict = None, ): """Calculate the costs of the flexibility event based on the electricity costs profile, the power flexibility profile and difference of stored energy. @@ -405,7 +423,7 @@ def _calculate_costs( eta_thermal_base: average efficiency of thermal generation unit integration_method: the integration method used to integrate KPISeries mpc_time_grid: the MPC time grid over the horizon - collocation_time_grid: Time grid of the mpc output with collocation discretization + time_grid_info: Dictionary with 'type' and 'grid' keys for discretization info """ @@ -441,9 +459,10 @@ def _calculate_costs( power_flex_full_integration = self._get_series_for_integration( series=self.power_flex_full, mpc_time_grid=mpc_time_grid ) - power_flex_full_integration.value = power_flex_full_integration.value.drop( - collocation_time_grid, errors="ignore" - ) + if time_grid_info and time_grid_info.get("type") == "collocation": + power_flex_full_integration.value = power_flex_full_integration.value.drop( + time_grid_info["grid"], errors="ignore" + ) # Difference in costs between shadow and baseline mpc delta_cost = cost_profile_shadow - cost_profile_base @@ -453,7 +472,6 @@ def _calculate_costs( # Calculate the costs and stores the original value costs = self.electricity_costs_series.integrate(time_unit="hours") - # correct the costs corrected_costs = costs - stored_energy_diff * np.mean(electricity_price_signal) / eta_thermal_base_avg @@ -647,7 +665,7 @@ def calculate( enable_energy_costs_correction: bool, calculate_flex_cost: bool, integration_method: INTEGRATION_METHOD, - collocation_time_grid: list = None, + time_grid_info: dict = None, ): """Calculate the KPIs for the positive and negative flexibility. @@ -655,7 +673,8 @@ def calculate( enable_energy_costs_correction: whether the energy costs should be corrected calculate_flex_cost: whether the cost of the flexibility should be calculated integration_method: method used for integration of KPISeries e.g. linear, constant - collocation_time_grid: Time grid of the mpc output with collocation discretization + time_grid_info: Dictionary with 'type' ('collocation', 'multiple_shooting', 'none') + and 'grid' (list of time points) keys """ self.kpis_pos.calculate( @@ -671,7 +690,7 @@ def calculate( enable_energy_costs_correction=enable_energy_costs_correction, calculate_flex_cost=calculate_flex_cost, integration_method=integration_method, - collocation_time_grid=collocation_time_grid, + time_grid_info=time_grid_info, ) self.kpis_neg.calculate( power_profile_base=self.power_profile_base, @@ -686,7 +705,7 @@ def calculate( enable_energy_costs_correction=enable_energy_costs_correction, calculate_flex_cost=calculate_flex_cost, integration_method=integration_method, - collocation_time_grid=collocation_time_grid, + time_grid_info=time_grid_info, ) self.reset_time_grid() return self.kpis_pos, self.kpis_neg diff --git a/agentlib_flexquant/data_structures/globals.py b/agentlib_flexquant/data_structures/globals.py index fced1ce3..0cec19d5 100644 --- a/agentlib_flexquant/data_structures/globals.py +++ b/agentlib_flexquant/data_structures/globals.py @@ -15,7 +15,9 @@ CONSTANT = 'constant' COLLOCATION = 'collocation' INTEGRATION_METHOD = Literal[LINEAR, CONSTANT] + FlexibilityDirections = Literal["positive", "negative"] + POWER_ALIAS_BASE = "_P_el_base" POWER_ALIAS_NEG = "_P_el_neg" POWER_ALIAS_POS = "_P_el_pos" @@ -25,7 +27,7 @@ full_trajectory_suffix: str = "_full" base_vars_to_communicate_suffix: str = "_base" shadow_suffix: str = "_shadow" -COLLOCATION_TIME_GRID = 'collocation_time_grid' +TIME_GRID_INFO = 'time_grid_info' PROVISION_VAR_NAME = "in_provision" ACCEPTED_POWER_VAR_NAME = "_P_external" RELATIVE_EVENT_START_TIME_VAR_NAME = "rel_start" @@ -43,11 +45,9 @@ def return_baseline_cost_function(power_variable: str, comfort_variable: str) -> str: """Return baseline cost function - Args: power_variable: name of the power variable comfort_variable: name of the comfort variable - Returns: Cost function in the baseline mpc, obj_std is to be evaluated according to user definition diff --git a/agentlib_flexquant/generate_flex_agents.py b/agentlib_flexquant/generate_flex_agents.py index 780c00cc..486d4b30 100644 --- a/agentlib_flexquant/generate_flex_agents.py +++ b/agentlib_flexquant/generate_flex_agents.py @@ -643,11 +643,11 @@ def adapt_indicator_module_config( parameter.value = self.baseline_mpc_module_config.time_step if parameter.name == glbs.PREDICTION_HORIZON: parameter.value = self.baseline_mpc_module_config.prediction_horizon - if parameter.name == glbs.COLLOCATION_TIME_GRID: + if parameter.name == glbs.TIME_GRID_INFO: dis_op = self.baseline_mpc_module_config.optimization_backend[ "discretization_options" ] - parameter.value = self.get_collocation_time_grid( + parameter.value = self.get_time_grid( discretization_options=dis_op ) # set power unit @@ -673,11 +673,11 @@ def adapt_market_module_config( self.flex_config.results_directory / module_config.results_file.name ) for parameter in module_config.parameters: - if parameter.name == glbs.COLLOCATION_TIME_GRID: + if parameter.name == glbs.TIME_GRID_INFO: dis_op = self.baseline_mpc_module_config.optimization_backend[ "discretization_options" ] - parameter.value = self.get_collocation_time_grid( + parameter.value = self.get_time_grid( discretization_options=dis_op ) if parameter.name == glbs.TIME_STEP: @@ -719,31 +719,35 @@ def adapt_and_dump_flex_config(self): config_json = self.flex_config.model_dump_json(exclude_defaults=True) f.write(config_json) - def get_collocation_time_grid(self, discretization_options: dict): - """Get the mpc output collocation grid over the horizon""" - # get the mpc time grid configuration + def get_time_grid(self, discretization_options: dict): + """Get the mpc output collocation grid over the horizon. + + Returns a dict with 'type' and 'grid' keys. + """ time_step = self.baseline_mpc_module_config.time_step prediction_horizon = self.baseline_mpc_module_config.prediction_horizon - # get the collocation configuration - collocation_method = discretization_options["collocation_method"] - collocation_order = discretization_options["collocation_order"] - # get the collocation points - options = CasadiDiscretizationOptions( - collocation_order=collocation_order, collocation_method=collocation_method - ) - collocation_points = DirectCollocation(options= - options)._collocation_polynomial().root - # compute the mpc output collocation grid - discretization_points = np.arange(0, time_step * prediction_horizon, time_step) - collocation_time_grid = ( - discretization_points[:, None] + collocation_points * time_step - ).ravel() - collocation_time_grid = collocation_time_grid[ - ~np.isin(collocation_time_grid, discretization_points) - ] - collocation_time_grid = collocation_time_grid.tolist() - return collocation_time_grid + # Check if using multiple shooting + if discretization_options.get("method") == "multiple_shooting": + grid = np.arange(0, (prediction_horizon + 1) * time_step, time_step) + return {"type": "multiple_shooting", "grid": grid.tolist()} + else: + collocation_method = discretization_options["collocation_method"] + collocation_order = discretization_options["collocation_order"] + # get the collocation points + options = CasadiDiscretizationOptions( + collocation_order=collocation_order, collocation_method=collocation_method + ) + collocation_points = DirectCollocation(options=options)._collocation_polynomial().root + # compute the mpc output collocation grid + discretization_points = np.arange(0, time_step * prediction_horizon, time_step) + time_grid = ( + discretization_points[:, None] + collocation_points * time_step + ).ravel() + time_grid = time_grid[ + ~np.isin(time_grid, discretization_points) + ] + return {"type": "collocation", "grid": time_grid.tolist()} def _generate_flex_model_definition(self): """Generate a python module for negative and positive flexibility agents from the Baseline MPC model.""" @@ -759,8 +763,7 @@ def _generate_flex_model_definition(self): model_fields = self.baseline_mpc_module_config.optimization_backend["model"] _ = model_fields.pop("type") config_instance = config_class(**model_fields) - # The " + " is just there to simplify the validation, it does not affect - # the generated code + self.check_variables_in_casadi_config( config_instance, self.flex_config.shadow_mpc_config_generator_data.neg_flex.flex_cost_function + @@ -769,6 +772,8 @@ def _generate_flex_model_definition(self): if self.flex_config.shadow_mpc_config_generator_data.neg_flex.flex_cost_function_appendix else ""), shadow_mpc_type="neg_flex" ) + # The " + " is just there to simplify the validation, it does not affect + # the generated code self.check_variables_in_casadi_config( config_instance, self.flex_config.shadow_mpc_config_generator_data.pos_flex.flex_cost_function + @@ -869,7 +874,7 @@ def run_config_validations(self): """Function to validate integrity of user-supplied flex config. Since the validation depends on interactions between multiple configurations, - it is performed within this function rather than using Pydantic’s built-in + it is performed within this function rather than using Pydantic's built-in validators for individual configurations. The following checks are performed: @@ -877,8 +882,8 @@ def run_config_validations(self): 2. Ensures the specified comfort variable exists in the MPC model states. 3. Validates that the stored energy variable exists in MPC outputs if energy cost correction is enabled. - 4. Verifies the supported collocation method is used; otherwise, - switches to 'legendre' and raises a warning. + 4. Verifies a supported discretization method is used (collocation or multiple shooting); + if collocation is used, validates the collocation method. 5. Ensures that the sum of prep time, market time, and flex event duration does not exceed the prediction horizon. 6. Ensures market time equals the MPC model time step if market config is @@ -905,8 +910,19 @@ def run_config_validations(self): class_name = mod_type["class_name"] # Get the class dynamic_class = cmng.get_class_from_file(file_path, class_name) + + model_config = self.baseline_mpc_module_config.optimization_backend.get("model", {}) + model_kwargs = {k: v for k, v in model_config.items() if k != "type"} + + try: + model_instance = dynamic_class(**model_kwargs) + except Exception as e: + self.logger.warning( + f"Could not instantiate model class {class_name} with full config " + ) + if self.flex_config.baseline_config_generator_data.comfort_variable not in [ - state.name for state in dynamic_class().states + state.name for state in model_instance.states ]: raise ConfigurationError( f"Given comfort variable " @@ -926,22 +942,13 @@ def run_config_validations(self): f"It must be defined in the base MPC model and config as output " f"if the correction of costs is enabled." ) + # validate discretization method (collocation or multiple shooting) + discretization_options = self.baseline_mpc_module_config.optimization_backend.get( + "discretization_options", {}) - # raise warning if unsupported collocation method is used and change - # to supported method - if ( - "collocation_method" - not in self.baseline_mpc_module_config.optimization_backend[ - "discretization_options"] - ): - raise ConfigurationError( - "Please use collocation as discretization method and define the " - "collocation_method in the mpc config" - ) - else: - collocation_method = self.baseline_mpc_module_config.optimization_backend[ - "discretization_options" - ]["collocation_method"] + # If using collocation, validate the collocation method + if "collocation_method" in discretization_options: + collocation_method = discretization_options["collocation_method"] if collocation_method != "legendre": self.logger.warning( "Collocation method %s is not supported. Switching to " @@ -1055,4 +1062,4 @@ def adapt_sim_results_path(self, simulator_agent_config: Union[str, Path], raise Exception(f"Could not adapt and create a new simulation config " f"due to: {e}. " f"Please check {simulator_agent_config} and " - f"'{save_name_suffix}'") + f"'{save_name_suffix}'") \ No newline at end of file diff --git a/agentlib_flexquant/modules/flexibility_indicator.py b/agentlib_flexquant/modules/flexibility_indicator.py index 32a5e011..15321490 100644 --- a/agentlib_flexquant/modules/flexibility_indicator.py +++ b/agentlib_flexquant/modules/flexibility_indicator.py @@ -111,6 +111,7 @@ def validate_constant_prices(self): "field in the flex config." ) ) + return self @@ -324,9 +325,9 @@ class FlexibilityIndicatorModuleConfig(agentlib.BaseModuleConfig): description="timestep of the mpc solution"), agentlib.AgentVariable(name=glbs.PREDICTION_HORIZON, unit="-", description="prediction horizon of the mpc solution"), - agentlib.AgentVariable(name=glbs.COLLOCATION_TIME_GRID, - alias=glbs.COLLOCATION_TIME_GRID, - description="Time grid of the mpc model output") + agentlib.AgentVariable(name=glbs.TIME_GRID_INFO, + alias=glbs.TIME_GRID_INFO, + description="Time grid info with 'type' and 'grid' keys") ] results_file: Optional[Path] = Field( @@ -398,7 +399,7 @@ def __init__(self,config: FlexibilityIndicatorModuleConfig): # set collocation time grid def get_param(cfg, name: str): return next(v for v in cfg.parameters if v.name == name) - self.collocation_time_grid = get_param(config, glbs.COLLOCATION_TIME_GRID).value + self.time_grid_info = get_param(config, glbs.TIME_GRID_INFO).value self.necessary_callback_variables = { glbs.POWER_ALIAS_BASE: {"name":"power_profile_base", "is_mpc":True}, glbs.POWER_ALIAS_NEG: {"name":"power_profile_flex_neg", "is_mpc":True}, @@ -513,7 +514,6 @@ def callback(self, inp, name): if name == glbs.PROVISION_VAR_NAME: self.in_provision = inp.value - if self.in_provision: self.data = self.callback_handler.set_all_callback_variables_to_none(data=self.data) else: @@ -579,9 +579,11 @@ def write_results(self, df: pd.DataFrame, ts: float, n: int) -> pd.DataFrame: values = self.data.electricity_price_series elif name == self.config.price_variable_feed_in: values = self.data.feed_in_price_series - elif name == glbs.COLLOCATION_TIME_GRID: - value = self.get(name).value - values = pd.Series(index=value, data=value) + elif name == glbs.TIME_GRID_INFO: + time_grid_info = self.get(name).value + # Store the grid as a series for results + grid = time_grid_info.get("grid", []) if time_grid_info else [] + values = pd.Series(index=grid, data=grid) if grid else pd.Series() else: values = self.get(name).value @@ -640,18 +642,25 @@ def calc_and_send_offer(self): """Calculate the flexibility KPIs for current predictions, send the flex offer and set the outputs, write and save the results.""" # Calculate the flexibility KPIs for current predictions - collocation_time_grid = self.get(glbs.COLLOCATION_TIME_GRID).value + time_grid_info = self.get(glbs.TIME_GRID_INFO).value self.data.calculate( enable_energy_costs_correction= self.config.correct_costs.enable_energy_costs_correction, calculate_flex_cost=self.config.calculate_costs.calculate_flex_costs, integration_method=self.config.integration_method, - collocation_time_grid=collocation_time_grid) + time_grid_info=time_grid_info) + + # get the grid from time_grid_info + time_grid = time_grid_info.get("grid", []) if time_grid_info else [] # get the full index during flex event including mpc_time_grid index and the - # collocation index - full_index = np.sort(np.concatenate([collocation_time_grid, - self.data.mpc_time_grid])) + is_collocation = time_grid_info and time_grid_info.get("type") == "collocation" + + if is_collocation: + full_index = np.sort(np.concatenate([time_grid,self.data.mpc_time_grid])) + else: + full_index = self.data.mpc_time_grid + flex_begin = self.get(glbs.MARKET_TIME).value + self.get(glbs.PREP_TIME).value flex_end = flex_begin + self.get(glbs.FLEX_EVENT_DURATION).value full_flex_offer_index = full_index[(full_index >= flex_begin) & @@ -660,16 +669,17 @@ def calc_and_send_offer(self): # reindex the power profiles to not send the simulation points to the market, # but only the values on the collocation points and the forward mean of them base_power_profile = self.data.power_profile_base.reindex( - collocation_time_grid).reindex(full_flex_offer_index) + time_grid).reindex(full_flex_offer_index) pos_diff_profile = self.data.kpis_pos.power_flex_offer.value.reindex( - collocation_time_grid).reindex(full_flex_offer_index) + time_grid).reindex(full_flex_offer_index) neg_diff_profile = self.data.kpis_neg.power_flex_offer.value.reindex( - collocation_time_grid).reindex(full_flex_offer_index) + time_grid).reindex(full_flex_offer_index) - # fill the mpc_time_grid with forward mean - base_power_profile = fill_nans(base_power_profile, method=MEAN) - pos_diff_profile = fill_nans(pos_diff_profile, method=MEAN) - neg_diff_profile = fill_nans(neg_diff_profile, method=MEAN) + if is_collocation: + # fill the mpc_time_grid with forward mean + base_power_profile = fill_nans(base_power_profile, method=MEAN) + pos_diff_profile = fill_nans(pos_diff_profile, method=MEAN) + neg_diff_profile = fill_nans(neg_diff_profile, method=MEAN) # Send flex offer self.send_flex_offer( @@ -746,6 +756,7 @@ def send_flex_offer( ) self.offer_count += 1 + def check_power_end_deviation(self, tol: float): """Calculate the deviation of the final value of the power profiles and warn the user if it exceeds the tolerance.""" diff --git a/agentlib_flexquant/modules/flexibility_market.py b/agentlib_flexquant/modules/flexibility_market.py index 51a54714..d17416af 100644 --- a/agentlib_flexquant/modules/flexibility_market.py +++ b/agentlib_flexquant/modules/flexibility_market.py @@ -43,7 +43,7 @@ class FlexibilityMarketModuleConfig(agentlib.BaseModuleConfig): ] parameters: list[AgentVariable] = [ - AgentVariable(name=glbs.COLLOCATION_TIME_GRID, alias=glbs.COLLOCATION_TIME_GRID, + AgentVariable(name=glbs.TIME_GRID_INFO, alias=glbs.TIME_GRID_INFO, description="Time grid of the mpc model output"), AgentVariable(name=glbs.TIME_STEP, unit="s", description="Time step of the mpc") ] @@ -183,10 +183,10 @@ def random_flexibility_callback(self, inp: AgentVariable, name: str): self.config.market_specs.accepted_offer_sample_points) if flex_power_feedback_method == glbs.COLLOCATION: profile = profile.reindex( - self.get(glbs.COLLOCATION_TIME_GRID).value) + self.get(glbs.TIME_GRID_INFO).value['grid']) elif flex_power_feedback_method == glbs.CONSTANT: index_to_keep = ~np.isin( - profile.index, self.get(glbs.COLLOCATION_TIME_GRID).value) + profile.index, self.get(glbs.TIME_GRID_INFO).value['grid']) profile = profile.get(index_to_keep) helper_indices = [i - 1 for i in profile.index[1:]] new_index = sorted(set(profile.index.tolist() + @@ -242,11 +242,10 @@ def single_flexibility_callback(self, inp: AgentVariable, name: str): flex_power_feedback_method = ( self.config.market_specs.accepted_offer_sample_points) if flex_power_feedback_method == glbs.COLLOCATION: - profile = profile.reindex(self.get( - glbs.COLLOCATION_TIME_GRID).value) + profile = profile.reindex(self.get(glbs.TIME_GRID_INFO).value['grid']) elif flex_power_feedback_method == glbs.CONSTANT: index_to_keep = ~np.isin(profile.index, - self.get(glbs.COLLOCATION_TIME_GRID).value) + self.get(glbs.TIME_GRID_INFO).value['grid']) profile = profile.get(index_to_keep) helper_indices = [i - 1 for i in profile.index[1:]] new_index = sorted(set(profile.index.tolist() + diff --git a/agentlib_flexquant/modules/shadow_mpc.py b/agentlib_flexquant/modules/shadow_mpc.py index a74393dc..3844277b 100644 --- a/agentlib_flexquant/modules/shadow_mpc.py +++ b/agentlib_flexquant/modules/shadow_mpc.py @@ -663,4 +663,4 @@ def _run_simulation( for output in self.var_ref.outputs: self.flex_results.loc[ (self.env.now, current_sim_time + t_sample), output - ] = self.flex_model.get_output(output).value + ] = self.flex_model.get_output(output).value \ No newline at end of file diff --git a/agentlib_flexquant/utils/data_handling.py b/agentlib_flexquant/utils/data_handling.py index 283b5f7a..1f19cf1b 100644 --- a/agentlib_flexquant/utils/data_handling.py +++ b/agentlib_flexquant/utils/data_handling.py @@ -21,6 +21,8 @@ def fill_nans(series: pd.Series, method: FillNansMethods) -> pd.Series: A pd.Series with nan filled. """ + # ignore lags from casadi_ml models + series = series[series.index >= 0] if method == MEAN: series = _set_mean_values(series=series) elif method == INTERPOLATE: @@ -104,4 +106,4 @@ def convert_timescale_of_index( ) else: df.index = df.index * time_conversion_factor - return df + return df \ No newline at end of file diff --git a/agentlib_flexquant/utils/parsing.py b/agentlib_flexquant/utils/parsing.py index f6c3088c..847ad46d 100644 --- a/agentlib_flexquant/utils/parsing.py +++ b/agentlib_flexquant/utils/parsing.py @@ -55,6 +55,7 @@ def create_ast_element(template_string: str) -> ast.expr: return ast.parse(template_string).body[0].value + def add_input( name: str, value: Union[bool, str, int], unit: str, description: str, type: str ) -> ast.expr: @@ -83,6 +84,7 @@ def add_input( ) + def add_parameter( name: str, value: Union[int, float], unit: str, description: str ) -> ast.expr: @@ -110,6 +112,7 @@ def add_parameter( ) + def add_output( name: str, unit: str, type: str, value: Union[str, float], description: str ) -> ast.expr: @@ -165,6 +168,7 @@ class SetupSystemModifier(ast.NodeTransformer): This class traverses the AST of the input file, identifies the relevant classes and methods, and performs the necessary modifications. + """ def __init__( @@ -187,15 +191,19 @@ def __init__( self.modify_config_class = self.modify_config_class_baseline self.modify_setup_system = self.modify_setup_system_baseline + def visit_Module(self, module: ast.Module) -> ast.Module: """Visit a module definition in the AST. + Append or delete the import statements at the top of the module. Args: + module: The module definition node in the AST. Returns: + The possibly modified module definition node. """ @@ -210,6 +218,7 @@ def visit_Module(self, module: ast.Module) -> ast.Module: self.generic_visit(module) return module + def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: """Visit a class definition in the AST. @@ -217,20 +226,22 @@ def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: BaselineMPCModelConfig and BaselineMPCModel classes and performs the necessary actions. Args: + node: The class definition node in the AST. Returns: + The possibly modified class definition node. """ for base in node.bases: - if isinstance(base, ast.Name) and base.id == "CasadiModelConfig": + if isinstance(base, ast.Name) and (base.id == "CasadiModelConfig" or base.id == "CasadiMLModelConfig"): # get ast object and trigger modification self.config_obj = node self.modify_config_class(node) # change class name node.name = self.mpc_data.class_name + "Config" - if isinstance(base, ast.Name) and base.id == "CasadiModel": + if isinstance(base, ast.Name) and (base.id == "CasadiModel" or base.id == "CasadiMLModel"): # get ast object and trigger modification self.model_obj = node for item in node.body: @@ -274,10 +285,12 @@ def get_leftmost_list( # If we get here, we couldn't find a list return None + def modify_config_class_shadow(self, node: ast.ClassDef): """Modify the config class of the shadow mpc. Args: + node: The class definition node of the config. """ @@ -340,6 +353,7 @@ def modify_config_class_baseline(self, node: ast.ClassDef): """Modify the config class of the baseline mpc. Args: + node: The class definition node of the config. """ @@ -372,7 +386,7 @@ def modify_config_class_baseline(self, node: ast.ClassDef): ): # Complex case with concatenated lists or tuple value_list = self.get_leftmost_list(body.value) - + # add the flexibility inputs if body.target.id == "inputs": if isinstance(body.value, ast.List): @@ -474,6 +488,7 @@ def modify_setup_system_shadow(self, node: ast.FunctionDef): .value ) item.value.elts.append(new_element) + break # loop through setup_system function to find return statement for i, stmt in enumerate(node.body): diff --git a/examples/OneRoom_SimpleLinRegMPC/flex_configs/flexibility_agent_config.json b/examples/OneRoom_SimpleLinRegMPC/flex_configs/flexibility_agent_config.json new file mode 100644 index 00000000..ae68f835 --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/flex_configs/flexibility_agent_config.json @@ -0,0 +1,54 @@ +{ + "prep_time": 900, + "flex_event_duration": 7200, + "market_time": 900, + "indicator_config": { + "agent_config": { + "id": "FlexibilityIndicator", + "modules": [ + { + "module_id": "Ag1Com", + "type": "local_broadcast" + }, + { + "module_id": "FlexibilityIndicator", + "type": "agentlib_flexquant.flexibility_indicator", + "price_variable": "r_pel", + "parameters": [ + { + "name": "time_step", + "value": 900 + }, + { + "name": "prediction_horizon", + "value": 48 + } + ], + "calculate_costs": {"calculate_flex_costs": true, + "use_constant_electricity_price": true, + "use_constant_feed_in_price": true, + "const_electricity_price": 10, + "const_feed_in_price": 0} + } + ] + }, + "name_of_created_file": "indicator.json" + }, + "market_config": "flex_configs/flexibility_market.json", + "baseline_config_generator_data": { + "power_variable": "P_el", + "power_unit": "kW", + "profile_deviation_weight": 100 + }, + "shadow_mpc_config_generator_data": { + "weights": [{"name": "s_P", "value": 10}], + "pos_flex": { + "flex_cost_function": "sum([self.s_T * self.T_slack ** 2, self.s_P * self.P_el])" + }, + "neg_flex": { + "flex_cost_function": "sum([self.s_T * self.T_slack ** 2, -self.s_P * self.P_el])" + } + }, + "delete_files": false, + "overwrite_files": true +} \ No newline at end of file diff --git a/examples/OneRoom_SimpleLinRegMPC/flex_configs/flexibility_market.json b/examples/OneRoom_SimpleLinRegMPC/flex_configs/flexibility_market.json new file mode 100644 index 00000000..f724e15c --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/flex_configs/flexibility_market.json @@ -0,0 +1,26 @@ +{ + "agent_config": { + "id": "FlexibilityMarket", + "modules": [ + { + "module_id": "Ag1Com", + "type": "local_broadcast" + }, + { + "module_id": "FlexibilityMarket", + "type": "agentlib_flexquant.flexibility_market", + "market_specs": { + "type": "single", + "cooldown": 10, + "minimum_average_flex": 0, + "options": { + "offer_acceptance_time": 9000, + "direction": "positive" + } + } + } + ] + + }, + "name_of_created_file": "market.json" + } \ No newline at end of file diff --git a/examples/OneRoom_SimpleLinRegMPC/main_one_room_flex.py b/examples/OneRoom_SimpleLinRegMPC/main_one_room_flex.py new file mode 100644 index 00000000..feee5be3 --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/main_one_room_flex.py @@ -0,0 +1,50 @@ +import logging +from agentlib_flexquant.generate_flex_agents import FlexAgentGenerator +from agentlib.utils.multi_agent_system import LocalMASAgency +from agentlib_flexquant.utils.interactive import Dashboard, CustomBound +from plot_results import plot_results + +# Set the log-level +logging.basicConfig(level=logging.WARN) +until = 7200 + +ENV_CONFIG = {"rt": False, "factor": 0.01, "t_sample": 900} + + +def run_example(until=until, with_dashboard=False): + mpc_config = "mpc_and_sim/simple_model.json" + sim_config = "mpc_and_sim/simple_sim.json" + predictor_config = "predictor/predictor_config.json" + flex_config = "flex_configs/flexibility_agent_config.json" + agent_configs = [sim_config, predictor_config] + + config_list = FlexAgentGenerator( + flex_config=flex_config, mpc_agent_config=mpc_config + ).generate_flex_agents() + agent_configs.extend(config_list) + + mas = LocalMASAgency( + agent_configs=agent_configs, env=ENV_CONFIG, variable_logging=False + ) + + mas.run(until=until) + results = mas.get_results(cleanup=False) + + plot_results(results_data=results) # Alternative plotscript using matplotlib, + if with_dashboard: + Dashboard( + flex_config="flex_configs/flexibility_agent_config.json", + simulator_agent_config="mpc_and_sim/simple_sim.json", + results=results + ).show( + custom_bounds=CustomBound( + for_variable="T", + lb_name="T_lower", + ub_name="T_upper" + ) + ) + return results + + +if __name__ == "__main__": + run_example(until, with_dashboard=True) diff --git a/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/Trainer/ml_model.json b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/Trainer/ml_model.json new file mode 100644 index 00000000..976e0381 --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/Trainer/ml_model.json @@ -0,0 +1 @@ +{"dt":900.0,"input":{"mDot":{"name":"mDot","lag":3},"load":{"name":"load","lag":2},"T_in":{"name":"T_in","lag":1}},"output":{"T":{"name":"T","lag":2,"output_type":"difference","recursive":true}},"agentlib_mpc_hash":"5302c96","training_info":null,"model_type":"LinReg","parameters":{"coef":[[-98.39137379499294,-39.21682530199981,-0.5848461265173618,2.59314258954646e-8,6.637570493239764e-9,-0.503047937143595,-0.492892772393688,0.3401290832302948]],"intercept":[187.30625067405674],"n_features_in":8,"rank":6,"singular":[35.99361869005735,18.9771165622424,0.2417850576555883,0.21421375073807045,0.023474240917668355,1.309867755231031e-12,4.219908371740855e-19,2.1045347182571248e-19]}} \ No newline at end of file diff --git a/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model.json b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model.json new file mode 100644 index 00000000..7ea32d87 --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model.json @@ -0,0 +1,95 @@ +{ + "id": "FlexModel", + "modules": [ + { + "module_id": "Ag1Com", + "type": "local_broadcast" + }, + { + "module_id": "BaselineMPC", + "type": "agentlib_mpc.mpc", + "optimization_backend": { + "type": "casadi_ml", + "model": { + "type": { + "file": "mpc_and_sim/simple_model.py", + "class_name": "BaselineMPCModel" + }, + "ml_model_sources": ["mpc_and_sim/Trainer/ml_model.json"] + }, + "discretization_options": { + "method": "multiple_shooting" + }, + "solver": { + "name": "ipopt", + "options": { + "ipopt": { + "max_iter": 100, + "tol": 1e-4 + } + } + }, + "results_file": "results/mpc.csv", + "save_results": true, + "overwrite_result_file": true + }, + "time_step": 900, + "prediction_horizon": 48, + "set_outputs": true, + "parameters": [ + { + "name": "s_T", + "value": 250 + }, + { + "name": "r_mDot", + "value": 1 + } + ], + "inputs": [ + { + "name": "load", + "value": 150 + }, + { + "name": "T_upper", + "value": 294.15 + }, + { + "name": "T_lower", + "value": 292.15 + }, + { + "name": "T_in", + "value": 280.15 + } + ], + "outputs": [ + { + "name": "T_out", + "alias": "T_out" + }, + { + "name": "P_el", + "alias": "P_el" + } + ], + "controls": [ + { + "name": "mDot", + "value": 0.02, + "ub": 0.05, + "lb": 0 + } + ], + "states": [ + { + "name": "T", + "value": 298.16, + "ub": 303.15, + "lb": 288.15 + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model.py b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model.py new file mode 100644 index 00000000..300f120f --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model.py @@ -0,0 +1,116 @@ +from agentlib_mpc.models.casadi_model import ( + CasadiModel, + CasadiInput, + CasadiState, + CasadiParameter, + CasadiOutput, + CasadiModelConfig, +) +from typing import List +from math import inf +from agentlib_mpc.models.casadi_ml_model import CasadiMLModel, CasadiMLModelConfig + + +class BaselineMPCModelConfig(CasadiMLModelConfig): + inputs: List[CasadiInput] = [ + # controls + CasadiInput( + name="mDot", value=0.0225, unit="kg/s", description="Air mass flow into zone" + ), + # disturbances + CasadiInput( + name="load", value=150, unit="W", description="Heat " "load into zone" + ), + CasadiInput( + name="T_in", value=280.15, unit="K", description="Inflow air temperature" + ), + # settings + CasadiInput( + name="T_upper", + value=294.15, + unit="K", + description="Upper boundary (soft) for T.", + ), + CasadiInput( + name="T_lower", + value=292.15, + unit="K", + description="Upper boundary (soft) for T.", + ), + ] + + states: List[CasadiState] = [ + # differential + CasadiState( + name="T", value=293.15, unit="K", description="Temperature of zone" + ), + # algebraic + # slack variables + CasadiState( + name="T_slack", + value=0, + unit="K", + description="Slack variable of temperature of zone", + ), + + ] + parameters: List[CasadiParameter] = [ + CasadiParameter( + name="cp", + value=1000, + unit="J/kg*K", + description="thermal capacity of the air", + ), + CasadiParameter( + name="C", value=100000, unit="J/K", description="thermal capacity of zone" + ), + CasadiParameter( + name="s_T", + value=1, + unit="-", + description="Weight for T in constraint function", + ), + CasadiParameter( + name="r_mDot", + value=1, + unit="-", + description="Weight for mDot in objective function", + ), + + ] + outputs: List[CasadiOutput] = [ + CasadiOutput(name="T_out", unit="K", description="Temperature of zone"), + CasadiOutput( + name="P_el", + unit="W", + description="The power input to the system", + ) + ] + +class BaselineMPCModel(CasadiMLModel): + config: BaselineMPCModelConfig + + def setup_system(self): + # Define ode + self.T_out.alg = self.T + self.P_el.alg = self.cp * self.mDot * (self.T - self.T_in) / 1000 + + # Constraints: List[(lower bound, function, upper bound)] + self.constraints = [ + # soft constraints + (self.T_lower, self.T + self.T_slack, inf), + (-inf, self.T - self.T_slack, self.T_upper), + (0, self.T_slack, inf) + ] + # Objective function + objective = sum( + [ + self.r_mDot * self.mDot, + self.s_T * self.T_slack ** 2, + ] + ) + return objective + + + + diff --git a/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model_sim.py b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model_sim.py new file mode 100644 index 00000000..88694d31 --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_model_sim.py @@ -0,0 +1,108 @@ +from agentlib_mpc.models.casadi_model import ( + CasadiModel, + CasadiInput, + CasadiState, + CasadiParameter, + CasadiOutput, + CasadiModelConfig, +) +from typing import List +from math import inf + + +class BaselineMPCModelConfig(CasadiModelConfig): + inputs: List[CasadiInput] = [ + # controls + CasadiInput( + name="mDot", value=0.0225, unit="kg/s", description="Air mass flow into zone" + ), + # disturbances + CasadiInput( + name="load", value=150, unit="W", description="Heat " "load into zone" + ), + CasadiInput( + name="T_in", value=280.15, unit="K", description="Inflow air temperature" + ), + # settings + CasadiInput( + name="T_upper", + value=294.15, + unit="K", + description="Upper boundary (soft) for T.", + ), + CasadiInput( + name="T_lower", + value=292.15, + unit="K", + description="Upper boundary (soft) for T.", + ), + + ] + + states: List[CasadiState] = [ + CasadiState(name="t_sim", value=0, unit="sec", description="simulation time"), + + # differential + CasadiState( + name="T", value=293.15, unit="K", description="Temperature of zone" + ), + # algebraic + # slack variables + CasadiState( + name="T_slack", + value=0, + unit="K", + description="Slack variable of temperature of zone", + ), + + ] + + parameters: List[CasadiParameter] = [ + CasadiParameter( + name="cp", + value=1000, + unit="J/kg*K", + description="thermal capacity of the air", + ), + CasadiParameter( + name="C", value=100000, unit="J/K", description="thermal capacity of zone" + ), + CasadiParameter( + name="s_T", + value=1, + unit="-", + description="Weight for T in constraint function", + ), + CasadiParameter( + name="r_mDot", + value=1, + unit="-", + description="Weight for mDot in objective function", + ), + + ] + outputs: List[CasadiOutput] = [ + CasadiOutput(name="T_out", unit="K", description="Temperature of zone"), + CasadiOutput( + name="P_el", + unit="W", + description="The power input to the system", + ) + ] + +class BaselineMPCModel(CasadiModel): + config: BaselineMPCModelConfig + + def setup_system(self): + # Define ode + self.T.ode = ( + self.cp * self.mDot / self.C * (self.T_in - self.T) + self.load / self.C + ) + self.P_el.alg = self.cp * self.mDot * (self.T - self.T_in)/1000 + + # Define ae + self.T_out.alg = self.T # math operation to get the symbolic variable + + + + diff --git a/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_sim.json b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_sim.json new file mode 100644 index 00000000..91545afb --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/mpc_and_sim/simple_sim.json @@ -0,0 +1,30 @@ +{ + "id": "SimAgent", + "modules": [ + { + "module_id": "Ag1Com", + "type": "local_broadcast" + }, + { + "module_id": "room", + "type": "simulator", + "model": { + "type": {"file": "mpc_and_sim/simple_model_sim.py", "class_name": "BaselineMPCModel"}, + "states": [{"name": "T", "value": 298}] + + }, + "t_sample_communication": 10, + "t_sample_simulation": 10, + "save_results": true, + "result_filename": "results/sim_room.csv", + "overwrite_result_file": true, + "outputs": [ + {"name": "T_out", "alias": "T"}, + {"name": "P_el","alias": "P_el_sim"} + ], + "inputs": [ + {"name": "mDot", "value": 0.02, "alias": "mDot"} + ] + } + ] +} \ No newline at end of file diff --git a/examples/OneRoom_SimpleLinRegMPC/plot_results.py b/examples/OneRoom_SimpleLinRegMPC/plot_results.py new file mode 100644 index 00000000..327a5052 --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/plot_results.py @@ -0,0 +1,185 @@ +import numpy as np +import matplotlib.pyplot as plt +import agentlib_mpc.utils.plotting.basic as mpcplot +from agentlib_mpc.utils.analysis import mpc_at_time_step +from agentlib_flexquant.data_structures.flex_results import Results + + +def plot_results(results_data: dict = None): + """ + Example how plotting with matplotlib and mpcplot from agentlib_mpc works + """ + if results_data is None: + res = Results( + flex_config="flex_configs/flexibility_agent_config.json", + simulator_agent_config="mpc_and_sim/simple_sim.json", + results="results" + ) + else: + res = Results( + flex_config="flex_configs/flexibility_agent_config.json", + simulator_agent_config="mpc_and_sim/simple_sim.json", + results=results_data + ) + + fig, axs = mpcplot.make_fig(style=mpcplot.Style(use_tex=False), rows=2) + (ax1, ax2) = axs + # load + ax1.set_ylabel(r"$\dot{Q}_{Room}$ in W") + res.df_simulation["load"].plot(ax=ax1) + # T_in + ax2.set_ylabel("$T_{in}$ in K") + res.df_simulation["T_in"].plot(ax=ax2) + x_ticks = np.arange(0, 3600 * 6 + 1, 3600) + x_tick_labels = [int(tick / 3600) for tick in x_ticks] + ax2.set_xticks(x_ticks) + ax2.set_xticklabels(x_tick_labels) + ax2.set_xlabel("Time in hours") + for ax in axs: + mpcplot.make_grid(ax) + ax.set_xlim(0, 3600 * 6) + + # room temp + fig, axs = mpcplot.make_fig(style=mpcplot.Style(use_tex=False), rows=1) + ax1 = axs[0] + # T out + ax1.set_ylabel("$T_{room}$ in K") + res.df_simulation["T_upper"].plot(ax=ax1, color="0.5") + res.df_simulation["T_lower"].plot(ax=ax1, color="0.5") + res.df_simulation["T_out"].plot(ax=ax1, color=mpcplot.EBCColors.dark_grey) + mpc_at_time_step( + data=res.df_neg_flex, time_step=9000, variable="T" + ).plot(ax=ax1, label="neg", linestyle="--", color=mpcplot.EBCColors.red) + mpc_at_time_step( + data=res.df_pos_flex, time_step=9000, variable="T" + ).plot(ax=ax1, label="pos", linestyle="--", color=mpcplot.EBCColors.blue) + mpc_at_time_step( + data=res.df_baseline, time_step=9900, variable="T" + ).plot(ax=ax1, label="base", linestyle="--", color=mpcplot.EBCColors.dark_grey) + + ax1.legend() + ax1.vlines(9000, ymin=0, ymax=500, colors="black") + ax1.vlines(9900, ymin=0, ymax=500, colors="black") + ax1.vlines(10800, ymin=0, ymax=500, colors="black") + ax1.vlines(18000, ymin=0, ymax=500, colors="black") + + ax1.set_ylim(289, 299) + x_ticks = np.arange(0, 3600 * 6 + 1, 3600) + x_tick_labels = [int(tick / 3600) for tick in x_ticks] + ax1.set_xticks(x_ticks) + ax1.set_xticklabels(x_tick_labels) + ax1.set_xlabel("Time in hours") + for ax in axs: + mpcplot.make_grid(ax) + ax.set_xlim(0, 3600 * 6) + + # predictions + fig, axs = mpcplot.make_fig(style=mpcplot.Style(use_tex=False), rows=2) + (ax1, ax2) = axs + # P_el + ax1.set_ylabel("$P_{el}$ in kW") + res.df_simulation["P_el"].plot(ax=ax1, color=mpcplot.EBCColors.dark_grey) + mpc_at_time_step( + data=res.df_neg_flex, time_step=9000, variable="P_el" + ).ffill().plot( + ax=ax1, + drawstyle="steps-post", + label="neg", + linestyle="--", + color=mpcplot.EBCColors.red, + ) + mpc_at_time_step( + data=res.df_pos_flex, time_step=9000, variable="P_el" + ).ffill().plot( + ax=ax1, + drawstyle="steps-post", + label="pos", + linestyle="--", + color=mpcplot.EBCColors.blue, + ) + mpc_at_time_step( + data=res.df_baseline, time_step=9000, variable="P_el" + ).ffill().plot( + ax=ax1, + drawstyle="steps-post", + label="base", + linestyle="--", + color=mpcplot.EBCColors.dark_grey, + ) + ax1.legend() + ax1.vlines(9000, ymin=-1000, ymax=5000, colors="black") + ax1.vlines(9900, ymin=-1000, ymax=5000, colors="black") + ax1.vlines(10800, ymin=-1000, ymax=5000, colors="black") + ax1.vlines(18000, ymin=-1000, ymax=5000, colors="black") + ax1.set_ylim(-0.1, 1) + + # mdot + ax2.set_ylabel(r"$\dot{m}$ in kg/s") + res.df_simulation["mDot"].plot(ax=ax2, color=mpcplot.EBCColors.dark_grey) + mpc_at_time_step( + data=res.df_neg_flex, time_step=9000, variable="mDot" + ).ffill().plot( + ax=ax2, + drawstyle="steps-post", + label="neg", + linestyle="--", + color=mpcplot.EBCColors.red, + ) + mpc_at_time_step( + data=res.df_pos_flex, time_step=9000, variable="mDot" + ).ffill().plot( + ax=ax2, + drawstyle="steps-post", + label="pos", + linestyle="--", + color=mpcplot.EBCColors.blue, + ) + mpc_at_time_step( + data=res.df_baseline, time_step=9900, variable="mDot" + ).ffill().plot( + ax=ax2, + drawstyle="steps-post", + label="base", + linestyle="--", + color=mpcplot.EBCColors.dark_grey, + ) + ax2.legend() + ax2.vlines(9000, ymin=0, ymax=500, colors="black") + ax2.vlines(9900, ymin=0, ymax=500, colors="black") + ax2.vlines(10800, ymin=0, ymax=500, colors="black") + ax2.vlines(18000, ymin=0, ymax=500, colors="black") + + ax2.set_ylim(0, 0.06) + + x_ticks = np.arange(0, 3600 * 6 + 1, 3600) + x_tick_labels = [int(tick / 3600) for tick in x_ticks] + ax2.set_xticks(x_ticks) + ax2.set_xticklabels(x_tick_labels) + ax2.set_xlabel("Time in hours") + for ax in axs: + mpcplot.make_grid(ax) + ax.set_xlim(0, 3600 * 6) + + # flexibility + # get only the first prediction time of each time step + energy_flex_neg = res.df_indicator.xs("negative_energy_flex", axis=1).droplevel(1).dropna() + energy_flex_pos = res.df_indicator.xs("positive_energy_flex", axis=1).droplevel(1).dropna() + fig, axs = mpcplot.make_fig(style=mpcplot.Style(use_tex=False), rows=1) + ax1 = axs[0] + ax1.set_ylabel(r"$\epsilon$ in kWh") + energy_flex_neg.plot(ax=ax1, label="neg") + energy_flex_pos.plot(ax=ax1, label="pos") + energy_flex_neg.plot(ax=ax1, label="neg", color=mpcplot.EBCColors.red) + energy_flex_pos.plot(ax=ax1, label="pos", color=mpcplot.EBCColors.blue) + + ax1.legend() + + x_ticks = np.arange(0, 3600 * 6 + 1, 3600) + x_tick_labels = [int(tick / 3600) for tick in x_ticks] + ax1.set_xticks(x_ticks) + ax1.set_xticklabels(x_tick_labels) + ax1.set_xlabel("Time in hours") + for ax in axs: + mpcplot.make_grid(ax) + ax.set_xlim(0, 3600 * 6) + diff --git a/examples/OneRoom_SimpleLinRegMPC/predictor/predictor_config.json b/examples/OneRoom_SimpleLinRegMPC/predictor/predictor_config.json new file mode 100644 index 00000000..baa0c6ad --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/predictor/predictor_config.json @@ -0,0 +1,26 @@ +{ + "id": "myPredictorAgent", + "modules": [ + { + "module_id": "Ag4Com", + "type": "local_broadcast" + }, + { + "module_id": "MyPredictor", + "type": { + "file": "predictor/simple_predictor.py", + "class_name": "PredictorModule" + }, + "parameters": [ + { + "name": "time_step", + "value": 900 + }, + { + "name": "prediction_horizon", + "value": 49 + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/OneRoom_SimpleLinRegMPC/predictor/simple_predictor.py b/examples/OneRoom_SimpleLinRegMPC/predictor/simple_predictor.py new file mode 100644 index 00000000..9bb3a2ea --- /dev/null +++ b/examples/OneRoom_SimpleLinRegMPC/predictor/simple_predictor.py @@ -0,0 +1,53 @@ +import agentlib as al +import numpy as np +import pandas as pd +from agentlib.core import Agent +from typing import List +import json +import csv +from datetime import datetime + +class PredictorModuleConfig(al.BaseModuleConfig): + """Module that outputs a prediction of the heat load at a specified + interval.""" + outputs: al.AgentVariables = [ + al.AgentVariable( + name="r_pel", unit="ct/kWh", type="pd.Series", description="Weight for P_el in objective function" + ), + ] + parameters: al.AgentVariables = [ + al.AgentVariable( + name="time_step", value=900, description="Sampling time for prediction." + ), + al.AgentVariable( + name="prediction_horizon", + value=8, + description="Number of sampling points for prediction.", + ) + ] + + + shared_variable_fields:List[str] = ["outputs"] + + +class PredictorModule(al.BaseModule): + """Module that outputs a prediction of the heat load at a specified + interval.""" + + config: PredictorModuleConfig + + def register_callbacks(self): + pass + + def process(self): + while True: + sample_time = self.env.config.t_sample + ts = self.get("time_step").value + k = self.get("prediction_horizon").value + now = self.env.now + + grid = np.arange(now, now + k * ts + 1, sample_time) + p_traj = pd.Series([1 for i in grid], index=list(grid)) + self.set("r_pel", p_traj) + + yield self.env.timeout(sample_time) diff --git a/examples/OneRoom_SimpleMPC/mpc_and_sim/simple_model.json b/examples/OneRoom_SimpleMPC/mpc_and_sim/simple_model.json index 3ee47248..cad7dc93 100644 --- a/examples/OneRoom_SimpleMPC/mpc_and_sim/simple_model.json +++ b/examples/OneRoom_SimpleMPC/mpc_and_sim/simple_model.json @@ -73,7 +73,7 @@ "name": "P_el", "alias": "P_el" }, - { + { "name": "E_out", "alias": "E_out" }, diff --git a/examples/OneRoom_SimpleMPC/mpc_and_sim/simple_model.py b/examples/OneRoom_SimpleMPC/mpc_and_sim/simple_model.py index 656a572c..5fcae883 100644 --- a/examples/OneRoom_SimpleMPC/mpc_and_sim/simple_model.py +++ b/examples/OneRoom_SimpleMPC/mpc_and_sim/simple_model.py @@ -51,7 +51,7 @@ class BaselineMPCModelConfig(CasadiModelConfig): unit="K", description="Slack variable of temperature of zone", ), - + ] parameters: list[CasadiParameter] = [ @@ -84,24 +84,24 @@ class BaselineMPCModelConfig(CasadiModelConfig): CasadiOutput(name="eta_heater", unit="-", value=1, description="Efficiency of electrical heater"), CasadiOutput(name="P_el", unit="W", description="The power input to the system", - ), + ) ] class BaselineMPCModel(CasadiModel): - config: BaselineMPCModelConfig - + config: BaselineMPCModelConfig + def setup_system(self): # Define ode self.T.ode = ( - self.cp * self.mDot / self.C * (self.T_in - self.T) + self.load / self.C + self.cp * self.mDot / self.C * (self.T_in - self.T) + self.load / self.C ) # Define ae self.P_el.alg = self.cp * self.mDot * (self.T - self.T_in) / 1000 / self.eta_heater self.eta_heater.alg = 1 self.T_out.alg = self.T # math operation to get the symbolic variable - self.E_out.alg = - self.T * self.C / (3600*1000) # stored electrical energy in kWh + self.E_out.alg = - self.T * self.C / (3600 * 1000) # stored electrical energy in kWh # Constraints: list[(lower bound, function, upper bound)] self.constraints = [ @@ -113,9 +113,9 @@ def setup_system(self): # Objective function objective = sum( - [ - self.r_mDot * self.mDot, - self.s_T * self.T_slack**2, - ] - ) + [ + self.r_mDot * self.mDot, + self.s_T * self.T_slack ** 2, + ] + ) return objective diff --git a/setup.py b/setup.py index e8c31b42..504f4eee 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,9 @@ author="", author_email="", description="Flexibility quantification setup based on agentlib_mpc", + extras_require={ + 'ml': ['agentlib_mpc[ml] @ git+https://github.com/RWTH-EBC/AgentLib-MPC.git@quickfix-custom-objectives'], + }, packages=setuptools.find_packages(), classifiers=[ "Programming Language :: Python :: 3.8", diff --git a/tests/snapshots/test_OneRoom_CIA/test_oneroom_cia/oneroom_cia_indicator_summary.json b/tests/snapshots/test_OneRoom_CIA/test_oneroom_cia/oneroom_cia_indicator_summary.json index 856cb099..e08d6309 100644 --- a/tests/snapshots/test_OneRoom_CIA/test_oneroom_cia/oneroom_cia_indicator_summary.json +++ b/tests/snapshots/test_OneRoom_CIA/test_oneroom_cia/oneroom_cia_indicator_summary.json @@ -34,7 +34,7 @@ "market_time", "flex_event_duration", "prediction_horizon", - "collocation_time_grid", + "time_grid_info", "time_step_mpc" ], "head_5_rows": { @@ -73,7 +73,7 @@ "market_time", "flex_event_duration", "prediction_horizon", - "collocation_time_grid", + "time_grid_info", "time_step_mpc" ], "data": [ @@ -368,16 +368,6 @@ "min": 0.0, "std": 0.0 }, - "collocation_time_grid": { - "25%": 1231.7, - "50%": 2400.0, - "75%": 3568.3, - "count": 384.0, - "max": 4736.6, - "mean": 2400.0, - "min": 63.4, - "std": 1387.4 - }, "flex_event_duration": { "25%": 2400.0, "50%": 2400.0, @@ -462,11 +452,11 @@ "25%": 0.0, "50%": 500.0, "75%": 500.0, - "count": 492.0, + "count": 300.0, "max": 500.0, - "mean": 309.3, + "mean": 303.2, "min": -500.0, - "std": 240.4 + "std": 244.2 }, "negative_power_flex_offer_avg": { "25%": 248.1, @@ -562,11 +552,11 @@ "25%": 0.0, "50%": 0.0, "75%": 0.0, - "count": 492.0, + "count": 300.0, "max": 500.0, - "mean": 51.0, + "mean": 47.6, "min": -500.0, - "std": 144.8 + "std": 150.6 }, "positive_power_flex_offer_avg": { "25%": 5.3, @@ -628,6 +618,16 @@ "min": 10.0, "std": 0.0 }, + "time_grid_info": { + "25%": 1231.7, + "50%": 2400.0, + "75%": 3568.3, + "count": 384.0, + "max": 4736.6, + "mean": 2400.0, + "min": 63.4, + "std": 1387.4 + }, "time_step_mpc": { "25%": 300.0, "50%": 300.0, @@ -675,7 +675,7 @@ "market_time", "flex_event_duration", "prediction_horizon", - "collocation_time_grid", + "time_grid_info", "time_step_mpc" ], "data": [ diff --git a/tests/snapshots/test_SimpleBuilding/test_simplebuilding/SimpleBuilding_indicator_summary.json b/tests/snapshots/test_SimpleBuilding/test_simplebuilding/SimpleBuilding_indicator_summary.json index 1de1ca66..405a1ce5 100644 --- a/tests/snapshots/test_SimpleBuilding/test_simplebuilding/SimpleBuilding_indicator_summary.json +++ b/tests/snapshots/test_SimpleBuilding/test_simplebuilding/SimpleBuilding_indicator_summary.json @@ -34,7 +34,7 @@ "market_time", "flex_event_duration", "prediction_horizon", - "collocation_time_grid", + "time_grid_info", "time_step_mpc" ], "head_5_rows": { @@ -73,7 +73,7 @@ "market_time", "flex_event_duration", "prediction_horizon", - "collocation_time_grid", + "time_grid_info", "time_step_mpc" ], "data": [ @@ -86,8 +86,8 @@ NaN, 1.0, 0.0, - 0.0, - 0.0, + NaN, + NaN, NaN, NaN, 41.42016, @@ -100,14 +100,14 @@ true, 90.74214, 11.64628, - 93.72299, - 15.9482, - 93.72299, - 15.9482, - 1.03285, - 1.36938, - 1.03285, - 1.36938, + 94.63477, + -1.61884, + 94.63477, + -1.61884, + 1.0429, + -0.139, + 1.0429, + -0.139, 900.0, 900.0, 7200.0, @@ -368,16 +368,6 @@ "min": 0.0, "std": 0.0 }, - "collocation_time_grid": { - "25%": 10895.09619, - "50%": 21600.0, - "75%": 32304.90381, - "count": 384.0, - "max": 43009.80762, - "mean": 21600.0, - "min": 190.19238, - "std": 12487.03557 - }, "flex_event_duration": { "25%": 7200.0, "50%": 7200.0, @@ -399,44 +389,44 @@ "std": 0.0 }, "negative_corrected_costs": { - "25%": 78.6882, - "50%": 79.95716, - "75%": 84.02288, + "25%": 79.60304, + "50%": 80.91256, + "75%": 84.99916, "count": 4.0, - "max": 93.72299, - "mean": 82.75392, - "min": 77.37836, - "std": 7.44416 + "max": 94.63477, + "mean": 83.68963, + "min": 78.29862, + "std": 7.43446 }, "negative_corrected_costs_rel": { - "25%": 1.04608, - "50%": 1.05136, - "75%": 1.05225, + "25%": 1.05832, + "50%": 1.06394, + "75%": 1.0645, "count": 4.0, - "max": 1.05227, - "mean": 1.04696, - "min": 1.03285, - "std": 0.00945 + "max": 1.06475, + "mean": 1.05888, + "min": 1.0429, + "std": 0.01067 }, "negative_costs": { - "25%": 78.6882, - "50%": 79.95716, - "75%": 84.02288, + "25%": 79.60304, + "50%": 80.91256, + "75%": 84.99916, "count": 4.0, - "max": 93.72299, - "mean": 82.75392, - "min": 77.37836, - "std": 7.44416 + "max": 94.63477, + "mean": 83.68963, + "min": 78.29862, + "std": 7.43446 }, "negative_costs_rel": { - "25%": 1.04608, - "50%": 1.05136, - "75%": 1.05225, + "25%": 1.05832, + "50%": 1.06394, + "75%": 1.0645, "count": 4.0, - "max": 1.05227, - "mean": 1.04696, - "min": 1.03285, - "std": 0.00945 + "max": 1.06475, + "mean": 1.05888, + "min": 1.0429, + "std": 0.01067 }, "negative_energy_flex": { "25%": 74.77996, @@ -452,21 +442,21 @@ "25%": 0.0, "50%": 0.0, "75%": 0.0, - "count": 580.0, + "count": 576.0, "max": 133.29934, - "mean": 6.8486, + "mean": 6.89616, "min": -59.19998, - "std": 23.89503 + "std": 23.97114 }, "negative_power_flex_offer": { - "25%": 36.45976, - "50%": 38.16392, - "75%": 46.2347, - "count": 100.0, + "25%": 35.77786, + "50%": 37.80566, + "75%": 45.97691, + "count": 36.0, "max": 133.29934, - "mean": 46.45813, + "mean": 39.30288, "min": -59.19998, - "std": 34.54558 + "std": 42.25591 }, "negative_power_flex_offer_avg": { "25%": 37.38998, @@ -499,44 +489,44 @@ "std": 5.3469 }, "positive_corrected_costs": { - "25%": 16.17708, - "50%": 16.41002, - "75%": 16.62597, + "25%": -1.34791, + "50%": -1.22117, + "75%": -1.17686, "count": 4.0, - "max": 16.8039, - "mean": 16.39303, - "min": 15.9482, - "std": 0.37253 + "max": -1.15322, + "mean": -1.3036, + "min": -1.61884, + "std": 0.21466 }, "positive_corrected_costs_rel": { - "25%": 1.37956, - "50%": 1.38457, - "75%": 1.38672, + "25%": -0.11238, + "50%": -0.10132, + "75%": -0.09897, "count": 4.0, - "max": 1.38836, - "mean": 1.38172, - "min": 1.36938, - "std": 0.00852 + "max": -0.09851, + "mean": -0.11003, + "min": -0.139, + "std": 0.01944 }, "positive_costs": { - "25%": 16.17708, - "50%": 16.41002, - "75%": 16.62597, + "25%": -1.34791, + "50%": -1.22117, + "75%": -1.17686, "count": 4.0, - "max": 16.8039, - "mean": 16.39303, - "min": 15.9482, - "std": 0.37253 + "max": -1.15322, + "mean": -1.3036, + "min": -1.61884, + "std": 0.21466 }, "positive_costs_rel": { - "25%": 1.37956, - "50%": 1.38457, - "75%": 1.38672, + "25%": -0.11238, + "50%": -0.10132, + "75%": -0.09897, "count": 4.0, - "max": 1.38836, - "mean": 1.38172, - "min": 1.36938, - "std": 0.00852 + "max": -0.09851, + "mean": -0.11003, + "min": -0.139, + "std": 0.01944 }, "positive_energy_flex": { "25%": 11.69175, @@ -552,21 +542,21 @@ "25%": 0.0, "50%": 0.0, "75%": 0.0, - "count": 580.0, + "count": 576.0, "max": 59.13035, - "mean": -1.35667, + "mean": -1.36609, "min": -144.49196, - "std": 22.69287 + "std": 22.77138 }, "positive_power_flex_offer": { - "25%": -2.04346, - "50%": 1.11256, - "75%": 9.24657, - "count": 100.0, + "25%": -2.04518, + "50%": 1.11201, + "75%": 9.24185, + "count": 36.0, "max": 59.13035, - "mean": 9.60406, + "mean": 6.83617, "min": -27.77965, - "std": 20.29285 + "std": 22.06284 }, "positive_power_flex_offer_avg": { "25%": 5.84588, @@ -628,6 +618,16 @@ "min": 1.0, "std": 0.0 }, + "time_grid_info": { + "25%": 10895.09619, + "50%": 21600.0, + "75%": 32304.90381, + "count": 384.0, + "max": 43009.80762, + "mean": 21600.0, + "min": 190.19238, + "std": 12487.03557 + }, "time_step_mpc": { "25%": 900.0, "50%": 900.0, @@ -675,7 +675,7 @@ "market_time", "flex_event_duration", "prediction_horizon", - "collocation_time_grid", + "time_grid_info", "time_step_mpc" ], "data": [ diff --git a/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_baseline_summary.json b/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_baseline_summary.json new file mode 100644 index 00000000..32df27cf --- /dev/null +++ b/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_baseline_summary.json @@ -0,0 +1,1048 @@ +{ + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "_P_external" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "rel_start" + ], + [ + "parameter", + "rel_end" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "profile_deviation_weight" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "head_5_rows": { + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "_P_external" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "rel_start" + ], + [ + "parameter", + "rel_end" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "profile_deviation_weight" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "data": [ + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + 298.16, + 0.02, + 298.16, + 303.15, + 288.15, + 0.02, + 0.05, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + 298.16, + 0.02, + 298.16, + 303.15, + 288.15, + 0.02, + 0.05, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + 1000.0, + 100000.0, + 250.0, + 1.0, + 100.0, + 298.16, + NaN, + 298.16, + 303.15, + 288.15, + 0.0411, + 0.05, + 0.0, + 4.01, + Infinity, + -Infinity, + 298.16, + 0.7401, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.012, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1683, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0096, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1347, + Infinity, + Infinity, + -Infinity, + -Infinity + ] + ], + "index": [ + [ + 0.0, + -1800.0 + ], + [ + 0.0, + -900.0 + ], + [ + 0.0, + 0.0 + ], + [ + 0.0, + 900.0 + ], + [ + 0.0, + 1800.0 + ] + ] + }, + "index_end": "(2700.0, 43200.0)", + "index_start": "(0.0, -1800.0)", + "shape": [ + 204, + 30 + ], + "statistics": { + "lower.P_el": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.T": { + "25%": 288.15, + "50%": 288.15, + "75%": 288.15, + "count": 204.0, + "max": 288.15, + "mean": 288.15, + "min": 288.15, + "std": 0.0 + }, + "lower.T_out": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.T_slack": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.mDot": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.C": { + "25%": 100000.0, + "50%": 100000.0, + "75%": 100000.0, + "count": 4.0, + "max": 100000.0, + "mean": 100000.0, + "min": 100000.0, + "std": 0.0 + }, + "parameter.T": { + "25%": 293.8146, + "50%": 296.1162, + "75%": 298.0245, + "count": 12.0, + "max": 298.16, + "mean": 295.9453, + "min": 293.636, + "std": 2.2255 + }, + "parameter.T_in": { + "25%": 280.15, + "50%": 280.15, + "75%": 280.15, + "count": 200.0, + "max": 280.15, + "mean": 280.15, + "min": 280.15, + "std": 0.0 + }, + "parameter.T_lower": { + "25%": 292.15, + "50%": 292.15, + "75%": 292.15, + "count": 200.0, + "max": 292.15, + "mean": 292.15, + "min": 292.15, + "std": 0.0 + }, + "parameter.T_upper": { + "25%": 294.15, + "50%": 294.15, + "75%": 294.15, + "count": 200.0, + "max": 294.15, + "mean": 294.15, + "min": 294.15, + "std": 0.0 + }, + "parameter._P_external": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.cp": { + "25%": 1000.0, + "50%": 1000.0, + "75%": 1000.0, + "count": 4.0, + "max": 1000.0, + "mean": 1000.0, + "min": 1000.0, + "std": 0.0 + }, + "parameter.in_provision": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.load": { + "25%": 150.0, + "50%": 150.0, + "75%": 150.0, + "count": 200.0, + "max": 150.0, + "mean": 150.0, + "min": 150.0, + "std": 0.0 + }, + "parameter.mDot": { + "25%": 0.0088, + "50%": 0.02, + "75%": 0.0411, + "count": 8.0, + "max": 0.0411, + "mean": 0.0236, + "min": 0.0076, + "std": 0.0153 + }, + "parameter.profile_deviation_weight": { + "25%": 100.0, + "50%": 100.0, + "75%": 100.0, + "count": 4.0, + "max": 100.0, + "mean": 100.0, + "min": 100.0, + "std": 0.0 + }, + "parameter.r_mDot": { + "25%": 1.0, + "50%": 1.0, + "75%": 1.0, + "count": 4.0, + "max": 1.0, + "mean": 1.0, + "min": 1.0, + "std": 0.0 + }, + "parameter.rel_end": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.rel_start": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.s_T": { + "25%": 250.0, + "50%": 250.0, + "75%": 250.0, + "count": 4.0, + "max": 250.0, + "mean": 250.0, + "min": 250.0, + "std": 0.0 + }, + "upper.P_el": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.T": { + "25%": 303.15, + "50%": 303.15, + "75%": 303.15, + "count": 204.0, + "max": 303.15, + "mean": 303.15, + "min": 303.15, + "std": 0.0 + }, + "upper.T_out": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.T_slack": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.mDot": { + "25%": 0.05, + "50%": 0.05, + "75%": 0.05, + "count": 200.0, + "max": 0.05, + "mean": 0.05, + "min": 0.05, + "std": 0.0 + }, + "variable.P_el": { + "25%": 0.1461, + "50%": 0.1461, + "75%": 0.1461, + "count": 192.0, + "max": 0.7401, + "mean": 0.1459, + "min": 0.0, + "std": 0.0481 + }, + "variable.T": { + "25%": 294.15, + "50%": 294.15, + "75%": 294.15, + "count": 204.0, + "max": 298.16, + "mean": 294.2758, + "min": 293.636, + "std": 0.681 + }, + "variable.T_out": { + "25%": 294.15, + "50%": 294.15, + "75%": 294.15, + "count": 192.0, + "max": 298.16, + "mean": 294.1675, + "min": 293.6765, + "std": 0.2925 + }, + "variable.T_slack": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0001, + "count": 192.0, + "max": 4.01, + "mean": 0.0215, + "min": 0.0, + "std": 0.2895 + }, + "variable.mDot": { + "25%": 0.0104, + "50%": 0.0104, + "75%": 0.0104, + "count": 200.0, + "max": 0.0411, + "mean": 0.0109, + "min": 0.0, + "std": 0.0047 + } + }, + "tail_5_rows": { + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "_P_external" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "rel_start" + ], + [ + "parameter", + "rel_end" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "profile_deviation_weight" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "data": [ + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.0, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 295.1767, + 303.15, + 288.15, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ] + ], + "index": [ + [ + 2700.0, + 39600.0 + ], + [ + 2700.0, + 40500.0 + ], + [ + 2700.0, + 41400.0 + ], + [ + 2700.0, + 42300.0 + ], + [ + 2700.0, + 43200.0 + ] + ] + } +} \ No newline at end of file diff --git a/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_indicator_summary.json b/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_indicator_summary.json new file mode 100644 index 00000000..491c91d1 --- /dev/null +++ b/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_indicator_summary.json @@ -0,0 +1,896 @@ +{ + "columns": [ + "_P_el_base", + "_P_el_neg", + "_P_el_pos", + "_E_stored_base", + "_E_stored_neg", + "_E_stored_pos", + "r_pel", + "c_pel_feed_in", + "negative_power_flex_full", + "positive_power_flex_full", + "negative_power_flex_offer", + "positive_power_flex_offer", + "negative_power_flex_offer_min", + "positive_power_flex_offer_min", + "negative_power_flex_offer_max", + "positive_power_flex_offer_max", + "negative_power_flex_offer_avg", + "positive_power_flex_offer_avg", + "negative_power_flex_within_boundary", + "positive_power_flex_within_boundary", + "negative_energy_flex", + "positive_energy_flex", + "negative_costs", + "positive_costs", + "negative_corrected_costs", + "positive_corrected_costs", + "negative_costs_rel", + "positive_costs_rel", + "negative_corrected_costs_rel", + "positive_corrected_costs_rel", + "prep_time", + "market_time", + "flex_event_duration", + "prediction_horizon", + "time_grid_info", + "time_step_mpc" + ], + "head_5_rows": { + "columns": [ + "_P_el_base", + "_P_el_neg", + "_P_el_pos", + "_E_stored_base", + "_E_stored_neg", + "_E_stored_pos", + "r_pel", + "c_pel_feed_in", + "negative_power_flex_full", + "positive_power_flex_full", + "negative_power_flex_offer", + "positive_power_flex_offer", + "negative_power_flex_offer_min", + "positive_power_flex_offer_min", + "negative_power_flex_offer_max", + "positive_power_flex_offer_max", + "negative_power_flex_offer_avg", + "positive_power_flex_offer_avg", + "negative_power_flex_within_boundary", + "positive_power_flex_within_boundary", + "negative_energy_flex", + "positive_energy_flex", + "negative_costs", + "positive_costs", + "negative_corrected_costs", + "positive_corrected_costs", + "negative_costs_rel", + "positive_costs_rel", + "negative_corrected_costs_rel", + "positive_corrected_costs_rel", + "prep_time", + "market_time", + "flex_event_duration", + "prediction_horizon", + "time_grid_info", + "time_step_mpc" + ], + "data": [ + [ + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 10.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + -0.1505, + 0.0, + 0.287, + 0.1146, + 0.0198, + 0.0234, + true, + true, + 0.0396, + 0.0467, + 0.3265, + -0.2546, + 0.3265, + -0.2546, + 8.2537, + -5.4467, + 8.2537, + -5.4467, + 900.0, + 900.0, + 7200.0, + 48.0, + 0.0, + 900.0 + ], + [ + 0.1683, + 0.1678, + 0.4532, + NaN, + NaN, + NaN, + 10.0, + 0.0, + 0.0, + -0.2849, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 900.0, + NaN + ], + [ + 0.1347, + 0.4198, + 0.0201, + NaN, + NaN, + NaN, + 10.0, + 0.0, + 0.2851, + 0.1146, + 0.2851, + 0.1146, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 1800.0, + NaN + ], + [ + 0.1505, + 0.0, + 0.0593, + NaN, + NaN, + NaN, + 10.0, + 0.0, + -0.1505, + 0.0912, + -0.1505, + 0.0912, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 2700.0, + NaN + ], + [ + 0.1444, + 0.318, + 0.1059, + NaN, + NaN, + NaN, + 10.0, + 0.0, + 0.1736, + 0.0385, + 0.1736, + 0.0385, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 3600.0, + NaN + ] + ], + "index": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 900.0 + ], + [ + 0.0, + 1800.0 + ], + [ + 0.0, + 2700.0 + ], + [ + 0.0, + 3600.0 + ] + ] + }, + "index_end": "(2700.0, 43200.0)", + "index_start": "(0.0, 0.0)", + "shape": [ + 196, + 36 + ], + "statistics": { + "_E_stored_base": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 0.0, + "max": NaN, + "mean": NaN, + "min": NaN, + "std": NaN + }, + "_E_stored_neg": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 0.0, + "max": NaN, + "mean": NaN, + "min": NaN, + "std": NaN + }, + "_E_stored_pos": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 0.0, + "max": NaN, + "mean": NaN, + "min": NaN, + "std": NaN + }, + "_P_el_base": { + "25%": 0.1461, + "50%": 0.1461, + "75%": 0.1461, + "count": 188.0, + "max": 0.1683, + "mean": 0.143, + "min": 0.0, + "std": 0.0213 + }, + "_P_el_neg": { + "25%": 0.1461, + "50%": 0.1461, + "75%": 0.1461, + "count": 188.0, + "max": 0.4335, + "mean": 0.1452, + "min": 0.0, + "std": 0.0872 + }, + "_P_el_pos": { + "25%": 0.1461, + "50%": 0.1461, + "75%": 0.1461, + "count": 188.0, + "max": 0.4532, + "mean": 0.1432, + "min": 0.0, + "std": 0.0501 + }, + "c_pel_feed_in": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 196.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "flex_event_duration": { + "25%": 7200.0, + "50%": 7200.0, + "75%": 7200.0, + "count": 4.0, + "max": 7200.0, + "mean": 7200.0, + "min": 7200.0, + "std": 0.0 + }, + "market_time": { + "25%": 900.0, + "50%": 900.0, + "75%": 900.0, + "count": 4.0, + "max": 900.0, + "mean": 900.0, + "min": 900.0, + "std": 0.0 + }, + "negative_corrected_costs": { + "25%": 0.2861, + "50%": 0.3262, + "75%": 0.3273, + "count": 4.0, + "max": 0.3298, + "mean": 0.2873, + "min": 0.167, + "std": 0.0802 + }, + "negative_corrected_costs_rel": { + "25%": 7.2199, + "50%": 8.1864, + "75%": 8.2325, + "count": 4.0, + "max": 8.2537, + "mean": 7.2659, + "min": 4.4373, + "std": 1.8863 + }, + "negative_costs": { + "25%": 0.2861, + "50%": 0.3262, + "75%": 0.3273, + "count": 4.0, + "max": 0.3298, + "mean": 0.2873, + "min": 0.167, + "std": 0.0802 + }, + "negative_costs_rel": { + "25%": 7.2199, + "50%": 8.1864, + "75%": 8.2325, + "count": 4.0, + "max": 8.2537, + "mean": 7.2659, + "min": 4.4373, + "std": 1.8863 + }, + "negative_energy_flex": { + "25%": 0.0391, + "50%": 0.0398, + "75%": 0.04, + "count": 4.0, + "max": 0.0401, + "mean": 0.0393, + "min": 0.0376, + "std": 0.0011 + }, + "negative_power_flex_full": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 188.0, + "max": 0.2874, + "mean": 0.0022, + "min": -0.1505, + "std": 0.0846 + }, + "negative_power_flex_offer": { + "25%": -0.1461, + "50%": -0.099, + "75%": 0.2603, + "count": 36.0, + "max": 0.2874, + "mean": 0.0248, + "min": -0.1505, + "std": 0.1905 + }, + "negative_power_flex_offer_avg": { + "25%": 0.0195, + "50%": 0.0199, + "75%": 0.02, + "count": 4.0, + "max": 0.02, + "mean": 0.0197, + "min": 0.0188, + "std": 0.0006 + }, + "negative_power_flex_offer_max": { + "25%": 0.2873, + "50%": 0.2874, + "75%": 0.2874, + "count": 4.0, + "max": 0.2874, + "mean": 0.2873, + "min": 0.287, + "std": 0.0002 + }, + "negative_power_flex_offer_min": { + "25%": -0.1474, + "50%": -0.1463, + "75%": -0.1462, + "count": 4.0, + "max": -0.1461, + "mean": -0.1473, + "min": -0.1505, + "std": 0.0021 + }, + "positive_corrected_costs": { + "25%": -0.3022, + "50%": -0.2566, + "75%": -0.2545, + "count": 4.0, + "max": -0.254, + "mean": -0.3, + "min": -0.4329, + "std": 0.0886 + }, + "positive_corrected_costs_rel": { + "25%": -6.4027, + "50%": -5.5097, + "75%": -5.4584, + "count": 4.0, + "max": -5.4467, + "mean": -6.3514, + "min": -8.9394, + "std": 1.7261 + }, + "positive_costs": { + "25%": -0.3022, + "50%": -0.2566, + "75%": -0.2545, + "count": 4.0, + "max": -0.254, + "mean": -0.3, + "min": -0.4329, + "std": 0.0886 + }, + "positive_costs_rel": { + "25%": -6.4027, + "50%": -5.5097, + "75%": -5.4584, + "count": 4.0, + "max": -5.4467, + "mean": -6.3514, + "min": -8.9394, + "std": 1.7261 + }, + "positive_energy_flex": { + "25%": 0.0465, + "50%": 0.0466, + "75%": 0.0472, + "count": 4.0, + "max": 0.0484, + "mean": 0.0471, + "min": 0.0465, + "std": 0.0009 + }, + "positive_power_flex_full": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 188.0, + "max": 0.142, + "mean": -0.0002, + "min": -0.2866, + "std": 0.0448 + }, + "positive_power_flex_offer": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.04, + "count": 36.0, + "max": 0.142, + "mean": 0.0278, + "min": -0.0026, + "std": 0.0449 + }, + "positive_power_flex_offer_avg": { + "25%": 0.0233, + "50%": 0.0233, + "75%": 0.0236, + "count": 4.0, + "max": 0.0242, + "mean": 0.0235, + "min": 0.0233, + "std": 0.0005 + }, + "positive_power_flex_offer_max": { + "25%": 0.117, + "50%": 0.1179, + "75%": 0.124, + "count": 4.0, + "max": 0.142, + "mean": 0.1231, + "min": 0.1146, + "std": 0.0127 + }, + "positive_power_flex_offer_min": { + "25%": -0.0006, + "50%": 0.0, + "75%": 0.0, + "count": 4.0, + "max": 0.0, + "mean": -0.0006, + "min": -0.0026, + "std": 0.0013 + }, + "prediction_horizon": { + "25%": 48.0, + "50%": 48.0, + "75%": 48.0, + "count": 4.0, + "max": 48.0, + "mean": 48.0, + "min": 48.0, + "std": 0.0 + }, + "prep_time": { + "25%": 900.0, + "50%": 900.0, + "75%": 900.0, + "count": 4.0, + "max": 900.0, + "mean": 900.0, + "min": 900.0, + "std": 0.0 + }, + "r_pel": { + "25%": 10.0, + "50%": 10.0, + "75%": 10.0, + "count": 196.0, + "max": 10.0, + "mean": 10.0, + "min": 10.0, + "std": 0.0 + }, + "time_grid_info": { + "25%": 10800.0, + "50%": 21600.0, + "75%": 32400.0, + "count": 196.0, + "max": 43200.0, + "mean": 21600.0, + "min": 0.0, + "std": 12760.516 + }, + "time_step_mpc": { + "25%": 900.0, + "50%": 900.0, + "75%": 900.0, + "count": 4.0, + "max": 900.0, + "mean": 900.0, + "min": 900.0, + "std": 0.0 + } + }, + "tail_5_rows": { + "columns": [ + "_P_el_base", + "_P_el_neg", + "_P_el_pos", + "_E_stored_base", + "_E_stored_neg", + "_E_stored_pos", + "r_pel", + "c_pel_feed_in", + "negative_power_flex_full", + "positive_power_flex_full", + "negative_power_flex_offer", + "positive_power_flex_offer", + "negative_power_flex_offer_min", + "positive_power_flex_offer_min", + "negative_power_flex_offer_max", + "positive_power_flex_offer_max", + "negative_power_flex_offer_avg", + "positive_power_flex_offer_avg", + "negative_power_flex_within_boundary", + "positive_power_flex_within_boundary", + "negative_energy_flex", + "positive_energy_flex", + "negative_costs", + "positive_costs", + "negative_corrected_costs", + "positive_corrected_costs", + "negative_costs_rel", + "positive_costs_rel", + "negative_corrected_costs_rel", + "positive_corrected_costs_rel", + "prep_time", + "market_time", + "flex_event_duration", + "prediction_horizon", + "time_grid_info", + "time_step_mpc" + ], + "data": [ + [ + 0.1461, + 0.1461, + 0.1461, + NaN, + NaN, + NaN, + 10.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 39600.0, + NaN + ], + [ + 0.1461, + 0.1461, + 0.1461, + NaN, + NaN, + NaN, + 10.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 40500.0, + NaN + ], + [ + 0.1461, + 0.1461, + 0.1461, + NaN, + NaN, + NaN, + 10.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 41400.0, + NaN + ], + [ + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + 10.0, + 0.0, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 42300.0, + NaN + ], + [ + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 10.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 43200.0, + NaN + ] + ], + "index": [ + [ + 2700.0, + 39600.0 + ], + [ + 2700.0, + 40500.0 + ], + [ + 2700.0, + 41400.0 + ], + [ + 2700.0, + 42300.0 + ], + [ + 2700.0, + 43200.0 + ] + ] + } +} \ No newline at end of file diff --git a/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_neg_flex_summary.json b/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_neg_flex_summary.json new file mode 100644 index 00000000..7068732b --- /dev/null +++ b/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_neg_flex_summary.json @@ -0,0 +1,1080 @@ +{ + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "mDot_full" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "prep_time" + ], + [ + "parameter", + "flex_event_duration" + ], + [ + "parameter", + "market_time" + ], + [ + "parameter", + "s_P" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "head_5_rows": { + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "mDot_full" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "prep_time" + ], + [ + "parameter", + "flex_event_duration" + ], + [ + "parameter", + "market_time" + ], + [ + "parameter", + "s_P" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "data": [ + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.02, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 298.16, + 0.02, + 298.16, + 303.15, + 288.15, + 0.02, + 0.05, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.02, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 298.16, + 0.02, + 298.16, + 303.15, + 288.15, + 0.02, + 0.05, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0411, + 0.0, + 1000.0, + 100000.0, + 250.0, + 1.0, + 900.0, + 7200.0, + 900.0, + 10.0, + 298.16, + NaN, + 298.16, + 303.15, + 288.15, + 0.0411, + 0.05, + 0.0, + 4.01, + Infinity, + -Infinity, + 298.16, + 0.7401, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.012, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.012, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1678, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0096, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.1532, + 303.15, + 288.15, + 0.03, + 0.05, + 0.0, + 0.0032, + Infinity, + -Infinity, + 294.1532, + 0.4198, + Infinity, + Infinity, + -Infinity, + -Infinity + ] + ], + "index": [ + [ + 0.0, + -1800.0 + ], + [ + 0.0, + -900.0 + ], + [ + 0.0, + 0.0 + ], + [ + 0.0, + 900.0 + ], + [ + 0.0, + 1800.0 + ] + ] + }, + "index_end": "(2700.0, 43200.0)", + "index_start": "(0.0, -1800.0)", + "shape": [ + 204, + 31 + ], + "statistics": { + "lower.P_el": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.T": { + "25%": 288.15, + "50%": 288.15, + "75%": 288.15, + "count": 204.0, + "max": 288.15, + "mean": 288.15, + "min": 288.15, + "std": 0.0 + }, + "lower.T_out": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.T_slack": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.mDot": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.C": { + "25%": 100000.0, + "50%": 100000.0, + "75%": 100000.0, + "count": 4.0, + "max": 100000.0, + "mean": 100000.0, + "min": 100000.0, + "std": 0.0 + }, + "parameter.T": { + "25%": 294.15, + "50%": 294.15, + "75%": 298.0245, + "count": 12.0, + "max": 298.16, + "mean": 295.408, + "min": 293.6765, + "std": 2.005 + }, + "parameter.T_in": { + "25%": 280.15, + "50%": 280.15, + "75%": 280.15, + "count": 200.0, + "max": 280.15, + "mean": 280.15, + "min": 280.15, + "std": 0.0 + }, + "parameter.T_lower": { + "25%": 292.15, + "50%": 292.15, + "75%": 292.15, + "count": 200.0, + "max": 292.15, + "mean": 292.15, + "min": 292.15, + "std": 0.0 + }, + "parameter.T_upper": { + "25%": 294.15, + "50%": 294.15, + "75%": 294.15, + "count": 200.0, + "max": 294.15, + "mean": 294.15, + "min": 294.15, + "std": 0.0 + }, + "parameter.cp": { + "25%": 1000.0, + "50%": 1000.0, + "75%": 1000.0, + "count": 4.0, + "max": 1000.0, + "mean": 1000.0, + "min": 1000.0, + "std": 0.0 + }, + "parameter.flex_event_duration": { + "25%": 7200.0, + "50%": 7200.0, + "75%": 7200.0, + "count": 4.0, + "max": 7200.0, + "mean": 7200.0, + "min": 7200.0, + "std": 0.0 + }, + "parameter.in_provision": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.load": { + "25%": 150.0, + "50%": 150.0, + "75%": 150.0, + "count": 200.0, + "max": 150.0, + "mean": 150.0, + "min": 150.0, + "std": 0.0 + }, + "parameter.mDot": { + "25%": 0.0101, + "50%": 0.02, + "75%": 0.0411, + "count": 8.0, + "max": 0.0411, + "mean": 0.024, + "min": 0.0093, + "std": 0.0148 + }, + "parameter.mDot_full": { + "25%": 0.0104, + "50%": 0.0104, + "75%": 0.0104, + "count": 200.0, + "max": 0.0411, + "mean": 0.0103, + "min": 0.0, + "std": 0.004 + }, + "parameter.market_time": { + "25%": 900.0, + "50%": 900.0, + "75%": 900.0, + "count": 4.0, + "max": 900.0, + "mean": 900.0, + "min": 900.0, + "std": 0.0 + }, + "parameter.prep_time": { + "25%": 900.0, + "50%": 900.0, + "75%": 900.0, + "count": 4.0, + "max": 900.0, + "mean": 900.0, + "min": 900.0, + "std": 0.0 + }, + "parameter.r_mDot": { + "25%": 1.0, + "50%": 1.0, + "75%": 1.0, + "count": 4.0, + "max": 1.0, + "mean": 1.0, + "min": 1.0, + "std": 0.0 + }, + "parameter.s_P": { + "25%": 10.0, + "50%": 10.0, + "75%": 10.0, + "count": 4.0, + "max": 10.0, + "mean": 10.0, + "min": 10.0, + "std": 0.0 + }, + "parameter.s_T": { + "25%": 250.0, + "50%": 250.0, + "75%": 250.0, + "count": 4.0, + "max": 250.0, + "mean": 250.0, + "min": 250.0, + "std": 0.0 + }, + "upper.P_el": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.T": { + "25%": 303.15, + "50%": 303.15, + "75%": 303.15, + "count": 204.0, + "max": 303.15, + "mean": 303.15, + "min": 303.15, + "std": 0.0 + }, + "upper.T_out": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.T_slack": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.mDot": { + "25%": 0.05, + "50%": 0.05, + "75%": 0.05, + "count": 200.0, + "max": 0.05, + "mean": 0.05, + "min": 0.05, + "std": 0.0 + }, + "variable.P_el": { + "25%": 0.1461, + "50%": 0.1461, + "75%": 0.1461, + "count": 192.0, + "max": 0.7401, + "mean": 0.1483, + "min": 0.0, + "std": 0.0964 + }, + "variable.T": { + "25%": 294.1488, + "50%": 294.15, + "75%": 294.15, + "count": 204.0, + "max": 298.16, + "mean": 294.0222, + "min": 292.1471, + "std": 0.8286 + }, + "variable.T_out": { + "25%": 294.1488, + "50%": 294.15, + "75%": 294.15, + "count": 192.0, + "max": 298.16, + "mean": 293.935, + "min": 292.1471, + "std": 0.6572 + }, + "variable.T_slack": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0002, + "count": 192.0, + "max": 4.01, + "mean": 0.0213, + "min": 0.0, + "std": 0.2894 + }, + "variable.mDot": { + "25%": 0.0104, + "50%": 0.0104, + "75%": 0.0104, + "count": 200.0, + "max": 0.0411, + "mean": 0.0111, + "min": 0.0, + "std": 0.0075 + } + }, + "tail_5_rows": { + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "mDot_full" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "prep_time" + ], + [ + "parameter", + "flex_event_duration" + ], + [ + "parameter", + "market_time" + ], + [ + "parameter", + "s_P" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "data": [ + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.0, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 295.1767, + 303.15, + 288.15, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ] + ], + "index": [ + [ + 2700.0, + 39600.0 + ], + [ + 2700.0, + 40500.0 + ], + [ + 2700.0, + 41400.0 + ], + [ + 2700.0, + 42300.0 + ], + [ + 2700.0, + 43200.0 + ] + ] + } +} \ No newline at end of file diff --git a/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_pos_flex_summary.json b/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_pos_flex_summary.json new file mode 100644 index 00000000..90cac4e2 --- /dev/null +++ b/tests/snapshots/test_oneRoom_SimpleLinRegMPC/test_oneroom_simple_mpc/oneroom_simpleMPC_pos_flex_summary.json @@ -0,0 +1,1080 @@ +{ + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "mDot_full" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "prep_time" + ], + [ + "parameter", + "flex_event_duration" + ], + [ + "parameter", + "market_time" + ], + [ + "parameter", + "s_P" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "head_5_rows": { + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "mDot_full" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "prep_time" + ], + [ + "parameter", + "flex_event_duration" + ], + [ + "parameter", + "market_time" + ], + [ + "parameter", + "s_P" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "data": [ + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.02, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 298.16, + 0.02, + 298.16, + 303.15, + 288.15, + 0.02, + 0.05, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.02, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 298.16, + 0.02, + 298.16, + 303.15, + 288.15, + 0.02, + 0.05, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0411, + 0.0, + 1000.0, + 100000.0, + 250.0, + 1.0, + 900.0, + 7200.0, + 900.0, + 10.0, + 298.16, + NaN, + 298.16, + 303.15, + 288.15, + 0.0411, + 0.05, + 0.0, + 4.01, + Infinity, + -Infinity, + 298.16, + 0.7401, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.012, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0324, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.4532, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0096, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 292.1478, + 303.15, + 288.15, + 0.0017, + 0.05, + 0.0, + 0.0022, + Infinity, + -Infinity, + 292.1478, + 0.0201, + Infinity, + Infinity, + -Infinity, + -Infinity + ] + ], + "index": [ + [ + 0.0, + -1800.0 + ], + [ + 0.0, + -900.0 + ], + [ + 0.0, + 0.0 + ], + [ + 0.0, + 900.0 + ], + [ + 0.0, + 1800.0 + ] + ] + }, + "index_end": "(2700.0, 43200.0)", + "index_start": "(0.0, -1800.0)", + "shape": [ + 204, + 31 + ], + "statistics": { + "lower.P_el": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.T": { + "25%": 288.15, + "50%": 288.15, + "75%": 288.15, + "count": 204.0, + "max": 288.15, + "mean": 288.15, + "min": 288.15, + "std": 0.0 + }, + "lower.T_out": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.T_slack": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": -Infinity, + "mean": -Infinity, + "min": -Infinity, + "std": NaN + }, + "lower.mDot": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.C": { + "25%": 100000.0, + "50%": 100000.0, + "75%": 100000.0, + "count": 4.0, + "max": 100000.0, + "mean": 100000.0, + "min": 100000.0, + "std": 0.0 + }, + "parameter.T": { + "25%": 294.15, + "50%": 294.15, + "75%": 298.0245, + "count": 12.0, + "max": 298.16, + "mean": 295.408, + "min": 293.6765, + "std": 2.005 + }, + "parameter.T_in": { + "25%": 280.15, + "50%": 280.15, + "75%": 280.15, + "count": 200.0, + "max": 280.15, + "mean": 280.15, + "min": 280.15, + "std": 0.0 + }, + "parameter.T_lower": { + "25%": 292.15, + "50%": 292.15, + "75%": 292.15, + "count": 200.0, + "max": 292.15, + "mean": 292.15, + "min": 292.15, + "std": 0.0 + }, + "parameter.T_upper": { + "25%": 294.15, + "50%": 294.15, + "75%": 294.15, + "count": 200.0, + "max": 294.15, + "mean": 294.15, + "min": 294.15, + "std": 0.0 + }, + "parameter.cp": { + "25%": 1000.0, + "50%": 1000.0, + "75%": 1000.0, + "count": 4.0, + "max": 1000.0, + "mean": 1000.0, + "min": 1000.0, + "std": 0.0 + }, + "parameter.flex_event_duration": { + "25%": 7200.0, + "50%": 7200.0, + "75%": 7200.0, + "count": 4.0, + "max": 7200.0, + "mean": 7200.0, + "min": 7200.0, + "std": 0.0 + }, + "parameter.in_provision": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0, + "count": 200.0, + "max": 0.0, + "mean": 0.0, + "min": 0.0, + "std": 0.0 + }, + "parameter.load": { + "25%": 150.0, + "50%": 150.0, + "75%": 150.0, + "count": 200.0, + "max": 150.0, + "mean": 150.0, + "min": 150.0, + "std": 0.0 + }, + "parameter.mDot": { + "25%": 0.0101, + "50%": 0.02, + "75%": 0.0411, + "count": 8.0, + "max": 0.0411, + "mean": 0.024, + "min": 0.0093, + "std": 0.0148 + }, + "parameter.mDot_full": { + "25%": 0.0104, + "50%": 0.0104, + "75%": 0.0104, + "count": 200.0, + "max": 0.0411, + "mean": 0.0103, + "min": 0.0, + "std": 0.004 + }, + "parameter.market_time": { + "25%": 900.0, + "50%": 900.0, + "75%": 900.0, + "count": 4.0, + "max": 900.0, + "mean": 900.0, + "min": 900.0, + "std": 0.0 + }, + "parameter.prep_time": { + "25%": 900.0, + "50%": 900.0, + "75%": 900.0, + "count": 4.0, + "max": 900.0, + "mean": 900.0, + "min": 900.0, + "std": 0.0 + }, + "parameter.r_mDot": { + "25%": 1.0, + "50%": 1.0, + "75%": 1.0, + "count": 4.0, + "max": 1.0, + "mean": 1.0, + "min": 1.0, + "std": 0.0 + }, + "parameter.s_P": { + "25%": 10.0, + "50%": 10.0, + "75%": 10.0, + "count": 4.0, + "max": 10.0, + "mean": 10.0, + "min": 10.0, + "std": 0.0 + }, + "parameter.s_T": { + "25%": 250.0, + "50%": 250.0, + "75%": 250.0, + "count": 4.0, + "max": 250.0, + "mean": 250.0, + "min": 250.0, + "std": 0.0 + }, + "upper.P_el": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.T": { + "25%": 303.15, + "50%": 303.15, + "75%": 303.15, + "count": 204.0, + "max": 303.15, + "mean": 303.15, + "min": 303.15, + "std": 0.0 + }, + "upper.T_out": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.T_slack": { + "25%": NaN, + "50%": NaN, + "75%": NaN, + "count": 192.0, + "max": Infinity, + "mean": Infinity, + "min": Infinity, + "std": NaN + }, + "upper.mDot": { + "25%": 0.05, + "50%": 0.05, + "75%": 0.05, + "count": 200.0, + "max": 0.05, + "mean": 0.05, + "min": 0.05, + "std": 0.0 + }, + "variable.P_el": { + "25%": 0.1461, + "50%": 0.1461, + "75%": 0.1461, + "count": 192.0, + "max": 0.7401, + "mean": 0.1463, + "min": 0.0, + "std": 0.0657 + }, + "variable.T": { + "25%": 294.15, + "50%": 294.15, + "75%": 294.15, + "count": 204.0, + "max": 298.16, + "mean": 294.1729, + "min": 292.1478, + "std": 0.6642 + }, + "variable.T_out": { + "25%": 294.15, + "50%": 294.15, + "75%": 294.15, + "count": 192.0, + "max": 298.16, + "mean": 294.0952, + "min": 292.1478, + "std": 0.4444 + }, + "variable.T_slack": { + "25%": 0.0, + "50%": 0.0, + "75%": 0.0001, + "count": 192.0, + "max": 4.01, + "mean": 0.0212, + "min": 0.0, + "std": 0.2894 + }, + "variable.mDot": { + "25%": 0.0104, + "50%": 0.0104, + "75%": 0.0104, + "count": 200.0, + "max": 0.0411, + "mean": 0.011, + "min": 0.0, + "std": 0.0056 + } + }, + "tail_5_rows": { + "columns": [ + [ + "parameter", + "load" + ], + [ + "parameter", + "T_upper" + ], + [ + "parameter", + "T_lower" + ], + [ + "parameter", + "T_in" + ], + [ + "parameter", + "mDot_full" + ], + [ + "parameter", + "in_provision" + ], + [ + "parameter", + "cp" + ], + [ + "parameter", + "C" + ], + [ + "parameter", + "s_T" + ], + [ + "parameter", + "r_mDot" + ], + [ + "parameter", + "prep_time" + ], + [ + "parameter", + "flex_event_duration" + ], + [ + "parameter", + "market_time" + ], + [ + "parameter", + "s_P" + ], + [ + "parameter", + "T" + ], + [ + "parameter", + "mDot" + ], + [ + "variable", + "T" + ], + [ + "upper", + "T" + ], + [ + "lower", + "T" + ], + [ + "variable", + "mDot" + ], + [ + "upper", + "mDot" + ], + [ + "lower", + "mDot" + ], + [ + "variable", + "T_slack" + ], + [ + "upper", + "T_slack" + ], + [ + "lower", + "T_slack" + ], + [ + "variable", + "T_out" + ], + [ + "variable", + "P_el" + ], + [ + "upper", + "T_out" + ], + [ + "upper", + "P_el" + ], + [ + "lower", + "T_out" + ], + [ + "lower", + "P_el" + ] + ], + "data": [ + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0104, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.1461, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + 150.0, + 294.15, + 292.15, + 280.15, + 0.0, + 0.0, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 294.15, + 303.15, + 288.15, + 0.0, + 0.05, + 0.0, + 0.0, + Infinity, + -Infinity, + 294.15, + 0.0, + Infinity, + Infinity, + -Infinity, + -Infinity + ], + [ + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + 295.1767, + 303.15, + 288.15, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN, + NaN + ] + ], + "index": [ + [ + 2700.0, + 39600.0 + ], + [ + 2700.0, + 40500.0 + ], + [ + 2700.0, + 41400.0 + ], + [ + 2700.0, + 42300.0 + ], + [ + 2700.0, + 43200.0 + ] + ] + } +} \ No newline at end of file diff --git a/tests/test_oneRoom_SimpleLinRegMPC.py b/tests/test_oneRoom_SimpleLinRegMPC.py new file mode 100644 index 00000000..68f22088 --- /dev/null +++ b/tests/test_oneRoom_SimpleLinRegMPC.py @@ -0,0 +1,174 @@ +import pytest +import pandas as pd +import os +import sys +from pathlib import Path +import importlib.util +import json +from util import module_cleanup, round_floats_in_structure +from agentlib.core.errors import OptionalDependencyError + +try: + import keras #check ML dependency +except ImportError: + raise OptionalDependencyError(used_object='agentlib_mpc[ml]', dependency_name="agentlib_mpc[ml]", dependency_install="pip install 'agentlib_mpc[ml] @ git+https://github.com/RWTH-EBC/AgentLib-MPC.git@quickfix-custom-objectives'") + +# Add the project root to the Python path to allow for absolute imports +# This helps in locating the agentlib_flexquant package if needed +root_path = Path(__file__).parent.parent +sys.path.insert(0, str(root_path)) + + +def create_dataframe_summary(df: pd.DataFrame, precision: int = 6) -> dict: + """Create a robust, compact summary of a DataFrame for snapshotting. + + This summary is designed to be insensitive to minor floating-point differences + while being highly sensitive to meaningful data changes. + + Args: + df: The pandas DataFrame to summarize. + precision: The number of decimal places to round float values to. + + Returns: + A dictionary containing the summary. + + """ + if df is None or df.empty: + return {"error": "DataFrame is empty or None"} + + # Get descriptive statistics and round them to handle float precision issues + summary_stats = df.describe().round(precision) + + # Convert the stats DataFrame to a dictionary. This may have tuple keys. + stats_dict_raw = summary_stats.to_dict() + + # Create a new dictionary, converting any tuple keys into strings. + # e.g., ('lower', 'P_el') becomes 'lower.P_el' + stats_dict_clean = { + ".".join(map(str, k)) if isinstance(k, tuple) else str(k): v + for k, v in stats_dict_raw.items() + } + + # Create the final summary object + summary = { + "shape": df.shape, + "columns": df.columns.tolist(), + "index_start": str(tuple(float(x) for x in df.index.min())), + "index_end": str(tuple(float(x) for x in df.index.max())), + "statistics": stats_dict_clean, + "head_5_rows": df.head(5).round(precision).to_dict(orient='split'), + "tail_5_rows": df.tail(5).round(precision).to_dict(orient='split'), + } + return summary + + +def assert_frame_matches_summary_snapshot(snapshot, df: pd.DataFrame, + snapshot_name: str): + """Assert that a DataFrame's summary matches a stored snapshot. + + This function creates a summary of the dataframe and uses pytest-snapshot + to compare it against a stored version. + + """ + # Create a summary of the dataframe + summary = create_dataframe_summary(df) + + # Round all numbers in the summary to handle cross-platform differences + rounded_summary = round_floats_in_structure(summary, precision=4) + + # Convert the summary dictionary to a formatted JSON string + summary_json = json.dumps(rounded_summary, indent=2, sort_keys=True) + + # Use snapshot.assert_match on the small, stable JSON string + snapshot.assert_match(summary_json, snapshot_name) + + +def run_example_from_path(example_path: Path): + """Dynamically import and run the 'run_example' function from a script + in the specified directory. + + This function robustly handles changing the working directory AND the + Python import path, ensuring the script can find both its local files + and its local modules. + + """ + run_script_path = example_path / 'main_one_room_flex.py' + if not run_script_path.is_file(): + raise FileNotFoundError( + f"Could not find the run script at {run_script_path}. " + "Please ensure it is named 'run.py' or adjust the test code." + ) + + # --- SETUP: Store original paths before changing them --- + original_cwd = Path.cwd() + original_sys_path = sys.path[:] # Create a copy of the sys.path list + + module_name = f"agentlib_flexquant.tests.examples.{example_path.name}" + + try: + # --- STEP 1: Change CWD for file access (e.g., config.json) --- + os.chdir(example_path) + + # --- STEP 2: Add example dir to sys.path for module imports --- + sys.path.insert(0, str(example_path)) + + # Dynamically import the run_example function from the script + spec = importlib.util.spec_from_file_location(module_name, run_script_path) + run_module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = run_module + spec.loader.exec_module(run_module) + + if not hasattr(run_module, 'run_example'): + raise AttributeError( + "The 'run.py' script must contain a 'run_example' function.") + + # Execute the function and get the results + results = run_module.run_example(until=3600) + return results + + finally: + # --- TEARDOWN: Always restore original paths to avoid side-effects --- + os.chdir(original_cwd) + sys.path[:] = original_sys_path # Restore the original sys.path + + +def test_oneroom_simple_mpc(snapshot, module_cleanup): + """Unit test for the oneroom_simpleMPC example using snapshot testing. + + This test runs the example via its own run script and compares the + full resulting dataframes against stored snapshots. + + """ + # Define the path to the example directory + example_path = root_path / 'examples' / 'OneRoom_SimpleLinRegMPC' + + # Run the example and get the results object + res = run_example_from_path(example_path) + + # Extract the full resulting dataframes as requested + df_neg_flex_res = res["NegFlexMPC"]["NegFlexMPC"] + df_pos_flex_res = res["PosFlexMPC"]["PosFlexMPC"] + df_baseline_res = res["Baseline"]["Baseline"] + df_indicator_res = res["FlexibilityIndicator"]["FlexibilityIndicator"] + + # Assert that a summary of each result DataFrame matches its snapshot + assert_frame_matches_summary_snapshot( + snapshot, + df_neg_flex_res, + 'oneroom_simpleMPC_neg_flex_summary.json' + ) + assert_frame_matches_summary_snapshot( + snapshot, + df_pos_flex_res, + 'oneroom_simpleMPC_pos_flex_summary.json' + ) + assert_frame_matches_summary_snapshot( + snapshot, + df_baseline_res, + 'oneroom_simpleMPC_baseline_summary.json' + ) + assert_frame_matches_summary_snapshot( + snapshot, + df_indicator_res, + 'oneroom_simpleMPC_indicator_summary.json' + )