def dulmage_mendelsohn(self, variables=None, constraints=None): """ Returns the Dulmage-Mendelsohn partition of the incidence graph of the provided variables and constraints. Returns: -------- ColPartition namedtuple and RowPartition namedtuple. The ColPartition is returned first to match the order of variables and constraints in the method arguments. These partition variables (columns) and constraints (rows) into overconstrained, underconstrained, unmatched, and square. """ variables, constraints = self._validate_input(variables, constraints) matrix = self._extract_submatrix(variables, constraints) row_partition, col_partition = dulmage_mendelsohn(matrix.tocoo()) con_partition = RowPartition( *[[constraints[i] for i in subset] for subset in row_partition] ) var_partition = ColPartition( *[[variables[i] for i in subset] for subset in col_partition] ) # Switch the order of the maps here to match the method call. # Hopefully this does not get too confusing... return var_partition, con_partition
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_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_dm_graph_interface(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) M, N = len(constraints), len(variables) top_nodes = list(range(M)) con_dmp, var_dmp = dulmage_mendelsohn(graph, top_nodes=top_nodes) con_dmp = tuple([constraints[i] for i in subset] for subset in con_dmp) var_dmp = tuple([variables[i - M] for i in subset] for subset in var_dmp) underconstrained_vars = ComponentSet(m.flow_comp.values()) underconstrained_vars.add(m.flow) underconstrained_cons = ComponentSet(m.flow_eqn.values()) self.assertEqual(len(var_dmp[0] + var_dmp[1]), len(underconstrained_vars)) for var in var_dmp[0] + var_dmp[1]: self.assertIn(var, underconstrained_vars) self.assertEqual(len(con_dmp[2]), len(underconstrained_cons)) for con in con_dmp[2]: self.assertIn(con, underconstrained_cons) overconstrained_cons = ComponentSet(m.holdup_eqn.values()) overconstrained_cons.add(m.density_eqn) overconstrained_cons.add(m.sum_eqn) overconstrained_vars = ComponentSet(m.x.values()) overconstrained_vars.add(m.rho) self.assertEqual(len(var_dmp[2]), len(overconstrained_vars)) for var in var_dmp[2]: self.assertIn(var, overconstrained_vars) self.assertEqual(len(con_dmp[0] + con_dmp[1]), len(overconstrained_cons)) for con in con_dmp[0] + con_dmp[1]: self.assertIn(con, overconstrained_cons)
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)