diff --git a/src/topupopt/problems/esipp/network.py b/src/topupopt/problems/esipp/network.py index abb30f5dc4c544f7ed77d7f5684e94b92f1e4446..1b129d72477d24dda1deb567d69039448ff64159 100644 --- a/src/topupopt/problems/esipp/network.py +++ b/src/topupopt/problems/esipp/network.py @@ -1256,6 +1256,31 @@ class Network(nx.MultiDiGraph): return nx.is_tree(network_view) + # ************************************************************************* + # ************************************************************************* + + def has_selected_antiparallel_arcs(self) -> bool: + "Returns True if any two nodes have selected arcs in both directions." + return len(self.find_selected_antiparallel_arcs()) != 0 + + # ************************************************************************* + # ************************************************************************* + + def find_selected_antiparallel_arcs(self) -> list: + """Returns True if any two nodes have (selected) forward and reverse arcs.""" + + # check the existence of forward and reverse arcs in the same segment + arcs = [ # get the arcs selected + arc_key[0:2] + for arc_key in self.edges(keys=True) + if True in self.edges[arc_key][Network.KEY_ARC_TECH].options_selected + ] + arcs = [ # get the selected arcs that exist both ways + arc_key + for arc_key in arcs + if (arc_key[1], arc_key[0]) in arcs + ] + return arcs # ***************************************************************************** # ***************************************************************************** diff --git a/src/topupopt/problems/esipp/time.py b/src/topupopt/problems/esipp/time.py index 7db047cca5783b790e3de5484a4ea56032d1f39a..7fc02bb54917c22c185ce6335b8609a151551635 100644 --- a/src/topupopt/problems/esipp/time.py +++ b/src/topupopt/problems/esipp/time.py @@ -240,6 +240,29 @@ class TimeFrame: # ************************************************************************* # ************************************************************************* + + def assessments_overlap(self) -> bool: + "Returns True if any period is covered by more than one assessment." + # if there is only one assessment, return False + if self.number_assessments() == 1: + return False + else: + # if there is more than one assessment, check whether two or more + # cover the same period + qs = tuple(self.assessments) + # for each assessment + for q1, q2 in zip(qs, qs[1:]): + # for each period in one assessment (q1) + for p in self.reporting_periods[q1]: + # check if it is covered by the other assessment (q2) + if p in self.reporting_periods[q2]: + # p is covered by at least two assessments (q1 and q2) + return True + # if no period is covered by more than one assessment, return False + return False + + # ************************************************************************* + # ************************************************************************* # ***************************************************************************** # ***************************************************************************** @@ -279,7 +302,7 @@ class EconomicTimeFrame(TimeFrame): # dict: 1 value per p and q self._discount_rates = dict(discount_rates_q) else: - raise ValueError('Unrecognised inputs.') + raise TypeError('Unrecognised inputs.') # TODO: validate the discount rate object diff --git a/tests/test_esipp_network.py b/tests/test_esipp_network.py index 0136b0fdf00847ed5623ad979ae55c0b7dcb3b62..743f24609d6a0c1ecdfaa0b60f0a75f66d6f3e86 100644 --- a/tests/test_esipp_network.py +++ b/tests/test_esipp_network.py @@ -2325,6 +2325,47 @@ class TestNetwork: except ValueError: error_raised = True assert error_raised + + # ************************************************************************* + # ************************************************************************* + + def test_antiparallel_arcs(self): + + # create network + net = Network() + + # add nodes + node_a = 'A' + net.add_waypoint_node(node_a) + node_b = 'B' + net.add_waypoint_node(node_b) + node_c = 'C' + net.add_waypoint_node(node_c) + + # add arcs + node_pairs = ((node_a, node_b), (node_b, node_a),) + + # test network + for node_pair in node_pairs: + net.add_preexisting_directed_arc( + *node_pair, + efficiency=None, + static_loss=None, + capacity=1, + capacity_is_instantaneous=False + ) + # identify the node types + net.identify_node_types() + + # assert that it can detected the selected antiparallel arcs + assert net.has_selected_antiparallel_arcs() + # check that it finds the right node pairs + identified_node_pairs = net.find_selected_antiparallel_arcs() + assert (node_a, node_b) in identified_node_pairs + assert (node_b, node_a) in identified_node_pairs + + # ************************************************************************* + # ************************************************************************* # ***************************************************************************** # ***************************************************************************** diff --git a/tests/test_esipp_time.py b/tests/test_esipp_time.py index 46bfdb2086d89b66174467e79e620d4fe53cc135..349f2701aaaa9e784f9310a89942352c012566ec 100644 --- a/tests/test_esipp_time.py +++ b/tests/test_esipp_time.py @@ -85,6 +85,8 @@ class TestTimeFrame: assert tf.number_reporting_periods(0) == 3 # number of time intervals assert tf.number_time_intervals(0) == 2 + # no overlapping assessments + assert not tf.assessments_overlap() # q: valid assert tf.valid_q(reporting_periods) @@ -243,6 +245,8 @@ class TestTimeFrame: # number of time intervals assert tf.number_time_intervals(0) == 2 assert tf.number_time_intervals(1) == 2 + # no overlapping assessments + assert not tf.assessments_overlap() # q: valid assert tf.valid_q(reporting_periods) @@ -390,6 +394,8 @@ class TestTimeFrame: assert not tf.valid_q({2: 1}) assert tf.complete_q(reporting_periods) assert not tf.complete_q({1: [365 * 24 * 3600]}) + # no overlapping assessments + assert not tf.assessments_overlap() # qk: valid assert tf.valid_qk( @@ -541,6 +547,8 @@ class TestTimeFrame: # number of time intervals assert tf.number_time_intervals(0) == 2 assert tf.number_time_intervals(1) == 2 + # assessments overlap + assert tf.assessments_overlap() # q: valid assert tf.valid_q(reporting_periods) @@ -737,6 +745,8 @@ class TestTimeFrame: assert tf.number_time_intervals(3) == 2 assert tf.number_time_intervals(4) == 2 assert tf.number_time_intervals(5) == 2 + # assessments overlap + assert tf.assessments_overlap() # q: valid assert tf.valid_q(reporting_periods) @@ -1113,6 +1123,33 @@ class TestTimeFrame: for df, true_df in zip(factors, true_factors): assert isclose(df, true_df, abs_tol=0.001) + + # ************************************************************************* + # ************************************************************************* + + def test_etf_unrecognised_input(self): + + # define the discount rate using a set + error_raised = False + try: + EconomicTimeFrame( + discount_rate={0.035}, + reporting_periods={ + 0: [0,1,2,3] + }, + reporting_period_durations={ + 0: [1,1,1,1] + }, + time_intervals={ + 0: [0] + }, + time_interval_durations={ + 0: [1] + }, + ) + except TypeError: + error_raised = True + assert error_raised # ***************************************************************************** # ***************************************************************************** diff --git a/tests/test_esipp_utils.py b/tests/test_esipp_utils.py index 49c7c0e131eb193aa3acbf03352943f2a4fdfe5a..f211c52d65fb05af5a348810ddf854f6c27dc12a 100644 --- a/tests/test_esipp_utils.py +++ b/tests/test_esipp_utils.py @@ -50,6 +50,9 @@ class TestProblemUtils: except ValueError: error_raised = True assert error_raised + + # ************************************************************************* + # ************************************************************************* # ***************************************************************************** # *****************************************************************************