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_missing_component(): 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()
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_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_missing_node_pressure(): wrong_node_name = 4 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 = {wrong_node_name: (100, False), 2: (100, False)} default_states = {'valve1': 'closed', 'valve2': 'open'} with pytest.raises(exceptions.BadInputError) as err: _ = top.PlumbingEngine({ 'valve1': pc1, 'valve2': pc2 }, component_mapping, pressures, default_states) assert str(err.value) == f"Node {wrong_node_name} not found in graph."
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_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_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 make_plumb(): """ The engine that's been set up looks like this: +-----+ +-----+ +-----+ +-----+ | | --C--> | | --40-> | | --5--> | | | 1 | | 2 | | 3 | | atm | | | <-10-- | | <-45-- | | <-5--- | | +-----+ +-----+ +-----+ +-----+ 100 100 100 0 Result annotation: - node 1 stays at 100 the entire duration because 1->2 is closed, so no pressure can flow out of node 1. - node 3's pressure drops much more quickly than node 2's because its teq to atm (5) is much lower than the teq in the 2->3 connection (50). Pressure from 3 flows into atm much faster than it's replenished by pressure from 2. - pressure at atm stays at 0, because pressure at atm is always 0. """ ret = test.two_valve_setup(1, 1, utils.CLOSED, 10, 40, 45, 1, 1) comp = test.create_component(5, 5, utils.CLOSED, utils.CLOSED, 'vent', 'A') mapping = { 1: 3, 2: utils.ATM, } ret.add_component(comp, mapping, 'open') ret.set_pressure(1, 100) ret.set_pressure(2, 100) return ret
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_wrong_node_mapping(): proper_node_name = 1 wrong_node_name = 5 pc1 = test.create_component(0, 0, 0, 0, 'valve1', 'A') pc2 = test.create_component(0, 0, 0, 0, 'valve2', 'B') component_mapping = { 'valve1': { wrong_node_name: 1, 2: 8 }, 'valve2': { 1: 8, 2: 3 } } pressures = {3: (100, False)} default_states = {'valve1': 'closed', 'valve2': 'open'} plumb = top.PlumbingEngine({ 'valve1': pc1, 'valve2': pc2 }, component_mapping, pressures, default_states) assert not plumb.is_valid() # Since the node name is wrong in the mapping, an error should be added # every time the mapping dict is accessed to find the matching graph node. # This translates to twice (once per component) when populating the main graph, # and twice (once per component) when assigning initial states by component, # since the component node stored in the component's states dict needs to be # translated into a main graph node. So 4 errors total, but they're identical # so we should get one original error and one multi-error note. assert len(plumb.errors()) == 2 error = invalid.InvalidComponentNode( f"Component 'valve1', node {proper_node_name} not found in mapping dict.", 'valve1', proper_node_name) duplicate_error = invalid.DuplicateError( invalid.multi_error_msg( f"Component 'valve1', node {proper_node_name} not found in mapping dict." ), error) assert error in plumb.errors() assert duplicate_error in plumb.errors()
def test_3(): pc = test.create_component(1, 1, utils.CLOSED, utils.CLOSED, 'vent', 'A') mapping = {'vent': {1: 1, 2: utils.ATM}} pressures = {1: (100, False)} default_states = {'vent': 'closed'} plumb = top.PlumbingEngine({'vent': pc}, mapping, pressures, default_states) test.assert_no_change(plumb)
def test_error_reset(): 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() plumb = top.PlumbingEngine() assert plumb.is_valid()
def test_invalid_engine(): 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() with pytest.raises(exceptions.InvalidEngineError) as err: plumb.step(5) assert str( err.value ) == "Step() cannot be called on an invalid engine. Check for errors."
def test_add_component_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."
def test_load_errorless_graph(): 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() 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.load_graph(plumb0.component_dict, plumb0.mapping, pressures, default_states) assert plumb.is_valid()
def test_engine_dicts_remain_unchanged(): 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'} component_dict = {'valve1': pc1, 'valve2': pc2} _ = top.PlumbingEngine(component_dict, component_mapping, pressures, default_states) assert component_mapping == { 'valve1': { 1: 1, 2: 2 }, 'valve2': { 1: 2, 2: 3 } } assert pressures == {3: (100, False)} assert default_states == {'valve1': 'closed', 'valve2': 'open'} assert component_dict == {'valve1': pc1, 'valve2': pc2}
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_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_reset_added_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.reset(True) plumb_initial = test.two_valve_setup(0.5, 0.2, 10, utils.CLOSED, 0.5, 0.2, 10, utils.CLOSED) assert plumb.time == 0 assert plumb.is_valid() assert plumb.time_res == plumb_initial.time_res assert plumb.edges() == plumb_initial.edges() assert plumb.nodes() == plumb_initial.nodes() assert plumb.current_state() == plumb_initial.current_state()
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)