def test_load_graph_to_empty():
    plumb0 = test.two_valve_setup(0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10,
                                  utils.CLOSED)
    pressures = {3: (100, False)}
    default_states = {'valve1': 'closed', 'valve2': 'open'}

    plumb = top.PlumbingEngine()
    plumb.load_graph(plumb0.component_dict, plumb0.mapping, pressures,
                     default_states)

    assert plumb.time_res == int(
        utils.s_to_micros(0.2) / utils.DEFAULT_RESOLUTION_SCALE)
    assert plumb.edges() == [(1, 2, 'valve1.A1', {
        'FC': utils.teq_to_FC(utils.s_to_micros(10))
    }), (2, 1, 'valve1.A2', {
        'FC': 0
    }), (2, 3, 'valve2.B1', {
        'FC': utils.teq_to_FC(utils.s_to_micros(0.5))
    }), (3, 2, 'valve2.B2', {
        'FC': utils.teq_to_FC(utils.s_to_micros(0.2))
    })]
    assert plumb.nodes() == [(1, {
        'body': top.GenericNode(0)
    }), (2, {
        'body': top.GenericNode(0)
    }), (3, {
        'body': top.GenericNode(100)
    })]
    assert plumb.current_state('valve1') == 'closed'
    assert plumb.current_state('valve2') == 'open'
def test_reset_keep_component():
    plumb = test.two_valve_setup(0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10,
                                 utils.CLOSED)

    pc = test.create_component(0, 0, 0, 1, 'valve3', 'C')
    mapping = {1: 3, 2: 4}

    plumb.add_component(pc, mapping, 'closed', {4: (50, False)})
    plumb.remove_component('valve2')
    plumb.reset()

    assert plumb.time == 0
    assert plumb.is_valid()
    assert plumb.time_res == int(
        utils.s_to_micros(0.2) / utils.DEFAULT_RESOLUTION_SCALE)
    assert plumb.edges() == [(1, 2, 'valve1.A1', {
        'FC': utils.teq_to_FC(utils.s_to_micros(10))
    }), (2, 1, 'valve1.A2', {
        'FC': 0
    }), (3, 4, 'valve3.C1', {
        'FC': utils.teq_to_FC(0)
    }), (4, 3, 'valve3.C2', {
        'FC': utils.teq_to_FC(utils.s_to_micros(1))
    })]
    assert plumb.nodes() == [(1, {
        'body': top.GenericNode(0)
    }), (2, {
        'body': top.GenericNode(0)
    }), (3, {
        'body': top.GenericNode(100)
    }), (4, {
        'body': top.GenericNode(0)
    })]
    assert plumb.current_state('valve1') == 'closed'
    assert plumb.current_state('valve3') == 'closed'
def test_reverse_orientation():
    plumb = test.two_valve_setup(0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10,
                                 utils.CLOSED)
    plumb.reverse_orientation('valve1')

    assert plumb.is_valid()
    assert plumb.time_res == int(
        utils.s_to_micros(0.2) / utils.DEFAULT_RESOLUTION_SCALE)
    assert plumb.edges() == [(1, 2, 'valve1.A1', {
        'FC': 0
    }), (2, 1, 'valve1.A2', {
        'FC': utils.teq_to_FC(utils.s_to_micros(10))
    }), (2, 3, 'valve2.B1', {
        'FC': utils.teq_to_FC(utils.s_to_micros(0.5))
    }), (3, 2, 'valve2.B2', {
        'FC': utils.teq_to_FC(utils.s_to_micros(0.2))
    })]
    assert plumb.nodes() == [
        (1, {
            'body': top.GenericNode(0)
        }),
        (2, {
            'body': top.GenericNode(0)
        }),
        (3, {
            'body': top.GenericNode(100)
        }),
    ]
    assert plumb.current_state('valve1') == 'closed'
    assert plumb.current_state('valve2') == 'open'
def test_set_pressure():
    plumb = test.two_valve_setup(0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10,
                                 utils.CLOSED)
    pc = test.create_component(0, 0, 0, 1, 'valve3', 'C')
    mapping = {1: 3, 2: 4}
    plumb.add_component(pc, mapping, 'closed', {4: (50, False)})

    plumb.set_pressure(1, 200)
    plumb.set_pressure(2, 7000)

    assert plumb.nodes() == [(1, {
        'body': top.GenericNode(200)
    }), (2, {
        'body': top.GenericNode(7000)
    }), (3, {
        'body': top.GenericNode(100)
    }), (4, {
        'body': top.GenericNode(50)
    })]

    plumb.set_pressure(4, 10)
    plumb.set_pressure(1, 0)

    assert plumb.nodes() == [(1, {
        'body': top.GenericNode(0)
    }), (2, {
        'body': top.GenericNode(7000)
    }), (3, {
        'body': top.GenericNode(100)
    }), (4, {
        'body': top.GenericNode(10)
    })]
def test_remove_errors_wrong_component_name():
    wrong_component_name = 'potato'
    pc1 = test.create_component(0, 0, 0, 0, 'valve1', 'A')
    pc2 = test.create_component(0, 0, 0, 0, 'valve2', 'B')

    component_mapping = {'valve1': {1: 1, 2: 2}, 'valve2': {1: 2, 2: 3}}

    pressures = {3: (100, False)}
    default_states = {'valve1': 'closed', 'valve2': 'open'}
    plumb = top.PlumbingEngine({
        wrong_component_name: pc1,
        'valve2': pc2
    }, component_mapping, pressures, default_states)

    assert not plumb.is_valid()
    assert len(plumb.errors()) == 2

    error1 = invalid.InvalidComponentName(
        f"Component with name '{wrong_component_name}' not found in mapping dict.",
        wrong_component_name)

    error2 = invalid.InvalidComponentName(
        f"Component '{wrong_component_name}' state not found in initial states dict.",
        wrong_component_name)

    assert error1 in plumb.errors()
    assert error2 in plumb.errors()

    plumb.remove_component(wrong_component_name)

    assert plumb.is_valid()
    assert plumb.time_res == int(
        utils.s_to_micros(0.2) / utils.DEFAULT_RESOLUTION_SCALE)
    assert plumb.edges() == [
        (2, 3, 'valve2.B1', {
            'FC': utils.teq_to_FC(utils.s_to_micros(0))
        }),
        (3, 2, 'valve2.B2', {
            'FC': utils.teq_to_FC(utils.s_to_micros(0))
        }),
    ]
    assert plumb.nodes() == [
        (2, {
            'body': top.GenericNode(0)
        }),
        (3, {
            'body': top.GenericNode(100)
        }),
    ]
    assert plumb.current_state('valve2') == 'open'
def test_generic_equality():
    pressure = 10
    node = top.GenericNode(pressure, True)

    same_node = top.GenericNode(pressure, True)
    assert node == same_node

    diff_pressure = top.GenericNode(0, True)
    diff_fixed = top.GenericNode(pressure, False)
    assert node != diff_pressure
    assert node != diff_fixed

    diff_type = top.AtmNode()
    assert node != diff_type
def test_set_pressure_errors():
    plumb = test.two_valve_setup(0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10,
                                 utils.CLOSED)

    pc = test.create_component(0, 0, 0, 1, 'valve3', 'C')
    mapping = {1: 3, 2: 4}
    plumb.add_component(pc, mapping, 'closed', {4: (50, False)})

    pc_vent = test.create_component(0, 0, 0, 0, 'vent', 'D')
    mapping_vent = {1: 4, 2: utils.ATM}
    plumb.add_component(pc_vent, mapping_vent, 'closed')

    negative_pressure = -20
    not_a_number = 'potato'
    with pytest.raises(exceptions.BadInputError) as err:
        plumb.set_pressure(4, negative_pressure)
    assert str(
        err.value) == f"Negative pressure {negative_pressure} not allowed."

    with pytest.raises(exceptions.BadInputError) as err:
        plumb.set_pressure(4, not_a_number)
    assert str(err.value) == f"Pressure {not_a_number} must be a number."

    assert plumb.nodes() == [(1, {
        'body': top.GenericNode(0)
    }), (2, {
        'body': top.GenericNode(0)
    }), (3, {
        'body': top.GenericNode(100)
    }), (4, {
        'body': top.GenericNode(50)
    }), (utils.ATM, {
        'body': top.AtmNode()
    })]

    nonexistent_node = 5
    with pytest.raises(exceptions.BadInputError) as err:
        plumb.set_pressure(nonexistent_node, 100)
    assert str(err.value) == f"Node {nonexistent_node} not found in graph."

    plumb.set_pressure(4, 100)
    assert plumb.nodes() == [(1, {
        'body': top.GenericNode(0)
    }), (2, {
        'body': top.GenericNode(0)
    }), (3, {
        'body': top.GenericNode(100)
    }), (4, {
        'body': top.GenericNode(100)
    }), (utils.ATM, {
        'body': top.AtmNode()
    })]

    with pytest.raises(exceptions.BadInputError) as err:
        plumb.set_pressure(utils.ATM, 100)
    assert str(
        err.value) == f"Pressure for atmosphere node ({utils.ATM}) must be 0."
def test_remove_add_errors():
    plumb = test.two_valve_setup(0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10,
                                 utils.CLOSED)

    name = 'valve3'
    wrong_node = 3
    right_node = 2
    pc = test.create_component(0, 0, 0, 1, name, 'C')
    mapping = {1: 3, wrong_node: 4}

    with pytest.raises(exceptions.BadInputError) as err:
        plumb.add_component(pc, mapping, 'closed', {4: (50, False)})
    assert str(err.value) ==\
        f"Component '{name}', node {right_node} not found in mapping dict."

    plumb.remove_component(name)

    assert plumb.is_valid()
    assert plumb.time_res == int(
        utils.s_to_micros(0.2) / utils.DEFAULT_RESOLUTION_SCALE)
    assert plumb.edges() == [
        (1, 2, 'valve1.A1', {
            'FC': utils.teq_to_FC(utils.s_to_micros(10))
        }),
        (2, 1, 'valve1.A2', {
            'FC': 0
        }),
        (2, 3, 'valve2.B1', {
            'FC': utils.teq_to_FC(utils.s_to_micros(0.5))
        }),
        (3, 2, 'valve2.B2', {
            'FC': utils.teq_to_FC(utils.s_to_micros(0.2))
        }),
    ]
    assert plumb.nodes() == [
        (1, {
            'body': top.GenericNode(0)
        }),
        (2, {
            'body': top.GenericNode(0)
        }),
        (3, {
            'body': top.GenericNode(100)
        }),
    ]
    assert plumb.current_state('valve1') == 'closed'
    assert plumb.current_state('valve2') == 'open'
def test_list_functions():
    pc1 = test.create_component(0.5, 0.2, 10, utils.CLOSED, 'valve1', 'A')
    pc2 = test.create_component(0.5, 0.2, 10, utils.CLOSED, 'valve2', 'B')

    component_mapping = {
        'valve1': {
            1: 1,
            2: 2
        },
        'valve2': {
            1: 2,
            2: 3
        }
    }

    negative_pressure = -50
    pressures = {3: (negative_pressure, False)}
    default_states = {'valve1': 'closed', 'valve2': 'open'}
    plumb = top.PlumbingEngine(
        {'valve1': pc1, 'valve2': pc2}, component_mapping, pressures, default_states)

    assert plumb.edges() == [
        (1, 2, 'valve1.A1', {'FC': utils.teq_to_FC(utils.s_to_micros(10))}),
        (2, 1, 'valve1.A2', {'FC': 0}),
        (2, 3, 'valve2.B1', {'FC': utils.teq_to_FC(utils.s_to_micros(0.5))}),
        (3, 2, 'valve2.B2', {'FC': utils.teq_to_FC(utils.s_to_micros(0.2))})
    ]

    assert plumb.edges(data=False) == [
        (1, 2, 'valve1.A1'),
        (2, 1, 'valve1.A2'),
        (2, 3, 'valve2.B1'),
        (3, 2, 'valve2.B2')
    ]

    # Pressure at node 3 will be 0, since the provided one was invalid
    assert plumb.nodes() == [
        (1, {'body': top.GenericNode(0)}),
        (2, {'body': top.GenericNode(0)}),
        (3, {'body': top.GenericNode(0)}),
    ]

    assert plumb.nodes(data=False) == [1, 2, 3]

    assert plumb.errors() == {
        invalid.InvalidNodePressure(f"Negative pressure {negative_pressure} not allowed.", 3)
    }
def test_add_remove():
    old_lowest_teq = 0.2
    plumb = test.two_valve_setup(0.5, old_lowest_teq, 10, utils.CLOSED, 0.5,
                                 old_lowest_teq, 10, utils.CLOSED)

    new_lowest_teq = 0.1
    pc = test.create_component(0, 0, 0, new_lowest_teq, 'valve3', 'C')
    mapping = {1: 3, 2: 4}

    plumb.add_component(pc, mapping, 'closed', {4: (50, False)})

    assert plumb.time_res ==\
        int(utils.s_to_micros(new_lowest_teq) / utils.DEFAULT_RESOLUTION_SCALE)

    plumb.remove_component('valve3')

    assert plumb.is_valid()
    assert plumb.time_res ==\
        int(utils.s_to_micros(old_lowest_teq) / utils.DEFAULT_RESOLUTION_SCALE)
    assert plumb.edges() == [
        (1, 2, 'valve1.A1', {
            'FC': utils.teq_to_FC(utils.s_to_micros(10))
        }),
        (2, 1, 'valve1.A2', {
            'FC': 0
        }),
        (2, 3, 'valve2.B1', {
            'FC': utils.teq_to_FC(utils.s_to_micros(0.5))
        }),
        (3, 2, 'valve2.B2', {
            'FC': utils.teq_to_FC(utils.s_to_micros(0.2))
        }),
    ]
    assert plumb.nodes() == [
        (1, {
            'body': top.GenericNode(0)
        }),
        (2, {
            'body': top.GenericNode(0)
        }),
        (3, {
            'body': top.GenericNode(100)
        }),
    ]
    assert plumb.current_state('valve1') == 'closed'
    assert plumb.current_state('valve2') == 'open'
def test_atm_equality():
    atm_node = top.AtmNode()
    another_atm_node = top.AtmNode()

    assert atm_node == another_atm_node

    almost_atm_node = top.GenericNode(0, True)
    assert atm_node != almost_atm_node
def test_add_to_empty():
    plumb = top.PlumbingEngine()
    pc = test.create_component(2, utils.CLOSED, 0, 0, 'valve', 'A')

    mapping = {1: 1, 2: 2}

    plumb.add_component(pc, mapping, 'open', {1: (20, False)})

    assert plumb.is_valid()
    assert plumb.time_res == utils.DEFAULT_TIME_RESOLUTION_MICROS
    assert plumb.edges() == [(1, 2, 'valve.A1', {
        'FC': utils.teq_to_FC(utils.s_to_micros(2))
    }), (2, 1, 'valve.A2', {
        'FC': 0
    })]
    assert plumb.nodes() == [(1, {
        'body': top.GenericNode(20)
    }), (2, {
        'body': top.GenericNode(0)
    })]
    assert plumb.current_state('valve') == 'open'
def test_open_closed_valves():
    plumb = test.two_valve_setup(0, 0, utils.CLOSED, utils.CLOSED, 0, 0,
                                 utils.CLOSED, utils.CLOSED)

    assert plumb.time_res == utils.DEFAULT_TIME_RESOLUTION_MICROS
    assert plumb.edges() == [(1, 2, 'valve1.A1', {
        'FC': 0
    }), (2, 1, 'valve1.A2', {
        'FC': 0
    }), (2, 3, 'valve2.B1', {
        'FC': utils.FC_MAX
    }), (3, 2, 'valve2.B2', {
        'FC': utils.FC_MAX
    })]
    assert plumb.nodes() == [(1, {
        'body': top.GenericNode(0)
    }), (2, {
        'body': top.GenericNode(0)
    }), (3, {
        'body': top.GenericNode(100)
    })]
    assert plumb.current_state('valve1') == 'closed'
    assert plumb.current_state('valve2') == 'open'
def test_generic_node():
    node = top.GenericNode()
    assert node.get_pressure() == 0
    assert not node.get_fixed()

    new_pressure = 100
    node.update_pressure(new_pressure)

    assert node.get_pressure() == new_pressure
    assert not node.get_fixed()

    node.update_fixed(True)
    assert node.get_pressure() == new_pressure
    assert node.get_fixed()

    # just make sure this doesn't error
    node.__str__()