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_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_perfect_matching(self): model = make_gas_expansion_model() igraph = IncidenceGraphInterface() # 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)) n_var = len(variables) matching = igraph.maximum_matching(variables, 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_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_imperfect_matching(self): model = make_gas_expansion_model() igraph = IncidenceGraphInterface(model) n_eqn = len(list(model.component_data_objects(pyo.Constraint))) matching = igraph.maximum_matching() values = ComponentSet(matching.values()) self.assertEqual(len(matching), n_eqn) self.assertEqual(len(values), n_eqn)
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_exception(self): model = make_gas_expansion_model() igraph = IncidenceGraphInterface(model) with self.assertRaises(ValueError) as exc: variables = [model.P] constraints = [model.ideal_gas] igraph.maximum_matching(variables, constraints) self.assertIn('must be unindexed', str(exc.exception)) with self.assertRaises(ValueError) as exc: variables = [model.P] constraints = [model.ideal_gas] igraph.block_triangularize(variables, constraints) self.assertIn('must be unindexed', str(exc.exception))
def test_diagonal_blocks_with_cached_maps(self): N = 5 model = make_gas_expansion_model(N) igraph = IncidenceGraphInterface() # 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)) igraph.block_triangularize(variables, constraints) var_blocks, con_blocks = igraph.get_diagonal_blocks( variables, constraints) self.assertIsNot(igraph.row_block_map, None) self.assertIsNot(igraph.col_block_map, None) self.assertEqual(len(var_blocks), N + 1) self.assertEqual(len(con_blocks), N + 1) for i, (vars, cons) in enumerate(zip(var_blocks, con_blocks)): var_set = ComponentSet(vars) con_set = ComponentSet(cons) if i == 0: pred_var_set = ComponentSet([model.P[0]]) self.assertEqual(pred_var_set, var_set) pred_con_set = ComponentSet([model.ideal_gas[0]]) self.assertEqual(pred_con_set, con_set) else: pred_var_set = ComponentSet( [model.rho[i], model.T[i], model.P[i], model.F[i]]) pred_con_set = ComponentSet([ model.ideal_gas[i], model.expansion[i], model.mbal[i], model.ebal[i], ]) self.assertEqual(pred_var_set, var_set) self.assertEqual(pred_con_set, con_set)
def test_triangularize_submatrix(self): # This test exercises the extraction of a somewhat nontrivial # submatrix from a cached incidence matrix. N = 5 model = make_gas_expansion_model(N) igraph = IncidenceGraphInterface(model) # These are the variables and constraints of a square, # nonsingular subsystem variables = [] half = N // 2 variables.extend(model.P[i] for i in model.streams if i >= half) variables.extend(model.T[i] for i in model.streams if i > half) variables.extend(model.rho[i] for i in model.streams if i > half) variables.extend(model.F[i] for i in model.streams if i > half) constraints = [] constraints.extend(model.ideal_gas[i] for i in model.streams if i >= half) constraints.extend(model.expansion[i] for i in model.streams if i > half) constraints.extend(model.mbal[i] for i in model.streams if i > half) constraints.extend(model.ebal[i] for i in model.streams if i > half) var_block_map, con_block_map = igraph.block_triangularize( variables, constraints) var_values = set(var_block_map.values()) con_values = set(con_block_map.values()) self.assertEqual(len(var_values), (N - half) + 1) self.assertEqual(len(con_values), (N - half) + 1) self.assertEqual(var_block_map[model.P[half]], 0) for i in model.streams: if i > half: idx = i - half self.assertEqual(var_block_map[model.rho[i]], idx) self.assertEqual(var_block_map[model.T[i]], idx) self.assertEqual(var_block_map[model.P[i]], idx) self.assertEqual(var_block_map[model.F[i]], idx) self.assertEqual(con_block_map[model.ideal_gas[i]], idx) self.assertEqual(con_block_map[model.expansion[i]], idx) self.assertEqual(con_block_map[model.mbal[i]], idx) self.assertEqual(con_block_map[model.ebal[i]], idx)
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_triangularize(self): N = 5 model = make_gas_expansion_model(N) model.obj = pyo.Objective(expr=0) nlp = PyomoNLP(model) igraph = IncidenceGraphInterface(nlp) # 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)) var_block_map, con_block_map = igraph.block_triangularize( variables, 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_remove(self): model = make_gas_expansion_model() igraph = IncidenceGraphInterface(model) n_eqn = len(list(model.component_data_objects(pyo.Constraint))) matching = igraph.maximum_matching() values = ComponentSet(matching.values()) self.assertEqual(len(matching), n_eqn) self.assertEqual(len(values), n_eqn) variable_set = ComponentSet(igraph.variables) self.assertIn(model.F[0], variable_set) self.assertIn(model.F[2], variable_set) var_dmp, con_dmp = igraph.dulmage_mendelsohn() underconstrained_set = ComponentSet(var_dmp.unmatched + var_dmp.underconstrained) self.assertIn(model.F[0], underconstrained_set) self.assertIn(model.F[2], underconstrained_set) N, M = igraph.incidence_matrix.shape # Say we know that these variables and constraints should # be matched... vars_to_remove = [model.F[0], model.F[2]] cons_to_remove = (model.mbal[1], model.mbal[2]) igraph.remove_nodes(vars_to_remove, cons_to_remove) variable_set = ComponentSet(igraph.variables) self.assertNotIn(model.F[0], variable_set) self.assertNotIn(model.F[2], variable_set) var_dmp, con_dmp = igraph.dulmage_mendelsohn() underconstrained_set = ComponentSet(var_dmp.unmatched + var_dmp.underconstrained) self.assertNotIn(model.F[0], underconstrained_set) self.assertNotIn(model.F[2], underconstrained_set) N_new, M_new = igraph.incidence_matrix.shape self.assertEqual(N_new, N - len(cons_to_remove)) self.assertEqual(M_new, M - len(vars_to_remove))
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)
def test_gas_expansion(self): N = 5 m = make_gas_expansion_model(N) m.rho[0].fix() m.F[0].fix() m.T[0].fix() constraints = list(m.component_data_objects(pyo.Constraint)) self.assertEqual( len(list(generate_strongly_connected_components(constraints))), N + 1, ) for i, (block, inputs) in enumerate( generate_strongly_connected_components(constraints)): with TemporarySubsystemManager(to_fix=inputs): if i == 0: # P[0], ideal_gas[0] self.assertEqual(len(block.vars), 1) self.assertEqual(len(block.cons), 1) var_set = ComponentSet([m.P[i]]) con_set = ComponentSet([m.ideal_gas[i]]) for var, con in zip(block.vars[:], block.cons[:]): self.assertIn(var, var_set) self.assertIn(con, con_set) # Other variables are fixed; not included self.assertEqual(len(block.input_vars), 0) elif i == 1: # P[1], rho[1], F[1], T[1], etc. self.assertEqual(len(block.vars), 4) self.assertEqual(len(block.cons), 4) var_set = ComponentSet([m.P[i], m.rho[i], m.F[i], m.T[i]]) con_set = ComponentSet( [m.ideal_gas[i], m.mbal[i], m.ebal[i], m.expansion[i]]) for var, con in zip(block.vars[:], block.cons[:]): self.assertIn(var, var_set) self.assertIn(con, con_set) # P[0] is in expansion[1] other_var_set = ComponentSet([m.P[i - 1]]) self.assertEqual(len(block.input_vars), 1) for var in block.input_vars[:]: self.assertIn(var, other_var_set) else: # P[i], rho[i], F[i], T[i], etc. self.assertEqual(len(block.vars), 4) self.assertEqual(len(block.cons), 4) var_set = ComponentSet([m.P[i], m.rho[i], m.F[i], m.T[i]]) con_set = ComponentSet( [m.ideal_gas[i], m.mbal[i], m.ebal[i], m.expansion[i]]) for var, con in zip(block.vars[:], block.cons[:]): self.assertIn(var, var_set) self.assertIn(con, con_set) # P[i-1], rho[i-1], F[i-1], T[i-1], etc. other_var_set = ComponentSet( [m.P[i - 1], m.rho[i - 1], m.F[i - 1], m.T[i - 1]]) self.assertEqual(len(block.input_vars), 4) for var in block.input_vars[:]: self.assertIn(var, other_var_set)
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_numeric_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) # Map constraint and variable indices to the values of the derivatives # Note that the derivative values calculated here depend on the model's # canonical form. deriv_lookup = {} m = model # for convenience for s in model.streams: # Ideal gas: i = con_idx_map[model.ideal_gas[s]] j = var_idx_map[model.P[s]] deriv_lookup[i, j] = 1.0 j = var_idx_map[model.rho[s]] deriv_lookup[i, j] = -model.R.value * model.T[s].value j = var_idx_map[model.T[s]] deriv_lookup[i, j] = -model.R.value * model.rho[s].value if s != model.streams.first(): # Expansion: i = con_idx_map[model.expansion[s]] j = var_idx_map[model.P[s]] deriv_lookup[i, j] = 1 / model.P[s - 1].value j = var_idx_map[model.P[s - 1]] deriv_lookup[i, j] = -model.P[s].value / model.P[s - 1]**2 j = var_idx_map[model.rho[s]] deriv_lookup[i, j] = pyo.value( -m.gamma * (m.rho[s] / m.rho[s - 1])**(m.gamma - 1) / m.rho[s - 1]) j = var_idx_map[model.rho[s - 1]] deriv_lookup[i, j] = pyo.value( -m.gamma * (m.rho[s] / m.rho[s - 1])**(m.gamma - 1) * (-m.rho[s] / m.rho[s - 1]**2)) # Energy balance: i = con_idx_map[m.ebal[s]] j = var_idx_map[m.rho[s - 1]] deriv_lookup[i, j] = pyo.value(m.F[s - 1] * m.T[s - 1]) j = var_idx_map[m.F[s - 1]] deriv_lookup[i, j] = pyo.value(m.rho[s - 1] * m.T[s - 1]) j = var_idx_map[m.T[s - 1]] deriv_lookup[i, j] = pyo.value(m.F[s - 1] * m.rho[s - 1]) j = var_idx_map[m.rho[s]] deriv_lookup[i, j] = pyo.value(-m.F[s] * m.T[s]) j = var_idx_map[m.F[s]] deriv_lookup[i, j] = pyo.value(-m.rho[s] * m.T[s]) j = var_idx_map[m.T[s]] deriv_lookup[i, j] = pyo.value(-m.F[s] * m.rho[s]) # Mass balance: i = con_idx_map[m.mbal[s]] j = var_idx_map[m.rho[s - 1]] deriv_lookup[i, j] = pyo.value(m.F[s - 1]) j = var_idx_map[m.F[s - 1]] deriv_lookup[i, j] = pyo.value(m.rho[s - 1]) j = var_idx_map[m.rho[s]] deriv_lookup[i, j] = pyo.value(-m.F[s]) j = var_idx_map[m.F[s]] deriv_lookup[i, j] = pyo.value(-m.rho[s]) # 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.assertAlmostEqual(pyo.value(deriv_lookup[i, j]), pyo.value(e), 8) # And no additional rows for con in csr_map: self.assertEqual(len(csr_map[con]), 0)