From 98ac2039b18af2038d6649c82ee16df88e0b1ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20L=2E=20Magalh=C3=A3es?= <pedro.magalhaes@uni-bremen.de> Date: Sun, 10 Mar 2024 01:21:40 +0100 Subject: [PATCH] Added TimeFrame class. --- src/topupopt/problems/esipp/converter.py | 231 +++-------- src/topupopt/problems/esipp/problem.py | 59 +-- tests/test_esipp_converter.py | 5 +- tests/test_esipp_problem.py | 278 +++++++++++++- tests/test_esipp_time.py | 469 +++++++++++++++++++++++ 5 files changed, 799 insertions(+), 243 deletions(-) create mode 100644 tests/test_esipp_time.py diff --git a/src/topupopt/problems/esipp/converter.py b/src/topupopt/problems/esipp/converter.py index 9a1ab26..fbb972d 100644 --- a/src/topupopt/problems/esipp/converter.py +++ b/src/topupopt/problems/esipp/converter.py @@ -1,32 +1,15 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Nov 17 13:04:53 2021 - -@author: pmede -""" - # standard libraries # local libraries, external - import numpy as np # local libraries, internal - from .dynsys import DynamicSystem - from .signal import Signal, FixedSignal # ***************************************************************************** # ***************************************************************************** -# objectives: -# 1) organise information for optimisation -# 2) upload optimisation results and compute the various objectives -# 3) retrieve information -# 4) - - # TODO: create constant terms using fixed signals @@ -35,8 +18,6 @@ class Converter: def __init__( self, - # converter name/key - key, # system information sys: DynamicSystem, # initial conditions @@ -49,6 +30,14 @@ class Converter: outputs: list or Signal = None, # information about states states: list or Signal = None, + # final A matrix dict: a_nnk + a_nnk: dict = None, + # final B matrix dict: b_nmk + b_nmk: dict = None, + # final C matrix dict: c_rnk + c_rnk: dict = None, + # final D matrix dict: d_rmk + d_rmk: dict = None, # input amplitude costs input_specific_amplitude_costs: dict = None, # output amplitude costs @@ -63,12 +52,24 @@ class Converter: ): # ********************************************************************* - self.key = key - self.sys = sys # ********************************************************************* + if type(a_nnk) == dict: + self.a_nnk = a_nnk # dict(a_nnk) + + if type(b_nmk) == dict: + self.b_nmk = b_nmk # dict(b_nmk) + + if type(c_rnk) == dict: + self.c_rnk = c_rnk # dict(c_rnk) + + if type(d_rmk) == dict: + self.d_rmk = d_rmk # dict(d_rmk) + + # ********************************************************************* + # inputs if type(inputs) == list: @@ -245,59 +246,59 @@ class Converter: number_intervals = self.inputs[0].number_samples - # a_innk + # a_nnk - a_innk = { - (self.key, n1, n2, k): self.sys.A_line_k[ - k if self.sys.A_line_is_time_varying else 0 - ][n1, n2] + a_nnk = { + (n1, n2, k): self.sys.A_line_k[k if self.sys.A_line_is_time_varying else 0][ + n1, n2 + ] for n1 in range(self.sys.number_states) # the state being defined for n2 in range(self.sys.number_states) # the influencing state for k in range(number_intervals) # the time interval } - # b_inmk + # b_nmk - b_inmk = { - (self.key, n1, m, k): self.sys.B_line_k[ - k if self.sys.B_line_is_time_varying else 0 - ][n1, m] + b_nmk = { + (n1, m, k): self.sys.B_line_k[k if self.sys.B_line_is_time_varying else 0][ + n1, m + ] for n1 in range(self.sys.number_states) # the state being defined for m in range(self.sys.number_inputs) # the influencing input if m not in self.fixed_inputs # free inputs only for k in range(number_intervals) # the time interval } - # c_irnk + # c_rnk - c_irnk = { - (self.key, r, n, k): self.sys.C_line_k[ - k if self.sys.C_line_is_time_varying else 0 - ][r, n] + c_rnk = { + (r, n, k): self.sys.C_line_k[k if self.sys.C_line_is_time_varying else 0][ + r, n + ] for r in range(self.sys.number_outputs) # the output being defined for n in range(self.sys.number_states) # the influencing state for k in range(number_intervals) # the time interval } - # d_irmk + # d_rmk - d_irmk = { - (self.key, r, m, k): self.sys.D_line_k[ - k if self.sys.D_line_is_time_varying else 0 - ][r, m] + d_rmk = { + (r, m, k): self.sys.D_line_k[k if self.sys.D_line_is_time_varying else 0][ + r, m + ] for r in range(self.sys.number_outputs) # the output being defined for m in range(self.sys.number_inputs) # the influencing input if m not in self.fixed_inputs # free inputs only for k in range(number_intervals) # the time interval } - # note: e_x_ink does not depend on the initial conditions since the - # a_innk coefficients contain the information to handle them elsewhere + # note: e_x_nk does not depend on the initial conditions since the + # a_nnk coefficients contain the information to handle them elsewhere - # e_x_ink: depends on fixed signals + # e_x_nk: depends on fixed signals - e_x_ink = { - (self.key, n, k): sum( + e_x_nk = { + (n, k): sum( self.sys.B_line_k[k if self.sys.B_line_is_time_varying else 0][n, m] * self.inputs[m].samples[k] for m in self.fixed_inputs # b_inmk*u_imk for fixed inputs @@ -306,13 +307,13 @@ class Converter: for k in range(number_intervals) # the time interval } - # e_y_irk: depends on fixed signals + # e_y_rk: depends on fixed signals - e_y_irk = { - (self.key, r, k): sum( + e_y_rk = { + (r, k): sum( self.sys.D_line_k[k if self.sys.D_line_is_time_varying else 0][r, m] * self.inputs[m].samples[k] - for m in self.fixed_inputs # d_irmk*u_imk for fixed inputs + for m in self.fixed_inputs # d_rmk*u_mk for fixed inputs ) for r in range(self.sys.number_outputs) # the output being defined for k in range(number_intervals) # the time interval @@ -320,136 +321,8 @@ class Converter: # return statement - return a_innk, b_inmk, c_irnk, d_irmk, e_x_ink, e_y_irk - - -# # ************************************************************************* -# # ************************************************************************* - -# def has_dimensionable_inputs(self): - -# if len(self.dimensionable_inputs) == 0: - -# # the system has no dimensionable inputs - -# return False - -# else: # the system has dimensionable inputs - -# return True - -# # ************************************************************************* -# # ************************************************************************* - -# def has_binary_inputs(self): - -# if len(self.binary_inputs) == 0: - -# # the system has no binary inputs - -# return False - -# else: # the system has binary inputs - -# return True - -# # ************************************************************************* -# # ************************************************************************* - -# def has_amplitude_penalised_inputs(self): - -# if len(self.amplitude_penalised_inputs) == 0: - -# # the system has no amplitude-penalised inputs - -# return False - -# else: # the system has amplitude-penalised inputs - -# return True - -# # ************************************************************************* -# # ************************************************************************* - -# def has_externality_inducing_inputs(self): - -# if len(self.externality_inducing_inputs) == 0: - -# # the system has no externality-inducing inputs - -# return False - -# else: # the system has externality-inducing inputs - -# return True - -# # ************************************************************************* -# # ************************************************************************* - -# def has_externality_inducing_outputs(self): - -# if len(self.externality_inducing_outputs) == 0: - -# # the system has no externality-inducing outputs - -# return False - -# else: # the system has externality-inducing outputs - -# return True - -# # ************************************************************************* -# # ************************************************************************* - -# def identify_dimensionable_inputs(self): - -# self.dimensionable_inputs = [ -# i -# for i, u in enumerate(self.inputs) -# if u.is_dimensionable] - -# # ************************************************************************* -# # ************************************************************************* - -# def identify_binary_inputs(self): - -# self.binary_inputs = [ -# i -# for i, u in enumerate(self.inputs) -# if u.is_binary] - -# # ************************************************************************* -# # ************************************************************************* - -# def identify_externality_inducing_inputs(self): - -# self.externality_inducing_inputs = [ -# i -# for i, c in enumerate(self.input_externalities) -# if c != 0] - -# # ************************************************************************* -# # ************************************************************************* - -# def identify_externality_inducing_outputs(self): - -# self.externality_inducing_outputs = [ -# i -# for i, c in enumerate(self.output_externalities) -# if c != 0] - -# # ************************************************************************* -# # ************************************************************************* - -# def identify_amplitude_penalised_inputs(self): - -# self.amplitude_penalised_inputs = [ -# i -# for i, c in enumerate(self.input_amplitude_costs) -# if c != 0] + return a_nnk, b_nmk, c_rnk, d_rmk, e_x_nk, e_y_rk -# # ************************************************************************* -# # ************************************************************************* # ***************************************************************************** # ***************************************************************************** diff --git a/src/topupopt/problems/esipp/problem.py b/src/topupopt/problems/esipp/problem.py index 5f013ff..a12b6d5 100644 --- a/src/topupopt/problems/esipp/problem.py +++ b/src/topupopt/problems/esipp/problem.py @@ -2768,80 +2768,57 @@ class InfrastructurePlanningProblem(EnergySystem): # param_f_amp_y_irqk = {} # output equations: C matrix coefficients - param_c_eq_y_irnqk = { - (converter_key, r, n, q, k): 0 # converter.dssm[k].C[r,n] - for converter_key, converter in self.converters.items() - for r in range(converter.number_outputs) - for n in range(converter.number_states) - for (q, k) in set_QK + (cvt_key, *rnqk): c_rnqk + for cvt_key, cvt in self.converters.items() + for rnqk, c_rnqk in cvt.c_rnqk.items() } # output equations: D matrix coefficients - param_d_eq_y_irmqk = { - (converter_key, r, m, q, k): 0 # converter.dssm[k].C[r,n] - for converter_key, converter in self.converters.items() - for r in range(converter.number_outputs) - for m in range(converter.number_inputs) - for (q, k) in set_QK + (cvt_key, *rmqk): d_rmqk + for cvt_key, cvt in self.converters.items() + for rmqk, d_rmqk in cvt.d_rmqk.items() } # output equations: constant term - param_e_eq_y_irqk = { - (converter_key, r, q, k): 0 # converter.dssm[k].C[r,n] - for converter_key, converter in self.converters.items() - for r in range(converter.number_outputs) - for (q, k) in set_QK + (cvt_key, *rqk): e_rqk + for cvt_key, cvt in self.converters.items() + for rqk, e_rqk in cvt.e_rqk.items() } # ********************************************************************* # states - # state equations: A matrix coefficients - param_a_eq_x_innqk = { - # (converter_key, r, n, k): - # 0 #converter.dssm[k].C[r,n] - # for converter_key, converter in self.converters.items() - # for r in range(converter.number_outputs) - # for n in range(converter.number_states) - # for k in set_K + (cvt_key, *nnqk): a_nnqk + for cvt_key, cvt in self.converters.items() + for nnqk, a_nnqk in cvt.a_nnqk.items() } # state equations: B matrix coefficients - param_b_eq_x_inmqk = { - # (converter_key, r, m, k): - # 0 #converter.dssm[k].C[r,n] - # for converter_key, converter in self.converters.items() - # for r in range(converter.number_outputs) - # for m in range(converter.number_inputs) - # for k in set_K + (cvt_key, *nmqk): b_nmqk + for cvt_key, cvt in self.converters.items() + for nmqk, b_nmqk in cvt.b_nmqk.items() } # state equations: constant term - param_e_eq_x_inqk = { - # (converter_key, r, k): - # 0 #converter.dssm[k].C[r,n] - # for converter_key, converter in self.converters.items() - # for r in range(converter.number_outputs) - # for k in set_K + (cvt_key, *nqk): e_nqk + for cvt_key, cvt in self.converters.items() + for nqk, e_nqk in cvt.e_nqk.items() } # initial states - param_x_inq0 = {} # upper bounds for states (default: none) - param_x_ub_inqk = {} # lower bounds for states (default: none) - param_x_lb_inqk = {} # maximum positive amplitude for inputs diff --git a/tests/test_esipp_converter.py b/tests/test_esipp_converter.py index d049d0e..15e64c2 100644 --- a/tests/test_esipp_converter.py +++ b/tests/test_esipp_converter.py @@ -272,7 +272,6 @@ def method_full_converter(time_step_durations: list): # create a converter cvn1 = cvn.Converter( - "cvn1", sys=ds, initial_states=x0, turn_key_cost=3, @@ -282,7 +281,9 @@ def method_full_converter(time_step_durations: list): ) # get the dictionaries - (a_innk, b_inmk, c_irnk, d_irmk, e_x_ink, e_y_irk) = cvn1.matrix_dictionaries() + (a_innk, b_inmk, c_irnk, d_irmk, e_x_ink, e_y_irk) = cvn1.matrix_dictionaries( + "cvn1" + ) # TODO: check the dicts diff --git a/tests/test_esipp_problem.py b/tests/test_esipp_problem.py index 8fe0e21..69bab4a 100644 --- a/tests/test_esipp_problem.py +++ b/tests/test_esipp_problem.py @@ -16,6 +16,7 @@ from src.topupopt.problems.esipp.network import Arcs, Network from src.topupopt.problems.esipp.resource import ResourcePrice from src.topupopt.problems.esipp.problem import simplify_peak_total_problem from src.topupopt.problems.esipp.problem import is_peak_total_problem +from src.topupopt.problems.esipp.time import TimeFrame # ***************************************************************************** # ***************************************************************************** @@ -38,8 +39,10 @@ class TestESIPPProblem: perform_analysis: bool = False, plot_results: bool = False, print_solver_output: bool = False, + time_frame: TimeFrame = None, irregular_time_intervals: bool = False, networks: dict = None, + converters: dict = None, number_intraperiod_time_intervals: int = 4, static_losses_mode=None, mandatory_arcs: list = None, @@ -47,7 +50,7 @@ class TestESIPPProblem: arc_groups_dict: dict = None, init_aux_sets: bool = False, discount_rates: dict = None, - reporting_periods: dict = None, + # reporting_periods: dict = None, time_intervals: dict = None, assessment_weights: dict = None, simplify_problem: bool = False, @@ -60,8 +63,11 @@ class TestESIPPProblem: if type(assessment_weights) != dict: assessment_weights = {} # default - if type(reporting_periods) != dict: - reporting_periods = {0: (0, 1)} + # if type(reporting_periods) != dict: + # reporting_periods = {0: (0, 1)} + + if type(converters) != dict: + converters = {} # time intervals @@ -114,7 +120,7 @@ class TestESIPPProblem: ipp = InfrastructurePlanningProblem( name="problem", discount_rates=discount_rates, - reporting_periods=reporting_periods, + reporting_periods=time_frame.reporting_periods, time_intervals=time_intervals, time_weights=time_weights, normalised_time_interval_duration=normalised_time_interval_duration, @@ -126,6 +132,11 @@ class TestESIPPProblem: for netkey, net in networks.items(): ipp.add_network(network_key=netkey, network=net) + # add converters + + for cvtkey, cvt in converters.items(): + ipp.add_converter(converter_key=cvtkey, converter=cvt) + # define arcs as mandatory if type(mandatory_arcs) == list: @@ -274,8 +285,13 @@ class TestESIPPProblem: q = 0 # time number_intervals = 3 - # periods - number_periods = 2 + + tf = TimeFrame( + reporting_periods={q: [0, 1]}, + reporting_period_durations={q: [365 * 24 * 3600, 365 * 24 * 3600]}, + time_intervals={q: [0, 1, 2]}, + time_interval_durations={q: [1, 1, 1]}, + ) # 2 nodes: one import, one regular mynet = Network() @@ -285,9 +301,11 @@ class TestESIPPProblem: mynet.add_import_node( node_key=node_IMP, prices={ - (q, p, k): ResourcePrice(prices=1.0, volumes=None) - for p in range(number_periods) - for k in range(number_intervals) + # (q, p, k): ResourcePrice(prices=1.0, volumes=None) + # for p in range(number_periods) + # for k in range(number_intervals) + qpk: ResourcePrice(prices=1.0, volumes=None) + for qpk in tf.qpk() }, ) @@ -305,8 +323,7 @@ class TestESIPPProblem: arc_tech_IA = Arcs( name="any", - # efficiency=[0.5, 0.5, 0.5], - efficiency={(q, 0): 0.5, (q, 1): 0.5, (q, 2): 0.5}, + efficiency={qk: 0.5 for qk in tf.qk()}, efficiency_reverse=None, static_loss=None, capacity=[3], @@ -338,6 +355,7 @@ class TestESIPPProblem: plot_results=False, # True, print_solver_output=False, # irregular_time_intervals=irregular_time_intervals, + time_frame=tf, networks={"mynet": mynet}, number_intraperiod_time_intervals=number_intervals, static_losses_mode=True, # just to reach a line, @@ -406,8 +424,13 @@ class TestESIPPProblem: q = 0 # time number_intervals = 3 - # periods - number_periods = 2 + + tf = TimeFrame( + reporting_periods={q: [0, 1]}, + reporting_period_durations={q: [365 * 24 * 3600, 365 * 24 * 3600]}, + time_intervals={q: [0, 1, 2]}, + time_interval_durations={q: [1, 1, 1]}, + ) # 2 nodes: one import, one regular mynet = Network() @@ -418,9 +441,11 @@ class TestESIPPProblem: mynet.add_import_node( node_key=node_IMP, prices={ - (q, p, k): ResourcePrice(prices=1.0, volumes=None) - for p in range(number_periods) - for k in range(number_intervals) + # (q, p, k): ResourcePrice(prices=1.0, volumes=None) + # for p in range(number_periods) + # for k in range(number_intervals) + qpk: ResourcePrice(prices=1.0, volumes=None) + for qpk in tf.qpk() }, ) @@ -439,8 +464,7 @@ class TestESIPPProblem: arc_tech_IA = Arcs( name="any", - # efficiency=[0.5, 0.5, 0.5], - efficiency={(q, 0): 0.5, (q, 1): 0.5, (q, 2): 0.5}, + efficiency={qk: 0.5 for qk in tf.qk()}, efficiency_reverse=None, static_loss=None, capacity=[3], @@ -472,6 +496,7 @@ class TestESIPPProblem: plot_results=False, # True, print_solver_output=False, # irregular_time_intervals=irregular_time_intervals, + time_frame=tf, networks={"mynet": mynet}, number_intraperiod_time_intervals=number_intervals, static_losses_mode=True, # just to reach a line, @@ -574,6 +599,13 @@ class TestESIPPProblem: # periods number_periods = 1 + tf = TimeFrame( + reporting_periods={q: [0]}, + reporting_period_durations={q: [365 * 24 * 3600]}, + time_intervals={q: [0]}, + time_interval_durations={q: [1]}, + ) + # 2 nodes: one import, one regular mynet = Network() @@ -582,9 +614,11 @@ class TestESIPPProblem: mynet.add_import_node( node_key=node_IMP, prices={ - (q, p, k): ResourcePrice(prices=[1.0, 2.0], volumes=[0.5, None]) - for p in range(number_periods) - for k in range(number_intervals) + # (q, p, k): ResourcePrice(prices=[1.0, 2.0], volumes=[0.5, None]) + # for p in range(number_periods) + # for k in range(number_intervals) + qpk: ResourcePrice(prices=[1.0, 2.0], volumes=[0.5, None]) + for qpk in tf.qpk() }, ) @@ -624,6 +658,7 @@ class TestESIPPProblem: plot_results=False, # True, print_solver_output=False, # irregular_time_intervals=irregular_time_intervals, + time_frame=tf, networks={"mynet": mynet}, number_intraperiod_time_intervals=number_intervals, static_losses_mode=True, # just to reach a line, @@ -687,6 +722,13 @@ class TestESIPPProblem: # periods number_periods = 1 + tf = TimeFrame( + reporting_periods={q: [0]}, + reporting_period_durations={q: [365 * 24 * 3600]}, + time_intervals={q: [0]}, + time_interval_durations={q: [1]}, + ) + # 2 nodes: one export, one regular mynet = Network() @@ -737,6 +779,7 @@ class TestESIPPProblem: plot_results=False, # True, print_solver_output=False, # irregular_time_intervals=irregular_time_intervals, + time_frame=tf, networks={"mynet": mynet}, number_intraperiod_time_intervals=number_intervals, static_losses_mode=True, # just to reach a line, @@ -800,6 +843,13 @@ class TestESIPPProblem: # periods number_periods = 1 + tf = TimeFrame( + reporting_periods={q: [0]}, + reporting_period_durations={q: [365 * 24 * 3600]}, + time_intervals={q: [0]}, + time_interval_durations={q: [1]}, + ) + # 3 nodes: one import, one export, one regular mynet = Network() @@ -877,6 +927,7 @@ class TestESIPPProblem: plot_results=False, # True, print_solver_output=False, # irregular_time_intervals=irregular_time_intervals, + time_frame=tf, networks={"mynet": mynet}, number_intraperiod_time_intervals=number_intervals, static_losses_mode=True, # just to reach a line, @@ -961,6 +1012,191 @@ class TestESIPPProblem: # ************************************************************************* # ************************************************************************* + def test_problem_converter_sink(self): + # scenario + q = 0 + # time + number_intervals = 3 + # periods + number_periods = 1 + + tf = TimeFrame( + reporting_periods={q: [0]}, + reporting_period_durations={q: [365 * 24 * 3600]}, + time_intervals={q: [0]}, + time_interval_durations={q: [1]}, + ) + + # 2 nodes: one import, one regular + mynet = Network() + + # import node + node_IMP = generate_pseudo_unique_key(mynet.nodes()) + mynet.add_import_node( + node_key=node_IMP, + prices={ + (q, p, k): ResourcePrice(prices=1.0, volumes=None) + for p in range(number_periods) + for k in range(number_intervals) + }, + ) + + # other nodes + + node_A = generate_pseudo_unique_key(mynet.nodes()) + + mynet.add_source_sink_node( + node_key=node_A, + # base_flow=[0.5, 0.0, 1.0], + base_flow={(q, 0): 0.50, (q, 1): 0.00, (q, 2): 1.00}, + ) + + # arc IA + + arc_tech_IA = Arcs( + name="any", + # efficiency=[0.5, 0.5, 0.5], + efficiency={(q, 0): 0.5, (q, 1): 0.5, (q, 2): 0.5}, + efficiency_reverse=None, + static_loss=None, + capacity=[3], + minimum_cost=[2], + specific_capacity_cost=1, + capacity_is_instantaneous=False, + validate=False, + ) + + mynet.add_directed_arc(node_key_a=node_IMP, node_key_b=node_A, arcs=arc_tech_IA) + + # identify node types + + mynet.identify_node_types() + + # converters + + # number of samples + time_step_durations = [1, 1, 1] + number_time_steps = len(time_step_durations) + + # get the coefficients + import numpy as np + + # a_innk + a_innk = { + ("cvt1", 0, 0, 0): 0.95, + ("cvt1", 0, 0, 1): 0.95, + ("cvt1", 0, 0, 2): 0.95, + } + + # b_inmk + b_inmk = {("cvt1", 0, 0, 0): 3, ("cvt1", 0, 0, 1): 3, ("cvt1", 0, 0, 2): 3} + + # c_irnk + c_irnk = {} + # d_irmk + d_irmk = {} + # e_x_ink: depends on fixed signals + e_x_ink = {} + # e_y_irk: depends on fixed signals + e_y_irk = {} + + # get the signals + inputs, states, outputs = get_two_node_model_signals(number_time_steps) + + # create a dynamic system + ds = dynsys.DynamicSystem( + time_interval_durations=time_step_durations, A=a, B=b, C=c, D=d + ) + + # create a converter + cvn1 = cvn.Converter( + "cvn1", + sys=ds, + initial_states=x0, + turn_key_cost=3, + inputs=inputs, + states=states, + outputs=outputs, + ) + + # no sos, regular time intervals + + ipp = self.build_solve_ipp( + # solver=solver, + solver_options={}, + # use_sos_arcs=use_sos_arcs, + # arc_sos_weight_key=sos_weight_key, + # arc_use_real_variables_if_possible=use_real_variables_if_possible, + # use_sos_sense=use_sos_sense, + # sense_sos_weight_key=sense_sos_weight_key, + # sense_use_real_variables_if_possible=sense_use_real_variables_if_possible, + # sense_use_arc_interfaces=use_arc_interfaces, + perform_analysis=False, + plot_results=False, # True, + print_solver_output=False, + # irregular_time_intervals=irregular_time_intervals, + time_frame=tf, + networks={"mynet": mynet}, + converters={"mycvt": cvt}, + number_intraperiod_time_intervals=number_intervals, + static_losses_mode=True, # just to reach a line, + mandatory_arcs=[], + max_number_parallel_arcs={}, + # init_aux_sets=init_aux_sets, + simplify_problem=False, + ) + + assert is_peak_total_problem(ipp) + assert ipp.results["Problem"][0]["Number of constraints"] == 24 + assert ipp.results["Problem"][0]["Number of variables"] == 22 + assert ipp.results["Problem"][0]["Number of nonzeros"] == 49 + + # ********************************************************************* + # ********************************************************************* + + # validation + + # the arc should be installed since it is required for feasibility + assert ( + True + in ipp.networks["mynet"] + .edges[(node_IMP, node_A, 0)][Network.KEY_ARC_TECH] + .options_selected + ) + + # the flows should be 1.0, 0.0 and 2.0 + assert math.isclose( + pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, q, 0)]), + 1.0, + abs_tol=1e-6, + ) + assert math.isclose( + pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, q, 1)]), + 0.0, + abs_tol=1e-6, + ) + assert math.isclose( + pyo.value(ipp.instance.var_v_glljqk[("mynet", node_IMP, node_A, 0, q, 2)]), + 2.0, + abs_tol=1e-6, + ) + + # arc amplitude should be two + assert math.isclose( + pyo.value(ipp.instance.var_v_amp_gllj[("mynet", node_IMP, node_A, 0)]), + 2.0, + abs_tol=0.01, + ) + + # capex should be four + assert math.isclose(pyo.value(ipp.instance.var_capex), 4.0, abs_tol=1e-3) + + # sdncf should be -5.7 + assert math.isclose(pyo.value(ipp.instance.var_sdncf_q[q]), -5.7, abs_tol=1e-3) + + # the objective function should be -9.7 + assert math.isclose(pyo.value(ipp.instance.obj_f), -9.7, abs_tol=1e-3) + # ***************************************************************************** # ***************************************************************************** diff --git a/tests/test_esipp_time.py b/tests/test_esipp_time.py new file mode 100644 index 0000000..6658df1 --- /dev/null +++ b/tests/test_esipp_time.py @@ -0,0 +1,469 @@ +# imports + +# local, internal +from src.topupopt.problems.esipp.time import TimeFrame + +# ***************************************************************************** +# ***************************************************************************** + + +class TestTimeFrame: + # ************************************************************************* + # ************************************************************************* + + # test problem from table 2.1 + + def test_nonpositive_durations(self): + # negative reporting period durations + + reporting_periods = {0: [0, 1, 2]} + reporting_period_durations = {0: [1, -1, 1]} + time_intervals = {0: [0, 1]} + time_interval_durations = {0: [1, 1]} + + error_raised = True + try: + TimeFrame( + reporting_periods=reporting_periods, + reporting_period_durations=reporting_period_durations, + time_intervals=time_intervals, + time_interval_durations=time_interval_durations, + ) + except ValueError: + error_raised = True + assert error_raised + + # zero time interval durations + + reporting_periods = {0: [0, 1, 2]} + reporting_period_durations = {0: [1, 1, 1]} + time_intervals = {0: [0, 1]} + time_interval_durations = {0: [1, 0]} + + error_raised = True + try: + TimeFrame( + reporting_periods=reporting_periods, + reporting_period_durations=reporting_period_durations, + time_intervals=time_intervals, + time_interval_durations=time_interval_durations, + ) + except ValueError: + error_raised = True + assert error_raised + + # ************************************************************************* + # ************************************************************************* + + # test problem from table 2.1 + + def test_table_21(self): + # summary: + # 1 assessment + # 3 reporting periods + # 2 time intervals + + reporting_periods = {0: [0, 1, 2]} + reporting_period_durations = { + 0: [365 * 24 * 3600, 365 * 24 * 3600, 365 * 24 * 3600] + } + time_intervals = {0: [0, 1]} + time_interval_durations = {0: [1, 1]} + + tf = TimeFrame( + reporting_periods=reporting_periods, + reporting_period_durations=reporting_period_durations, + time_intervals=time_intervals, + time_interval_durations=time_interval_durations, + ) + + # qk: valid + assert tf.valid_qk( + { + (0, 0): 1, + (0, 1): 1, + } + ) + # qk: not valid + assert not tf.valid_qk( + { + (0, 0): 1, + (0, 2): 1, + } + ) + # qk: valid and complete + assert tf.complete_qk( + { + (0, 0): 1, + (0, 1): 1, + } + ) + # qk: valid but not complete + assert not tf.complete_qk( + { + (0, 0): 1, + } + ) + + # qp: valid + assert tf.valid_qp( + { + (0, 0): 1, + } + ) + # qp: not valid + assert not tf.valid_qp({(0, 0): 1, (0, 1): 1, (0, 3): 1}) + # qp: valid and complete + assert tf.complete_qp({(0, 0): 1, (0, 1): 1, (0, 2): 1}) + # qp: valid but not complete + assert not tf.complete_qp({(0, 0): 1, (0, 1): 1}) + + # qpk: valid + assert tf.valid_qpk( + { + (0, 0, 0): 1, + (0, 0, 1): 1, + } + ) + # qpk: not valid + assert not tf.valid_qpk( + { + (0, 0, 0): 1, + (0, 0, 1): 1, + (0, 1, 0): 1, + (0, 1, 1): 1, + (0, 3, 0): 1, + (0, 3, 1): 1, + } + ) + # qpk: valid and complete + assert tf.complete_qpk( + { + (0, 0, 0): 1, + (0, 0, 1): 1, + (0, 1, 0): 1, + (0, 1, 1): 1, + (0, 2, 0): 1, + (0, 2, 1): 1, + } + ) + # qpk: valid but not complete + assert not tf.complete_qpk( + {(0, 0, 0): 1, (0, 0, 1): 1, (0, 1, 0): 1, (0, 1, 1): 1} + ) + + qk_dict = {qk: None for qk in tf.qk()} + qp_dict = {qp: None for qp in tf.qp()} + qpk_dict = {qpk: None for qpk in tf.qpk()} + + assert tf.complete_qk(qk_dict) + assert tf.complete_qp(qp_dict) + assert tf.complete_qpk(qpk_dict) + + # ************************************************************************* + # ************************************************************************* + + # test problem from table 2.2 + + def test_table_22(self): + # summary: + # 1 assessment + # 3 reporting periods + # 2 time intervals + + reporting_periods = {0: [0], 1: [1, 2]} + reporting_period_durations = { + 0: [365 * 24 * 3600], + 1: [365 * 24 * 3600, 365 * 24 * 3600], + } + time_intervals = {0: [0, 1], 1: [0, 1]} + time_interval_durations = {0: [1, 1], 1: [1, 1]} + + tf = TimeFrame( + reporting_periods=reporting_periods, + reporting_period_durations=reporting_period_durations, + time_intervals=time_intervals, + time_interval_durations=time_interval_durations, + ) + + # qk: valid + assert tf.valid_qk( + { + (0, 0): 1, + (0, 1): 1, + (1, 0): 1, + (1, 1): 1, + } + ) + # qk: not valid + assert not tf.valid_qk( + { + (0, 0): 1, + (0, 1): 1, + (1, 0): 1, + (1, 1): 1, + (1, 2): 1, + } + ) + # qk: valid and complete + assert tf.complete_qk( + { + (0, 0): 1, + (0, 1): 1, + (1, 0): 1, + (1, 1): 1, + } + ) + # qk: valid but not complete + assert not tf.complete_qk( + { + (0, 0): 1, + # (0,1): 1, + (1, 0): 1, + (1, 1): 1, + } + ) + + # qp: valid + assert tf.valid_qp( + { + (0, 0): 1, + (1, 1): 1, + (1, 2): 1, + } + ) + # qp: not valid + assert not tf.valid_qp( + { + (0, 0): 1, + (1, 1): 1, + (1, 3): 1, + } + ) + # qp: valid and complete + assert tf.complete_qp( + { + (0, 0): 1, + (1, 1): 1, + (1, 2): 1, + } + ) + # qp: valid but not complete + assert not tf.complete_qp({(0, 0): 1, (1, 1): 1}) + + # qpk: valid + assert tf.valid_qpk( + { + (0, 0, 0): 1, + (1, 1, 1): 1, + } + ) + # qpk: not valid + assert not tf.valid_qpk( + { + (0, 0, 0): 1, + (0, 0, 1): 1, + (1, 1, 0): 1, + (1, 1, 1): 1, + (1, 3, 0): 1, + (1, 3, 1): 1, + } + ) + # qpk: valid and complete + assert tf.complete_qpk( + { + (0, 0, 0): 1, + (0, 0, 1): 1, + (1, 1, 0): 1, + (1, 1, 1): 1, + (1, 2, 0): 1, + (1, 2, 1): 1, + } + ) + # qpk: valid but not complete + assert not tf.complete_qpk( + {(0, 0, 0): 1, (0, 0, 1): 1, (1, 1, 0): 1, (1, 1, 1): 1} + ) + + qk_dict = {qk: None for qk in tf.qk()} + qp_dict = {qp: None for qp in tf.qp()} + qpk_dict = {qpk: None for qpk in tf.qpk()} + + assert tf.complete_qk(qk_dict) + assert tf.complete_qp(qp_dict) + assert tf.complete_qpk(qpk_dict) + + # ************************************************************************* + # ************************************************************************* + + # TODO: table 2.3 + + def test_table_23(self): + # Problem with three reporting periods relying on two assessments + # with two and three intervals: one assessment for the first two periods with two + # intervals; and another for last period with three intervals. + + reporting_periods = {0: [0, 1], 1: [2]} + reporting_period_durations = { + 1: [365 * 24 * 3600], + 0: [365 * 24 * 3600, 365 * 24 * 3600], + } + time_intervals = {0: [0, 1], 1: [0, 1, 2]} + time_interval_durations = {0: [1, 1], 1: [1, 1, 1]} + + tf = TimeFrame( + reporting_periods=reporting_periods, + reporting_period_durations=reporting_period_durations, + time_intervals=time_intervals, + time_interval_durations=time_interval_durations, + ) + + # qk: valid + assert tf.valid_qk( + { + (0, 0): 1, + (0, 1): 1, + (1, 0): 1, + (1, 1): 1, + (1, 2): 1, + } + ) + # qk: not valid + assert not tf.valid_qk( + { + (0, 0): 1, + (0, 1): 1, + (1, 0): 1, + (1, 1): 1, + (1, 2): 1, + (1, 3): 1, + } + ) + # qk: valid and complete + assert tf.complete_qk( + { + (0, 0): 1, + (0, 1): 1, + (1, 0): 1, + (1, 1): 1, + (1, 2): 1, + } + ) + # qk: valid but not complete + assert not tf.complete_qk( + { + (0, 0): 1, + # (0,1): 1, + (1, 0): 1, + (1, 1): 1, + } + ) + + # qp: valid + assert tf.valid_qp( + { + (0, 0): 1, + (0, 1): 1, + (1, 2): 1, + } + ) + # qp: not valid + assert not tf.valid_qp( + { + (0, 0): 1, + (0, 1): 1, + (1, 3): 1, + } + ) + # qp: valid and complete + assert tf.complete_qp( + { + (0, 0): 1, + (0, 1): 1, + (1, 2): 1, + } + ) + # qp: valid but not complete + assert not tf.complete_qp({(0, 0): 1, (1, 1): 1}) + + # qpk: valid + assert tf.valid_qpk( + { + (0, 0, 0): 1, + (0, 1, 1): 1, + (1, 2, 1): 1, + } + ) + # qpk: not valid + assert not tf.valid_qpk( + { + (0, 0, 0): 1, + (0, 0, 1): 1, + (1, 1, 0): 1, + (1, 1, 1): 1, + (1, 3, 0): 1, + (1, 3, 1): 1, + } + ) + # qpk: valid and complete + assert tf.complete_qpk( + { + (0, 0, 0): 1, + (0, 0, 1): 1, + (0, 1, 0): 1, + (0, 1, 1): 1, + (1, 2, 0): 1, + (1, 2, 1): 1, + (1, 2, 2): 1, + } + ) + # qpk: valid but not complete + assert not tf.complete_qpk( + { + (0, 0, 0): 1, + (0, 0, 1): 1, + (0, 1, 0): 1, + (0, 1, 1): 1, + (1, 2, 0): 1, + (1, 2, 1): 1, + } + ) + + qk_dict = {qk: None for qk in tf.qk()} + qp_dict = {qp: None for qp in tf.qp()} + qpk_dict = {qpk: None for qpk in tf.qpk()} + + assert tf.complete_qk(qk_dict) + assert tf.complete_qp(qp_dict) + assert tf.complete_qpk(qpk_dict) + + # ************************************************************************* + # ************************************************************************* + + # TODO: table 2.4 + + def test_table_24(self): + pass + + # ************************************************************************* + # ************************************************************************* + + # TODO: table 2.5 + + def test_table_25(self): + pass + + # ************************************************************************* + # ************************************************************************* + + # TODO: table 2.6 + + def test_table_26(self): + pass + + # ************************************************************************* + # ************************************************************************* + + +# ***************************************************************************** +# ***************************************************************************** -- GitLab