# imports

# standard

import random

from networkx import binomial_tree, MultiDiGraph

# local

from src.topupopt.problems.esipp.network import Arcs, Network

from src.topupopt.problems.esipp.network import ArcsWithoutLosses

from src.topupopt.problems.esipp.network import ArcsWithoutProportionalLosses

from src.topupopt.problems.esipp.network import ArcsWithoutStaticLosses

from src.topupopt.problems.esipp.resource import ResourcePrice

# *****************************************************************************
# *****************************************************************************

# TODO: add test for directed arcs between import and export nodes with static losses

# TODO: add test for undirected arcs involving import and export nodes

class TestNetwork:
        
    # *************************************************************************
    # *************************************************************************

    def test_tree_topology(self):
        
        # create a network object with a tree topology
        
        tree_network = binomial_tree(3, create_using=MultiDiGraph)
        
        network = Network(tree_network)
        
        for edge_key in network.edges(keys=True):
            
            arc = ArcsWithoutLosses(
                name=str(edge_key), 
                capacity=[5, 10], 
                minimum_cost=[3, 6], 
                specific_capacity_cost=0, 
                capacity_is_instantaneous=False
                )
            
            network.add_edge(
                *edge_key,
                **{Network.KEY_ARC_TECH: arc}
                )
        
        # assert that it does not have a tree topology
        
        assert not network.has_tree_topology()
    
        # select all the nodes
        
        for edge_key in network.edges(keys=True):
            
            network.edges[edge_key][
                Network.KEY_ARC_TECH].options_selected[0] = True
            
        # assert that it has a tree topology
    
        assert network.has_tree_topology()
    
    # *************************************************************************
    # *************************************************************************
    
    def test_arc_technologies_static_losses(self):
        
        # *********************************************************************
        # *********************************************************************
        
        number_time_intervals = 3
        number_scenarios = 2
        number_options = 4
        
        efficiency_dict = {
            (q,k): 0.95
            for q in range(number_scenarios)
            for k in range(number_time_intervals)
            }
        
        static_loss_dict = {
            (h,q,k): 1
            for h in range(number_options)
            for q in range(number_scenarios)
            for k in range(number_time_intervals)
            }
        
        for capacity_is_instantaneous in (True, False):
        
            arc_tech = Arcs(
                name='any',
                efficiency=efficiency_dict, 
                efficiency_reverse=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                static_loss=static_loss_dict,
                validate=True
                )
            
            assert arc_tech.has_proportional_losses()
    
            assert arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            # isotropic
            
            arc_tech = Arcs(
                name='any',
                efficiency=None, 
                efficiency_reverse=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                static_loss=static_loss_dict,
                validate=True
                )
            
            assert not arc_tech.has_proportional_losses()
    
            assert arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=False)
        
            # create arc technology with only one option
            
            arc_tech = Arcs(
                name='any',
                efficiency=efficiency_dict, 
                efficiency_reverse=None,
                capacity=(1,),
                minimum_cost=(1,),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                static_loss={
                    (0,q,k): 1
                    #for h in range(number_options)
                    for q in range(number_scenarios)
                    for k in range(number_time_intervals)
                    },
                validate=True
                )
            
            assert arc_tech.has_proportional_losses()
    
            assert arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=False)
        
            # create arc technology for one time interval
            
            arc_tech = Arcs(
                name='any',
                efficiency={
                    (q, 0): 0.5
                    for q in range(number_scenarios)
                    #for k in range(number_time_intervals)
                    }, 
                efficiency_reverse=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                static_loss={
                    (h,q,0): 1
                    for h in range(number_options)
                    for q in range(number_scenarios)
                    #for k in range(number_time_intervals)
                    },
                validate=True
                )
            
            assert arc_tech.has_proportional_losses()
    
            assert arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            # *********************************************************************
        
            # TypeError: The static losses should be given as a dict or None.
            
            error_triggered = False
            try:
                _ = Arcs(
                    name='any',
                    efficiency=None, 
                    efficiency_reverse=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    static_loss=tuple(
                        [k for k in range(number_time_intervals)] 
                        for o in range(number_options)),
                    validate=True
                    )
            except TypeError:
                error_triggered = True
            assert error_triggered
            
            # ValueError('The static losses should be specified for each arc 
            # option.')
            
            error_triggered = False
            try:
                _ = Arcs(
                    name='any',
                    efficiency=None, 
                    efficiency_reverse=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    static_loss={
                        (h, q,): 1
                        for h in range(number_options)
                        for q in range(number_scenarios)
                        },
                    validate=True
                    )
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # TypeError('The static losses must be specified via a list of lists.')
            
            error_triggered = False
            try:
                _ = Arcs(
                    name='any',
                    efficiency=None, 
                    efficiency_reverse=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    static_loss=[
                        tuple(k for k in range(number_time_intervals))
                        for o in range(number_options)],
                    validate=True
                    )
            except TypeError:
                error_triggered = True
            assert error_triggered
                    
            # ValueError('The static loss values are inconsistent with the number '
            # 'of options, scenarios and intervals.')
            
            error_triggered = False
            try:
                arc_tech = Arcs(
                    name='any',
                    efficiency=None, 
                    efficiency_reverse=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    static_loss={
                        (h,q,k): 1
                        for h in range(number_options)
                        for q in range(number_scenarios)
                        for k in range(number_time_intervals-1)
                        },
                    validate=True
                    )
                
                arc_tech.validate_sizes(number_options=number_options, 
                                        number_scenarios=number_scenarios, 
                                        number_intervals=[
                                            number_time_intervals
                                            for _ in range(number_scenarios)])
            except ValueError:
                error_triggered = True
            assert error_triggered
                    
            # TypeError('The static losses were not provided as numbers.')
            
            error_triggered = False
            try:
                _ = Arcs(
                    name='any',
                    efficiency=None, 
                    efficiency_reverse=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    static_loss={
                        (h,q,k): str(3.54)
                        for h in range(number_options)
                        for q in range(number_scenarios)
                        for k in range(number_time_intervals)
                        },
                    validate=True
                    )
            except TypeError:
                error_triggered = True
            assert error_triggered
                        
            # ValueError('The static losses must be positive or zero.')
            
            error_triggered = False
            try:
                _ = Arcs(
                    name='any',
                    efficiency=None, 
                    efficiency_reverse=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    static_loss={
                        (h,q,k): -random.randint(0, 1)*random.random()
                        for h in range(number_options)
                        for q in range(number_scenarios)
                        for k in range(number_time_intervals)
                        },
                    validate=True
                    )
            except ValueError:
                error_triggered = True
            assert error_triggered
                         
            # TypeError: The static loss dict keys must be tuples
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=None,
                    efficiency_reverse=None,
                    static_loss={k:1 for k in range(number_time_intervals)}, 
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
                 
            #ValueError( 'The static loss dict keys must be tuples of size 3.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=None,
                    efficiency_reverse=None,
                    static_loss={(k,3): 1 for k in range(number_time_intervals)}, 
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # TypeError(The staticl osses should be given as a dict or None.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=None, 
                    efficiency_reverse=None,
                    static_loss=[1 for k in range(number_time_intervals)],
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
                 
            # ValueError(
            #     'No static loss values were provided. There should be one'+
            #     ' value per option, scenario and time interval.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=None,
                    efficiency_reverse=None,
                    static_loss={}, 
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
            
    # *************************************************************************
    # *************************************************************************

    def test_arc_technologies(self):
    
        # *********************************************************************
        
        # create arc technology using instantaneous capacities
        
        number_scenarios = 2
        number_options = 4
        number_time_intervals = 3
        
        efficiency_dict = {
            (q,k): 0.85
            for q in range(number_scenarios)
            for k in range(number_time_intervals)
            }
        
        for capacity_is_instantaneous in (True, False):
        
            arc_tech = Arcs(
                name='any',
                efficiency=efficiency_dict, 
                efficiency_reverse=None, 
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True)
        
            assert arc_tech.has_proportional_losses()
        
            assert not arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            assert arc_tech.has_constant_efficiency()
        
            # create arc technology with only one option
            
            arc_tech = Arcs(
                name='any',
                efficiency=efficiency_dict, 
                efficiency_reverse=None, 
                static_loss=None,
                capacity=(1,),
                minimum_cost=(1,),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True)
        
            assert arc_tech.has_proportional_losses()
        
            assert not arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            assert arc_tech.has_constant_efficiency()
        
            # create arc technology for one time interval
            
            arc_tech = Arcs(
                name='any',
                efficiency={(0,0): 0.95}, 
                efficiency_reverse=None, 
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True)
            
            assert arc_tech.has_proportional_losses()
        
            assert not arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            assert arc_tech.has_constant_efficiency()
            
            # create arc technology for one time interval and isotropic
            
            arc_tech = Arcs(
                name='any',
                efficiency={(0,0): 0.95}, 
                efficiency_reverse={(0,0): 0.95},
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True)
            
            assert arc_tech.has_proportional_losses()
        
            assert not arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            assert arc_tech.has_constant_efficiency()
            
            # create arc technology for one time interval and anisotropic
            
            arc_tech = Arcs(
                name='any',
                efficiency={(0,0): 0.95}, 
                efficiency_reverse={(0,0): 1},
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True)
            
            assert arc_tech.has_proportional_losses()
        
            assert not arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            assert not arc_tech.has_constant_efficiency()
            
            # create arc technology for one time interval and anisotropic
            
            arc_tech = Arcs(
                name='any',
                efficiency={(0,0): 1}, 
                efficiency_reverse={(0,0): 0.95},
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True)
            
            assert arc_tech.has_proportional_losses()
        
            assert not arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert not arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            assert not arc_tech.has_constant_efficiency()
            
            # create arc technology for one time interval and anisotropic
            
            arc_tech = Arcs(
                name='any',
                efficiency={(0,0): 0.95}, 
                efficiency_reverse={(0,0): 0.95},
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True)
            
            assert arc_tech.has_proportional_losses()
        
            assert not arc_tech.has_static_losses()
            
            assert not arc_tech.is_infinite_capacity()
            
            assert not arc_tech.has_been_selected()
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=True)
            
            assert arc_tech.is_isotropic(reverse_none_means_isotropic=False)
            
            assert arc_tech.has_constant_efficiency()
        
            # *****************************************************************
            # *****************************************************************
            
            # trigger errors
            
            # TypeError('The name attribute is not hashable.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name=[1,2,3],
                    efficiency=efficiency_dict, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
                 
            #TypeError:The efficiency dict keys must be (scenario, interval) tuples
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency={k:1 for k in range(number_time_intervals)}, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
                 
            #ValueError( 'The efficiency dict keys must be tuples of size 2.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency={(k,3,4) :1 for k in range(number_time_intervals)}, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # TypeError(The efficiency should be given as a dict or None.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=[1 for k in range(number_time_intervals)], 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
            
            # TypeError('The reverse efficiency has to match the nominal'+
            #     ' one when there are no proportional losses.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=None, 
                    efficiency_reverse={}, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
            
            # TypeError:'The reverse efficiency should be given as a dict or None.'
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse=[1 for k in range(number_time_intervals)], 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
            
            # ValueError(
            #     'No efficiency values were provided. There should be '+
            #     'one value per scenario and time interval.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse={}, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # ValueError: The keys for the efficiency dicts do not match.
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse={
                        (key[1],key[0]): value
                        for key, value in efficiency_dict.items()}, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # TypeError: Efficiency values must be provided as numeric types.
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse={
                        (key[0],key[1]): str(value)
                        for key, value in efficiency_dict.items()}, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
         
            # ValueError('Efficiency values must be positive.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse={
                        (key[0],key[1]): -1
                        for key, value in efficiency_dict.items()}, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
                
            #TypeError('The capacity should be given as a list or tuple.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity={o: 1+o for o in range(number_options)},
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
            
            # TypeError: The minimum cost values should be given as a list or tuple
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost={o: 1+o for o in range(number_options)},
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True
                    )
            except TypeError:
                error_triggered = True
            assert error_triggered
        
            # TypeError: The specific capacity cost was not given as a numeric type
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=[1], 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
             
            # ValueError:The number of capacity and minimum cost entries must match
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options+1)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # ValueError: No entries for capacity and minimum cost were provided. 
            # At least one option should be provided.
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(),
                    minimum_cost=tuple(),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # ValueError: No entries for efficiency were provided. There should be 
            # one entry per time interval.
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency={}, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # ValueError('The number of efficiency values must match the number of 
            # time intervals.')
            
            arc_tech = Arcs(
                name='hey',
                efficiency={
                    (q,k): 0.85
                    for q in range(number_scenarios)
                    for k in range(number_time_intervals+1)
                    }, 
                efficiency_reverse=None, 
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True)        
                
            error_triggered = False  
            try:
                arc_tech.validate_sizes(number_options=number_options,
                                        number_scenarios=number_scenarios,
                                        number_intervals=[
                                            number_time_intervals
                                            for _ in range(number_scenarios)])
            except ValueError:
                error_triggered = True
            assert error_triggered
            
            # ValueError('The number of efficiency values must match the number of 
            # time intervals.')
                
            error_triggered = False  
            try:
                arc_tech = Arcs(
                    name='hey',
                    efficiency={
                        (q,k): 0.85
                        for q in range(number_scenarios)
                        for k in range(number_time_intervals)
                        }, 
                    efficiency_reverse={
                        (q,k): 0.85
                        for q in range(number_scenarios)
                        for k in range(number_time_intervals-1)
                        }, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)  
                arc_tech.validate_sizes(number_options=number_options,
                                        number_scenarios=number_scenarios,
                                        number_intervals=[
                                            number_time_intervals
                                            for _ in range(number_scenarios)])
            except ValueError:
                error_triggered = True
            assert error_triggered
                
            # ValueError('The number of capacity values must match the number of 
            # options.')
            
            arc_tech = Arcs(
                name='hey',
                efficiency=efficiency_dict, 
                efficiency_reverse=None, 
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options+1)),
                minimum_cost=tuple(1+o for o in range(number_options+1)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True
                )        
            
            error_triggered = False  
            try:
                arc_tech.validate_sizes(number_options=number_options,
                                        number_scenarios=number_scenarios,
                                        number_intervals=[
                                            number_time_intervals
                                            for _ in range(number_scenarios)])
            except ValueError:
                error_triggered = True
            assert error_triggered
                        
            # ValueError: The minimum cost values are inconsistent with the number 
            # of options.
            
            arc_tech = Arcs(
                name='hey',
                efficiency=efficiency_dict, 
                efficiency_reverse=None, 
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options+1)),
                minimum_cost=tuple(1+o for o in range(number_options+1)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=capacity_is_instantaneous,
                validate=True
                )        
            
            error_triggered = False  
            try:
                arc_tech.validate_sizes(number_options=number_options,
                                        number_scenarios=number_scenarios,
                                        number_intervals=[
                                            number_time_intervals
                                            for _ in range(number_scenarios)])
            except ValueError:
                error_triggered = True
            assert error_triggered
        
            # TypeError('Efficiency values must be provided as numeric types.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency={key: str(value)
                                for key, value in efficiency_dict.items()}, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
               
            # ValueError('Efficiency values must be positive.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency={key: -value*random.randint(0,1)
                                for key, value in efficiency_dict.items()}, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
                 
            # TypeError('Capacity values must be provided as numeric types.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict,
                    efficiency_reverse=None,  
                    static_loss=None,
                    capacity=tuple(str(1+o) for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
                
            # ValueError('Capacity values must be positive.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(-random.randint(0,1)
                              for o in range(number_options)),
                    minimum_cost=tuple(1+o for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
                
            # TypeError('Minimum cost values must be provided as numeric types.')
            
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict,
                    efficiency_reverse=None, 
                    static_loss=None, 
                    capacity=tuple(1+o for o in range(number_options)),
                    minimum_cost=tuple(str(1+o) for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except TypeError:
                error_triggered = True
            assert error_triggered
                
            # ValueError('Minimum cost values must be positive or zero.')
                
            error_triggered = False  
            try:
                _ = Arcs(
                    name='hey',
                    efficiency=efficiency_dict, 
                    efficiency_reverse=None, 
                    static_loss=None,
                    capacity=tuple(1+o
                              for o in range(number_options)),
                    minimum_cost=tuple(-1
                                  for o in range(number_options)),
                    specific_capacity_cost=1, 
                    capacity_is_instantaneous=capacity_is_instantaneous,
                    validate=True)
            except ValueError:
                error_triggered = True
            assert error_triggered
        
        # TypeError('The information about capacities being instantaneous or not 
        # should be given as a boolean variable.')
        
        error_triggered = False  
        try:
            _ = Arcs(
                name='hey',
                efficiency=efficiency_dict, 
                efficiency_reverse=None, 
                static_loss=None,
                capacity=tuple(1+o for o in range(number_options)),
                minimum_cost=tuple(1+o for o in range(number_options)),
                specific_capacity_cost=1, 
                capacity_is_instantaneous=1,
                validate=True)
        except TypeError:
            error_triggered = True
        assert error_triggered
        
        # *********************************************************************
        # *********************************************************************
        
        # Network
        
        arc_tech_AB = Arcs(
            name='AB',
            efficiency=efficiency_dict, 
            efficiency_reverse=None, 
            static_loss=None,
            capacity=tuple(1+o for o in range(number_options)),
            minimum_cost=tuple(1+o for o in range(number_options)),
            specific_capacity_cost=1, 
            capacity_is_instantaneous=False,
            validate=True)
        
        arc_tech_AB.options_selected[0] = True
        
        assert arc_tech_AB.number_options() == number_options
        
        net = Network()
        
        # add undirected arc
            
        net.add_undirected_arc(
            node_key_a='A',
            node_key_b='B',
            arcs=arc_tech_AB)
        
        # add directed arc
            
        net.add_directed_arc(
            node_key_a='A',
            node_key_b='B',
            arcs=arc_tech_AB)
        
        # add infinite capacity arc
        
        net.add_infinite_capacity_arc(
            node_key_a='C',
            node_key_b='D',
            efficiency={
                (i, j): 1
                for i in range(3)
                for j in range(4)},
            static_loss=None)
        
        # add pre-existing directed arc
        
        net.add_preexisting_directed_arc(
            node_key_a='E', 
            node_key_b='F', 
            efficiency=efficiency_dict, 
            static_loss=None, 
            capacity=3, 
            capacity_is_instantaneous=True)
        
        # add pre-existing undirected arc
        
        net.add_preexisting_undirected_arc(
            node_key_a='A', 
            node_key_b='C', 
            efficiency=efficiency_dict, 
            efficiency_reverse=efficiency_dict, 
            static_loss=None, 
            capacity=3, 
            capacity_is_instantaneous=True)
        
        net.modify_network_arc(
            node_key_a='A',
            node_key_b='C',
            arc_key_ab='AC',
            data_dict={net.KEY_ARC_TECH: arc_tech_AB, net.KEY_ARC_UND: False})
        
        # *********************************************************************
        # *********************************************************************
        
        # add import node
        
        imp_resource_price = ResourcePrice(
            prices=[random.random()
                    for k in range(number_time_intervals)],
            volumes=[ *[random.random() for k in range(number_time_intervals-1)], None]
            )
        
        net.add_import_node(node_key='G', prices={(0,0,0): imp_resource_price})
    
        # add export node
        
        exp_resource_price = ResourcePrice(
            prices=[random.random()
                    for k in range(number_time_intervals)],
            volumes=[ *[random.random() for k in range(number_time_intervals-1)], None]
            )
        
        net.add_export_node(node_key='H', prices={(0,0,0): exp_resource_price})
        
        net.add_waypoint_node(node_key='Z')
        
        base_flow = {
            (i,j): random.random()
            for i in range(3)
            for j in range(4)
            }
        
        net.add_source_sink_node(node_key='Y', base_flow=base_flow)
        
        base_flow[(2,3)] = random.random()
        
        net.modify_network_node(
            node_key='Y', 
            node_data={net.KEY_NODE_BASE_FLOW: base_flow}
            )
        
        net.identify_node_types()
        
        assert 'Z' in net.waypoint_nodes
        
        assert 'G' in net.import_nodes
        
        assert 'H' in net.export_nodes
        
        assert 'Y' in net.source_sink_nodes
    
    # *************************************************************************
    # *************************************************************************

    def test_arcs_without_losses(self):
        
        # test arc without (static and proportional) losses
        
        arc_tech = ArcsWithoutLosses(
            name='AB', 
            capacity=(1,2,3), 
            minimum_cost=(4,5,6), 
            specific_capacity_cost=6, 
            capacity_is_instantaneous=False,
            validate=True
            )
        
        assert not arc_tech.has_proportional_losses()
    
        assert not arc_tech.has_static_losses()
        
        assert not arc_tech.is_infinite_capacity()
        
        assert arc_tech.has_constant_efficiency()
        
        # test arc without static losses
        
        arc_tech = ArcsWithoutStaticLosses(
            name='AB', 
            efficiency={(0,0):1, (0,1):0.9, (0,2):0.8},
            efficiency_reverse=None, 
            capacity=(1,2,3), 
            minimum_cost=(4,5,6), 
            specific_capacity_cost=6, 
            capacity_is_instantaneous=False,
            validate=True
            )
        
        assert arc_tech.has_proportional_losses()
    
        assert not arc_tech.has_static_losses()
        
        assert not arc_tech.is_infinite_capacity()
        
        assert not arc_tech.has_constant_efficiency()
        
        # test arc without proportional losses
        
        arc_tech = ArcsWithoutProportionalLosses(
            name='AB', 
            static_loss={(0,0,0):0.1, (0,0,1):0.2, (0,0,2):0.3,
                         (1,0,0):0.15, (1,0,1):0.25, (1,0,2):0.35,
                         (2,0,0):0.16, (2,0,1):0.26, (2,0,2):0.36},
            capacity=(1,2,3), 
            minimum_cost=(4,5,6), 
            specific_capacity_cost=6, 
            capacity_is_instantaneous=False,
            validate=True
            )
        
        assert not arc_tech.has_proportional_losses()
    
        assert arc_tech.has_static_losses()
        
        assert not arc_tech.is_infinite_capacity()
        
        assert arc_tech.has_constant_efficiency()
        
    # *************************************************************************
    # *************************************************************************
    
    def test_modifying_nodes(self):
        
        # *********************************************************************
        
        net = Network()
        
        number_intervals = 3
        
        resource_price = ResourcePrice(
            prices=[random.random() for k in range(number_intervals)], 
            volumes=[
                *[random.random() for k in range(number_intervals-1)], None
                ]
            )
        
        base_flow = {
            (0,k): random.random()
            for k in range(number_intervals)}
        
        arc_tech = ArcsWithoutLosses(
            name='hello', 
            capacity=[5], 
            minimum_cost=[3], 
            specific_capacity_cost=3, 
            capacity_is_instantaneous=False
            )
        
        # add isolated import node
        
        net.add_import_node(node_key='I_iso', 
                            prices={(0,0,0): resource_price})
        
        # add import node with outgoing arcs
    
        net.add_import_node(node_key='I', 
                            prices={(0,0,0): resource_price})
                        
        # add isolated export node
        
        net.add_import_node(node_key='E_iso', 
                            prices={(0,0,0): resource_price})
        
        # add export node with incoming arcs
    
        net.add_export_node(node_key='E', 
                            prices={(0,0,0): resource_price})
        
        # add isolated normal node
        
        net.add_source_sink_node(node_key='A_iso', 
                                 base_flow=base_flow)
    
        # add normal node with incoming arcs
        
        net.add_source_sink_node(node_key='A_in',  
                                 base_flow=base_flow)
    
        # add normal node with outgoing arcs
        
        net.add_source_sink_node(node_key='A_out',  
                                 base_flow=base_flow)
    
        # add normal node with incoming and outgoing arcs
        
        net.add_source_sink_node(node_key='A',  
                                 base_flow=base_flow)
        
        # *********************************************************************
        
        # arcs
        
        net.add_directed_arc(node_key_a='I', 
                             node_key_b='A_in', 
                             arcs=arc_tech)
                             
        net.add_directed_arc(node_key_a='I', 
                             node_key_b='A', 
                             arcs=arc_tech)
                             
        net.add_directed_arc(node_key_a='A_out', 
                             node_key_b='E', 
                             arcs=arc_tech)
                             
        net.add_directed_arc(node_key_a='A', 
                             node_key_b='E', 
                             arcs=arc_tech)
        
        # *********************************************************************
        
        # change I_iso to regular: okay
        
        net.modify_network_node(
            node_key='I_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='I_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # change I_iso to export: okay
        
        net.modify_network_node(
            node_key='I_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='I_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # change I_iso to waypoint: okay
        
        net.modify_network_node(
            node_key='I_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_WAY})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='I_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # *********************************************************************
        
        # change E_iso to regular: okay
        
        net.modify_network_node(
            node_key='E_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='E_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # change E_iso to import: okay
        
        net.modify_network_node(
            node_key='E_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='E_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # change E_iso to waypoint: okay
        
        net.modify_network_node(
            node_key='E_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_WAY})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='E_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # *********************************************************************
        
        # change A_iso to export: okay
        
        net.modify_network_node(
            node_key='A_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='A_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # change A_iso to import: okay
        
        net.modify_network_node(
            node_key='A_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='A_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # change A_iso to waypoint: okay
        
        net.modify_network_node(
            node_key='A_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_WAY})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='A_iso', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
    
        # *********************************************************************
        
        # change I to regular: okay
        
        net.modify_network_node(
            node_key='I', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='I', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                       net.KEY_NODE_PRICES: resource_price})
                       
        # change I to waypoint: okay
        
        net.modify_network_node(
            node_key='I', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_WAY})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='I', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                       net.KEY_NODE_PRICES: resource_price})
    
        # *********************************************************************
        
        # change E to regular: okay
        
        net.modify_network_node(
            node_key='E', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='E', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                       net.KEY_NODE_PRICES: resource_price})
                       
        # change E to waypoint: okay
        
        net.modify_network_node(
            node_key='E', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_WAY})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='E', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                       net.KEY_NODE_PRICES: resource_price})
    
        # *********************************************************************
        
        # change A_in to export: okay
        
        net.modify_network_node(
            node_key='A_in', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='A_in', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # change A_in to waypoint: okay
        
        net.modify_network_node(
            node_key='A_in', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_WAY})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='A_in', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # *********************************************************************
        
        # change A_out to import: okay
        
        net.modify_network_node(
            node_key='A_out', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                       net.KEY_NODE_PRICES: resource_price})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='A_out', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
        
        # change A_out to waypoint: okay
        
        net.modify_network_node(
            node_key='A_out', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_WAY})
        
        # reverse: okay
        
        net.modify_network_node(
            node_key='A_out', 
            node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_SOURCE_SINK,
                       net.KEY_NODE_BASE_FLOW: base_flow})
    
        # *********************************************************************
        
        # change I to export: fail
        
        error_triggered = False
        try:
            net.modify_network_node(
                node_key='I', 
                node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                           net.KEY_NODE_PRICES: resource_price}
                )
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # change E to import: fail
        
        error_triggered = False
        try:
            net.modify_network_node(
                node_key='E', 
                node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                           net.KEY_NODE_PRICES: resource_price}
                )
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # change A_out to export: fail
        
        error_triggered = False
        try:
            net.modify_network_node(
                node_key='A_out', 
                node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                           net.KEY_NODE_PRICES: resource_price}
                )
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # change A_in to import: fail
        
        error_triggered = False
        try:
            net.modify_network_node(
                node_key='A_in', 
                node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                           net.KEY_NODE_PRICES: resource_price}
                )
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # change A to export: fail
        
        error_triggered = False
        try:
            net.modify_network_node(
                node_key='A', 
                node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_EXP,
                           net.KEY_NODE_PRICES: resource_price}
                )
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # change A to import: fail
        
        error_triggered = False
        try:
            net.modify_network_node(
                node_key='A', 
                node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_IMP,
                           net.KEY_NODE_PRICES: resource_price}
                )
        except ValueError:
            error_triggered = True
        assert error_triggered
    
        # *********************************************************************
        
        # try to modify a non-existent node
        
        error_triggered = False
        try:
            net.modify_network_node(
                node_key='ABCD', 
                node_data={net.KEY_NODE_TYPE: net.KEY_NODE_TYPE_WAY}
                )
        except ValueError:
            error_triggered = True
        assert error_triggered
    
        # *********************************************************************
        
    # *************************************************************************
    # *************************************************************************
    
    def test_network_disallowed_cases(self):
        
        # *********************************************************************
        
        net = Network()
        
        number_intervals = 3
        
        resource_price = ResourcePrice(
            prices=[random.random() for k in range(number_intervals)], 
            volumes=[
                *[random.random() for k in range(number_intervals-1)], None
                ]
            )
        
        base_flow = {
            (0,k): random.random()
            for k in range(number_intervals)}
        
        lossless_arcs = ArcsWithoutLosses(
            name='hello', 
            capacity=[5], 
            minimum_cost=[3], 
            specific_capacity_cost=3, 
            capacity_is_instantaneous=False
            )
        
        lossy_arcs = ArcsWithoutProportionalLosses(
            name='hello back',
            static_loss={
                (0,0,k): random.random()
                for k in range(number_intervals)
                }, 
            capacity=(1,),
            minimum_cost=(5,), 
            specific_capacity_cost=0,
            capacity_is_instantaneous=False
            )
        
        # add import node I
        
        net.add_import_node(node_key='I',
                            prices={(0,0,0): resource_price})
        
        # add export node E
        
        net.add_export_node(node_key='E',
                            prices={(0,0,0): resource_price})
        
        # add regular node A
        
        net.add_source_sink_node(node_key='A', 
                                 base_flow=base_flow)
        
        # add regular node B
        
        net.add_source_sink_node(node_key='B', 
                                 base_flow=base_flow)
        
        # add a valid import-export arc
        
        net.add_directed_arc(node_key_a='I',
                             node_key_b='E',
                             arcs=lossless_arcs)
        
        # identify the nodes and validate
        
        net.identify_node_types()
        
        # *********************************************************************
        # *********************************************************************
        
        # trigger errors using pre-identified nodes
        
        # directed arcs cannot start in an export node: E -> B
        
        error_triggered = False
        try:
            net.add_directed_arc(node_key_a='E',
                                 node_key_b='B',
                                 arcs=lossless_arcs)
        except ValueError:
            error_triggered = True
        assert error_triggered
                
        # directed arcs cannot end on an import node: A -> I
        
        error_triggered = False
        try:
            net.add_directed_arc(node_key_a='A',
                                 node_key_b='I',
                                 arcs=lossless_arcs)
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # import-export nodes cannot have static losses
        
        error_triggered = False
        try:
            net.add_directed_arc(node_key_a='I',
                                 node_key_b='E', 
                                 arcs=lossy_arcs)
        except ValueError:
            error_triggered = True
        assert error_triggered
                
        # undirected arcs cannot involve import nor export nodes
        
        error_triggered = False
        try:
            net.add_undirected_arc(node_key_a='I',
                                   node_key_b='A',
                                   arcs=lossless_arcs)
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # undirected arcs cannot involve import nor export nodes
        
        error_triggered = False
        try:
            net.add_undirected_arc(node_key_a='B',
                                   node_key_b='E',
                                   arcs=lossless_arcs)
        except ValueError:
            error_triggered = True
        assert error_triggered
                         
        # *********************************************************************
        # *********************************************************************
        
        # trigger errors using non-identified nodes
        
        # *********************************************************************
        
        # create a new export node
        
        net.add_export_node(node_key='E1',
                            prices={(0,0,0): resource_price})
        
        # create an arc starting in that export node
        
        error_triggered = False
        try:
            net.add_directed_arc(node_key_a='E1',
                                 node_key_b='B',
                                 arcs=lossless_arcs)
            net.identify_node_types()
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # remove the troublesome arc
        
        net.remove_edge(u='E1', v='B')
    
        # *********************************************************************
        
        # create a new import node
        
        net.add_import_node(node_key='I1',
                            prices={(0,0,0): resource_price})
        
        # create an arc ending in that import node
        
        error_triggered = False
        try:
            net.add_directed_arc(node_key_a='A',
                                    node_key_b='I1',
                                    arcs=lossless_arcs)
            net.identify_node_types()
        except ValueError:
            error_triggered = True
        assert error_triggered
        
        # remove the troublesome arc
        
        net.remove_edge(u='A', v='I1')
        
        # *********************************************************************
        
        # check non-existent arc
        
        net.arc_is_undirected(('X','Y', 1))
                                
    # *************************************************************************
    # *************************************************************************
    
    def test_pseudo_unique_key_generation(self):
        
        # create network
        
        network = Network()
        
        # add node A
        
        network.add_waypoint_node(node_key='A')
        
        # add node B
        
        network.add_waypoint_node(node_key='B')
        
        # identify nodes
        
        network.identify_node_types()
        
        # add arcs
        
        key_list = ['3e225573-4e78-48c8-bb08-efbeeb795c22',
                    'f6d30428-15d1-41e9-a952-0742eaaa5a31',
                    '8c29b906-2518-41c5-ada8-07b83508b5b8',
                    'f9a72a39-1422-4a02-af97-906ce79c32a3',
                    'b6941a48-10cc-465d-bf53-178bd2939bd1']
        
        for key in key_list:
            
            network.add_edge(
                u_for_edge='A',
                v_for_edge='B',
                key=key,
                **{network.KEY_ARC_UND: False,
                   network.KEY_ARC_TECH: None}
                )
            
        # use a seed number to trigger more iterations
        
        import uuid
        rand = random.Random()
        rand.seed(360)
        uuid.uuid4 = lambda: uuid.UUID(int=rand.getrandbits(128), version=4)
        
        error_triggered = False
        try:
            _ = network.get_pseudo_unique_arc_key(
                node_key_start='A', 
                node_key_end='B',
                max_iterations=len(key_list)-1)
        except Exception:
            error_triggered = True
        assert error_triggered

# *****************************************************************************
# *****************************************************************************