From d83c6f964267dd88f0f5825e5d4cafb6e0c72645 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pedro=20L=2E=20Magalh=C3=A3es?= <pmlpm@posteo.de>
Date: Thu, 25 Apr 2024 17:49:33 +0200
Subject: [PATCH] Started revising analysis methods.

---
 src/topupopt/problems/esipp/network.py | 25 ++++++++++++++++
 src/topupopt/problems/esipp/time.py    | 25 +++++++++++++++-
 tests/test_esipp_network.py            | 41 ++++++++++++++++++++++++++
 tests/test_esipp_time.py               | 37 +++++++++++++++++++++++
 tests/test_esipp_utils.py              |  3 ++
 5 files changed, 130 insertions(+), 1 deletion(-)

diff --git a/src/topupopt/problems/esipp/network.py b/src/topupopt/problems/esipp/network.py
index abb30f5..1b129d7 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 7db047c..7fc02bb 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 0136b0f..743f246 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 46bfdb2..349f270 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 49c7c0e..f211c52 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
+        
+    # *************************************************************************
+    # *************************************************************************
 
 # *****************************************************************************
 # *****************************************************************************
-- 
GitLab