def test_perfect_matching(self): model = make_gas_expansion_model() # These are the variables and constraints of the square, # nonsingular subsystem variables = [] variables.extend(model.P.values()) variables.extend(model.T[i] for i in model.streams if i != model.streams.first()) variables.extend(model.rho[i] for i in model.streams if i != model.streams.first()) variables.extend(model.F[i] for i in model.streams if i != model.streams.first()) constraints = list(model.component_data_objects(pyo.Constraint)) imat = get_structural_incidence_matrix(variables, constraints) con_idx_map = ComponentMap((c, i) for i, c in enumerate(constraints)) n_var = len(variables) matching = maximum_matching(imat) matching = ComponentMap( (c, variables[matching[con_idx_map[c]]]) for c in constraints) values = ComponentSet(matching.values()) self.assertEqual(len(matching), n_var) self.assertEqual(len(values), n_var) # The subset of variables and equations we have identified # do not have a unique perfect matching. But we at least know # this much. self.assertIs(matching[model.ideal_gas[0]], model.P[0])
def test_rectangular_system(self): N_model = 2 m = make_gas_expansion_model(N_model) variables = list(m.component_data_objects(pyo.Var)) constraints = list(m.component_data_objects(pyo.Constraint)) imat = get_structural_incidence_matrix(variables, constraints) M, N = imat.shape self.assertEqual(M, 4 * N_model + 1) self.assertEqual(N, 4 * (N_model + 1)) row_partition, col_partition = dulmage_mendelsohn(imat) # No unmatched constraints self.assertEqual(row_partition[0], []) self.assertEqual(row_partition[1], []) # All constraints are matched with potentially unmatched variables matched_con_set = set(range(len(constraints))) self.assertEqual(set(row_partition[2]), matched_con_set) # 3 unmatched variables self.assertEqual(len(col_partition[0]), 3) # All variables are potentially unmatched potentially_unmatched = col_partition[0] + col_partition[1] potentially_unmatched_set = set(range(len(variables))) self.assertEqual(set(potentially_unmatched), potentially_unmatched_set)
def test_square_well_posed_model(self): N = 4 m = make_gas_expansion_model(N) m.F[0].fix() m.rho[0].fix() m.T[0].fix() variables = [ v for v in m.component_data_objects(pyo.Var) if not v.fixed ] constraints = list(m.component_data_objects(pyo.Constraint)) imat = get_structural_incidence_matrix(variables, constraints) N, M = imat.shape self.assertEqual(N, M) row_partition, col_partition = dulmage_mendelsohn(imat) # Unmatched, reachable-from-unmatched, and matched-with-reachable # subsets are all empty. self.assertEqual(len(row_partition[0]), 0) self.assertEqual(len(row_partition[1]), 0) self.assertEqual(len(row_partition[2]), 0) self.assertEqual(len(col_partition[0]), 0) self.assertEqual(len(col_partition[1]), 0) self.assertEqual(len(col_partition[2]), 0) # All nodes belong to the "well-determined" subset self.assertEqual(len(row_partition[3]), M) self.assertEqual(len(col_partition[3]), N)
def test_incidence_matrix(self): N = 5 model = make_gas_expansion_model(N) all_vars = list(model.component_data_objects(pyo.Var)) all_cons = list(model.component_data_objects(pyo.Constraint)) imat = get_structural_incidence_matrix(all_vars, all_cons) n_var = 4 * (N + 1) n_con = 4 * N + 1 self.assertEqual(imat.shape, (n_con, n_var)) var_idx_map = ComponentMap((v, i) for i, v in enumerate(all_vars)) con_idx_map = ComponentMap((c, i) for i, c in enumerate(all_cons)) # Map constraints to the variables they contain. csr_map = ComponentMap() csr_map.update((model.mbal[i], ComponentSet([ model.F[i], model.F[i - 1], model.rho[i], model.rho[i - 1], ])) for i in model.streams if i != model.streams.first()) csr_map.update((model.ebal[i], ComponentSet([ model.F[i], model.F[i - 1], model.rho[i], model.rho[i - 1], model.T[i], model.T[i - 1], ])) for i in model.streams if i != model.streams.first()) csr_map.update((model.expansion[i], ComponentSet([ model.rho[i], model.rho[i - 1], model.P[i], model.P[i - 1], ])) for i in model.streams if i != model.streams.first()) csr_map.update((model.ideal_gas[i], ComponentSet([ model.P[i], model.rho[i], model.T[i], ])) for i in model.streams) # Want to test that the columns have the rows we expect. i = model.streams.first() for i, j, e in zip(imat.row, imat.col, imat.data): con = all_cons[i] var = all_vars[j] self.assertIn(var, csr_map[con]) csr_map[con].remove(var) self.assertEqual(e, 1.0) # And no additional rows for con in csr_map: self.assertEqual(len(csr_map[con]), 0)
def test_incidence_graph(self): m = make_degenerate_solid_phase_model() variables = list(m.component_data_objects(pyo.Var)) constraints = list(m.component_data_objects(pyo.Constraint)) graph = get_incidence_graph(variables, constraints) matrix = get_structural_incidence_matrix(variables, constraints) from_matrix = from_biadjacency_matrix(matrix) self.assertEqual(graph.nodes, from_matrix.nodes) self.assertEqual(graph.edges, from_matrix.edges)
def test_imperfect_matching(self): model = make_gas_expansion_model() all_vars = list(model.component_data_objects(pyo.Var)) all_cons = list(model.component_data_objects(pyo.Constraint)) imat = get_structural_incidence_matrix(all_vars, all_cons) n_eqn = len(all_cons) matching = maximum_matching(imat) values = set(matching.values()) self.assertEqual(len(matching), n_eqn) self.assertEqual(len(values), n_eqn)
def test_triangularize(self): N = 5 model = make_gas_expansion_model(N) # These are the variables and constraints of the square, # nonsingular subsystem variables = [] variables.extend(model.P.values()) variables.extend(model.T[i] for i in model.streams if i != model.streams.first()) variables.extend(model.rho[i] for i in model.streams if i != model.streams.first()) variables.extend(model.F[i] for i in model.streams if i != model.streams.first()) constraints = list(model.component_data_objects(pyo.Constraint)) imat = get_structural_incidence_matrix(variables, constraints) con_idx_map = ComponentMap((c, i) for i, c in enumerate(constraints)) var_idx_map = ComponentMap((v, i) for i, v in enumerate(variables)) row_block_map, col_block_map = block_triangularize(imat) var_block_map = ComponentMap( (v, col_block_map[var_idx_map[v]]) for v in variables) con_block_map = ComponentMap( (c, row_block_map[con_idx_map[c]]) for c in constraints) var_values = set(var_block_map.values()) con_values = set(con_block_map.values()) self.assertEqual(len(var_values), N + 1) self.assertEqual(len(con_values), N + 1) self.assertEqual(var_block_map[model.P[0]], 0) for i in model.streams: if i != model.streams.first(): self.assertEqual(var_block_map[model.rho[i]], i) self.assertEqual(var_block_map[model.T[i]], i) self.assertEqual(var_block_map[model.P[i]], i) self.assertEqual(var_block_map[model.F[i]], i) self.assertEqual(con_block_map[model.ideal_gas[i]], i) self.assertEqual(con_block_map[model.expansion[i]], i) self.assertEqual(con_block_map[model.mbal[i]], i) self.assertEqual(con_block_map[model.ebal[i]], i)
def test_rectangular_model(self): m = make_dynamic_model() m.height[0].fix() variables = [ v for v in m.component_data_objects(pyo.Var) if not v.fixed ] constraints = list(m.component_data_objects(pyo.Constraint)) imat = get_structural_incidence_matrix(variables, constraints) M, N = imat.shape var_idx_map = ComponentMap((v, i) for i, v in enumerate(variables)) con_idx_map = ComponentMap((c, i) for i, c in enumerate(constraints)) row_partition, col_partition = dulmage_mendelsohn(imat) # No unmatched rows self.assertEqual(row_partition[0], []) self.assertEqual(row_partition[1], []) # Assert that the square subsystem contains the components we expect self.assertEqual(len(row_partition[3]), 1) self.assertEqual(row_partition[3][0], con_idx_map[m.flow_out_eqn[0]]) self.assertEqual(len(col_partition[3]), 1) self.assertEqual(col_partition[3][0], var_idx_map[m.flow_out[0]]) # Assert that underdetermined subsystem contains the components # we expect # Rows matched with potentially unmatched columns self.assertEqual(len(row_partition[2]), M - 1) row_indices = set( [i for i in range(M) if i != con_idx_map[m.flow_out_eqn[0]]]) self.assertEqual(set(row_partition[2]), row_indices) # Potentially unmatched columns self.assertEqual(len(col_partition[0]), N - M) self.assertEqual(len(col_partition[1]), M - 1) potentially_unmatched = col_partition[0] + col_partition[1] col_indices = set( [i for i in range(N) if i != var_idx_map[m.flow_out[0]]]) self.assertEqual(set(potentially_unmatched), col_indices)
def test_square_ill_posed_model(self): N = 1 m = make_gas_expansion_model(N) m.P[0].fix() m.rho[0].fix() m.T[0].fix() variables = [ v for v in m.component_data_objects(pyo.Var) if not v.fixed ] constraints = list(m.component_data_objects(pyo.Constraint)) imat = get_structural_incidence_matrix(variables, constraints) var_idx_map = ComponentMap((v, i) for i, v in enumerate(variables)) con_idx_map = ComponentMap((c, i) for i, c in enumerate(constraints)) N, M = imat.shape self.assertEqual(N, M) row_partition, col_partition = dulmage_mendelsohn(imat) # Only unmatched constraint is ideal_gas[0] unmatched_rows = [con_idx_map[m.ideal_gas[0]]] self.assertEqual(row_partition[0], unmatched_rows) # No other constraints can possibly be unmatched. self.assertEqual(row_partition[1], []) # The potentially unmatched variables have four constraints # between them matched_con_set = set(con_idx_map[con] for con in constraints if con is not m.ideal_gas[0]) self.assertEqual(set(row_partition[2]), matched_con_set) # All variables are potentially unmatched potentially_unmatched_set = set(range(len(variables))) potentially_unmatched = col_partition[0] + col_partition[1] self.assertEqual(set(potentially_unmatched), potentially_unmatched_set)