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_FC_name_overlap():
    pc1 = test.create_component(0, 0, 0, 0, 'fill_valve', 'A')
    pc2 = test.create_component(1, 1, 1, 1, 'remote_fill_valve', 'B')

    component_mapping = {
        'fill_valve': {
            1: 1,
            2: 2
        },
        'remote_fill_valve': {
            1: 2,
            2: 3
        }
    }

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

    assert plumb.current_FC('fill_valve') == {
        (1, 2, 'fill_valve.A1'): utils.FC_MAX,
        (2, 1, 'fill_valve.A2'): utils.FC_MAX
    }

    # Since these edges belongs only to remote_fill_valve
    assert (2, 3, 'remote_fill_valve.B1') not in plumb.current_FC('fill_valve')
    assert (3, 2, 'remote_fill_valve.B2') not in plumb.current_FC('fill_valve')

    assert plumb.current_FC('remote_fill_valve') == {
        (2, 3, 'remote_fill_valve.B1'): utils.teq_to_FC(utils.s_to_micros(1)),
        (3, 2, 'remote_fill_valve.B2'): utils.teq_to_FC(utils.s_to_micros(1))
    }
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_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_6():
    states = {
        'open': {
            (1, 2, 'A1'): 1,
            (2, 1, 'A2'): 1,
            (1, 3, 'B1'): utils.CLOSED,
            (3, 1, 'B2'): utils.CLOSED
        }
    }
    edges = [(1, 2, 'A1'), (2, 1, 'A2'), (1, 3, 'B1'), (3, 1, 'B2')]
    pc = top.PlumbingComponent('three', states, edges)
    mapping = {'three': {1: 1, 2: 2, 3: 3}}
    pressures = {1: (100, False), 2: (0, False), 3: (0, False)}

    steady_by = utils.s_to_micros(1)
    converged = {1: 50, 2: 50, 3: 0}

    step_plumb = top.PlumbingEngine({'three': pc}, mapping, pressures,
                                    {'three': 'open'})
    step_state = step_plumb.step(1e6)

    solve_plumb = top.PlumbingEngine({'three': pc}, mapping, pressures,
                                     {'three': 'open'})
    solve_state = solve_plumb.solve()

    len_plumb = top.PlumbingEngine({'three': pc}, mapping, pressures,
                                   {'three': 'open'})
    solve_len = len(len_plumb.solve(return_resolution=len_plumb.time_res))
    test.validate_plumbing_engine(step_plumb, solve_plumb, steady_by,
                                  converged, solve_state, step_state,
                                  solve_len, len_plumb.time_res)
def test_new_component_state():
    plumb = test.two_valve_setup(0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10,
                                 utils.CLOSED)
    plumb.set_component_state('valve1', 'open')

    assert plumb.edges() == [(1, 2, 'valve1.A1', {
        'FC': utils.teq_to_FC(utils.s_to_micros(0.5))
    }), (2, 1, 'valve1.A2', {
        'FC': utils.teq_to_FC(utils.s_to_micros(0.2))
    }), (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.current_state('valve1') == 'open'
    assert plumb.current_state('valve2') == 'open'
def test_2():
    pc = test.create_component(1, 1, utils.CLOSED, utils.CLOSED, 'valve', 'A')
    mapping = {'valve': {1: 1, 2: 2}}
    pressures = {1: (100, False)}
    default_states = {'valve': 'open'}
    step_plumb = top.PlumbingEngine({'valve': pc}, mapping, pressures,
                                    default_states)
    step_plumb.set_component_state('valve', 'closed')
    curr_nodes = step_plumb.nodes()
    step_plumb.step()
    assert curr_nodes == step_plumb.nodes()

    steady_by = utils.s_to_micros(1)
    converged = {1: 50, 2: 50}

    step_plumb.set_component_state('valve', 'open')
    step_state = step_plumb.step(1e6)

    solve_plumb = top.PlumbingEngine({'valve': pc}, mapping, pressures,
                                     default_states)
    solve_state = solve_plumb.solve()

    len_plumb = top.PlumbingEngine({'valve': pc}, mapping, pressures,
                                   default_states)
    solve_len = len(len_plumb.solve(return_resolution=len_plumb.time_res))
    test.validate_plumbing_engine(step_plumb, solve_plumb, steady_by,
                                  converged, solve_state, step_state,
                                  solve_len, len_plumb.time_res)
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_s_to_micros():
    large_second_amount = 1000

    epsilon = 1e-8
    i = 0
    while i < large_second_amount:
        assert abs(utils.s_to_micros(i) - (i * 1e6)) < epsilon
        i += 0.5
def test_current_FC():
    plumb = test.two_valve_setup(
        0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10, utils.CLOSED)

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

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

    assert plumb.current_FC((1, 2, 'valve1.A1')) == utils.teq_to_FC(utils.s_to_micros(10))

    assert plumb.current_FC((1, 2, 'valve1.A1'), (2, 3, 'valve2.B1')) == {
        (1, 2, 'valve1.A1'): utils.teq_to_FC(utils.s_to_micros(10)),
        (2, 3, 'valve2.B1'): utils.teq_to_FC(utils.s_to_micros(0.5))
    }

    assert plumb.current_FC('valve1') == {
        (1, 2, 'valve1.A1'): utils.teq_to_FC(utils.s_to_micros(10)),
        (2, 1, 'valve1.A2'): 0,
    }

    wrong_name = 'potato'
    with pytest.raises(exceptions.BadInputError) as err:
        plumb.current_FC(wrong_name)
    assert str(err.value) == f"'{wrong_name}' not found as component name or edge identifier."

    with pytest.raises(exceptions.BadInputError) as err:
        plumb.current_FC((1, 2, wrong_name))
    assert str(err.value) ==\
        f"'(1, 2, '{wrong_name}')' not found as component name or edge identifier."
def test_misc_engine():
    steady_by = utils.s_to_micros(1)
    converged = {1: 33, 2: 33, 3: 33}

    solve_plumb = test.two_valve_setup(1, 1, 1, 1, 1, 1, 1, 1)
    solve_state = solve_plumb.solve()
    step_plumb = test.two_valve_setup(1, 1, 1, 1, 1, 1, 1, 1)
    step_state = step_plumb.step(steady_by)
    len_plumb = test.two_valve_setup(1, 1, 1, 1, 1, 1, 1, 1)
    solve_len = len(len_plumb.solve(return_resolution=len_plumb.time_res))
    test.validate_plumbing_engine(step_plumb, solve_plumb, steady_by,
                                  converged, solve_state, step_state,
                                  solve_len, len_plumb.time_res)
def test_create_fixed_pressure():
    steady_by = utils.s_to_micros(3)
    converged = {1: 100, 2: 100, 3: 100}

    solve_plumb = test.two_valve_setup_fixed(1, 1, 1, 1, 1, 1, 1, 1)
    solve_state = solve_plumb.solve()
    step_plumb = test.two_valve_setup_fixed(1, 1, 1, 1, 1, 1, 1, 1)
    step_state = step_plumb.step(steady_by)
    len_plumb = test.two_valve_setup_fixed(1, 1, 1, 1, 1, 1, 1, 1)
    solve_len = len(len_plumb.solve(return_resolution=len_plumb.time_res))
    test.validate_plumbing_engine(step_plumb, solve_plumb, steady_by,
                                  converged, solve_state, step_state,
                                  solve_len, len_plumb.time_res)
def test_set_teq():
    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
    which_edge = {
        'closed': {
            (2, 1, 'A2'): new_lowest_teq,
            (1, 2, 'A1'): 7
        },
        'open': {
            (1, 2, 'A1'): 1
        }
    }

    plumb.set_teq('valve1', which_edge)

    assert plumb.time_res ==\
        int(utils.s_to_micros(new_lowest_teq) / utils.DEFAULT_RESOLUTION_SCALE)
    assert plumb.edges() == [
        (1, 2, 'valve1.A1', {
            'FC': utils.teq_to_FC(utils.s_to_micros(7))
        }),
        (2, 1, 'valve1.A2', {
            'FC': utils.teq_to_FC(utils.s_to_micros(new_lowest_teq))
        }), (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.component_dict['valve1'].states == {
        'open': {
            (1, 2, 'A1'): utils.teq_to_FC(utils.s_to_micros(1)),
            (2, 1, 'A2'): utils.teq_to_FC(utils.s_to_micros(old_lowest_teq))
        },
        'closed': {
            (1, 2, 'A1'): utils.teq_to_FC(utils.s_to_micros(7)),
            (2, 1, 'A2'): utils.teq_to_FC(utils.s_to_micros(new_lowest_teq))
        }
    }
def test_timeout():
    converged = {1: 33, 2: 33, 3: 33}

    big_teq = 100
    plumb = test.two_valve_setup(big_teq, big_teq, big_teq, big_teq, big_teq,
                                 big_teq, big_teq, big_teq)

    solve_state = plumb.solve(max_time=1)
    assert solve_state != converged

    plumb_long = test.two_valve_setup(big_teq, big_teq, big_teq, big_teq,
                                      big_teq, big_teq, big_teq, big_teq)
    solve_len = len(
        plumb_long.solve(max_time=1, return_resolution=plumb_long.time_res))
    assert solve_len == utils.s_to_micros(1) / plumb_long.time_res
def test_set_fixed_pressure():
    # steady_by needs to be a bit longer for this one since having the
    # fixed pressure makes equalization take longer than the teq.
    steady_by = utils.s_to_micros(3)
    converged = {1: 100, 2: 100, 3: 100}

    solve_plumb = test.two_valve_setup(1, 1, 1, 1, 1, 1, 1, 1)
    solve_plumb.set_pressure(3, 100, fixed=True)
    solve_state = solve_plumb.solve()
    step_plumb = test.two_valve_setup(1, 1, 1, 1, 1, 1, 1, 1)
    step_plumb.set_pressure(3, 100, fixed=True)
    step_state = step_plumb.step(steady_by)
    len_plumb = test.two_valve_setup(1, 1, 1, 1, 1, 1, 1, 1)
    len_plumb.set_pressure(3, 100, fixed=True)
    solve_len = len(len_plumb.solve(return_resolution=len_plumb.time_res))
    test.validate_plumbing_engine(step_plumb, solve_plumb, steady_by,
                                  converged, solve_state, step_state,
                                  solve_len, len_plumb.time_res)
Exemplo n.º 19
0
    def solve(self, min_delta=0.1, max_time=30, return_resolution=None):
        """Simulate time passing in the engine until node pressures reach steady state.

        The simulation proceeds until either all node pressures are no longer changing (within
        a certain tolerance), or until it times out. Depending on the value of return_resolution,
        it returns either a map of {node: pressure} for each node in the graph at the end of the
        simulation, or a list of maps at intervals of return_resolution.

        Parameters
        ----------

        min_delta: float
            min_delta is the minimum delta pressure over time (Pa/s) for the simulation to keep
            going. If after any step all nodes have had a lower dp/t, then the engine is considered
            to be in steady state and the simulation will end.

        max_time: int
            max_time is the maximum time in seconds that the simulation will run before timing out
            and ending.

        return_resolution: int
            return_resolution specifies (in microseconds) the intervals at which dicts of engine
            pressures will be taken (and returned). If set to None, only a {node: pressure} dict of
            the final state will be returned. return_resolution must be greater than
            MIN_TIME_RESOLUTION, otherwise an error will be raised. If less than
            self.time_res, time_res will be set to return_resolution.
        """
        max_time = self.time + utils.s_to_micros(max_time)

        timestep = self.time_res
        if return_resolution is not None:
            timestep = return_resolution

        all_states = []
        while not utils.all_converged(all_states, timestep,
                                      min_delta) and self.time < max_time:
            all_states.append(self.step(timestep))

        if return_resolution is None:
            return all_states[-1]

        return all_states
Exemplo n.º 20
0
    def set_teq(self, component_name, which_edge):
        """Set teq at each edge in provided dict for one component.

        which_edge is a dict of {edge: teq}. edge is the standard tuple of the form
        (source, target, key), where source and target are nodes, and key is a unique
        identifier.
        """

        if component_name not in self.component_dict:
            raise exceptions.BadInputError(
                f"Component name '{component_name}' not found in component dict."
            )

        component = self.component_dict[component_name]
        which_edge = copy.deepcopy(which_edge)

        for state_id, edge_dict in which_edge.items():
            if state_id not in component.states:
                raise exceptions.BadInputError(
                    f"State '{state_id}' not found in component {component_name}'s states dict."
                )

            for edge, teq in edge_dict.items():
                teq = utils.s_to_micros(teq)
                if teq < utils.TEQ_MIN:
                    raise exceptions.BadInputError(
                        f"Provided teq {utils.micros_to_s(teq)} (component '{component_name}',"
                        f" state '{state_id}', edge {edge}) too low. "
                        f"Minimum teq is {utils.micros_to_s(utils.TEQ_MIN)}s.")
                if edge not in component.states[state_id]:
                    raise exceptions.BadInputError(
                        f"State '{state_id}', edge {edge} not found in component"
                        f" {component_name}'s states dict.")

                component.states[state_id][edge] = utils.teq_to_FC(teq)

        # Update teq changes on main plumbing graph
        if component.current_state in which_edge.keys():
            self.set_component_state(component_name, component.current_state)

        self._set_time_res(component_name)
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_1():
    pc = test.create_component(1, 1, 1, 1, 'vent', 'A')
    mapping = {'vent': {1: 1, 2: utils.ATM}}
    pressures = {1: (100, False)}
    default_states = {'vent': 'open'}

    steady_by = utils.s_to_micros(1)
    converged = {1: 0, utils.ATM: 0}

    step_plumb = top.PlumbingEngine({'vent': pc}, mapping, pressures,
                                    default_states)
    step_state = step_plumb.step(1e6)

    solve_plumb = top.PlumbingEngine({'vent': pc}, mapping, pressures,
                                     default_states)
    solve_state = solve_plumb.solve()

    len_plumb = top.PlumbingEngine({'vent': pc}, mapping, pressures,
                                   default_states)
    solve_len = len(len_plumb.solve(return_resolution=len_plumb.time_res))
    test.validate_plumbing_engine(step_plumb, solve_plumb, steady_by,
                                  converged, solve_state, step_state,
                                  solve_len, len_plumb.time_res)
    def __init__(self, name, states, edge_list):
        """
        Initialize plumbing component.

        Unlike the engine, a component cannot be created empty.

        Parameters
        ----------

        name: string
            name is the name of the component.

        states: dict
            states is a dict of dicts of form {state_name: {edge: teq}}. state_name
            is the state's string name, and its value is a dict of each edge with its
            corresponding teq in the given state.

        edge_list:
            edge_list is a list of edges in the form (source, target, key), where
            source and target are nodes and key should be distinct from that of other
            edges between the same nodes.

        Errors from malformed input are written to the component's error set. A non-empty
        error set renders a component invalid; invalid components cannot be added to a
        plumbing engine.
        """
        self.name = name
        self.component_graph = nx.MultiDiGraph(edge_list)
        self.states = copy.deepcopy(states)
        self.current_state = None
        self.error_set = set()

        edge_set = set()
        for edge in edge_list:
            if edge in edge_set:
                error = invalid.InvalidComponentEdge(
                    f"Duplicate edges '{edge}' found in edge list.", edge)
                invalid.add_error(error, self.error_set)
            edge_set.add(edge)

        # Convert provided teq values into FC values
        for state_id, state in self.states.items():
            for edge in state:
                if edge not in edge_list:
                    error = invalid.InvalidComponentEdge(
                        f"Edge '{edge}' not found in provided edge list.",
                        edge)
                    invalid.add_error(error, self.error_set)
                    continue
                og_teq = state[edge]
                if isinstance(state[edge], (float, int)):
                    state[edge] = int(utils.s_to_micros(state[edge]))
                elif isinstance(state[edge], str):
                    if state[edge] != utils.CLOSED:
                        error = invalid.InvalidTeq(
                            f"Invalid provided teq value ('{state[edge]}'), accepted keyword is: "
                            f"'{utils.CLOSED}'", self.name, state_id, edge,
                            og_teq)
                        invalid.add_error(error, self.error_set)
                        state[edge] = utils.CLOSED

                # TODO(jacob/wendi): Look into eventually implementing this with datetime.timedelta.
                state[edge] = utils.teq_to_FC(state[edge])

                if state[edge] > utils.FC_MAX:
                    error = invalid.InvalidTeq(
                        "Provided teq value too low, minimum value is: "
                        f"{utils.micros_to_s(utils.TEQ_MIN)}s", self.name,
                        state_id, edge, og_teq)
                    invalid.add_error(error, self.error_set)
                    state[edge] = utils.FC_MAX