diff --git a/.gitignore b/.gitignore index 0c93093761..afed299126 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ build* debug/ release/ cmake-build-debug/ +cmake-build-release/ # check folder, files created by or for `make test` check/results/ diff --git a/CHANGELOG b/CHANGELOG index 5f4dce0666..7d4bdacf65 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,8 @@ Features - detailed information about applied symmetry handling techniques can be printed to the terminal - added a new separator sepa_multilinear to generate flower cuts from AND constraints nonlinear product expressions. - added functionality to deal with hypergraphs by means of efficient access to vertices, edges and intersections edges. +- added support for (transposed) network matrix detection in pub_network.h +- added a new presolver presol_implint which detects implied integers by detecting (transposed) network submatrices in the problem. For now, this plugin is disabled by default. Performance improvements ------------------------ @@ -44,6 +46,8 @@ Interface changes - SCIPhypergraphCreate(), SCIPhypergraphClear(), SCIPhypergraphFree(), SCIPhypergraphAddVertex(), SCIPhypergraphAddEdge(), SCIPhypergraphIsValid() and SCIPhypergraphComputeVerticesEdges() for building and deleting hypergraphs, SCIPhypergraphComputeOverlaps(), SCIPhypergraphOverlapFind(), SCIPhypergraphIntersectEdges(), SCIPhypergraphComputeOverlapsEdges(), SCIPhypergraphComputeVerticesOverlaps(), SCIPhypergraphOverlapsDisjoint() to deal with pair-wise intersections of hyperedges, SCIPhypergraphIterInit(), SCIPhypergraphIterClear(), SCIPhypergraphIterStart(), SCIPhypergraphIterValid(), SCIPhypergraphIterNext(), SCIPhypergraphIterBase(), SCIPhypergraphIterAdjacent(), SCIPhypergraphIterMinVertex(), SCIPhypergraphIterOverlap() for iterating over adjacent edges, SCIPhypergraphHasVertexEdges() SCIPhypergraphHasOverlaps(), SCIPhypergraphHasOverlapsEdges(), SCIPhypergraphHasVertexOverlaps(), SCIPhypergraphGetNVertices(), SCIPhypergraphGetNEdges(), SCIPhypergraphBlkmem(), SCIPhypergraphGetNOverlaps(), SCIPhypergraphVertexData(), SCIPhypergraphEdgeData(), SCIPhypergraphEdgeSize(), SCIPhypergraphEdgeVertices(), SCIPhypergraphVertexEdgesFirst(), SCIPhypergraphVertexEdgesBeyond(), SCIPhypergraphVertexEdgesGetAtIndex(), SCIPhypergraphOverlapData(), SCIPhypergraphOverlapSize(), SCIPhypergraphOverlapVertices() SCIPhypergraphEdgesOverlapsFirst(), SCIPhypergraphEdgesOverlapsBeyond(), SCIPhypergraphEdgesOverlapsGetAtIndex(), SCIPhypergraphOverlapsEdgesFirst(), SCIPhypergraphOverlapsEdgesBeyond(), SCIPhypergraphOverlapsEdgesGetAtIndex(), SCIPhypergraphVertexOverlapsFirst(), SCIPhypergraphVertexOverlapsBeyond() and SCIPhypergraphVertexOverlapsGetAtIndex() to query information about vertices, edges and overlaps as well as their incidences. - SCIPdebugClearSol() for clearing the debug solution - Renamed XML functions to avoid name clash with libxml2 by adding "SCIP": SCIPxmlProcess(), SCIPxmlNewNode(), SCIPxmlNewAttr(), SCIPxmlAddAttr(), SCIPxmlAppendChild(), SCIPxmlFreeNode(), SCIPxmlShowNode(), SCIPxmlGetAttrval(), SCIPxmlFirstNode(), SCIPxmlNextNode(), SCIPxmlFindNode(), SCIPxmlFindNodeMaxdepth(), SCIPxmlNextSibl(), SCIPxmlPrevSibl(), SCIPxmlFirstChild(), SCIPxmlLastChild(), SCIPxmlGetName(), SCIPxmlGetLine(), SCIPxmlGetData(), SCIPxmlFindPcdata(). +- SCIPincludePresolImplint() to include the new implied integer presolver +- SCIPnetmatdecCreate() and SCIPnetmatdecFree() for creating and deleting a network matrix decomposition. SCIPnetmatdecTryAddCol() and SCIPnetmatdecTryAddRow() are used to add columns and rows of the matrix to the decomposition. SCIPnetmatdecContainsRow() and SCIPnetmatdecContainsColumn() check if the decomposition contains the given row or columns. SCIPnetmatdecRemoveComponent() can remove connected components from the decomposition. SCIPnetmatdecCreateDiGraph() can be used to expose the underlying digraph. SCIPnetmatdecIsMinimal() and SCIPnetmatdecVerifyCycle() check if certain invariants of the decomposition are satisfied and are used in tests. ### Changes in preprocessor macros @@ -63,6 +67,8 @@ Interface changes - new parameter "propagating/symmetry/handlesignedorbitopes" to control whether special symmetry handling techniques for orbitopes whose columns can be (partially) reflected shall be applied - new parameter "propagating/symmetry/usesimplesgncomp" to control whether symmetry components all of whose variables are simultaneously reflected by a symmetry shall be handled by a special inequality - new parameter "propagating/symmetry/dispsyminfo" to control whether information about which symmetry handling methods are applied are printed +- new parameter "presolving/implint/columnrowratio" indicates the ratio of rows/columns where the row-wise network matrix detection algorithm is used instead of the column-wise network matrix detection algorithm +- new parameter "presolving/implint/numericslimit" determines the limit for absolute integral coefficients beyond which the corresponding rows and variables are excluded from implied integer detection ### Data structures diff --git a/Makefile b/Makefile index bf00461dea..2584671225 100644 --- a/Makefile +++ b/Makefile @@ -717,6 +717,7 @@ SCIPPLUGINLIBOBJ= scip/benders_default.o \ scip/presol_dualinfer.o\ scip/presol_gateextraction.o \ scip/presol_implics.o \ + scip/presol_implint.o \ scip/presol_inttobinary.o \ scip/presol_qpkktref.o \ scip/presol_redvub.o \ @@ -860,6 +861,7 @@ SCIPLIBOBJ = scip/boundstore.o \ scip/misc.o \ scip/misc_linear.o \ scip/misc_rowprep.o \ + scip/network.o \ scip/nlhdlr.o \ scip/nlp.o \ scip/nlpi.o \ diff --git a/doc/xternal.c b/doc/xternal.c index 64d5cb3dc7..8319684065 100644 --- a/doc/xternal.c +++ b/doc/xternal.c @@ -9054,6 +9054,11 @@ * @brief methods for creating and accessing user decompositions */ +/**@defgroup NetworkMatrix Network Matrix + * @ingroup DataStructures + * @brief methods for detecting network matrices and converting them to the underlying graphs + */ + /**@defgroup SymGraph Symmetry Detection Graph * @ingroup DataStructures * @brief methods for creating and manipulating symmetry detection graphs diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 670e6d125b..b3533bd5e2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -168,6 +168,7 @@ set(scipsources scip/heur_zirounding.c scip/hypergraph.c scip/message_default.c + scip/network.c scip/nlhdlr_bilinear.c scip/nlhdlr_convex.c scip/nlhdlr_default.c @@ -194,6 +195,7 @@ set(scipsources scip/presol_dualinfer.c scip/presol_gateextraction.c scip/presol_implics.c + scip/presol_implint.c scip/presol_inttobinary.c scip/presol_qpkktref.c scip/presol_redvub.c @@ -691,6 +693,7 @@ set(scipheaders scip/presol_gateextraction.h scip/presol.h scip/presol_implics.h + scip/presol_implint.h scip/presol_inttobinary.h scip/presol_qpkktref.h scip/presol_redvub.h @@ -746,6 +749,7 @@ set(scipheaders scip/pub_misc_rowprep.h scip/pub_misc_select.h scip/pub_misc_sort.h + scip/pub_network.h scip/pub_nlhdlr.h scip/pub_nlp.h scip/pub_nlpi.h diff --git a/src/scip/network.c b/src/scip/network.c new file mode 100644 index 0000000000..ac07a3dfaa --- /dev/null +++ b/src/scip/network.c @@ -0,0 +1,11718 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the program and library */ +/* SCIP --- Solving Constraint Integer Programs */ +/* */ +/* Copyright (c) 2002-2024 Zuse Institute Berlin (ZIB) */ +/* */ +/* Licensed under the Apache License, Version 2.0 (the "License"); */ +/* you may not use this file except in compliance with the License. */ +/* You may obtain a copy of the License at */ +/* */ +/* http://www.apache.org/licenses/LICENSE-2.0 */ +/* */ +/* Unless required by applicable law or agreed to in writing, software */ +/* distributed under the License is distributed on an "AS IS" BASIS, */ +/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ +/* See the License for the specific language governing permissions and */ +/* limitations under the License. */ +/* */ +/* You should have received a copy of the Apache-2.0 license */ +/* along with SCIP; see the file LICENSE. If not visit scipopt.org. */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/**@file network.c + * @ingroup OTHER_CFILES + * @brief Methods for detecting network (sub)matrices + * @author Rolf van der Hulst + * + * Detecting if a matrix is a network matrix can be quite complex. Below is an introductory text, which may help with + * navigating the functions and datastructures in this file by giving a general overview. + * More details can be found in; + * R.P. van der Hulst and M. Walter "A row-wise algorithm for graph realization" + * and + * R.E. Bixby and D.K. Wagner "An almost linear-time algorithm for graph realization" + * for the column-wise algorithm. + * + * The main difficulty with detecting network matrices is that there may exist many pairs of a graph and a spanning tree + * that realize a matrix. The ambiguity of these graphs may be characterized in terms of \f$k\f$-separations on the + * graph associated to the network matrix. An edge partition \f$(E_1,E_2)\f$ is considered a \f$k\f$-separation if + * \f$|E_i|\geq k \f$ holds for \f$i\in\{1,2\}\f$. The ambiguity of network matrices is completely given by all + * the 1-separations and 2-separations of the associated graph(s). In particular, if the graph realizing a + * network matrix is 3-connected, then it is unique, up to inverting all edges of the graph. + * + * A 1-separation given by edge sets \f$(E_1,E_2)\f$, which is given by a singular node that is referred to as an + * articulation node, implies that no column of the network matrix contains edges in both \f$E_1\f$ and \f$E_2\f$. + * Remember that each edge in a realizing graph is associated with a row or column of the network matrix. Then, we have + * a 1-separation exactly when the network matrix contains two (or more) disconnected blocks, where the rows and columns + * of each block are contained in either \f$E_1\f$ or \f$E_2\f$. To obtain a graph realizing our network matrix, we can + * attach the graph realizing \f$E_2\f$ at any node of the graph realizing \f$E_1\f$. + * Thus, we store the graphs corresponding to the connected blocks of the network matrix separately. + * Each block has a 2-connected realization graph. + * + * If a graph \f$G\f$ realizing the network matrix has a 2-separation \f$(E_1,E_2)\f$ at vertices u and v, then we can + * obtain another graph representing the network matrix, by inverting the direction of all edges in \f$E_2\f$ and + * replacing u by v and vice-versa. One can also imagine + * adding a virtual edge \f$e'=\{u,v\}\f$ to both E_1 and E_2. In the trivial realization, we simply map the head of + * \f$e'\f$ in \f$E_1\f$ to the head of \f$e'\f$ in \f$E_2\f$ and remove \f$e'\f$ from both graphs. In the second + * realization we do the same thing, but first invert all the edges in \f$E_2\f$, including \f$e'\f$. + * An SPQR tree \f$\mathcal{T}=(\mathcal{V},\mathcal{E})\f$ is a tree data structure that represents the structure of + * all 2-separations in a 2-connected graph. Each member \f$\nu\in\mathcal{V}\f$ has an associated skeleton graph that + * has one of four different types: + * - (S) The member's skeleton graph is a cycle with at least 3 edges (also referred to as series or polygon) + * - (P) The member's skeleton graph consists of at least 3 parallel edges and 2 nodes (also referred to as bond) + * - (Q) The member's skeleton graph consists of at most 2 edges connecting two nodes (also referred to as loop) + * - (R) The member's skeleton graph is 3-connected and consists of at least 4 edges. + * + * An SPQR tree is considered minimal if it has no P-P or S-S connections. Each connected matrix has a unique minimal + * SPQR tree. Each edge \f$\{\nu,\mu\}\in\mathcal{E}\f$ defines a 2-separation of the underlying graph. In particular, + * each edge has one virtual edge in the member graph that it connects to the other member graph in the edge. + * + * We can obtain a realization of the graph underlying the network matrix by doing the following operations: + * 1. Permute the edges of each (S)-member arbitrarily + * 2. For each edge in \f$\mathcal{E}\f$, pick one of the two orientations of the virtual edges and merge the adjacent + * member graphs accordingly. + * + * In this way, all the graphs given by the network matrix are represented. In order to efficiently perform the merge + * of two member graphs, the member and node labels are given by union-find datastructures. Additionally, we also + * introduce a signed-union-find datastructure on the arcs of the graphs, so that we can efficiently invert the arcs + * of one side of a 2-separation. + * The 1-separations can be handled by storing an SPQR forest, with a (minimal) SPQR tree for every connected block + * of the network matrix. + * + * For adding a column to the network matrix, one can show that one can add a column only if the nonzeros of the column + * form a path with the correct signs in some graph represented by the network matrix. We solve the problem for each + * graph represented by the network matrix simultaneously by decomposing over the SPQR tree. First, we compute the path + * in each member. Then, we attempt to combine the paths by orienting the 2-separations so that the different member + * paths form a path in the represented graph. + * If some member has a set of edges that do not form a path, we can terminate. + * An important step is 'propagation'; when we are in a leaf node of the sub-SPQR tree containing path edges and the + * path in our leaf node forms a cycle with the virtual arc e connecting to the rest of the sub-tree, then we can + * remove the leaf node from the SPQR tree and mark the virtual arc f that is paired with e. + * After performing all such propagations, the sub-SPQR tree should form a path. Then, we merge these members into one, + * forming a single path. By adding the new column edge to the end nodes of this path, we form a new member of type R. + * Finally, we can easily join the paths of multiple SPQR trees using a series node to obtain the final path. + * + * The ideas for the row-wise algorithm have many parallels with the column-wise algorithm. One can add a row to a + * network matrix if and only if a node is 'splittable' with respect to a certain auxiliary graph formed by the nonzero + * columns indices of the row, for a graph represented by the network matrix. In particular, this auxiliary graph must + * be a directed bipartite graph; then, the arcs incident to the given node can be reassigned to two new nodes, so that + * the paths of the columns corresponding to the nonzeros of the row can be elongated to contain the new row, which is + * placed between the two new nodes. + * Similarly to the column-case, splittability of each graph represented by the network matrix can be computed at once + * by computing the splittability (and the corresponding bipartition) of every member graph. + * Similarly to the column algorithm, we can propagate; If a member is a leaf of the SPQR tree and both nodes of the + * 2-separation connecting it to the rest of graph are splittable, then we can remove the leaf from the reduced + * SPQR tree and mark the virtual edge connecting to the subtree. + * Finally, we are left with some minimal subtree with splittable vertices for each member graph. If we can merge all + * splittable vertices of the member graphs in the subtree into a single splittable vertex, then we perform this merge, + * and split this vertex. This yields us a new, larger node of type R (rigid). + * + * Implementation notes: + * 1. Quite a few algorithms used for network matrix detection are recursive in nature. However, recursive calls can + * cause stack overflows, particularly with large graphs. Quite frequently in the code, we need to allocate the + * call-data of these algorithms on the heap, instead, and use while loops to simulate the recursion. + * + * 2. In order to make the code fast in practice, a lot of emphasis is put on reusing allocated memory and avoiding + * allocations. In particular for the column-wise algorithm, even allocating and zeroing an array of size m+n for an + * m x n matrix can significantly slow down the code! + * + * 3. The graphs of the S, P and Q members do not need to be stored explicitly, as they always have the same structure. + * This also makes it easier to permute edges of S nodes on the fly. + */ + + /* TODO: fix tracking connectivity more cleanly, should not be left up to the algorithms ideally + */ + +#include + +#include "scip/misc.h" +#include "scip/pub_misc.h" +#include "scip/pub_network.h" +#include "scip/scip.h" +#include "blockmemshell/memory.h" + +/* types which define matrix sizes */ +typedef int spqr_matrix_size; +typedef spqr_matrix_size spqr_row; +typedef spqr_matrix_size spqr_col; + +#define SPQR_INVALID INT_MAX +#define SPQR_INVALID_ROW SPQR_INVALID +#define SPQR_INVALID_COL SPQR_INVALID + + +/* Only check in debug mode if the used indices are valid */ +#ifndef NDEBUG + +/** Determine if the row index is invalid */ +static +SCIP_Bool SPQRrowIsInvalid( + spqr_row row /**< The row to check */ + ) +{ + return row == SPQR_INVALID_ROW; +} + +/** Determine if the column index is invalid */ +static +SCIP_Bool SPQRcolIsInvalid( + spqr_col col /**< The column to check */ + ) +{ + return col == SPQR_INVALID_COL; +} + +/** Determine if the row index is valid */ +static +SCIP_Bool SPQRrowIsValid( + spqr_row row /**< The row to check */ + ) +{ + return !SPQRrowIsInvalid(row); +} + +/** Determine if the column index is valid */ +static +SCIP_Bool SPQRcolIsValid( + spqr_col col /**< The column to check */ + ) +{ + return !SPQRcolIsInvalid(col); +} + +#endif + +/** Columns 0..x correspond to elements 0..x and rows 0..y correspond to elements -1.. -y-1 */ +#define MARKER_ROW_ELEMENT (INT_MIN) +#define MARKER_COLUMN_ELEMENT (INT_MAX) +typedef int spqr_element; + +/** Checks if an element is a row */ +static +SCIP_Bool SPQRelementIsRow( + spqr_element element /**< The element to check */ + ) +{ + return element < 0; +} + +/** Checks if an element is a column */ +static +SCIP_Bool SPQRelementIsColumn( + spqr_element element /**< The element to check */ +) +{ + return !SPQRelementIsRow(element); +} + +/** Convert an element to the corresponding row index */ +static +spqr_row SPQRelementToRow( + spqr_element element /**< The element to convert */ +) +{ + assert(SPQRelementIsRow(element)); + return (spqr_row) ( -element - 1 ); +} + +/** Convert a row to the corresponding element */ +static +spqr_element SPQRrowToElement( + spqr_row row /**< The row to convert */ + ) +{ + assert(SPQRrowIsValid(row)); + return (spqr_element) -row - 1; +} + +/** Convert an element to the corresponding column index */ +static +spqr_col SPQRelementToColumn( + spqr_element element /**< The element to convert */ + ) +{ + assert(SPQRelementIsColumn(element)); + return (spqr_col) element; +} + +/** Convert a column to the corresponding element */ +static +spqr_element SPQRcolumnToElement( + spqr_col column /**< The column to convert */ + ) +{ + assert(SPQRcolIsValid(column)); + return (spqr_element) column; +} + +/** spqr_member is an index for the members of the SPQR decomposition + * + * The members are the nodes of the SPQR tree. + * Each member has an associated subgraph, sometimes called a skeleton. + * If two members are adjacent in the SPQR tree, the two corresponding subgraphs are connected by a 2-separation in any + * graph represented by the matrix. + * For members, we reserve all negative values as invalid. We use these negative values in union-find datastructure, + * where we store the rank of the representative as a negative number. + */ +typedef int spqr_member; +#define SPQR_INVALID_MEMBER (-1) + +/** Check if a member is invalid */ +static +SCIP_Bool SPQRmemberIsInvalid( + spqr_member member /**< The member to check */ + ) +{ + return member < 0; +} + +/** Check if a member is valid */ +static SCIP_Bool SPQRmemberIsValid( + spqr_member member /**< The member to check */ + ) +{ + return !SPQRmemberIsInvalid(member); +} + +/** spqr_node is an index for the nodes stored in the decomposition. + * + * The nodes are part of each member's skeleton. + * Similar to spqr_member, we reserve all negative values as invalid and use these in union-find. + */ +typedef int spqr_node; +#define SPQR_INVALID_NODE (-1) + +/** Check if a node is invalid */ +static +SCIP_Bool SPQRnodeIsInvalid( + spqr_node node /**< The node to check */ +) +{ + return node < 0; +} + +/** Check if a node is valid */ +static +SCIP_Bool SPQRnodeIsValid( + spqr_node node /**< The node to check */ +) +{ + return !SPQRnodeIsInvalid(node); +} + +/** spqr_arc is an index for the arcs stored in the decomposition. + * + * The arcs are part of each member's skeleton. + * Similar to spqr_node and spqr_member, we reserve all negative values as invalid and use these in some union-find. + * However, in contrast to spqr_node and spqr_member, the union-find data structure does not represent the arcs, + * but rather if all arcs that have the same representative we know they are in the same member. + */ +typedef int spqr_arc; +#define SPQR_INVALID_ARC (-1) + +/** Check if an arc is invalid */ +static +SCIP_Bool SPQRarcIsInvalid( + spqr_arc arc /**< The arc to check */ + ) +{ + return arc < 0; +} + +/** Check if an arc is valid */ +static +SCIP_Bool SPQRarcIsValid( + spqr_arc arc /**< The arc to check */ + ) +{ + return !SPQRarcIsInvalid(arc); +} + +/** The type of the member */ +typedef enum +{ + SPQR_MEMBERTYPE_RIGID = 0, /**< The member's skeleton is 3-connected and has at least 4 edges */ + SPQR_MEMBERTYPE_PARALLEL = 1, /**< The member's skeleton consists of 2 nodes with at least 3 edges */ + SPQR_MEMBERTYPE_SERIES = 2, /**< The member's skeleton is a cycle with at least 3 edges */ + SPQR_MEMBERTYPE_LOOP = 3, /**< The member's skeleton consists of 2 nodes connected by 1 or 2 edges */ + SPQR_MEMBERTYPE_UNASSIGNED = 4 /**< To indicate that the member is not a representative member anymore */ +} SPQRMemberType; + +/** Represents a single node of cyclic doubly-linked list of arc edges*/ +typedef struct +{ + spqr_arc previous; + spqr_arc next; +} SPQRNetworkDecompositionArcListNode; + +/** This structure stores the relevant data for a single node. */ +typedef struct +{ + spqr_node representativeNode; /**< Points to the next node in the union-find data structure. + * Stores the rank as a negative number if this node represents itself */ + spqr_arc firstArc; /**< Points to the head node of the cyclic doubly linked list containing + * the arcs that are adjacent to this node. */ + int numArcs; /**< The number of arcs adjacent to this node */ +} SPQRNetworkDecompositionNode; + +/** Structure that stores the relevant data for a single arc. */ +typedef struct +{ + spqr_node head; /**< The head node of the arc */ + spqr_node tail; /**< The tail node of the arc */ + spqr_member member; /**< The member that contains the arc */ + spqr_member childMember; /**< Stores the child member, if this arc points to one */ + SPQRNetworkDecompositionArcListNode headArcListNode; /**< Linked-list node for iterating over the head's arcs */ + SPQRNetworkDecompositionArcListNode tailArcListNode; /**< Linked-list node for iterating over the tail's arcs */ + SPQRNetworkDecompositionArcListNode arcListNode; /**< Linked-list node for iterating over the member's arcs */ + + spqr_element element; /**< The element associated to this arc */ + + /** @name Signed union-find for arc directions. + * @{ + * + * If an arc is reversed, it's head becomes its tail and vice-versa. + * For non-rigid members every arc is it's own representative, and the direction is simply given by the boolean. + * For rigid members, every arc is represented by another arc in the member, + * and the direction can be found by multiplying the signs along the union-find path + * We use this data structure to efficiently reverse all arcs in a skeleton. + */ + spqr_arc representative; /**< The representative of the arc */ + SCIP_Bool reversed; /**< Whether the arc's head and tail are reversed, or not */ + /** @} */ +} SPQRNetworkDecompositionArc; + +/** Structure that stores the relevant data for a single member */ +typedef struct +{ + spqr_member representativeMember; /**< The representative of this member (union-find) */ + SPQRMemberType type; /**< The type of this member */ + + /** @name The SPQR tree is stored as an arborescence. + * @{ + * + * Each member stores its parents, and each edge of the member + * pointing to a child member stores the associated member in childMember + */ + spqr_member parentMember; /**< The parent of this member in the arborescence */ + spqr_arc markerToParent; /**< The arc pointing to the parent */ + spqr_arc markerOfParent; /**< The arc of the parent pointing to this member */ + /** @} */ + + spqr_arc firstArc; /**< First arc of the linked list containing the member's arcs */ + int numArcs; /**< The number of arcs associated to the member */ +} SPQRNetworkDecompositionMember; + +/** Stores the SPQR forest data structure and its relevant data */ +typedef struct +{ + int numArcs; /**< The number of slots used in the arc data array */ + int memArcs; /**< The amount of space allocated in the arc data array */ + SPQRNetworkDecompositionArc* arcs; /**< Array of arcs of the SPQR forest, indexed by spqr_arc */ + spqr_arc firstFreeArc; /**< Points to the first unused slot in the arcs array */ + + int memMembers; /**< The amount of space allocated in the member data array */ + int numMembers; /**< The number of slots used in the member data array */ + SPQRNetworkDecompositionMember* members; /**< Array of members of the SPQR forest. Indexed by spqr_member */ + + int memNodes; /**< The amount of space allocated in the node data array */ + int numNodes; /**< The number of slots used in the node data array */ + SPQRNetworkDecompositionNode* nodes; /**< Array of nodes of the SPQR forest. Indexed by spqr_node */ + + int memRows; /**< The (maximal) number of rows of the matrix */ + spqr_arc* rowArcs; /**< Maps the rows of the matrix to arcs in the decomposition */ + + int memColumns; /**< The (maximal) number of columns of the matrix */ + spqr_arc* columnArcs; /**< Maps the columns of the matrix to arcs in the decomposition */ + + BMS_BLKMEM * env; /**< used memory allocator */ + + int numConnectedComponents; /**< The number of disjoint SPQR trees in the SPQR forest */ +} SCIP_NETMATDECDATA; + + +#ifndef NDEBUG + +/** Check if a node is a representative in the union-find data structure for nodes */ +static +SCIP_Bool nodeIsRepresentative( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_node node /**< The node to check if it is representative */ +) +{ + assert(dec); + assert(node < dec->memNodes); + assert(SPQRnodeIsValid(node)); + + return SPQRnodeIsInvalid(dec->nodes[node].representativeNode); +} + +#endif + +/** Find the node its representative node in the union-find data structure */ +static +spqr_node findNode( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_node node /**< The node to find the representative for */ +) +{ + assert(dec); + assert(SPQRnodeIsValid(node)); + assert(node < dec->memNodes); + + spqr_node current = node; + spqr_node next; + + //traverse down tree to find the root + while( SPQRnodeIsValid(next = dec->nodes[current].representativeNode) ) + { + current = next; + assert(current < dec->memNodes); + } + + spqr_node root = current; + current = node; + + //update all pointers along path to point to root, flattening the tree + while( SPQRnodeIsValid(next = dec->nodes[current].representativeNode) ) + { + dec->nodes[current].representativeNode = root; + current = next; + assert(current < dec->memNodes); + } + return root; +} + +/** Find the node its representative node in the union-find data structure, without compressing the union-find tree. + * + * Should only be used for debugging or asserts. + */ +static +spqr_node findNodeNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_node node /**< The node to find the representative for */ +) +{ + assert(dec); + assert(SPQRnodeIsValid(node)); + assert(node < dec->memNodes); + + spqr_node current = node; + spqr_node next; + + //traverse down tree to find the root + while( SPQRnodeIsValid(next = dec->nodes[current].representativeNode) ) + { + current = next; + assert(current < dec->memNodes); + } + spqr_node root = current; + return root; +} + +/** Find the arc's tail node in the union find data structure of the nodes. + * + * Updates the arc's tail to point to the representative for faster future queries. + */ +static +spqr_node findArcTail( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc whose tail we want to find */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + spqr_node representative = findNode(dec, dec->arcs[arc].tail); + dec->arcs[arc].tail = representative;//update the arc information + + return representative; +} + +/** Find the arc's head node in the union find data structure of the nodes. + * + * Updates the arc's head to point to the representative for faster future queries. + */ +static +spqr_node findArcHead( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc whose head we want to find */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + spqr_node representative = findNode(dec, dec->arcs[arc].head); + dec->arcs[arc].head = representative;//update the arc information + + return representative; +} + +/** Find the arc's head node in the union find data structure of the nodes, without compressing the union-find tree. + * + * Should only be used in debugging statements or asserts. + */ +static +spqr_node findArcHeadNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc whose head we want to find */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + spqr_node representative = findNodeNoCompression(dec, dec->arcs[arc].head); + return representative; +} + +/** Find the arc's tail node in the union find data structure of the nodes, without compressing the union-find tree. + * + * Should only be used in debugging statements or asserts. + */ +static +spqr_node findArcTailNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc whose tail we want to find */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + spqr_node representative = findNodeNoCompression(dec, dec->arcs[arc].tail); + return representative; +} + +/** Find the first arc in the list of arcs that are adjacent to the given node. + * + * These arcs form a cyclic linked-list. + */ +static +spqr_arc getFirstNodeArc( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_node node /**< The node to find the arc for */ +) +{ + assert(dec); + assert(SPQRnodeIsValid(node)); + assert(node < dec->memNodes); + + return dec->nodes[node].firstArc; +} + +/** Given the current arc adjacent to this node, find the next arc in the cyclic linked list of adjacent arcs to the + * given node. + * + * This function does not compress the union-find tree, and should only be used in debugging or asserts. + */ +static +spqr_arc getNextNodeArcNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc, /**< The current arc that is adjacent to this node */ + spqr_node node /**< The node to which the arc is adjacent */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + assert(nodeIsRepresentative(dec, node)); + + if( findArcHeadNoCompression(dec, arc) == node ) + { + arc = dec->arcs[arc].headArcListNode.next; + } + else + { + assert(findArcTailNoCompression(dec, arc) == node); + arc = dec->arcs[arc].tailArcListNode.next; + } + return arc; +} + +/** Given the current arc adjacent to this node, find the next arc in the cyclic linked list of adjacent arcs to the + * given node. + */ +static +spqr_arc getNextNodeArc( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc, /**< The current arc that is adjacent to this node */ + spqr_node node /**< The node to which the arc is adjacent */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + assert(nodeIsRepresentative(dec, node)); + + if( findArcHead(dec, arc) == node ) + { + arc = dec->arcs[arc].headArcListNode.next; + } + else + { + assert(findArcTailNoCompression(dec, arc) == node); + dec->arcs[arc].tail = node; //This assignment is not necessary but speeds up future queries. + arc = dec->arcs[arc].tailArcListNode.next; + } + return arc; +} + +/** Given the current arc adjacent to this node, find the previous arc in the cyclic linked list of adjacent arcs + * to the given node. + */ +static +spqr_arc getPreviousNodeArc( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc, /**< The current arc that is adjacent to this node */ + spqr_node node /**< The node to which the arc is adjacent */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + assert(nodeIsRepresentative(dec, node)); + + if( findArcHead(dec, arc) == node ) + { + arc = dec->arcs[arc].headArcListNode.previous; + } + else + { + assert(findArcTailNoCompression(dec, arc) == node); + dec->arcs[arc].tail = node;//This assignment is not necessary but speeds up future queries. + arc = dec->arcs[arc].tailArcListNode.previous; + } + return arc; +} + +/** Update the cyclic node-arc incidence data structure to move all arcs adjacent to one node to another node. + * + * We typically call this when two nodes are identified with one another, and we need to merge their adjacent arcs. + */ +static +void mergeNodeArcList( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_node toMergeInto, /**< The node that we want to give all the arcs of both nodes */ + spqr_node toRemove /**< The node whose arcs we want to remove */ +) +{ + spqr_arc firstIntoArc = getFirstNodeArc(dec, toMergeInto); + spqr_arc firstFromArc = getFirstNodeArc(dec, toRemove); + if( SPQRarcIsInvalid(firstIntoArc)) + { + //new node has no arcs + dec->nodes[toMergeInto].numArcs += dec->nodes[toRemove].numArcs; + dec->nodes[toRemove].numArcs = 0; + + dec->nodes[toMergeInto].firstArc = dec->nodes[toRemove].firstArc; + dec->nodes[toRemove].firstArc = SPQR_INVALID_ARC; + + return; + } + if( SPQRarcIsInvalid(firstFromArc)) + { + //Old node has no arcs; we can just return + return; + } + + spqr_arc lastIntoArc = getPreviousNodeArc(dec, firstIntoArc, toMergeInto); + assert(SPQRarcIsValid(lastIntoArc)); + spqr_arc lastFromArc = getPreviousNodeArc(dec, firstFromArc, toRemove); + assert(SPQRarcIsValid(lastFromArc)); + + + SPQRNetworkDecompositionArcListNode* firstIntoNode = + findArcHead(dec, firstIntoArc) == toMergeInto ? &dec->arcs[firstIntoArc].headArcListNode + : &dec->arcs[firstIntoArc].tailArcListNode; + SPQRNetworkDecompositionArcListNode* lastIntoNode = + findArcHead(dec, lastIntoArc) == toMergeInto ? &dec->arcs[lastIntoArc].headArcListNode + : &dec->arcs[lastIntoArc].tailArcListNode; + + SPQRNetworkDecompositionArcListNode* firstFromNode = + findArcHead(dec, firstFromArc) == toRemove ? &dec->arcs[firstFromArc].headArcListNode + : &dec->arcs[firstFromArc].tailArcListNode; + SPQRNetworkDecompositionArcListNode* lastFromNode = + findArcHead(dec, lastFromArc) == toRemove ? &dec->arcs[lastFromArc].headArcListNode + : &dec->arcs[lastFromArc].tailArcListNode; + + firstIntoNode->previous = lastFromArc; + lastIntoNode->next = firstFromArc; + firstFromNode->previous = lastIntoArc; + lastFromNode->next = firstIntoArc; + + dec->nodes[toMergeInto].numArcs += dec->nodes[toRemove].numArcs; + dec->nodes[toRemove].numArcs = 0; + dec->nodes[toRemove].firstArc = SPQR_INVALID_ARC; +} + +/** Flips the direction a given arc. */ +static +void arcFlipReversed( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc that we want to flip */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + dec->arcs[arc].reversed = !dec->arcs[arc].reversed; +} + +/** Sets the direction of a given arc */ +static +void arcSetReversed( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc, /**< The given arc */ + SCIP_Bool reversed /**< Are the head and tail reversed? */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + dec->arcs[arc].reversed = reversed; +} + + +/** Sets the representative of a given arc. + * + * The arcs reversed field is given with respect to the representative. + * In particular, whether an arc is reversed or not is determined by the sign of the path in the signed union-find + * data structure for arcs, which can be computed by multiplying the signs of the individual edges (xor over bools). + */ +static +void arcSetRepresentative( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc, /**< The given arc */ + spqr_arc representative /**< The representative to set the arc to */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + assert(representative == SPQR_INVALID_ARC || SPQRarcIsValid(representative)); + + dec->arcs[arc].representative = representative; +} + +/** Merge two representative nodes (Union operation) in the union-find data structure for nodes. + * + * Returns the id of the node that becomes representative for both. + */ +static +spqr_node mergeNodes( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_node first, /**< A node to merge */ + spqr_node second /**< A second node to merge */ +) +{ + assert(dec); + assert(nodeIsRepresentative(dec, first)); + assert(nodeIsRepresentative(dec, second)); + assert(first != second);//We cannot merge a node into itself + assert(first < dec->memNodes); + assert(second < dec->memNodes); + + //The rank is stored as a negative number: we decrement it making the negative number larger. + //We want the new root to be the one with 'largest' rank, so smallest number. If they are equal, we decrement. + spqr_node firstRank = dec->nodes[first].representativeNode; + spqr_node secondRank = dec->nodes[second].representativeNode; + if( firstRank > secondRank ) + { + SCIPswapInts(&first, &second); + } + //first becomes representative; we merge all of the arcs of second into first + mergeNodeArcList(dec, first, second); + dec->nodes[second].representativeNode = first; + if( firstRank == secondRank ) + { + --dec->nodes[first].representativeNode; + } + return first; +} + +/** Check if a member is a representative in the union-find data structure for members. */ +static +SCIP_Bool memberIsRepresentative( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_member member /**< The member to check */ +) +{ + assert(dec); + assert(member < dec->memMembers); + assert(SPQRmemberIsValid(member)); + + return SPQRmemberIsInvalid(dec->members[member].representativeMember); +} + +/** Find the member its representative member in the union-find data structure */ +static +spqr_member findMember( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_member member /**< The member to find the representative for */ +) +{ + assert(dec); + assert(member < dec->memMembers); + assert(SPQRmemberIsValid(member)); + + spqr_member current = member; + spqr_member next; + + //traverse down tree to find the root + while( SPQRmemberIsValid(next = dec->members[current].representativeMember) ) + { + current = next; + assert(current < dec->memMembers); + } + + spqr_member root = current; + current = member; + + //update all pointers along path to point to root, flattening the tree + while( SPQRmemberIsValid(next = dec->members[current].representativeMember) ) + { + dec->members[current].representativeMember = root; + current = next; + assert(current < dec->memMembers); + } + return root; +} + +/** Find the member's representative member in the union-find data structure, without compressing the union-find tree. + * + * Should only be used for debugging or asserts. + */ +static +spqr_member findMemberNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_member member /**< The member to find the representative for */ +) +{ + assert(dec); + assert(member < dec->memMembers); + assert(SPQRmemberIsValid(member)); + + spqr_member current = member; + spqr_member next; + + //traverse down tree to find the root + while( SPQRmemberIsValid(next = dec->members[current].representativeMember) ) + { + current = next; + assert(current < dec->memMembers); + } + + spqr_member root = current; + return root; +} + +/** Merge two representative members (Union operation) in the union-find data structure. + * + * Returns the id of the member that becomes representative for both. + */ +static +spqr_member mergeMembers( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_member first, /**< The first member to merge */ + spqr_member second /**< The second member to merge */ +) +{ + assert(dec); + assert(memberIsRepresentative(dec, first)); + assert(memberIsRepresentative(dec, second)); + assert(first != second);//We cannot merge a member into itself + assert(first < dec->memMembers); + assert(second < dec->memMembers); + + //The rank is stored as a negative number: we decrement it making the negative number larger. + // We want the new root to be the one with 'largest' rank, so smallest number. If they are equal, we decrement. + spqr_member firstRank = dec->members[first].representativeMember; + spqr_member secondRank = dec->members[second].representativeMember; + if( firstRank > secondRank ) + { + SCIPswapInts(&first, &second); + } + dec->members[second].representativeMember = first; + if( firstRank == secondRank ) + { + --dec->members[first].representativeMember; + } + return first; +} + +/** Finds the member in which the arc is located */ +static +spqr_member findArcMember( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the member for */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + spqr_member representative = findMember(dec, dec->arcs[arc].member); + dec->arcs[arc].member = representative; + return representative; +} + +/** Finds the member in which the arc is located, without compressing the member union-find tree */ +static +spqr_member findArcMemberNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the member for */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + spqr_member representative = findMemberNoCompression(dec, dec->arcs[arc].member); + return representative; +} + +/** Find the representative parent member of the given member. + * + * Note the given member must be representative. + */ +static +spqr_member findMemberParent( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_member member /**< The member to find the parent for. Must be representative. */ +) +{ + assert(dec); + assert(member < dec->memMembers); + assert(SPQRmemberIsValid(member)); + assert(memberIsRepresentative(dec, member)); + + if( SPQRmemberIsInvalid(dec->members[member].parentMember)) + { + return dec->members[member].parentMember; + } + spqr_member parent_representative = findMember(dec, dec->members[member].parentMember); + dec->members[member].parentMember = parent_representative; + + return parent_representative; +} + +/** Find the representative parent member of the given member. + * + * Note the given member must be representative. + * This version does not perform compression of the union-find tree, and should only be used in debug or asserts. + */ +static +spqr_member findMemberParentNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_member member /**< The member to find the parent for. Must be representative. */ +) +{ + assert(dec); + assert(member < dec->memMembers); + assert(SPQRmemberIsValid(member)); + assert(memberIsRepresentative(dec, member)); + + if( SPQRmemberIsInvalid(dec->members[member].parentMember)) + { + return dec->members[member].parentMember; + } + spqr_member parent_representative = findMemberNoCompression(dec, dec->members[member].parentMember); + return parent_representative; +} + +/** Find the child member associated to the given arc, which must be virtual. */ +static +spqr_member findArcChildMember( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the child member for */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + spqr_member representative = findMember(dec, dec->arcs[arc].childMember); + dec->arcs[arc].childMember = representative; + return representative; +} + +/** Find the child member associated to the given virtual arc. + * + * This version does not compress the union-find tree and should only be used for debugging and asserts. + */ +static +spqr_member findArcChildMemberNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the child member for */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + spqr_member representative = findMemberNoCompression(dec, dec->arcs[arc].childMember); + return representative; +} + +/** Checks if the arc has a child member */ +static +SCIP_Bool arcIsChildMarker( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to check */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + return SPQRmemberIsValid(dec->arcs[arc].childMember); +} + +/** Check whether the given arc is a tree arc or not, i.e. whether it belongs to a (virtual) row or column or not */ +static +SCIP_Bool arcIsTree( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to check */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + return SPQRelementIsRow(dec->arcs[arc].element); +} + +/** Output data structure that stores both the arc's sign and representative */ +typedef struct +{ + spqr_arc representative; + SCIP_Bool reversed; +} ArcSign; + +#ifndef NDEBUG + +/** Check if an arc is a representative in the signed union-find data structure for arc directions. + * + * In each member, exactly one arc is the representative. + */ +static +SCIP_Bool arcIsRepresentative( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to check */ +) +{ + assert(dec); + assert(arc < dec->memArcs); + assert(SPQRarcIsValid(arc)); + + return SPQRarcIsInvalid(dec->arcs[arc].representative); +} + +#endif + +/** Find an arcs representative and its direction in the signed union-find data structure for arcs. */ +static +ArcSign findArcSign( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the representative and direction for */ +) +{ + assert(dec); + assert(arc < dec->memArcs); + assert(SPQRarcIsValid(arc)); + + spqr_arc current = arc; + spqr_arc next; + + SCIP_Bool totalReversed = dec->arcs[current].reversed; + //traverse down tree to find the root + while( SPQRarcIsValid(next = dec->arcs[current].representative) ) + { + current = next; + assert(current < dec->memArcs); + //swap boolean only if new arc is reversed + totalReversed = ( totalReversed != dec->arcs[current].reversed ); + } + + spqr_arc root = current; + current = arc; + + SCIP_Bool currentReversed = totalReversed != dec->arcs[root].reversed; + //update all pointers along path to point to root, flattening the tree + + while( SPQRarcIsValid(next = dec->arcs[current].representative) ) + { + SCIP_Bool wasReversed = dec->arcs[current].reversed; + + dec->arcs[current].reversed = currentReversed; + currentReversed = ( currentReversed != wasReversed ); + + dec->arcs[current].representative = root; + current = next; + assert(current < dec->memArcs); + } + + ArcSign sign; + sign.reversed = totalReversed; + sign.representative = root; + return sign; +} + +/** Find an arcs representative and its direction in the signed union-find data structure for arcs. + * + * This version does not compress the union-find tree and should only be used in debug and asserts. + */ +static +ArcSign findArcSignNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the representative and direction for */ +) +{ + assert(dec); + assert(arc < dec->memArcs); + assert(SPQRarcIsValid(arc)); + + spqr_arc current = arc; + spqr_arc next; + + SCIP_Bool totalReversed = dec->arcs[current].reversed; + //traverse down tree to find the root + while( SPQRarcIsValid(next = dec->arcs[current].representative)) + { + current = next; + assert(current < dec->memArcs); + //swap boolean only if new arc is reversed + totalReversed = ( totalReversed != dec->arcs[current].reversed ); + } + ArcSign sign; + sign.reversed = totalReversed; + sign.representative = current; + return sign; +} + +/** Finds the arcs head, taking into account whether it is reversed by the signed union-find data structure. */ +static +spqr_node findEffectiveArcHead( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the head for */ +) +{ + assert(dec); + if( findArcSign(dec, arc).reversed ) + { + return findArcTail(dec, arc); + } + else + { + return findArcHead(dec, arc); + } +} + +/** Finds the arcs tail, taking into account whether it is reversed by the signed union-find data structure. */ +static +spqr_node findEffectiveArcTail( + SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the tail for */ +) +{ + assert(dec); + if( findArcSign(dec, arc).reversed ) + { + return findArcHead(dec, arc); + } + else + { + return findArcTail(dec, arc); + } +} + +/** Finds the arcs head, taking into account whether it is reversed by the signed union-find data structure. + * + * This version does not compress the union-find tree and should only be used in debug and asserts. + */ +static +spqr_node findEffectiveArcHeadNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the head for */ +) +{ + assert(dec); + + if( findArcSignNoCompression(dec, arc).reversed ) + { + return findArcTailNoCompression(dec, arc); + } + else + { + return findArcHeadNoCompression(dec, arc); + } +} + +/** Finds the arcs tail, taking into account whether it is reversed by the signed union-find data structure. + * + * This version does not compress the union-find tree and should only be used in debug and asserts. + */ +static +spqr_node findEffectiveArcTailNoCompression( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to find the tail for */ +) +{ + assert(dec); + if( findArcSignNoCompression(dec, arc).reversed ) + { + return findArcHeadNoCompression(dec, arc); + } + else + { + return findArcTailNoCompression(dec, arc); + } +} + +/** Merges the sign union-find structures for two arc sets. + * + * If reflectRelative is set to true then all arcs of the represented by the second arc are reversed + * w.r.t. their current orientation. Otherwise, all arcs keep the same + * reversed status with respect to the root node of the union find tree. + */ +static +spqr_arc mergeArcSigns( + SCIP_NETMATDECDATA* dec, /**< The decomposition data structure */ + spqr_arc first, /**< Representative arc of the first arc set */ + spqr_arc second, /**< Representative arc of the second arc set */ + SCIP_Bool reflectRelative /**< Should all arcs in the second arc set be reversed?*/ +) +{ + assert(dec); + assert(arcIsRepresentative(dec, first)); + assert(arcIsRepresentative(dec, second)); + assert(first != second);//We cannot merge a member into itself + assert(first < dec->memArcs); + assert(second < dec->memArcs); + + //The rank is stored as a negative number: we decrement it making the negative number larger. + //We want the new root to be the one with 'largest' rank, so smallest number. If they are equal, we decrement. + spqr_member firstRank = dec->arcs[first].representative; + spqr_member secondRank = dec->arcs[second].representative; + + if( firstRank > secondRank ) + { + SCIPswapInts(&first, &second); + } + dec->arcs[second].representative = first; + if( firstRank == secondRank ) + { + --dec->arcs[first].representative; + } + //These boolean formula's cover all 16 possible cases, such that the relative orientation of the first is not changed + //but the relative orientation of the second is changed based on whether we want to reflect. + SCIP_Bool equal = dec->arcs[first].reversed == dec->arcs[second].reversed; + dec->arcs[second].reversed = ( equal == reflectRelative ); + if( firstRank > secondRank ) + { + dec->arcs[first].reversed = ( dec->arcs[first].reversed != reflectRelative ); + } + return first; +} + +/** Checks whether an arc is reversed for arcs in non-rigid members. + * + * For non-rigid members, we do not use the union-find datastructure, as we can get away without it. + */ +static +SCIP_Bool arcIsReversedNonRigid( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to check */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + return dec->arcs[arc].reversed; +} + +/** Gets the element (row/column) associated to the given arc. */ +static +spqr_element arcGetElement( + const SCIP_NETMATDECDATA* dec, /**< The network decomposition */ + spqr_arc arc /**< The arc to get the element for */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + return dec->arcs[arc].element; +} + +/** Check whether the network matrix decomposition contains an edge for the given row index. */ +static +SCIP_Bool netMatDecDataContainsRow( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + int row /**< The row index that is checked */ +) +{ + assert(SPQRrowIsValid(row) && row < dec->memRows); + assert(dec); + + return SPQRarcIsValid(dec->rowArcs[row]); +} + +/** Check whether the network matrix decomposition contains an edge for the given column index. */ +static +SCIP_Bool netMatDecDataContainsColumn( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + int column /**< The column index that is checked */ +) +{ + assert(SPQRcolIsValid(column) && column < dec->memColumns); + assert(dec); + + return SPQRarcIsValid(dec->columnArcs[column]); +} + +/** Associate the given arc to the given column in the network matrix decomposition. */ +static +void setDecompositionColumnArc( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_col col, /**< The column to associate */ + spqr_arc arc /**< The arc to associate to the column */ +) +{ + assert(SPQRcolIsValid(col) && col < dec->memColumns); + assert(dec); + assert(SPQRarcIsValid(arc)); + + dec->columnArcs[col] = arc; +} + +/** Associate the given arc to the given row in the network matrix decomposition. */ +static +void setDecompositionRowArc( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_row row, /**< The row to associate */ + spqr_arc arc /**< The arc to associate to the row */ +) +{ + assert(SPQRrowIsValid(row) && row < dec->memRows); + assert(dec); + assert(SPQRarcIsValid(arc)); + + dec->rowArcs[row] = arc; +} + +/** Get the decomposition arc associated to the given column. */ +static +spqr_arc getDecompositionColumnArc( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_col col /**< The given column */ +) +{ + assert(SPQRcolIsValid(col) && col < dec->memColumns); + assert(dec); + + return dec->columnArcs[col]; +} + +/** Get the decomposition arc associated to the given row. */ +static +spqr_arc getDecompositionRowArc( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_row row /**< The given row */ +) +{ + assert(SPQRrowIsValid(row) && row < dec->memRows); + assert(dec); + + return dec->rowArcs[row]; +} + +/** Initialize the network matrix decomposition data structure. */ +static +SCIP_RETCODE netMatDecDataCreate( + BMS_BLKMEM* blkmem, /**< Block memory */ + SCIP_NETMATDECDATA** pdec, /**< buffer to store pointer to created decomposition */ + int nrows, /**< The maximal number of rows that the decomposition can expect */ + int ncols /**< The maximal number of columns that the decomposition can expect */ +) +{ + assert(blkmem); + assert(pdec); + assert(!*pdec); + + SCIP_ALLOC( BMSallocBlockMemory(blkmem, pdec) ); + SCIP_NETMATDECDATA* dec = *pdec; + dec->env = blkmem; + + //Initialize arc array data + int initialMemArcs = 8; + { + assert(initialMemArcs > 0); + dec->memArcs = initialMemArcs; + dec->numArcs = 0; + SCIP_ALLOC( BMSallocBlockMemoryArray(blkmem, &dec->arcs, dec->memArcs) ); + for( spqr_arc i = 0; i < dec->memArcs; ++i ) + { + dec->arcs[i].arcListNode.next = i + 1; + dec->arcs[i].member = SPQR_INVALID_MEMBER; + } + dec->arcs[dec->memArcs - 1].arcListNode.next = SPQR_INVALID_ARC; + dec->firstFreeArc = 0; + } + + //Initialize member array data + int initialMemMembers = 8; + { + assert(initialMemMembers > 0); + dec->memMembers = initialMemMembers; + dec->numMembers = 0; + SCIP_ALLOC( BMSallocBlockMemoryArray(blkmem, &dec->members, dec->memMembers) ); + } + + //Initialize node array data + int initialMemNodes = 8; + { + assert(initialMemNodes > 0); + dec->memNodes = initialMemNodes; + dec->numNodes = 0; + SCIP_ALLOC( BMSallocBlockMemoryArray(blkmem, &dec->nodes, dec->memNodes) ); + } + + //Initialize mappings for rows + { + dec->memRows = nrows; + SCIP_ALLOC( BMSallocBlockMemoryArray(blkmem, &dec->rowArcs, dec->memRows) ); + for( int i = 0; i < dec->memRows; ++i ) + { + dec->rowArcs[i] = SPQR_INVALID_ARC; + } + } + //Initialize mappings for columns + { + dec->memColumns = ncols; + SCIP_ALLOC( BMSallocBlockMemoryArray(blkmem, &dec->columnArcs, dec->memColumns) ); + for( int i = 0; i < dec->memColumns; ++i ) + { + dec->columnArcs[i] = SPQR_INVALID_ARC; + } + } + + dec->numConnectedComponents = 0; + return SCIP_OKAY; +} + +/** Free the network matrix decomposition data structure */ +static +void netMatDecDataFree( + SCIP_NETMATDECDATA** pdec /**< pointer to the network matrix decomposition to freed */ +) +{ + assert(pdec); + assert(*pdec); + + SCIP_NETMATDECDATA* dec = *pdec; + BMSfreeBlockMemoryArray(dec->env, &dec->columnArcs, dec->memColumns); + BMSfreeBlockMemoryArray(dec->env, &dec->rowArcs, dec->memRows); + BMSfreeBlockMemoryArray(dec->env, &dec->nodes, dec->memNodes); + BMSfreeBlockMemoryArray(dec->env, &dec->members, dec->memMembers); + BMSfreeBlockMemoryArray(dec->env, &dec->arcs, dec->memArcs); + + BMSfreeBlockMemory(dec->env, pdec); +} + +/** Get the first arc of the linked list of arcs contained in the given member. */ +static +spqr_arc getFirstMemberArc( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member /**< The given member */ +) +{ + assert(dec); + assert(SPQRmemberIsValid(member)); + assert(member < dec->memMembers); + + return dec->members[member].firstArc; +} + +/** Given the current arc, get the next arc of the linked list of arcs that are in the same member. */ +static +spqr_arc getNextMemberArc( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc /**< The current arc */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + arc = dec->arcs[arc].arcListNode.next; + return arc; +} + +/** Given the current arc, get the previous arc of the linked list of arcs that are in the same member. */ +static +spqr_arc getPreviousMemberArc( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc /**< The current arc */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + + arc = dec->arcs[arc].arcListNode.previous; + return arc; +} + +/** Adds an arc to the linked list of arcs of the given member. */ +static +void addArcToMemberArcList( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The arc to add */ + spqr_member member /**< The member to add the arc to */ +) +{ + spqr_arc firstMemberArc = getFirstMemberArc(dec, member); + + if( SPQRarcIsValid(firstMemberArc)) + { + spqr_arc lastMemberArc = getPreviousMemberArc(dec, firstMemberArc); + dec->arcs[arc].arcListNode.next = firstMemberArc; + dec->arcs[arc].arcListNode.previous = lastMemberArc; + dec->arcs[firstMemberArc].arcListNode.previous = arc; + dec->arcs[lastMemberArc].arcListNode.next = arc; + } + else + { + assert(dec->members[member].numArcs == 0); + dec->arcs[arc].arcListNode.next = arc; + dec->arcs[arc].arcListNode.previous = arc; + } + dec->members[member].firstArc = arc; + ++( dec->members[member].numArcs ); +} + +/** Create a new arc in the network matrix decomposition. */ +static +SCIP_RETCODE createArc( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member, /**< The member that contains the arc */ + SCIP_Bool reversed, /**< Is the arc reversed or not? */ + spqr_arc* pArc /**< out-pointer to the id that is assigned to the arc. */ +) +{ + assert(dec); + assert(pArc); + assert(SPQRmemberIsInvalid(member) || memberIsRepresentative(dec, member)); + + spqr_arc idx = dec->firstFreeArc; + if( SPQRarcIsValid(idx)) + { + dec->firstFreeArc = dec->arcs[idx].arcListNode.next; + } + else + { + //Enlarge array, no free nodes in arc list + int newSize = 2 * dec->memArcs; + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &dec->arcs, dec->memArcs, newSize) ); + for( int i = dec->memArcs + 1; i < newSize; ++i ) + { + dec->arcs[i].arcListNode.next = i + 1; + dec->arcs[i].member = SPQR_INVALID_MEMBER; + } + dec->arcs[newSize - 1].arcListNode.next = SPQR_INVALID_ARC; + dec->firstFreeArc = dec->memArcs + 1; + idx = dec->memArcs; + dec->memArcs = newSize; + } + + dec->arcs[idx].tail = SPQR_INVALID_NODE; + dec->arcs[idx].head = SPQR_INVALID_NODE; + dec->arcs[idx].member = member; + dec->arcs[idx].childMember = SPQR_INVALID_MEMBER; + dec->arcs[idx].reversed = reversed; + + dec->arcs[idx].headArcListNode.next = SPQR_INVALID_ARC; + dec->arcs[idx].headArcListNode.previous = SPQR_INVALID_ARC; + dec->arcs[idx].tailArcListNode.next = SPQR_INVALID_ARC; + dec->arcs[idx].tailArcListNode.previous = SPQR_INVALID_ARC; + + dec->numArcs++; + + *pArc = idx; + + return SCIP_OKAY; +} + +/** Create a new arc in the network matrix decomposition that is associated to the given row. */ +static +SCIP_RETCODE createRowArc( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member, /**< The member that contains the arc */ + spqr_arc* pArc, /**< out-pointer to the id that is assigned to the arc. */ + spqr_row row, /**< The row associated to the arc */ + SCIP_Bool reversed /**< Is the arc reversed or not? */ +) +{ + SCIP_CALL( createArc(dec, member, reversed, pArc) ); + setDecompositionRowArc(dec, row, *pArc); + addArcToMemberArcList(dec, *pArc, member); + dec->arcs[*pArc].element = SPQRrowToElement(row); + + return SCIP_OKAY; +} + +/** Create a new arc in the network matrix decomposition that is associated to the given column. */ +static +SCIP_RETCODE createColumnArc( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member, /**< The member that contains the arc */ + spqr_arc* pArc, /**< out-pointer to the id that is assigned to the arc. */ + spqr_col column, /**< The column associated to the arc */ + SCIP_Bool reversed /**< Is the arc reversed or not? */ +) +{ + SCIP_CALL( createArc(dec, member, reversed, pArc) ); + setDecompositionColumnArc(dec, column, *pArc); + addArcToMemberArcList(dec, *pArc, member); + dec->arcs[*pArc].element = SPQRcolumnToElement(column); + + return SCIP_OKAY; +} + +/** Create a new member in the network matrix decomposition. */ +static +SCIP_RETCODE createMember( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SPQRMemberType type, /**< The SPQR-type of the member */ + spqr_member* pMember /**< out-pointer to the id that is assigned to the member */ +) +{ + assert(dec); + assert(pMember); + + if( dec->numMembers == dec->memMembers ) + { + int newSize = dec->memMembers * 2; + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &dec->members, dec->memMembers, newSize) ); + dec->memMembers = newSize; + } + SPQRNetworkDecompositionMember* data = &dec->members[dec->numMembers]; + data->markerOfParent = SPQR_INVALID_ARC; + data->markerToParent = SPQR_INVALID_ARC; + data->firstArc = SPQR_INVALID_ARC; + data->representativeMember = SPQR_INVALID_MEMBER; + data->numArcs = 0; + data->parentMember = SPQR_INVALID_MEMBER; + data->type = type; + + *pMember = dec->numMembers; + + dec->numMembers++; + return SCIP_OKAY; +} + +/** Create a new node in the network matrix decomposition. */ +static +SCIP_RETCODE createNode( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_node* pNode /**< out-pointer to the id that is assigned to the node */ +) +{ + + if( dec->numNodes == dec->memNodes ) + { + int newSize = dec->memNodes * 2; + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &dec->nodes, dec->memNodes, newSize) ); + dec->memNodes = newSize; + } + *pNode = dec->numNodes; + dec->nodes[dec->numNodes].representativeNode = SPQR_INVALID_NODE; + dec->nodes[dec->numNodes].firstArc = SPQR_INVALID_ARC; + dec->nodes[dec->numNodes].numArcs = 0; + dec->numNodes++; + + return SCIP_OKAY; +} + +/** Remove an arc from the linked list of arcs that are adjacent to a given node. */ +static +void removeArcFromNodeArcList( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The arc to remove */ + spqr_node node, /**< The node to remove the arc from */ + SCIP_Bool nodeIsHead /**< Indicates whether the node is the arcs head, or tail */ +) +{ + SPQRNetworkDecompositionArcListNode* arcListNode = nodeIsHead ? &dec->arcs[arc].headArcListNode + : &dec->arcs[arc].tailArcListNode; + + if( dec->nodes[node].numArcs == 1 ) + { + dec->nodes[node].firstArc = SPQR_INVALID_ARC; + } + else + { + spqr_arc next_arc = arcListNode->next; + spqr_arc prev_arc = arcListNode->previous; + SPQRNetworkDecompositionArcListNode* nextListNode = + findArcHead(dec, next_arc) == node ? &dec->arcs[next_arc].headArcListNode + : &dec->arcs[next_arc].tailArcListNode; + SPQRNetworkDecompositionArcListNode* prevListNode = + findArcHead(dec, prev_arc) == node ? &dec->arcs[prev_arc].headArcListNode + : &dec->arcs[prev_arc].tailArcListNode; + + nextListNode->previous = prev_arc; + prevListNode->next = next_arc; + + if( dec->nodes[node].firstArc == arc ) + { + dec->nodes[node].firstArc = next_arc; + } + } + --( dec->nodes[node].numArcs ); +} + +/** Add an arc to the linked list of arcs that are adjacent to a given node. */ +static +void addArcToNodeArcList( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The arc to add */ + spqr_node node, /**< The node to add the arc to */ + SCIP_Bool nodeIsHead /**< Indicates whether the node is the arcs head, or tail */ +) +{ + assert(nodeIsRepresentative(dec, node)); + + spqr_arc firstNodeArc = getFirstNodeArc(dec, node); + + SPQRNetworkDecompositionArcListNode* arcListNode = nodeIsHead ? &dec->arcs[arc].headArcListNode + : &dec->arcs[arc].tailArcListNode; + if( SPQRarcIsValid(firstNodeArc)) + { + SCIP_Bool nextIsHead = findArcHead(dec, firstNodeArc) == node; + SPQRNetworkDecompositionArcListNode* nextListNode = nextIsHead ? &dec->arcs[firstNodeArc].headArcListNode + : &dec->arcs[firstNodeArc].tailArcListNode; + spqr_arc lastNodeArc = nextListNode->previous; + + arcListNode->next = firstNodeArc; + arcListNode->previous = lastNodeArc; + + + SCIP_Bool previousIsHead = findArcHead(dec, lastNodeArc) == node; + SPQRNetworkDecompositionArcListNode* previousListNode = previousIsHead ? &dec->arcs[lastNodeArc].headArcListNode + : &dec->arcs[lastNodeArc].tailArcListNode; + previousListNode->next = arc; + nextListNode->previous = arc; + + } + else + { + arcListNode->next = arc; + arcListNode->previous = arc; + } + dec->nodes[node].firstArc = arc; + ++dec->nodes[node].numArcs; + if( nodeIsHead ) + { + dec->arcs[arc].head = node; + } + else + { + dec->arcs[arc].tail = node; + } +} + +/** Initializes the data structures of the arcs head and tail nodes. */ +static +void setArcHeadAndTail( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The arc to initialize */ + spqr_node head, /**< The arc its head node*/ + spqr_node tail /**< The arc its tail node*/ +) +{ + addArcToNodeArcList(dec, arc, head, TRUE); + addArcToNodeArcList(dec, arc, tail, FALSE); +} + +/** Deinitializes the data structures of the arcs head and tail nodes. */ +static +void clearArcHeadAndTail( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc /**< The arc to deinitialize */ +) +{ + removeArcFromNodeArcList(dec, arc, findArcHead(dec, arc), TRUE); + removeArcFromNodeArcList(dec, arc, findArcTail(dec, arc), FALSE); + dec->arcs[arc].head = SPQR_INVALID_NODE; + dec->arcs[arc].tail = SPQR_INVALID_NODE; +} + +/** Change the arc's head node to a new node. */ +static +void changeArcHead( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The given arc */ + spqr_node oldHead, /**< The current head node of the arc */ + spqr_node newHead /**< The new head node of the arc */ +) +{ + assert(nodeIsRepresentative(dec, oldHead)); + assert(nodeIsRepresentative(dec, newHead)); + + removeArcFromNodeArcList(dec, arc, oldHead, TRUE); + addArcToNodeArcList(dec, arc, newHead, TRUE); +} + +/** Change the arc's head node to a new node. */ +static +void changeArcTail( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The given arc */ + spqr_node oldTail, /**< The current tail node of the arc */ + spqr_node newTail /**< The new tail node of the arc */ +) +{ + assert(nodeIsRepresentative(dec, oldTail)); + assert(nodeIsRepresentative(dec, newTail)); + + removeArcFromNodeArcList(dec, arc, oldTail, FALSE); + addArcToNodeArcList(dec, arc, newTail, FALSE); +} + +/** Returns the degree of the given node. */ +static +int nodeDegree( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_node node /**< The given node */ +) +{ + assert(dec); + assert(SPQRnodeIsValid(node)); + assert(node < dec->memNodes); + + return dec->nodes[node].numArcs; +} + +/** Get the SPQR-type of the given member. */ +static +SPQRMemberType getMemberType( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member /**< The given member */ +) +{ + assert(dec); + assert(SPQRmemberIsValid(member)); + assert(member < dec->memMembers); + assert(memberIsRepresentative(dec, member)); + + return dec->members[member].type; +} + +/** Update the SPQR-type of the given member. */ +static +void updateMemberType( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member, /**< The member to update the type for */ + SPQRMemberType type /**< The new SPQR-type of the member */ +) +{ + assert(dec); + assert(SPQRmemberIsValid(member)); + assert(member < dec->memMembers); + assert(memberIsRepresentative(dec, member)); + + dec->members[member].type = type; +} + +/** Returns the virtual arc pointing to the parent member (in the arborescence) of the given member. */ +static +spqr_arc markerToParent( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member /**< The given member */ +) +{ + assert(dec); + assert(SPQRmemberIsValid(member)); + assert(member < dec->memMembers); + assert(memberIsRepresentative(dec, member)); + + return dec->members[member].markerToParent; +} + +/** Updates the parent information of a member that is identified with another another member. */ +static +void updateMemberParentInformation( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + const spqr_member newMember, /**< The new large member containing both members */ + const spqr_member toRemove /**< The member that was merged and removed */ +) +{ + assert(memberIsRepresentative(dec, newMember)); + assert(findMemberNoCompression(dec, toRemove) == newMember); + + dec->members[newMember].markerOfParent = dec->members[toRemove].markerOfParent; + dec->members[newMember].markerToParent = dec->members[toRemove].markerToParent; + dec->members[newMember].parentMember = dec->members[toRemove].parentMember; + + dec->members[toRemove].markerOfParent = SPQR_INVALID_ARC; + dec->members[toRemove].markerToParent = SPQR_INVALID_ARC; + dec->members[toRemove].parentMember = SPQR_INVALID_MEMBER; +} + +/** Returns the virtual arc of the parent member that points to the given member. */ +static +spqr_arc markerOfParent( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member /**< The given member */ +) +{ + assert(dec); + assert(SPQRmemberIsValid(member)); + assert(member < dec->memMembers); + assert(memberIsRepresentative(dec, member)); + + return dec->members[member].markerOfParent; +} + +/** Returns the number of arcs in the member. */ +static +int getNumMemberArcs( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member /**< The member to get the number of arcs of */ +) +{ + assert(dec); + assert(SPQRmemberIsValid(member)); + assert(member < dec->memMembers); + assert(memberIsRepresentative(dec, member)); + + return dec->members[member].numArcs; +} + +/** Returns the number of nodes in complete network matrix decomposition. */ +static +int getNumNodes( + const SCIP_NETMATDECDATA* dec /**< The network matrix decomposition */ + ) +{ + assert(dec); + + return dec->numNodes; +} + +/** Returns the number of members in complete network matrix decomposition. */ +static +int getNumMembers( + const SCIP_NETMATDECDATA* dec /**< The network matrix decomposition */ + ) +{ + assert(dec); + + return dec->numMembers; +} + +/** Creates a standalone parallel member with the given row and columns that is not connected to other members of the + * network matrix decomposition. + * + * New arcs are created for the given row and columns. + */ +static +SCIP_RETCODE createStandaloneParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_col* columns, /**< The columns in the parallel member */ + SCIP_Bool* reversed, /**< Array indicating the direction of each column edge */ + int num_columns, /**< The number of columns in the parallel member */ + spqr_row row, /**< The row in the parallel member */ + spqr_member* pMember /**< out-pointer to the new parallel member id */ +) +{ + spqr_member member; + SCIP_CALL( createMember(dec, num_columns <= 1 ? SPQR_MEMBERTYPE_LOOP : SPQR_MEMBERTYPE_PARALLEL, &member) ); + + spqr_arc row_arc; + SCIP_CALL( createRowArc(dec, member, &row_arc, row, num_columns <= 1) ); + + spqr_arc col_arc; + for( int i = 0; i < num_columns; ++i ) + { + SCIP_CALL( createColumnArc(dec, member, &col_arc, columns[i], reversed[i]) ); + } + *pMember = member; + + ++dec->numConnectedComponents; + + return SCIP_OKAY; +} + +/** Creates a parallel member with the given row and columns is connected to other members of the + * network matrix decomposition. + * + * New arcs are created for the given row and columns. + */ +static +SCIP_RETCODE createConnectedParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_col* columns, /**< The columns in the parallel member */ + SCIP_Bool* reversed, /**< Array indicating the direction of each column edge */ + int num_columns, /**< The number of columns in the parallel member */ + spqr_row row, /**< The row in the parallel member */ + spqr_member* pMember /**< out-pointer to the new parallel member id */ +) +{ + spqr_member member; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, &member) ); + + spqr_arc row_arc; + SCIP_CALL( createRowArc(dec, member, &row_arc, row, FALSE) ); + + spqr_arc col_arc; + for( int i = 0; i < num_columns; ++i ) + { + SCIP_CALL( createColumnArc(dec, member, &col_arc, columns[i], reversed[i]) ); + } + *pMember = member; + + return SCIP_OKAY; +} + +/** Creates a standalone series member with the given row and columns that is not connected to other members of the + * network matrix decomposition. + * + * New arcs are created for the given rows and column. + */ +static +SCIP_RETCODE createStandaloneSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_row* rows, /**< The rows in the series member */ + SCIP_Bool* reversed, /**< Array indicating the direction of each row edge */ + int numRows, /**< The number of rows in the parallel member */ + spqr_col col, /**< The column in the parallel member */ + spqr_member* pMember /**< out-pointer to the new series member id */ +) +{ + spqr_member member; + SCIP_CALL( createMember(dec, numRows <= 1 ? SPQR_MEMBERTYPE_LOOP : SPQR_MEMBERTYPE_SERIES, &member) ); + + spqr_arc colArc; + SCIP_CALL( createColumnArc(dec, member, &colArc, col, FALSE) ); + + spqr_arc rowArc; + for( int i = 0; i < numRows; ++i ) + { + SCIP_CALL( createRowArc(dec, member, &rowArc, rows[i], !reversed[i]) ); + } + *pMember = member; + ++dec->numConnectedComponents; + + return SCIP_OKAY; +} + +/** Creates a series member with the given row and columns that is connected to some other member of the + * network matrix decomposition. + * + * New arcs are created for the given rows and column. + */ +static +SCIP_RETCODE createConnectedSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_row* rows, /**< The rows in the series member */ + SCIP_Bool* reversed, /**< Array indicating the direction of each row edge */ + int numRows, /**< The number of rows in the parallel member */ + spqr_col col, /**< The column in the parallel member */ + spqr_member* pMember /**< out-pointer to the new series member id */ +) +{ + spqr_member member; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &member) ); + + spqr_arc colArc; + SCIP_CALL( createColumnArc(dec, member, &colArc, col, FALSE) ); + + spqr_arc rowArc; + for( int i = 0; i < numRows; ++i ) + { + SCIP_CALL( createRowArc(dec, member, &rowArc, rows[i], !reversed[i]) ); + } + *pMember = member; + + return SCIP_OKAY; +} + +/** Remove an arc from the linked list containing all arcs of a single member. */ +static +void removeArcFromMemberArcList( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The arc to remove */ + spqr_member member /**< The member to remove it from */ +) +{ + assert(findArcMemberNoCompression(dec, arc) == member); + assert(memberIsRepresentative(dec, member)); + + if( dec->members[member].numArcs == 1 ) + { + dec->members[member].firstArc = SPQR_INVALID_ARC; + } + else + { + spqr_arc nextArc = dec->arcs[arc].arcListNode.next; + spqr_arc prevArc = dec->arcs[arc].arcListNode.previous; + + dec->arcs[nextArc].arcListNode.previous = prevArc; + dec->arcs[prevArc].arcListNode.next = nextArc; + + if( dec->members[member].firstArc == arc ) + { + dec->members[member].firstArc = nextArc; + } + } + + --( dec->members[member].numArcs ); +} + +/** Data structure for the algorithms to find fundamental cycle within the network matrix decomposition */ +typedef struct +{ + spqr_arc arc; + SCIP_Bool reversed; +} FindCycleCall; + +/** Processes a single arc for the algorithm to find cycles in the network matrix decomposition. + * + * if virtual, pushes it on the callstack, if non-virtual, adds it to the found cycle + */ +static +void process_arc( + spqr_row* cyclearcs, /**< The found cycle so far */ + int* num_cycle_arcs, /**< The number of arcs in the cycle so far */ + FindCycleCall* callStack, /**< The call stack of virtual edges to process still */ + int* callStackSize, /**< The number of virtual edges on the callstack */ + spqr_arc arc, /**< The current arc to process */ + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_Bool* cycledir, /**< Whether the current arc is reversed w.r.t to the cycle/path */ + SCIP_Bool arcIsReversed /**< The arcIsReversed status from the network matrix decomposition */ +) +{ + assert(arcIsTree(dec, arc)); + + if( !arcIsChildMarker(dec, arc)) + { + spqr_member current_member = findArcMemberNoCompression(dec, arc); + if( markerToParent(dec, current_member) == arc ) + { + spqr_arc other_arc = markerOfParent(dec, current_member); + assert(!arcIsTree(dec, other_arc)); + callStack[*callStackSize].arc = other_arc; + callStack[*callStackSize].reversed = arcIsReversed; + ++( *callStackSize ); + } + else + { + spqr_element element = arcGetElement(dec, arc); + assert(SPQRelementIsRow(element)); + spqr_row row = SPQRelementToRow(element); + cyclearcs[*num_cycle_arcs] = row; + cycledir[*num_cycle_arcs] = arcIsReversed; + ++( *num_cycle_arcs ); + } + } + else + { + spqr_member child_member = findArcChildMemberNoCompression(dec, arc); + spqr_arc other_arc = markerToParent(dec, child_member); + assert(!arcIsTree(dec, other_arc)); + callStack[*callStackSize].arc = other_arc; + callStack[*callStackSize].reversed = arcIsReversed; + ++( *callStackSize ); + } +} + +/** Find the fundamental path of a cycle. + * + * This is a slow method and only intended for debugging and testing. + */ +static +int decompositionGetFundamentalCycleRows( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_col column, /**< The column to find the fundamental path for */ + spqr_row* output, /**< preallocated array to store fundamental path in. Must have at least + * the number of rows in the decomposition allocated */ + SCIP_Bool* computedSignStorage /**< Boolean array for storage of whether the path occurs forwards or + * backwards. Must have at least the same size as output array. */ +) +{ + /*Basic idea; for each component, do a dfs over the tree formed by the row arcs to find the relevant edges. + This is easy for SPQ-nodes, for R nodes we need to search */ + spqr_arc arc = getDecompositionColumnArc(dec, column); + if( SPQRarcIsInvalid(arc)) + { + return 0; + } + int num_rows = 0; + + FindCycleCall* callStack; + if( BMSallocBlockMemoryArray(dec->env, &callStack, dec->memRows) == NULL ) + { + return -1; + } + int callStackSize = 1; + callStack[0].arc = arc; + callStack[0].reversed = FALSE; + + SCIP_Bool* nodeVisited; + if( BMSallocBlockMemoryArray(dec->env, &nodeVisited, dec->numNodes) == NULL ) + { + return -1; + } + for( int i = 0; i < dec->numNodes; ++i ) + { + nodeVisited[i] = FALSE; + } + + typedef struct + { + spqr_node node; + spqr_arc nodeArc; + } DFSCallData; + DFSCallData* pathSearchCallStack; + if( BMSallocBlockMemoryArray(dec->env, &pathSearchCallStack, dec->numNodes) == NULL ) + { + return -1; + } + int pathSearchCallStackSize = 0; + + while( callStackSize > 0 ) + { + spqr_arc column_arc = callStack[callStackSize - 1].arc; + SCIP_Bool reverseEverything = callStack[callStackSize - 1].reversed; + --callStackSize; + spqr_member column_arc_member = findArcMemberNoCompression(dec, column_arc); + switch( getMemberType(dec, column_arc_member)) + { + case SPQR_MEMBERTYPE_RIGID: + { + spqr_node source = findEffectiveArcTailNoCompression(dec, column_arc); + spqr_node target = findEffectiveArcHeadNoCompression(dec, column_arc); + + assert(pathSearchCallStackSize == 0); + pathSearchCallStack[0].node = source; + pathSearchCallStack[0].nodeArc = getFirstNodeArc(dec, source); + pathSearchCallStackSize++; + while( pathSearchCallStackSize > 0 ) + { + DFSCallData* dfsData = &pathSearchCallStack[pathSearchCallStackSize - 1]; + nodeVisited[dfsData->node] = TRUE; + //cannot be a tree arc which is its parent + if( arcIsTree(dec, dfsData->nodeArc) && + ( pathSearchCallStackSize <= 1 || + dfsData->nodeArc != pathSearchCallStack[pathSearchCallStackSize - 2].nodeArc )) + { + spqr_node head = findEffectiveArcHeadNoCompression(dec, dfsData->nodeArc); + spqr_node tail = findEffectiveArcTailNoCompression(dec, dfsData->nodeArc); + spqr_node other = head == dfsData->node ? tail : head; + assert(other != dfsData->node); + assert(!nodeVisited[other]); + if( other == target ) + { + break; + } + //We go up a level: add new node to the call stack + + pathSearchCallStack[pathSearchCallStackSize].node = other; + pathSearchCallStack[pathSearchCallStackSize].nodeArc = getFirstNodeArc(dec, other); + ++pathSearchCallStackSize; + continue; + } + do + { + dfsData->nodeArc = getNextNodeArcNoCompression(dec, dfsData->nodeArc, dfsData->node); + if( dfsData->nodeArc == getFirstNodeArc(dec, dfsData->node)) + { + --pathSearchCallStackSize; + dfsData = &pathSearchCallStack[pathSearchCallStackSize - 1]; + } + else + { + break; + } + } + while( pathSearchCallStackSize > 0 ); + } + for( int i = 0; i < pathSearchCallStackSize; ++i ) + { + if( arcIsTree(dec, pathSearchCallStack[i].nodeArc)) + { + SCIP_Bool arcReversedInPath = + findEffectiveArcHeadNoCompression(dec, pathSearchCallStack[i].nodeArc) == + pathSearchCallStack[i].node; + process_arc(output, &num_rows, callStack, &callStackSize, pathSearchCallStack[i].nodeArc, dec, + computedSignStorage, arcReversedInPath != reverseEverything); + } + } + + pathSearchCallStackSize = 0; + break; + } + case SPQR_MEMBERTYPE_PARALLEL: + { + SCIP_Bool columnReversed = arcIsReversedNonRigid(dec, column_arc); + + spqr_arc first_arc = getFirstMemberArc(dec, column_arc_member); + spqr_arc iter_arc = first_arc; + int tree_count = 0; + do + { + if( arcIsTree(dec, iter_arc)) + { + SCIP_Bool treeIsReversed = arcIsReversedNonRigid(dec, iter_arc); + process_arc(output, &num_rows, callStack, &callStackSize, iter_arc, dec, + computedSignStorage, ( columnReversed != treeIsReversed ) != reverseEverything); + tree_count++; + } + iter_arc = getNextMemberArc(dec, iter_arc); + } + while( iter_arc != first_arc ); + if( tree_count != 1 ) + { + return -1; + } + break; + } + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_SERIES: + { + SCIP_Bool columnReversed = arcIsReversedNonRigid(dec, column_arc); + spqr_arc first_arc = getFirstMemberArc(dec, column_arc_member); + spqr_arc iter_arc = first_arc; + int nontree_count = 0; + do + { + if( arcIsTree(dec, iter_arc)) + { + SCIP_Bool treeIsReversed = arcIsReversedNonRigid(dec, iter_arc); + process_arc(output, &num_rows, callStack, &callStackSize, iter_arc, dec, + computedSignStorage, ( columnReversed == treeIsReversed ) != reverseEverything); + } + else + { + nontree_count++; + } + iter_arc = getNextMemberArc(dec, iter_arc); + } + while( iter_arc != first_arc ); + if( nontree_count != 1 ) + { + return -1; + } + break; + } + case SPQR_MEMBERTYPE_UNASSIGNED: + assert(FALSE); + } + } + BMSfreeBlockMemoryArray(dec->env, &pathSearchCallStack, dec->numNodes); + BMSfreeBlockMemoryArray(dec->env, &nodeVisited, dec->numNodes); + BMSfreeBlockMemoryArray(dec->env, &callStack, dec->memRows); + + return num_rows; +} + +/** Given a cycle (e.g. a matrix column), checks if the column's cycle matches the cycle in the + * network matrix decomposition. + */ +static +SCIP_Bool netMatDecDataVerifyCycle( + BMS_BUFMEM* bufmem, /**< Buffer memory */ + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + int column, /**< The column to check */ + const int* nonzrowidx, /**< Array with the nonzero row indices of the column */ + const double* nonzvals, /**< Array with the nonzero entry values of the column's row indices */ + int num_rows, /**< The number of nonzeros in the column */ + int* pathrowstorage, /**< Temporary storage vector for storing the fundamental path. Must have + * at least as many entries allocated as the number of rows in dec. */ + SCIP_Bool* pathsignstorage /**< Temporary storage for the fundamental path directions. Must have + * at least as many entries allocated as the number of rows in dec. */ +) +{ + int num_found_rows = decompositionGetFundamentalCycleRows(dec, column, pathrowstorage, pathsignstorage); + + if( num_found_rows != num_rows ) + { + return FALSE; + } + if( num_rows == 0 ) + { + return TRUE; + } + spqr_row * pathRow; + int * pathRowReversed; + + if( BMSallocBufferMemoryArray(bufmem, &pathRow, num_rows) == NULL ) + { + return FALSE; + } + + if( BMSallocBufferMemoryArray(bufmem, &pathRowReversed, num_rows) == NULL ) + { + BMSfreeBufferMemoryArray(bufmem, &pathRow); + return FALSE; + } + for( int i = 0; i < num_rows; ++i ) + { + pathRow[i] = pathrowstorage[i]; + pathRowReversed[i] = pathsignstorage[i] ? 1 : 0; + } + SCIPsortIntInt(pathRow, pathRowReversed, num_rows); + + spqr_row * secondPathRow; + int * secondPathRowReversed; + if( BMSallocBufferMemoryArray(bufmem, &secondPathRow, num_rows) == NULL ) + { + BMSfreeBufferMemoryArray(bufmem, &pathRow); + BMSfreeBufferMemoryArray(bufmem, &pathRowReversed); + return FALSE; + } + + if( BMSallocBufferMemoryArray(bufmem, &secondPathRowReversed, num_rows) == NULL ) + { + BMSfreeBufferMemoryArray(bufmem, &pathRow); + BMSfreeBufferMemoryArray(bufmem, &pathRowReversed); + BMSfreeBufferMemoryArray(bufmem, &secondPathRow); + return FALSE; + } + for( int i = 0; i < num_rows; ++i ) + { + secondPathRow[i] = nonzrowidx[i]; + secondPathRowReversed[i] = nonzvals[i] < 0.0 ? 1 : 0; + } + + SCIPsortIntInt(secondPathRow, secondPathRowReversed, num_rows); + + SCIP_Bool good = TRUE; + for( int i = 0; i < num_rows; ++i ) + { + if( pathRow[i] != secondPathRow[i] || pathRowReversed[i] != secondPathRowReversed[i] ) + { + good = FALSE; + break; + } + } + BMSfreeBufferMemoryArray(bufmem, &secondPathRowReversed); + BMSfreeBufferMemoryArray(bufmem, &secondPathRow); + BMSfreeBufferMemoryArray(bufmem, &pathRowReversed); + BMSfreeBufferMemoryArray(bufmem, &pathRow); + + return good; +} + +/** Returns the largest member id that is currently in the decomposition. */ +static +spqr_member largestMemberID( + const SCIP_NETMATDECDATA* dec /**< The network matrix decomposition */ + ) +{ + return dec->numMembers; +} + +/** Returns the largest arc id that is currently in the decomposition. */ +static +spqr_arc largestArcID( + const SCIP_NETMATDECDATA* dec /**< The network matrix decomposition */ + ) +{ + return dec->numArcs; +} + +/** Returns the largest node id that is currently in the decomposition. */ +static +spqr_node largestNodeID( + const SCIP_NETMATDECDATA* dec /**< The network matrix decomposition */ + ) +{ + return dec->numNodes; +} + +/** Returns the number of SPQR trees in the SPQR forest, i.e. the number of connected components. */ +static +int numConnectedComponents( + const SCIP_NETMATDECDATA* dec /**< The network matrix decomposition */ + ) +{ + return dec->numConnectedComponents; +} + +/** Creates a child marker in the network decomposition. */ +static +SCIP_RETCODE createChildMarker( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member, /**< The member to create the arc in */ + spqr_member child, /**< The member that the arc points to */ + SCIP_Bool isTree, /**< Indicates if the new arc is a tree arc */ + spqr_arc* pArc, /**< Out-pointer to store the new arc's id */ + SCIP_Bool reversed /**< Sets the reversed field of the arc */ +) +{ + SCIP_CALL( createArc(dec, member, reversed, pArc) ); + dec->arcs[*pArc].element = isTree ? MARKER_ROW_ELEMENT : MARKER_COLUMN_ELEMENT; + dec->arcs[*pArc].childMember = child; + + addArcToMemberArcList(dec, *pArc, member); + + return SCIP_OKAY; +} + +/** Creates a parent marker in the network decomposition. */ +static +SCIP_RETCODE createParentMarker( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member, /**< The member to create the arc in */ + SCIP_Bool isTree, /**< Indicates if the new arc is a tree arc */ + spqr_member parent, /**< The member that the arc points to */ + spqr_arc parentMarker, /**< The parent arc in the parent that points to this member */ + spqr_arc* arc, /**< Out-pointer to store the new arc's id */ + SCIP_Bool reversed /**< Sets the reversed field of the arc */ +) +{ + + SCIP_CALL( createArc(dec, member, reversed, arc) ); + dec->arcs[*arc].element = isTree ? MARKER_ROW_ELEMENT : MARKER_COLUMN_ELEMENT; + + addArcToMemberArcList(dec, *arc, member); + + dec->members[member].parentMember = parent; + dec->members[member].markerOfParent = parentMarker; + dec->members[member].markerToParent = *arc; + + return SCIP_OKAY; +} + +/** Creates a child-marker parent-marker pair in the network decomposition. */ +static +SCIP_RETCODE createMarkerPair( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member parentMember, /**< The parent member */ + spqr_member childMember, /**< The child member */ + SCIP_Bool parentIsTree, /**< Is the edge in the parent member (the child marker) a tree edge? */ + SCIP_Bool childMarkerReversed, /**< Is the child marker arc reversed?*/ + SCIP_Bool parentMarkerReversed /**< IS the parent marker arc reversed? */ +) +{ + spqr_arc parentToChildMarker = SPQR_INVALID_ARC; + SCIP_CALL( createChildMarker(dec, parentMember, childMember, parentIsTree, &parentToChildMarker, childMarkerReversed) ); + + spqr_arc childToParentMarker = SPQR_INVALID_ARC; + SCIP_CALL( createParentMarker(dec, childMember, !parentIsTree, parentMember, parentToChildMarker, + &childToParentMarker, parentMarkerReversed) ); + + return SCIP_OKAY; +} + +/** Creates a child-marker parent-marker pair in the network decomposition, and returns the assigned arc id's. */ +static +SCIP_RETCODE createMarkerPairWithReferences( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member parentMember, /**< The parent member */ + spqr_member childMember, /**< The child member */ + SCIP_Bool parentIsTree, /**< Is the edge in the parent member (the child marker) a tree edge? */ + SCIP_Bool childMarkerReversed, /**< Is the child marker arc reversed?*/ + SCIP_Bool parentMarkerReversed, /**< IS the parent marker arc reversed? */ + spqr_arc* parentToChild, /**< Output-pointer containing arc id of the arc in the parent member */ + spqr_arc* childToParent /**< Output-pointer containing arc id of the arc in the child member */ +) +{ + SCIP_CALL( createChildMarker(dec, parentMember, childMember, parentIsTree, parentToChild, childMarkerReversed) ); + SCIP_CALL( createParentMarker(dec, childMember, !parentIsTree, parentMember, *parentToChild, childToParent, + parentMarkerReversed) ); + + return SCIP_OKAY; +} + +/** Moves a given arc from one member to another, updating the linked lists it is contained in and the parent/child + * information of the relevant members. + */ +static +void moveArcToNewMember( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The arc to move */ + spqr_member oldMember, /**< The member that currently contains the arc */ + spqr_member newMember /**< The member to move the arc to */ +) +{ + assert(SPQRarcIsValid(arc)); + assert(arc < dec->memArcs); + assert(dec); + + assert(memberIsRepresentative(dec, oldMember)); + assert(memberIsRepresentative(dec, newMember)); + //Need to change the arc's member, remove it from the current member list and add it to the new member list + assert(findArcMemberNoCompression(dec, arc) == oldMember); + + removeArcFromMemberArcList(dec, arc, oldMember); + addArcToMemberArcList(dec, arc, newMember); + + dec->arcs[arc].member = newMember; + + //If this arc has a childMember, update the information correctly! + spqr_member childMember = dec->arcs[arc].childMember; + if( SPQRmemberIsValid(childMember)) + { + spqr_member childRepresentative = findArcChildMember(dec, arc); + dec->members[childRepresentative].parentMember = newMember; + } + //If this arc is a marker to the parent, update the child arc marker of the parent to reflect the move + if( dec->members[oldMember].markerToParent == arc ) + { + dec->members[newMember].markerToParent = arc; + dec->members[newMember].parentMember = dec->members[oldMember].parentMember; + dec->members[newMember].markerOfParent = dec->members[oldMember].markerOfParent; + + assert(findArcChildMemberNoCompression(dec, dec->members[oldMember].markerOfParent) == oldMember); + dec->arcs[dec->members[oldMember].markerOfParent].childMember = newMember; + } +} + +/** Merges the arc linked list of two members into one linked list. */ +static +void mergeMemberArcList( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member toMergeInto, /**< The member id that gets the new large linked list containing both */ + spqr_member toRemove /**< The member id that is invalidated, whose linked list is moved away. */ +) +{ + spqr_arc firstIntoArc = getFirstMemberArc(dec, toMergeInto); + spqr_arc firstFromArc = getFirstMemberArc(dec, toRemove); + assert(SPQRarcIsValid(firstIntoArc)); + assert(SPQRarcIsValid(firstFromArc)); + + spqr_arc lastIntoArc = getPreviousMemberArc(dec, firstIntoArc); + spqr_arc lastFromArc = getPreviousMemberArc(dec, firstFromArc); + + //Relink linked lists to merge them effectively + dec->arcs[firstIntoArc].arcListNode.previous = lastFromArc; + dec->arcs[lastIntoArc].arcListNode.next = firstFromArc; + dec->arcs[firstFromArc].arcListNode.previous = lastIntoArc; + dec->arcs[lastFromArc].arcListNode.next = firstIntoArc; + + //Clean up old + dec->members[toMergeInto].numArcs += dec->members[toRemove].numArcs; + dec->members[toRemove].numArcs = 0; + dec->members[toRemove].firstArc = SPQR_INVALID_ARC; +} + +/** Changes the type of a member from loop to series. */ +static +void changeLoopToSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member /**< The member whose type is changed */ +) +{ + assert(SPQRmemberIsValid(member)); + assert(member < dec->memMembers); + assert(dec); + assert(( getMemberType(dec, member) == SPQR_MEMBERTYPE_PARALLEL || + getMemberType(dec, member) == SPQR_MEMBERTYPE_SERIES || + getMemberType(dec, member) == SPQR_MEMBERTYPE_LOOP ) && + getNumMemberArcs(dec, member) == 2); + assert(memberIsRepresentative(dec, member)); + dec->members[member].type = SPQR_MEMBERTYPE_SERIES; +} + +/** Changes the type of a member from loop to parallel. */ +static +void changeLoopToParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member /**< The member whose type is changed */ +) +{ + assert(SPQRmemberIsValid(member)); + assert(member < dec->memMembers); + assert(dec); + assert(( getMemberType(dec, member) == SPQR_MEMBERTYPE_PARALLEL || + getMemberType(dec, member) == SPQR_MEMBERTYPE_SERIES || + getMemberType(dec, member) == SPQR_MEMBERTYPE_LOOP ) && + getNumMemberArcs(dec, member) == 2); + assert(memberIsRepresentative(dec, member)); + dec->members[member].type = SPQR_MEMBERTYPE_PARALLEL; +} + +/** Checks if the network decomposition is minimal, i.e. if it does not contain two adjacent parallel or series + * members. + */ +static +SCIP_Bool netMatDecDataIsMinimal( + const SCIP_NETMATDECDATA* dec /**< The network matrix decomposition */ + ) +{ + //Relies on parents/children etc. being set correctly in the tree + SCIP_Bool isMinimal = TRUE; + for( spqr_member member = 0; member < dec->numMembers; ++member ) + { + if( !memberIsRepresentative(dec, member) || getMemberType(dec, member) == SPQR_MEMBERTYPE_UNASSIGNED ) + { + continue; + } + spqr_member memberParent = findMemberParentNoCompression(dec, member); + if( SPQRmemberIsValid(memberParent)) + { + assert(memberIsRepresentative(dec, memberParent)); + SPQRMemberType memberType = getMemberType(dec, member); + SPQRMemberType parentType = getMemberType(dec, memberParent); + if( memberType == parentType && memberType != SPQR_MEMBERTYPE_RIGID ) + { + isMinimal = FALSE; + break; + } + } + } + return isMinimal; +} + +/** Decreases the count of the number of connected components in the network matrix decomposition. */ +static +void decreaseNumConnectedComponents( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + int by /**< The number of connected components to remove. */ +) +{ + dec->numConnectedComponents -= by; + assert(dec->numConnectedComponents >= 1); +} + +/** Reorders the arborescence of the SPQR that contains a given member so that the given member becomes the new root + * of the arborescence. + */ +static +void reorderComponent( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member newRoot /**< The member to make the new root */ +) +{ + assert(dec); + assert(memberIsRepresentative(dec, newRoot)); + //If the newRoot has no parent, it is already the root, so then there's no need to reorder. + if( SPQRmemberIsValid(dec->members[newRoot].parentMember)) + { + spqr_member member = findMemberParent(dec, newRoot); + spqr_member newParent = newRoot; + spqr_arc newMarkerToParent = dec->members[newRoot].markerOfParent; + spqr_arc markerOfNewParent = dec->members[newRoot].markerToParent; + + //Recursively update the parent + do + { + assert(SPQRmemberIsValid(member)); + assert(SPQRmemberIsValid(newParent)); + spqr_member oldParent = findMemberParent(dec, member); + spqr_arc oldMarkerToParent = dec->members[member].markerToParent; + spqr_arc oldMarkerOfParent = dec->members[member].markerOfParent; + + dec->members[member].markerToParent = newMarkerToParent; + dec->members[member].markerOfParent = markerOfNewParent; + dec->members[member].parentMember = newParent; + dec->arcs[markerOfNewParent].childMember = member; + dec->arcs[newMarkerToParent].childMember = SPQR_INVALID_MEMBER; + + if( SPQRmemberIsValid(oldParent)) + { + newParent = member; + member = oldParent; + newMarkerToParent = oldMarkerOfParent; + markerOfNewParent = oldMarkerToParent; + } + else + { + break; + } + } + while( TRUE ); /*lint !e506*/ + dec->members[newRoot].parentMember = SPQR_INVALID_MEMBER; + dec->members[newRoot].markerToParent = SPQR_INVALID_ARC; + dec->members[newRoot].markerOfParent = SPQR_INVALID_ARC; + } +} + +/** Deletes the SPQR tree (connected component) containing the given component rows and columns. + * + * Note that the implementation of this method does not actually modify the SPQR tree, but rather unlinks the rows and + * columns from the relevant arcs. Thus, this method is a bit hacky and should be used with care. + */ +static +void netMatDecDataRemoveComponent( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + const int* componentRows, /**< The rows of the connected component*/ + int numRows, /**< The number of rows */ + const int* componentCols, /**< The columns of the connected component*/ + int numCols /**< The number of columns */ +) +{ + //The below just removes the 'link' but not the internal datastructures. + //This is sufficient for our purposes, as long as we do not re-introduce any of the 'negated' rows/columns back into the decomposition. + + for( int i = 0; i < numRows; ++i ) + { + spqr_row row = componentRows[i]; + if( SPQRarcIsValid(dec->rowArcs[row]) ) + { + dec->rowArcs[row] = SPQR_INVALID_ARC; + } + } + + for( int i = 0; i < numCols; ++i ) + { + spqr_col col = componentCols[i]; + if( SPQRarcIsValid(dec->columnArcs[col]) ) + { + dec->columnArcs[col] = SPQR_INVALID_ARC; + } + } +} + +#ifdef SCIP_DEBUG +/** Converts the members type to an associated character */ //Debugging functions to print the decomposition +static +char typeToChar( + SPQRMemberType type /**< The member type */ + ) +{ + switch (type) + { + case SPQR_MEMBERTYPE_RIGID: + return 'R'; + case SPQR_MEMBERTYPE_PARALLEL: + return 'P'; + case SPQR_MEMBERTYPE_SERIES: + return 'S'; + case SPQR_MEMBERTYPE_LOOP: + return 'L'; + default: + return '?'; + } +} + +/** Prints an arc in DOT format */ +static +void arcToDot( + FILE* stream, /**< The stream to write the arc to */ + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_arc arc, /**< The arc to write */ + unsigned long dot_head, /**< The head id of the arc */ + unsigned long dot_tail, /**< The tail id of the arc */ + SCIP_Bool useElementNames /**< If TRUE, prints the corresponding row/column index. If FALSE, + **< prints the spqr_arc id instead. */ + ) +{ + assert(SPQRarcIsValid(arc)); + spqr_member member = findArcMemberNoCompression(dec, arc); + SPQRMemberType member_type = getMemberType(dec, member); + char type = typeToChar(member_type); + const char *color = arcIsTree(dec, arc) ? ",color=red" : ",color=blue"; + + int arc_name = arc; + + if( markerToParent(dec, member) == arc ) + { + if( useElementNames ) + { + arc_name = -1; + } + fprintf(stream, " %c_%d_%lu -> %c_p_%d [label=\"%d\",style=dashed%s];\n", type, member, dot_tail, type, member, arc_name, color); + fprintf(stream, " %c_p_%d -> %c_%d_%lu [label=\"%d\",style=dashed%s];\n", type, member, type, member, dot_head, arc_name, color); + fprintf(stream, " %c_%d_%lu [shape=box];\n", type, member, dot_tail); + fprintf(stream, " %c_%d_%lu [shape=box];\n", type, member, dot_head); + fprintf(stream, " %c_p_%d [style=dashed];\n", type, member); + } + else if( arcIsChildMarker(dec, arc) ) + { + spqr_member child = findArcChildMemberNoCompression(dec, arc); + char childType = typeToChar(getMemberType(dec, child)); + if( useElementNames ) + { + arc_name = -1; + } + fprintf(stream, " %c_%d_%lu -> %c_c_%d [label=\"%d\",style=dotted%s];\n", type, member, dot_tail, type, child, arc_name, color); + fprintf(stream, " %c_c_%d -> %c_%d_%lu [label=\"%d\",style=dotted%s];\n", type, child, type, member, dot_head, arc_name, color); + fprintf(stream, " %c_%d_%lu [shape=box];\n", type, member, dot_tail); + fprintf(stream, " %c_%d_%lu [shape=box];\n", type, member, dot_head); + fprintf(stream, " %c_c_%d [style=dotted];\n", type, child); + fprintf(stream, " %c_p_%d -> %c_c_%d [style=dashed,dir=forward];\n", childType, child, type, child); + } + else + { + if( useElementNames ) + { + spqr_element element = dec->arcs[arc].element; + if( SPQRelementIsRow(element) ) + { + arc_name = (int) SPQRelementToRow(element); + } + else + { + arc_name = (int) SPQRelementToColumn(element); + } + } + + fprintf(stream, " %c_%d_%lu -> %c_%d_%lu [label=\"%d \",style=bold%s];\n", type, member, dot_tail, type, member, dot_head, + arc_name, color); + fprintf(stream, " %c_%d_%lu [shape=box];\n", type, member, dot_tail); + fprintf(stream, " %c_%d_%lu [shape=box];\n", type, member, dot_head); + } +} + +/** Outputs the network decomposition as a DOT file. */ +static +void decompositionToDot( + FILE* stream, /**< The stream to write to */ + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_Bool useElementNames /**< If TRUE, prints the corresponding row/column index. If FALSE, + * prints the spqr_arc id instead. */ + ) +{ + fprintf(stream, "//decomposition\ndigraph decomposition{\n compound = TRUE;\n"); + for( spqr_member member = 0; member < dec->numMembers; ++member ) + { + if( !memberIsRepresentative(dec, member) ) + continue; + fprintf(stream, " subgraph member_%d{\n", member); + switch( getMemberType(dec, member) ) + { + case SPQR_MEMBERTYPE_RIGID: + { + spqr_arc first_arc = getFirstMemberArc(dec, member); + spqr_arc arc = first_arc; + do + { + unsigned long arcHead = (unsigned long) findEffectiveArcHeadNoCompression(dec, arc); + unsigned long arcTail = (unsigned long) findEffectiveArcTailNoCompression(dec, arc); + arcToDot(stream, dec, arc, arcHead, arcTail, useElementNames); + arc = getNextMemberArc(dec, arc); + } + while( arc != first_arc ); + break; + } + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_PARALLEL: + { + spqr_arc first_arc = getFirstMemberArc(dec, member); + spqr_arc arc = first_arc; + do + { + if( arcIsReversedNonRigid(dec, arc) ) + { + arcToDot(stream, dec, arc, 1, 0, useElementNames); + } + else + { + arcToDot(stream, dec, arc, 0, 1, useElementNames); + } + arc = getNextMemberArc(dec, arc); + } + while( arc != first_arc ); + break; + } + case SPQR_MEMBERTYPE_SERIES: + { + unsigned long i = 0; + unsigned long num_member_arcs = (unsigned long) getNumMemberArcs(dec, member); + spqr_arc first_arc = getFirstMemberArc(dec, member); + spqr_arc arc = first_arc; + do + { + unsigned long head = i; + unsigned long tail = (i + 1) % num_member_arcs; + if( arcIsReversedNonRigid(dec, arc) ) + { + unsigned long temp = head; + head = tail; + tail = temp; + } + arcToDot(stream, dec, arc, head, tail, useElementNames); + arc = getNextMemberArc(dec, arc); + i++; + } + while( arc != first_arc ); + break; + } + case SPQR_MEMBERTYPE_UNASSIGNED: + break; + } + fprintf(stream, " }\n"); + } + fprintf(stream, "}\n"); +} +#endif + +static +SCIP_RETCODE mergeGivenMemberIntoParent( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member member, /**< The member to merge */ + spqr_member parent, /**< The parent of the member to merge into */ + spqr_arc parentToChild, /**< The arc from the parent pointing to the member */ + spqr_arc childToParent, /**< The arc in the child pointing to the parent */ + SCIP_Bool headToHead, /**< Identify the head of parentToChild with the head of childToParent?*/ + spqr_member* mergedMember /**< Out-pointer to the member id of the final member */ +) +{ + assert(dec); + assert(SPQRmemberIsValid(member)); + assert(memberIsRepresentative(dec, member)); + assert(SPQRmemberIsValid(parent)); + assert(memberIsRepresentative(dec, parent)); + assert(findMemberParentNoCompression(dec, member) == parent); + assert(markerOfParent(dec, member) == parentToChild); + assert(markerToParent(dec, member) == childToParent); + + removeArcFromMemberArcList(dec, parentToChild, parent); + removeArcFromMemberArcList(dec, childToParent, member); + + spqr_node parentTail = findEffectiveArcTail(dec, parentToChild) ; + spqr_node parentHead = findEffectiveArcHead(dec, parentToChild); + spqr_node childTail = findEffectiveArcTail(dec, childToParent); + spqr_node childHead = findEffectiveArcHead(dec, childToParent); + spqr_node parentArcNodes[2] = { parentTail, parentHead }; + spqr_node childArcNodes[2] = { childTail, childHead }; + + clearArcHeadAndTail(dec, parentToChild); + clearArcHeadAndTail(dec, childToParent); + + spqr_node first = childArcNodes[headToHead ? 0 : 1]; + spqr_node second = childArcNodes[headToHead ? 1 : 0]; + { + spqr_node newNode = mergeNodes(dec, parentArcNodes[0], first); + spqr_node toRemoveFrom = newNode == first ? parentArcNodes[0] : first; + mergeNodeArcList(dec, newNode, toRemoveFrom); + } + { + spqr_node newNode = mergeNodes(dec, parentArcNodes[1], second); + spqr_node toRemoveFrom = newNode == second ? parentArcNodes[1] : second; + mergeNodeArcList(dec, newNode, toRemoveFrom); + } + + spqr_member newMember = mergeMembers(dec, member, parent); + spqr_member toRemoveFrom = newMember == member ? parent : member; + mergeMemberArcList(dec, newMember, toRemoveFrom); + if( toRemoveFrom == parent ) + { + updateMemberParentInformation(dec, newMember, toRemoveFrom); + } + updateMemberType(dec, newMember, SPQR_MEMBERTYPE_RIGID); + *mergedMember = newMember; + + return SCIP_OKAY; +} + +static +SCIP_RETCODE netMatDecDataCreateDiGraph( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + BMS_BLKMEM * blkmem, /**< The block memory to use for the created digraph */ + SCIP_DIGRAPH** pdigraph, /**< Pointer to the pointer to store the created digraph */ + SCIP_Bool createrowarcs /**< Should the row arcs be added to the created digraph? */ +) +{ + /* Compute the number of rows m in the network matrix decomposition. The underlying graph has m+1 vertices. */ + int nrows = 0; + for( int i = 0; i < dec->memRows; ++i ) + if( netMatDecDataContainsRow(dec, i) ) + ++nrows; + + int nvertices = nrows + 1; + SCIP_CALL( SCIPdigraphCreate(pdigraph, blkmem, nvertices) ); + + /* Map decomposition nodes to graph nodes. The first node of each component is mapped to node 0 */ + int largestNode = largestNodeID(dec); + int* dectographnode; + SCIP_ALLOC( BMSallocBlockMemoryArray(blkmem, &dectographnode, largestNode) ); + for( int i = 0; i < largestNode; ++i ) + dectographnode[i] = -1; + + SCIP_DIGRAPH* digraph = *pdigraph; + /* Recursively explore the decomposition, adding the edges of each component and identifying them as needed */ + + typedef struct { + spqr_member member; + spqr_arc arc; + int graphfirstarchead; + int graphfirstarctail; + } CallData; + spqr_member largestmember = largestMemberID(dec); + CallData* calldata; + int ncalldata = 0; + SCIP_ALLOC( BMSallocBlockMemoryArray(blkmem, &calldata, largestmember) ); + + SCIP_Bool* rowprocessed; + SCIP_ALLOC( BMSallocClearBlockMemoryArray(blkmem, &rowprocessed, dec->memRows) ); + + /* This outer loop loops over all components; this is a bit ugly unfortunately */ + int nextnodeindex = 1; /* 0 is assigned to the first arc of the root members' head */ + for( int i = 0; i < dec->memRows ; ++i ) + { + spqr_arc rowarc = dec->rowArcs[i]; + if( SPQRarcIsInvalid(rowarc) ) + continue; + + assert(netMatDecDataContainsRow(dec, i)) ; + if( rowprocessed[i] ) + continue; + + /* Find the root member of the SPQR tree */ + spqr_member arcmember = findArcMember(dec, rowarc); + spqr_member rootmember = arcmember; + do + { + spqr_member parent = findMemberParent(dec, rootmember); + if( SPQRmemberIsInvalid(parent) ) + break; + rootmember = parent; + } + while( TRUE ); /*lint !e506*/ + + /* We identify all the SPQR trees at node 0 */ + calldata[0].member = rootmember; + calldata[0].arc = getFirstMemberArc(dec, rootmember); + calldata[0].graphfirstarchead = 0; + calldata[0].graphfirstarctail = nextnodeindex; + ++nextnodeindex; + ++ncalldata; + + while( ncalldata != 0 ) + { + /* Pop the next member to process of the call stack */ + CallData explore = calldata[ncalldata-1]; + --ncalldata; + + switch( getMemberType(dec, explore.member) ) + { + case SPQR_MEMBERTYPE_RIGID: + { + /* First, we set the initial arc's nodes */ + { + assert(dectographnode[findEffectiveArcHeadNoCompression(dec, explore.arc)] == -1); + assert(dectographnode[findEffectiveArcTailNoCompression(dec, explore.arc)] == -1); + spqr_node exploreHead = findArcHead(dec, explore.arc); + spqr_node exploreTail = findArcTail(dec, explore.arc); + if( findArcSign(dec, explore.arc).reversed ) + SCIPswapInts(&exploreTail, &exploreTail); + dectographnode[exploreHead] = explore.graphfirstarchead; + dectographnode[exploreTail] = explore.graphfirstarctail; + } + + /* Add all the members arcs to the graph */ + spqr_arc firstArc = getFirstMemberArc(dec, explore.member); + spqr_arc arc = firstArc; + do + { + spqr_node head = findArcHead(dec, arc); + spqr_node tail = findArcTail(dec, arc); + if( findArcSign(dec, arc).reversed ) + SCIPswapInts(&head, &tail); + + if( dectographnode[head] == -1 ) + { + dectographnode[head] = nextnodeindex; + ++nextnodeindex; + } + if( dectographnode[tail] == -1 ) + { + dectographnode[tail] = nextnodeindex; + ++nextnodeindex; + } + + /* If an arc is a marker to a child node, we add it to the call stack */ + if( arcIsChildMarker(dec, arc)) + { + spqr_member child = findArcChildMember(dec, arc); + spqr_arc toparent = markerToParent(dec, child); + + /* Identify head with head and tail with tail */ + /* Push member onto the processing stack (recursive call) */ + calldata[ncalldata].member = child; + calldata[ncalldata].arc = toparent; + calldata[ncalldata].graphfirstarchead = dectographnode[head]; + calldata[ncalldata].graphfirstarctail = dectographnode[tail]; + + ++ncalldata; + } + else if( arc != markerToParent(dec, explore.member) ) + { + /* We only realize non-virtual arcs */ + if( createrowarcs || !arcIsTree(dec, arc) ) + { + spqr_element element = arcGetElement(dec, arc); + size_t ind; + if( SPQRelementIsRow(element) ) + { + ind = SPQRelementToRow(element); /*lint !e732 */ + } + else + { + ind = SPQRelementToColumn(element) + dec->memRows; /*lint !e732 !e776 */ + } + SCIP_CALL( SCIPdigraphAddArc(digraph, dectographnode[tail], dectographnode[head], + (void*) ind) ); + } + } + spqr_element element = arcGetElement(dec, arc); + if( SPQRelementIsRow(element) && element != MARKER_ROW_ELEMENT ) + { + spqr_row row = SPQRelementToRow(element); + assert(row < dec->memRows); + rowprocessed[row] = TRUE; + } + arc = getNextMemberArc(dec, arc); + } + while( arc != firstArc ); + break; + } + + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_PARALLEL: + { + spqr_arc firstArc = getFirstMemberArc(dec, explore.member); + spqr_arc arc = firstArc; + do + { + /* If an edge is a marker to a child node, we add it to the call stack */ + if( arcIsChildMarker(dec, arc) ) + { + spqr_member child = findArcChildMember(dec, arc); + spqr_arc toparent = markerToParent(dec, child); + SCIP_Bool directionsmatch = + arcIsReversedNonRigid(dec, explore.arc) == arcIsReversedNonRigid(dec, arc); + + /* Identify head with head and tail with tail */ + /* Push member onto the processing stack (recursive call) */ + calldata[ncalldata].member = child; + calldata[ncalldata].arc = toparent; + calldata[ncalldata].graphfirstarchead = directionsmatch ? explore.graphfirstarchead + : explore.graphfirstarctail; + calldata[ncalldata].graphfirstarctail = directionsmatch ? explore.graphfirstarctail + : explore.graphfirstarchead; + + ++ncalldata; + } + else if( arc != markerToParent(dec, explore.member) ) + { + /* We only realize non-virtual arcs */ + if( createrowarcs || !arcIsTree(dec, arc) ) + { + spqr_element element = arcGetElement(dec, arc); + size_t ind; + if( SPQRelementIsRow(element) ) + { + ind = SPQRelementToRow(element); /*lint !e732 */ + } + else + { + ind = SPQRelementToColumn(element) + dec->memRows; /*lint !e732 !e776 */ + } + if( arcIsReversedNonRigid(dec, explore.arc) == arcIsReversedNonRigid(dec, arc) ) + { + SCIP_CALL( SCIPdigraphAddArc(digraph, explore.graphfirstarctail, + explore.graphfirstarchead, (void*) ind) ); + } + else + { + SCIP_CALL( SCIPdigraphAddArc(digraph, explore.graphfirstarchead, + explore.graphfirstarctail, (void*) ind) ); + } + } + } + spqr_element element = arcGetElement(dec, arc); + if( SPQRelementIsRow(element) && element != MARKER_ROW_ELEMENT ) + { + spqr_row row = SPQRelementToRow(element); + assert(row < dec->memRows); + rowprocessed[row] = TRUE; + } + arc = getNextMemberArc(dec, arc); + } + while( arc != firstArc ); + break; + } + + case SPQR_MEMBERTYPE_SERIES: + { + int count = getNumMemberArcs(dec, explore.member); + spqr_arc arc = explore.arc; + int firstnode = explore.graphfirstarchead; + int secondnode = explore.graphfirstarctail; + for( int j = 0; j < count; ++j ) + { + if( arcIsChildMarker(dec, arc) ) + { + spqr_member child = findArcChildMember(dec, arc); + spqr_arc toparent = markerToParent(dec, child); + SCIP_Bool directionsmatch = + arcIsReversedNonRigid(dec, explore.arc) == arcIsReversedNonRigid(dec, arc); + + /* Identify head with head and tail with tail */ + /* Push member onto the processing stack (recursive call) */ + calldata[ncalldata].member = child; + calldata[ncalldata].arc = toparent; + calldata[ncalldata].graphfirstarchead = directionsmatch ? firstnode : secondnode; + calldata[ncalldata].graphfirstarctail = directionsmatch ? secondnode : firstnode; + + ++ncalldata; + } + else if( arc != markerToParent(dec, explore.member) ) + { + /* We only realize non-virtual arcs */ + if( createrowarcs || !arcIsTree(dec, arc) ) + { + spqr_element element = arcGetElement(dec,arc); + size_t ind; + if( SPQRelementIsRow(element) ) + { + ind = SPQRelementToRow(element); /*lint !e732 */ + } + else + { + ind = SPQRelementToColumn(element) + dec->memRows; /*lint !e732 !e776 */ + } + + if( arcIsReversedNonRigid(dec, explore.arc) == arcIsReversedNonRigid(dec, arc) ) + { + SCIP_CALL( SCIPdigraphAddArc(digraph, secondnode, firstnode, (void*) ind) ); + } + else + { + SCIP_CALL( SCIPdigraphAddArc(digraph, firstnode, secondnode, (void*) ind) ); + } + } + } + + spqr_element element = arcGetElement(dec, arc); + if( SPQRelementIsRow(element) && element != MARKER_ROW_ELEMENT ) + { + spqr_row row = SPQRelementToRow(element); + rowprocessed[row] = TRUE; + } + + arc = getNextMemberArc(dec, arc); + secondnode = firstnode; + if( j >= count - 2 ) + { + firstnode = explore.graphfirstarctail; + } + else + { + firstnode = nextnodeindex; + ++nextnodeindex; + } + } + assert(arc == explore.arc); /* Check that we added all arcs */ + break; + } + + case SPQR_MEMBERTYPE_UNASSIGNED: + { + SCIPerrorMessage("Can not create graph for unassigned member type, exiting.\n"); + SCIPABORT(); + return SCIP_ERROR; + } + } + } + } + + /* We cannot have more vertices then there are in the graph. + * If we processed everything right, all vertices should have been hit. + */ + assert(nextnodeindex == nvertices); + + BMSfreeBlockMemoryArray(blkmem, &rowprocessed, dec->memRows); + BMSfreeBlockMemoryArray(blkmem, &calldata, largestmember); + BMSfreeBlockMemoryArray(blkmem, &dectographnode, largestNode); + + return SCIP_OKAY; +} + +/* ---------- START functions for column addition ------------------------------------------------------------------- */ + +/** Path arcs are all those arcs that correspond to nonzeros of the column to be added. */ +typedef int path_arc_id; +#define INVALID_PATH_ARC (-1) + +/** Returns true if the path arc is invalid. */ +static +SCIP_Bool pathArcIsInvalid( + const path_arc_id arc /**< The path arc to check */ + ) +{ + return arc < 0; +} + +/** Returns true if the path arc is valid. */ +static +SCIP_Bool pathArcIsValid( + const path_arc_id arc /**< The path arc to check */ + ) +{ + return !pathArcIsInvalid(arc); +} + +/** A forward linked list-node for the path arcs. + * + * Contains a pointer to the next path arc in the member graph and the + * next path arc in the SPQR tree. We additionally store copies of the corresponding arc's node indices. + */ +typedef struct +{ + spqr_arc arc; /**< The arc corresponding to the path ar c*/ + spqr_node arcHead; /**< A copy of the arc's head node index */ + spqr_node arcTail; /**< A copy of the arc's tail node index */ + path_arc_id nextMember; /**< Array index of the next path arc in the member path arc linked list */ + path_arc_id nextOverall; /**< Array index of the next path arc in the total path arc linked list */ + SCIP_Bool reversed; /**< Is the path arc occuring forwards or backwards in the path? + * Corresponds to the sign of the nonzero */ +} PathArcListNode; + +/** Index for the reduced members, which are the members of the sub-SPQR tree containing all path arcs. + * + * Note that these are indexed separately from the members of the SPQR tree! + */ +typedef int reduced_member_id; +#define INVALID_REDUCED_MEMBER (-1) + +/** Returns true if the reduced member is invalid. */ +static +SCIP_Bool reducedMemberIsInvalid( + const reduced_member_id id /**< The reduced member to check */ + ) +{ + return id < 0; +} + +/** Returns true if the reduced member is valid. */ +static +SCIP_Bool reducedMemberIsValid( + const reduced_member_id id /**< The reduced member to check */ + ) +{ + return !reducedMemberIsInvalid(id); +} + +/** Array index for the children of a reduced member */ +typedef int children_idx; + +/** Type of the member, signifies to what degree we processed the member and how to treat with it when updating the + * graph. + */ +typedef enum +{ + REDUCEDMEMBER_TYPE_UNASSIGNED = 0, /**< We have not yet decided if the reduced member contains a valid path */ + REDUCEDMEMBER_TYPE_CYCLE = 1, /**< The reduced member is a leaf node of the SPQR tree and contains a path + * that forms a cycle with the corresponding virtual edge, i.e. + * it is propagated */ + REDUCEDMEMBER_TYPE_MERGED = 2, /**< The reduced member contains a path and is not propagated */ + REDUCEDMEMBER_TYPE_NOT_NETWORK = 3 /**< The reduced member does not have a valid path or can not be oriented + * to form a valid path with the other members. */ +} ReducedMemberType; + +/** Defines the structure of the path in the reduced member */ +typedef enum +{ + INTO_HEAD = 0, /**< The directed path goes into the head of the virtual arc */ + INTO_TAIL = 1, /**< The directed path goes into the tail of the virtual arc */ + OUT_HEAD = 2, /**< The directed path goes out of the head of the virtual arc */ + OUT_TAIL = 3 /**< The directed path goes out of the tail of the virtual arc */ +} MemberPathType; + +/** Check if the path type is into. */ +static +SCIP_Bool isInto( + MemberPathType type /**< The path type to check */ + ) +{ + return type == INTO_HEAD || type == INTO_TAIL; +} + +/** Check if the path end node is the head or tail node of the corresponding arc. */ +static +SCIP_Bool isHead( + MemberPathType type /**< The path type to check */ + ) +{ + return type == INTO_HEAD || type == OUT_HEAD; +} + +/** A struct that keeps track of the relevant data for the members that are in the subtree given by the specified row + * arcs. + * + * We typically call these 'reduced' members (members of the reduced tree). + */ +typedef struct +{ + spqr_member member; /**< The id of the member */ + spqr_member rootMember; /**< The root member of the arborescence that contains this member */ + int depth; /**< The depth of this member in the arborescence */ + ReducedMemberType type; /**< The type of the member */ + reduced_member_id parent; /**< The reduced member id of the parent of this reduced member */ + + children_idx firstChild; /**< The index of the first child in the children array. */ + children_idx numChildren; /**< The number of children in the arborescence of this reduced member */ + + path_arc_id firstPathArc; /**< Head of the linked list containing the path arcs */ + int numPathArcs; /**< The number of path arcs in the linked list */ + + SCIP_Bool reverseArcs; /**< Will the arcs in this component be reversed? */ + spqr_node rigidPathStart; /**< The start node of the path. Only used for Rigid/3-connected nodes */ + spqr_node rigidPathEnd; /**< The end node of the path. Only used for Rigid/3-connected nodes */ + + SCIP_Bool pathBackwards; /**< Indicates if the path direction is reversed with respect to the + * default orientation within series and parallel members. */ + + int numPropagatedChildren; /**< Counts the number of children that are cycles that propagated to this + * reduced member */ + int componentIndex; /**< Stores the index of the component of the SPQR forest that this reduced + * member is contained in. */ + + MemberPathType pathType; /**< The type of the path with respect to the virtual arc */ + reduced_member_id nextPathMember; /**< Indicates the id of the next reduced member in the path during merging. + * During merging, the SPQR tree must be a path itself. */ + SCIP_Bool nextPathMemberIsParent; /**< Indicates if the next reduced member in the path is a parent of + * this member */ + spqr_arc pathSourceArc; /**< The virtual arc from where the path originates */ + spqr_arc pathTargetArc; /**< The virtual arc where the path has to go */ +} SPQRColReducedMember; + +/** Keeps track of the data relevant for each SPQR tree in the SPQR forest. */ +typedef struct +{ + int rootDepth; /**< The depth of the root node of the subtree in the arborescence */ + reduced_member_id root; /**< The reduced member id of the root */ + + reduced_member_id pathEndMembers[2]; /**< The reduced members that contain the ends of the path */ + int numPathEndMembers; /**< The number of reduced members that contain an end of the path */ +} SPQRColReducedComponent; + +/** Keeps track of the reduced member and the reduced member that is the root of the arborescence for a single member */ +typedef struct +{ + reduced_member_id reducedMember; /**< The ID of the associated reduced member */ + reduced_member_id rootDepthMinimizer; /**< The ID of the reduced member that is the root of the arborescence this + * reduced member is contained in. */ +} MemberInfo; + +/** Data to be used for the recursive algorithms that creates the sub-SPQR trees that contain the path edges */ +typedef struct +{ + spqr_member member; /**< The current member */ +} CreateReducedMembersCallstack; + +/** The main datastructure that manages all the data for column-addition in network matrices */ +typedef struct +{ + SCIP_Bool remainsNetwork; /**< Does the addition of the current column give a network matrix? */ + + SPQRColReducedMember* reducedMembers; /**< The array of reduced members, that form the subtree containing the + * rows of the current column. */ + int memReducedMembers; /**< Number of allocated slots in the reduced member array */ + int numReducedMembers; /**< Number of used slots in the reduced member array */ + + SPQRColReducedComponent* reducedComponents;/**< The array of reduced components, + * that represent the SPQR trees in the SPQR forest */ + int memReducedComponents;/**< Number of allocated slots in the reduced component array */ + int numReducedComponents;/**< Number of used slots in the reduced component array */ + + MemberInfo* memberInformation; /**< Array with member information; tracks the reduced member id that + * corresponds to every member in the decomposition. */ + int memMemberInformation;/**< Number of allocated slots in the member information array */ + int numMemberInformation;/**< Number of used slots in the member information array */ + + reduced_member_id* childrenStorage; /**< Array that stores the children of the reduced member arborescences. + * Each reduced member has a 'firstChild' field and a length, that points + * to the subarray within this array with its children. This array is + * shared here in order to minimize allocations across iterations. */ + int memChildrenStorage; /**< Number of allocated slots for the children storage array */ + int numChildrenStorage; /**< Number of used slots for the children storage array */ + + PathArcListNode* pathArcs; /**< Array that contains the linked-list nodes of the path arcs, that + * correspond to the rows of the current column. */ + int memPathArcs; /**< Number of allocated slots for the path arc array */ + int numPathArcs; /**< Number of used slots for the path arc array */ + path_arc_id firstOverallPathArc;/**< Head node of the linked list containing all path arcs */ + + int* nodeInPathDegree; /**< Array that contains the in degree of all nodes */ + int* nodeOutPathDegree; /**< Array that contains the out degree of all nodes */ + int memNodePathDegree; /**< The number of allocated slots for the node-degree arrays */ + + SCIP_Bool* arcInPath; /**< Is the given arc in the path? */ + SCIP_Bool* arcInPathReversed; /**< Is the given arc's direction reversed in the path? */ + int memArcsInPath; /**< The number of allocated slots for the arcInPath(Reversed) arrays */ + + CreateReducedMembersCallstack* createReducedMembersCallStack; /**< Callstack for createReducedMembers() */ + int memCreateReducedMembersCallStack; /**< Allocated memory for callstack for createReducedMembers() */ + + spqr_col newColIndex; /**< The index of the new column to be added */ + + spqr_row* newRowArcs; /**< The row indices of the nonzeros of the column to be added, that are + * not yet in the decomposition. */ + SCIP_Bool* newRowArcReversed; /**< True if the nonzero corresponding to the row index is -1, + * false otherwise */ + int memNewRowArcs; /**< Number of allocated slots in newRowArcs(Reversed) */ + int numNewRowArcs; /**< Number of new rows in the column to be added */ + + spqr_arc* decompositionRowArcs; /**< For each row nonzero that is in the decomposition, + * stores the corresponding decomposition arc */ + SCIP_Bool* decompositionArcReversed; /**< For each row nonzero that is in the decomposition, + * stores whether the corresponding decomposition arc is reversed */ + int memDecompositionRowArcs; /**< Number of allocated slots in decompositionRowArcs(Reversed) */ + int numDecompositionRowArcs; /**< Number of used slots in decompositionRowArcs(Reversed) */ + + spqr_member* leafMembers; /**< Array that stores the leaf members of the SPQR forest */ + int numLeafMembers; /**< Number of used slots in leafMembers array */ + int memLeafMembers; /**< Number of allocated slots in leafMembers array */ +} SCIP_NETCOLADD; + +/** Clean up internal temporary data structures that were used in the previous column iteration. */ +static +void cleanupPreviousIteration( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol /**< The network matrix column addition data structure */ + ) +{ + assert(dec); + assert(newCol); + + path_arc_id pathArc = newCol->firstOverallPathArc; + while( pathArcIsValid(pathArc) ) + { + spqr_node head = newCol->pathArcs[pathArc].arcHead; + spqr_node tail = newCol->pathArcs[pathArc].arcTail; + if( SPQRnodeIsValid(head) ) + { + newCol->nodeInPathDegree[head] = 0; + } + if( SPQRnodeIsValid(tail) ) + { + newCol->nodeOutPathDegree[tail] = 0; + } + + spqr_arc arc = newCol->pathArcs[pathArc].arc; + if( arc < newCol->memArcsInPath ) + { + newCol->arcInPath[arc] = FALSE; + newCol->arcInPathReversed[arc] = FALSE; + } + pathArc = newCol->pathArcs[pathArc].nextOverall; + } +#ifndef NDEBUG + for( int i = 0; i < newCol->memArcsInPath; ++i ) + { + assert(newCol->arcInPath[i] == FALSE); + assert(newCol->arcInPathReversed[i] == FALSE); + } + + for( int i = 0; i < newCol->memNodePathDegree; ++i ) + { + assert(newCol->nodeInPathDegree[i] == 0); + assert(newCol->nodeOutPathDegree[i] == 0); + } +#endif + + newCol->firstOverallPathArc = INVALID_PATH_ARC; + newCol->numPathArcs = 0; +} + +/** Create a new network column addition datastructure. */ +static +SCIP_RETCODE netcoladdCreate( + BMS_BLKMEM* blkmem, /**< Block memory */ + SCIP_NETCOLADD** pcoladd /**< Out-pointer for the created network column addition datastructure */ +) +{ + assert(blkmem); + + SCIP_ALLOC( BMSallocBlockMemory(blkmem, pcoladd) ); + SCIP_NETCOLADD* newCol = *pcoladd; + + newCol->remainsNetwork = FALSE; + newCol->reducedMembers = NULL; + newCol->memReducedMembers = 0; + newCol->numReducedMembers = 0; + + newCol->reducedComponents = NULL; + newCol->memReducedComponents = 0; + newCol->numReducedComponents = 0; + + newCol->memberInformation = NULL; + newCol->memMemberInformation = 0; + newCol->numMemberInformation = 0; + + newCol->childrenStorage = NULL; + newCol->memChildrenStorage = 0; + newCol->numChildrenStorage = 0; + + newCol->pathArcs = NULL; + newCol->memPathArcs = 0; + newCol->numPathArcs = 0; + newCol->firstOverallPathArc = INVALID_PATH_ARC; + + newCol->nodeInPathDegree = NULL; + newCol->nodeOutPathDegree = NULL; + newCol->memNodePathDegree = 0; + + newCol->arcInPath = NULL; + newCol->arcInPathReversed = NULL; + newCol->memArcsInPath = 0; + + newCol->createReducedMembersCallStack = NULL; + newCol->memCreateReducedMembersCallStack = 0; + + newCol->newColIndex = SPQR_INVALID_COL; + + newCol->newRowArcs = NULL; + newCol->newRowArcReversed = NULL; + newCol->memNewRowArcs = 0; + newCol->numNewRowArcs = 0; + + newCol->decompositionRowArcs = NULL; + newCol->decompositionArcReversed = NULL; + newCol->memDecompositionRowArcs = 0; + newCol->numDecompositionRowArcs = 0; + + newCol->leafMembers = NULL; + newCol->numLeafMembers = 0; + newCol->memLeafMembers = 0; + + return SCIP_OKAY; +} + +/** Free a network column addition datastructure */ +static +void netcoladdFree( + BMS_BLKMEM* blkmem, /**< Block memory */ + SCIP_NETCOLADD** pcoladd /**< Pointer to the network column addition datastructure to be freed */ +) +{ + assert(blkmem); + + SCIP_NETCOLADD* newCol = *pcoladd; + BMSfreeBlockMemoryArray(blkmem, &newCol->decompositionRowArcs, newCol->memDecompositionRowArcs); + BMSfreeBlockMemoryArray(blkmem, &newCol->decompositionArcReversed, newCol->memDecompositionRowArcs); + BMSfreeBlockMemoryArray(blkmem, &newCol->newRowArcs, newCol->memNewRowArcs); + BMSfreeBlockMemoryArray(blkmem, &newCol->newRowArcReversed, newCol->memNewRowArcs); + BMSfreeBlockMemoryArray(blkmem, &newCol->createReducedMembersCallStack, newCol->memCreateReducedMembersCallStack); + BMSfreeBlockMemoryArray(blkmem, &newCol->arcInPath, newCol->memArcsInPath); + BMSfreeBlockMemoryArray(blkmem, &newCol->arcInPathReversed, newCol->memArcsInPath); + BMSfreeBlockMemoryArray(blkmem, &newCol->nodeInPathDegree, newCol->memNodePathDegree); + BMSfreeBlockMemoryArray(blkmem, &newCol->nodeOutPathDegree, newCol->memNodePathDegree); + BMSfreeBlockMemoryArray(blkmem, &newCol->pathArcs, newCol->memPathArcs); + BMSfreeBlockMemoryArray(blkmem, &newCol->childrenStorage, newCol->memChildrenStorage); + BMSfreeBlockMemoryArray(blkmem, &newCol->memberInformation, newCol->memMemberInformation); + BMSfreeBlockMemoryArray(blkmem, &newCol->reducedComponents, newCol->memReducedComponents); + BMSfreeBlockMemoryArray(blkmem, &newCol->reducedMembers, newCol->memReducedMembers); + BMSfreeBlockMemoryArray(blkmem, &newCol->leafMembers, newCol->memLeafMembers); + + BMSfreeBlockMemory(blkmem, pcoladd); +} + +/** Adds members to the reduced member tree, by starting at the given member, jumping up to the parent, repeating this + * procedure until the root has been added. + */ +static +reduced_member_id createReducedMembersToRoot( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + const spqr_member firstMember /**< The member to create a reduced member for */ +) +{ + assert(SPQRmemberIsValid(firstMember)); + + /* Originally a recursive algorithm, but for large matrices we need to unroll recursion to prevent stack overflows + * from too many recursive calls */ + CreateReducedMembersCallstack* callstack = newCol->createReducedMembersCallStack; + callstack[0].member = firstMember; + int callDepth = 0; + + while( callDepth >= 0 ) + { + spqr_member member = callstack[callDepth].member; + reduced_member_id reducedMember = newCol->memberInformation[member].reducedMember; + + SCIP_Bool reducedValid = reducedMemberIsValid(reducedMember); + if( !reducedValid ) + { + //reduced member was not yet created; we create it + reducedMember = newCol->numReducedMembers; + + SPQRColReducedMember* reducedMemberData = &newCol->reducedMembers[reducedMember]; + ++newCol->numReducedMembers; + + reducedMemberData->member = member; + reducedMemberData->numChildren = 0; + + reducedMemberData->type = REDUCEDMEMBER_TYPE_UNASSIGNED; + reducedMemberData->numPropagatedChildren = 0; + reducedMemberData->firstPathArc = INVALID_PATH_ARC; + reducedMemberData->numPathArcs = 0; + reducedMemberData->rigidPathStart = SPQR_INVALID_NODE; + reducedMemberData->rigidPathEnd = SPQR_INVALID_NODE; + + reducedMemberData->componentIndex = -1; + //The children are set later + + newCol->memberInformation[member].reducedMember = reducedMember; + assert(memberIsRepresentative(dec, member)); + spqr_member parentMember = findMemberParent(dec, member); + + if( SPQRmemberIsValid(parentMember) ) + { + //recursive call to parent member + ++callDepth; + assert(callDepth < newCol->memCreateReducedMembersCallStack); + callstack[callDepth].member = parentMember; + continue; + } + else + { + //we found a new reduced decomposition component + + reducedMemberData->parent = INVALID_REDUCED_MEMBER; + reducedMemberData->depth = 0; + reducedMemberData->rootMember = member; + reducedMemberData->componentIndex = newCol->numReducedComponents; + + assert(newCol->numReducedComponents < newCol->memReducedComponents); + newCol->reducedComponents[newCol->numReducedComponents].root = reducedMember; + newCol->reducedComponents[newCol->numReducedComponents].numPathEndMembers = 0; + newCol->reducedComponents[newCol->numReducedComponents].pathEndMembers[0] = INVALID_REDUCED_MEMBER; + newCol->reducedComponents[newCol->numReducedComponents].pathEndMembers[1] = INVALID_REDUCED_MEMBER; + ++newCol->numReducedComponents; + } + } + if( reducedValid ) + { + assert(reducedMember < newCol->numReducedMembers); + //Reduced member was already created in earlier call + //update the depth of the root if appropriate + reduced_member_id* depthMinimizer = &newCol->memberInformation[newCol->reducedMembers[reducedMember].rootMember].rootDepthMinimizer; + if( reducedMemberIsInvalid(*depthMinimizer) || + newCol->reducedMembers[reducedMember].depth < newCol->reducedMembers[*depthMinimizer].depth ) + { + *depthMinimizer = reducedMember; + } + } + while( TRUE ) /*lint !e716*/ + { + --callDepth; + if( callDepth < 0 ) + break; + spqr_member parentMember = callstack[callDepth + 1].member; + reduced_member_id parentReducedMember = newCol->memberInformation[parentMember].reducedMember; + spqr_member currentMember = callstack[callDepth].member; + reduced_member_id currentReducedMember = newCol->memberInformation[currentMember].reducedMember; + + SPQRColReducedMember* parentReducedMemberData = &newCol->reducedMembers[parentReducedMember]; + SPQRColReducedMember* reducedMemberData = &newCol->reducedMembers[currentReducedMember]; + + reducedMemberData->parent = parentReducedMember; + reducedMemberData->depth = parentReducedMemberData->depth + 1; + reducedMemberData->rootMember = parentReducedMemberData->rootMember; + //ensure that all newly created reduced members are pointing to the correct component + assert(parentReducedMemberData->componentIndex >= 0); + reducedMemberData->componentIndex = parentReducedMemberData->componentIndex; + + newCol->reducedMembers[parentReducedMember].numChildren++; + } + } + + reduced_member_id returnedMember = newCol->memberInformation[callstack[0].member].reducedMember; + return returnedMember; +} + +/** Construct the reduced decomposition, which is the smallest subtree containing all members path arcs. */ +static +SCIP_RETCODE constructReducedDecomposition( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol /**< The network matrix column addition data structure */ +) +{ + assert(dec); + assert(newCol); +#ifndef NDEBUG + for( int i = 0; i < newCol->memMemberInformation; ++i ) + { + assert(reducedMemberIsInvalid(newCol->memberInformation[i].reducedMember)); + } +#endif + + newCol->numReducedComponents = 0; + newCol->numReducedMembers = 0; + if( newCol->numDecompositionRowArcs == 0 ) + {//Early return in case the reduced decomposition will be empty + return SCIP_OKAY; + } + assert(newCol->numReducedMembers == 0); + assert(newCol->numReducedComponents == 0); + + int newSize = largestMemberID(dec);//Is this sufficient? + if( newSize > newCol->memReducedMembers ) + { + int newArraySize = MAX(2 * newCol->memReducedMembers, newSize); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->reducedMembers, newCol->memReducedMembers, newArraySize) ); + newCol->memReducedMembers = newArraySize; + } + if( newSize > newCol->memMemberInformation ) + { + int updatedSize = MAX(2 * newCol->memMemberInformation, newSize); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->memberInformation, newCol->memMemberInformation, updatedSize) ); + for( int i = newCol->memMemberInformation; i < updatedSize; ++i ) + { + newCol->memberInformation[i].reducedMember = INVALID_REDUCED_MEMBER; + newCol->memberInformation[i].rootDepthMinimizer = INVALID_REDUCED_MEMBER; + } + newCol->memMemberInformation = updatedSize; + } + + int numComponents = numConnectedComponents(dec); + if( numComponents > newCol->memReducedComponents ) + { + int updatedSize = MAX(2 * newCol->memReducedComponents, numComponents); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->reducedComponents, newCol->memReducedComponents, updatedSize) ); + newCol->memReducedComponents = updatedSize; + } + + int numMembers = getNumMembers(dec); + if( newCol->memCreateReducedMembersCallStack < numMembers ) + { + int updatedSize = MAX(2 * newCol->memCreateReducedMembersCallStack, numMembers); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->createReducedMembersCallStack, + newCol->memCreateReducedMembersCallStack, updatedSize) ); + newCol->memCreateReducedMembersCallStack = updatedSize; + } + + //Create the reduced members (recursively) + for( int i = 0; i < newCol->numDecompositionRowArcs; ++i ) + { + assert(i < newCol->memDecompositionRowArcs); + spqr_arc arc = newCol->decompositionRowArcs[i]; + spqr_member arcMember = findArcMember(dec, arc); + reduced_member_id reducedMember = createReducedMembersToRoot(dec, newCol, arcMember); + reduced_member_id* depthMinimizer = &newCol->memberInformation[newCol->reducedMembers[reducedMember].rootMember].rootDepthMinimizer; + if( reducedMemberIsInvalid(*depthMinimizer) ) + { + *depthMinimizer = reducedMember; + } + } + + //Set the reduced roots according to the root depth minimizers + for( int i = 0; i < newCol->numReducedComponents; ++i ) + { + SPQRColReducedComponent* component = &newCol->reducedComponents[i]; + spqr_member rootMember = newCol->reducedMembers[component->root].member; + reduced_member_id reducedMinimizer = newCol->memberInformation[rootMember].rootDepthMinimizer; + component->rootDepth = newCol->reducedMembers[reducedMinimizer].depth; + component->root = reducedMinimizer; + + //This simplifies code further down which does not need to be component-aware; just pretend that the reduced member is the new root. + newCol->reducedMembers[component->root].parent = INVALID_REDUCED_MEMBER; + assert(memberIsRepresentative(dec, rootMember)); + } + + //update the children array + int numTotalChildren = 0; + for( int i = 0; i < newCol->numReducedMembers; ++i ) + { + SPQRColReducedMember* reducedMember = &newCol->reducedMembers[i]; + reduced_member_id minimizer = newCol->memberInformation[reducedMember->rootMember].rootDepthMinimizer; + if( reducedMember->depth >= newCol->reducedMembers[minimizer].depth ) + { + reducedMember->firstChild = numTotalChildren; + numTotalChildren += reducedMember->numChildren; + reducedMember->numChildren = 0; + } + } + + if( newCol->memChildrenStorage < numTotalChildren ) + { + int newMemSize = MAX(newCol->memChildrenStorage * 2, numTotalChildren); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->childrenStorage, newCol->memChildrenStorage, newMemSize) ); + newCol->memChildrenStorage = newMemSize; + } + newCol->numChildrenStorage = numTotalChildren; + + //Fill up the children array` + for( reduced_member_id reducedMember = 0; reducedMember < newCol->numReducedMembers; ++reducedMember ) + { + SPQRColReducedMember* reducedMemberData = &newCol->reducedMembers[reducedMember]; + if( reducedMemberData->depth <= + newCol->reducedMembers[newCol->memberInformation[reducedMemberData->rootMember].rootDepthMinimizer].depth ) + { + continue; + } + spqr_member parentMember = findMemberParent(dec, reducedMemberData->member); + reduced_member_id parentReducedMember = SPQRmemberIsValid(parentMember) + ? newCol->memberInformation[parentMember].reducedMember + : INVALID_REDUCED_MEMBER; + if( reducedMemberIsValid(parentReducedMember)) + { + SPQRColReducedMember* parentReducedMemberData = &newCol->reducedMembers[parentReducedMember]; + newCol->childrenStorage[parentReducedMemberData->firstChild + + parentReducedMemberData->numChildren] = reducedMember; + ++parentReducedMemberData->numChildren; + } + } + + //Clean up the root depth minimizers. + for( int i = 0; i < newCol->numReducedMembers; ++i ) + { + SPQRColReducedMember* reducedMember = &newCol->reducedMembers[i]; + assert(reducedMember); + spqr_member rootMember = reducedMember->rootMember; + assert(rootMember >= 0); + assert(rootMember < dec->memMembers); + newCol->memberInformation[rootMember].rootDepthMinimizer = INVALID_REDUCED_MEMBER; + } + + return SCIP_OKAY; +} + +/** Clean up the memberinformation array at the end of an iteration. */ +static +void cleanUpMemberInformation( + SCIP_NETCOLADD* newCol /**< The network matrix column addition data structure */ + ) +{ + //This loop is at the end as memberInformation is also used to assign the cut arcs during propagation + //Clean up the memberInformation array + for( int i = 0; i < newCol->numReducedMembers; ++i ) + { + newCol->memberInformation[newCol->reducedMembers[i].member].reducedMember = INVALID_REDUCED_MEMBER; + } +#ifndef NDEBUG + for( int i = 0; i < newCol->memMemberInformation; ++i ) + { + assert(reducedMemberIsInvalid(newCol->memberInformation[i].reducedMember)); + } +#endif +} + +/** Marks the given arc as a path arc and adds it to the relevant data structures. */ +static +void createPathArc( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + const spqr_arc arc, /**< The arc to mark as a path arc */ + const reduced_member_id reducedMember, /**< The reduced member containing the arc */ + SCIP_Bool reversed /**< Indicates if the new column entry has +1 or -1 (TRUE) for the arc */ +) +{ + assert(dec); + assert(newCol); + + path_arc_id path_arc = newCol->numPathArcs; + PathArcListNode* listNode = &newCol->pathArcs[path_arc]; + listNode->arc = arc; + + listNode->nextMember = newCol->reducedMembers[reducedMember].firstPathArc; + newCol->reducedMembers[reducedMember].firstPathArc = path_arc; + newCol->reducedMembers[reducedMember].numPathArcs += 1; + + listNode->nextOverall = newCol->firstOverallPathArc; + newCol->firstOverallPathArc = path_arc; + + ++newCol->numPathArcs; + assert(newCol->numPathArcs <= newCol->memPathArcs); + + assert(arc < newCol->memArcsInPath); + newCol->arcInPath[arc] = TRUE; + newCol->arcInPathReversed[arc] = reversed; + assert(memberIsRepresentative(dec, newCol->reducedMembers[reducedMember].member)); + if( getMemberType(dec, newCol->reducedMembers[reducedMember].member) == SPQR_MEMBERTYPE_RIGID ) + { + + listNode->arcHead = findEffectiveArcHead(dec, arc); + listNode->arcTail = findEffectiveArcTail(dec, arc); + if( reversed ) + { + SCIPswapInts(&listNode->arcHead, &listNode->arcTail); + } + assert(SPQRnodeIsValid(listNode->arcHead) && SPQRnodeIsValid(listNode->arcTail)); + assert(listNode->arcHead < newCol->memNodePathDegree && listNode->arcTail < newCol->memNodePathDegree); + ++newCol->nodeInPathDegree[listNode->arcHead]; + ++newCol->nodeOutPathDegree[listNode->arcTail]; + } + else + { + listNode->arcHead = SPQR_INVALID_NODE; + listNode->arcTail = SPQR_INVALID_NODE; + } + listNode->reversed = reversed; +} + +/** Mark all the row indices of the new column as path arcs */ +static +SCIP_RETCODE createPathArcs( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol /**< The network matrix column addition data structure */ +) +{ + int maxNumPathArcs = newCol->numDecompositionRowArcs + getNumMembers(dec); + if( newCol->memPathArcs < maxNumPathArcs ) + { + int newMaxNumArcs = 2 * maxNumPathArcs;//safety factor to prevent very frequent reallocations + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->pathArcs, newCol->memPathArcs, newMaxNumArcs) ); + newCol->memPathArcs = newMaxNumArcs; + } + int maxPathArcIndex = largestArcID(dec); + if( newCol->memArcsInPath < maxPathArcIndex ) + { + int newSize = 2 * maxPathArcIndex;//safety factor to prevent very frequent reallocations + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->arcInPath, newCol->memArcsInPath, newSize) ); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->arcInPathReversed, newCol->memArcsInPath, newSize) ); + + for( int i = newCol->memArcsInPath; i < newSize; ++i ) + { + newCol->arcInPath[i] = FALSE; + newCol->arcInPathReversed[i] = FALSE; + } + newCol->memArcsInPath = newSize; + } + int maxNumNodes = largestNodeID(dec); + if( newCol->memNodePathDegree < maxNumNodes ) + { + int newSize = 2 * maxNumNodes;//safety factor to prevent very frequent reallocations + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->nodeInPathDegree, newCol->memNodePathDegree, newSize) ); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->nodeOutPathDegree, newCol->memNodePathDegree, newSize) ); + for( int i = newCol->memNodePathDegree; i < newSize; ++i ) + { + newCol->nodeInPathDegree[i] = 0; + newCol->nodeOutPathDegree[i] = 0; + } + newCol->memNodePathDegree = newSize; + } + for( int i = 0; i < newCol->numDecompositionRowArcs; ++i ) + { + spqr_arc arc = newCol->decompositionRowArcs[i]; + spqr_member member = findArcMember(dec, arc); + reduced_member_id reducedMember = newCol->memberInformation[member].reducedMember; + createPathArc(dec, newCol, arc, reducedMember, newCol->decompositionArcReversed[i]); + } + + return SCIP_OKAY; +} + + +/** Saves the information of the current row and partitions it based on whether or not the given columns are + * already part of the decomposition. + */ +static +SCIP_RETCODE newColUpdateColInformation( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + spqr_col column, /**< The column that is checked */ + const spqr_row* nonzeroRows, /**< The column's row indices */ + const double* nonzeroValues, /**< The column's nonzero values */ + int numNonzeros /**< The number of nonzeros in the column */ +) +{ + newCol->newColIndex = column; + + newCol->numDecompositionRowArcs = 0; + newCol->numNewRowArcs = 0; + + for( int i = 0; i < numNonzeros; ++i ) + { + spqr_arc rowArc = getDecompositionRowArc(dec, nonzeroRows[i]); + SCIP_Bool reversed = nonzeroValues[i] < 0.0; + if( SPQRarcIsValid(rowArc) ) + {//If the arc is the current decomposition: save it in the array + if( newCol->numDecompositionRowArcs == newCol->memDecompositionRowArcs ) + { + int newNumArcs = newCol->memDecompositionRowArcs == 0 ? 8 : 2 * newCol->memDecompositionRowArcs; + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->decompositionRowArcs, + newCol->memDecompositionRowArcs, newNumArcs) ); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->decompositionArcReversed, + newCol->memDecompositionRowArcs, newNumArcs) ); + newCol->memDecompositionRowArcs = newNumArcs; + } + newCol->decompositionRowArcs[newCol->numDecompositionRowArcs] = rowArc; + newCol->decompositionArcReversed[newCol->numDecompositionRowArcs] = reversed; + ++newCol->numDecompositionRowArcs; + } + else + { + //Not in the decomposition: add it to the set of arcs which are newly added with this row. + if( newCol->numNewRowArcs == newCol->memNewRowArcs ) + { + int newNumArcs = newCol->memNewRowArcs == 0 ? 8 : 2 * newCol->memNewRowArcs; + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->newRowArcs, + newCol->memNewRowArcs, newNumArcs) ); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->newRowArcReversed, + newCol->memNewRowArcs, newNumArcs) ); + newCol->memNewRowArcs = newNumArcs; + } + newCol->newRowArcs[newCol->numNewRowArcs] = nonzeroRows[i]; + newCol->newRowArcReversed[newCol->numNewRowArcs] = reversed; + newCol->numNewRowArcs++; + } + } + + return SCIP_OKAY; +} + +/** Compute and store the leaf members of the reduced SPQR forest */ +static +SCIP_RETCODE computeLeafMembers( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol /**< The network matrix column addition data structure */ +) +{ + if( newCol->numReducedMembers > newCol->memLeafMembers ) + { + int newSize = MAX(newCol->numReducedMembers, 2 * newCol->memLeafMembers); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newCol->leafMembers, newCol->memLeafMembers, newSize) ); + newCol->memLeafMembers = newSize; + } + newCol->numLeafMembers = 0; + + for( reduced_member_id reducedMember = 0; reducedMember < newCol->numReducedMembers; ++reducedMember ) + { + if( newCol->reducedMembers[reducedMember].numChildren == 0 ) + { + newCol->leafMembers[newCol->numLeafMembers] = reducedMember; + ++newCol->numLeafMembers; + } + } + + return SCIP_OKAY; +} + +/** Checks if the path arcs in the given rigid member form a path */ +static +void determineRigidPath( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + SPQRColReducedMember* redMem /**< The rigid reduced member to determine the path in */ +) +{ + assert(dec); + assert(newCol); + assert(redMem); + + /* Simply count the in and out degrees of the nodes to check if the path arcs form a path. We have a valid path if + * and only if the head of the tail of the path are the only nodes with 1 adjacent edge, and all other nodes + * have 2 adjacent edges*/ + SCIP_Bool isValidPath = TRUE; + redMem->rigidPathStart = SPQR_INVALID_NODE; + redMem->rigidPathEnd = SPQR_INVALID_NODE; + for( path_arc_id pathArc = redMem->firstPathArc; pathArcIsValid(pathArc); pathArc = newCol->pathArcs[pathArc].nextMember ) + { + spqr_node head = newCol->pathArcs[pathArc].arcHead; + spqr_node tail = newCol->pathArcs[pathArc].arcTail; + assert(nodeIsRepresentative(dec, head) && nodeIsRepresentative(dec, tail)); + + if( newCol->nodeInPathDegree[head] > 1 || newCol->nodeOutPathDegree[tail] > 1 ) + { + //not network -> stop + isValidPath = FALSE; + break; + } + if( newCol->nodeOutPathDegree[head] == 0 ) + { + //found end node + //If this is the second, stop + if( SPQRnodeIsValid(redMem->rigidPathEnd) ) + { + isValidPath = FALSE; + break; + } + redMem->rigidPathEnd = head; + } + if( newCol->nodeInPathDegree[tail] == 0 ) + { + //Found start node. + //If this is the second, stop. + if( SPQRnodeIsValid(redMem->rigidPathStart) ) + { + isValidPath = FALSE; + break; + } + redMem->rigidPathStart = tail; + } + } + //assert that both a start and end node have been found + assert(!isValidPath || ( SPQRnodeIsValid(redMem->rigidPathStart) && SPQRnodeIsValid(redMem->rigidPathEnd))); + if( !isValidPath ) + { + redMem->rigidPathStart = SPQR_INVALID_NODE; + redMem->rigidPathEnd = SPQR_INVALID_NODE; + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + } +} + +/** Determines the member's type for the case where the reduced tree consists of a single rigid member. */ +static +void determineSingleRigidType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMember /**< The reduced member to check */ + ) +{ + assert(dec); + assert(newCol); + assert(reducedMemberIsValid(reducedMember)); + + SPQRColReducedMember* redMem = &newCol->reducedMembers[reducedMember]; + assert(pathArcIsValid(redMem->firstPathArc)); + determineRigidPath(dec, newCol, redMem); + if( redMem->type != REDUCEDMEMBER_TYPE_NOT_NETWORK ) + { + redMem->type = REDUCEDMEMBER_TYPE_MERGED; + } +} + +/** Determines the member's type for the case where the reduced tree consists of a single member. */ +static +void determineSingleComponentType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMember /**< The reduced member to check */ +) +{ + assert(dec); + assert(newCol); + + int numNonPropagatedAdjacent = + newCol->reducedMembers[reducedMember].numChildren - newCol->reducedMembers[reducedMember].numPropagatedChildren; + if( reducedMemberIsValid(newCol->reducedMembers[reducedMember].parent) && + newCol->reducedMembers[newCol->reducedMembers[reducedMember].parent].type != REDUCEDMEMBER_TYPE_CYCLE ) + { + ++numNonPropagatedAdjacent; + } + + if( numNonPropagatedAdjacent > 2 ) + { + newCol->reducedMembers[reducedMember].type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + + spqr_member member = findMember(dec, newCol->reducedMembers[reducedMember].member); + SPQRMemberType type = getMemberType(dec, member); + switch( type ) + { + case SPQR_MEMBERTYPE_RIGID: + { + determineSingleRigidType(dec, newCol, reducedMember); + break; + } + case SPQR_MEMBERTYPE_PARALLEL: + { + SPQRColReducedMember* redMem = &newCol->reducedMembers[reducedMember]; + assert(pathArcIsValid(redMem->firstPathArc)); + SCIP_Bool pathForwards = newCol->pathArcs[redMem->firstPathArc].reversed == + arcIsReversedNonRigid(dec, newCol->pathArcs[redMem->firstPathArc].arc); + redMem->pathBackwards = !pathForwards; + redMem->type = REDUCEDMEMBER_TYPE_CYCLE; + break; + } + case SPQR_MEMBERTYPE_SERIES: + case SPQR_MEMBERTYPE_LOOP: + { + SPQRColReducedMember* redMem = &newCol->reducedMembers[reducedMember]; + int countedPathArcs = 0; + SCIP_Bool good = TRUE; + SCIP_Bool passesForwards = TRUE; + for( path_arc_id pathArc = redMem->firstPathArc; pathArcIsValid(pathArc); + pathArc = newCol->pathArcs[pathArc].nextMember ) + { + if( countedPathArcs == 0 ) + { + passesForwards = + newCol->pathArcs[pathArc].reversed != arcIsReversedNonRigid(dec, newCol->pathArcs[pathArc].arc); + } + else if( + (newCol->pathArcs[pathArc].reversed != arcIsReversedNonRigid(dec, newCol->pathArcs[pathArc].arc)) != + passesForwards ) + { + good = FALSE; + break; + } + ++countedPathArcs; + } + if( !good ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + break; + } + + redMem->pathBackwards = !passesForwards; + if( countedPathArcs == getNumMemberArcs(dec, findMember(dec, redMem->member)) - 1 ) + { + //Type -> Cycle; + //Propagate arc + redMem->type = REDUCEDMEMBER_TYPE_CYCLE; + } + else + { + //Type -> single_end + redMem->type = REDUCEDMEMBER_TYPE_MERGED; + } + break; + } + case SPQR_MEMBERTYPE_UNASSIGNED: + { + assert(FALSE); + break; + } + } +} + +/** Determines the path type of a series member. */ +static +void determinePathSeriesType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMember, /**< The reduced member to check */ + spqr_member member, /**< The reduced member's member id */ + MemberPathType previousType, /**< Type of the previous reduced member in the path */ + spqr_arc source, /**< The marker connecting to the previous reduced member in the path */ + spqr_arc target /**< The marker connecting to the next reduced member in the path */ +) +{ + assert(dec); + assert(newCol); + assert(reducedMemberIsValid(reducedMember)); + assert(SPQRmemberIsValid(member) && memberIsRepresentative(dec, member)); + assert(getMemberType(dec, member) == SPQR_MEMBERTYPE_SERIES); + + SPQRColReducedMember* redMem = &newCol->reducedMembers[reducedMember]; + int countedPathArcs = 0; + + SCIP_Bool good = TRUE; + SCIP_Bool passesForwards = TRUE; + for( path_arc_id pathArc = redMem->firstPathArc; pathArcIsValid(pathArc); + pathArc = newCol->pathArcs[pathArc].nextMember ) + { + if( countedPathArcs == 0 ) + { + passesForwards = + newCol->pathArcs[pathArc].reversed != arcIsReversedNonRigid(dec, newCol->pathArcs[pathArc].arc); + } + else if(( newCol->pathArcs[pathArc].reversed != arcIsReversedNonRigid(dec, newCol->pathArcs[pathArc].arc)) != + passesForwards ) + { + good = FALSE; + break; + } + ++countedPathArcs; + } + //If the internal directions of the arcs do not agree, we have no way to realize + if( !good ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + //If we are in the first skeleton processed, ignore the previous member type + if( !SPQRarcIsValid(source)) + { + assert(countedPathArcs > 0); + assert(SPQRarcIsValid(target)); + SCIP_Bool firstReversed = arcIsReversedNonRigid(dec, newCol->pathArcs[redMem->firstPathArc].arc); + SCIP_Bool targetReversed = arcIsReversedNonRigid(dec, target); + SCIP_Bool reversePath = newCol->pathArcs[redMem->firstPathArc].reversed; + + if(( firstReversed == targetReversed ) == reversePath ) + { + redMem->pathType = INTO_HEAD; + } + else + { + redMem->pathType = OUT_HEAD; + } + redMem->pathBackwards = !passesForwards; + return; + } + SCIP_Bool sourceReversed = arcIsReversedNonRigid(dec, source); + if( countedPathArcs > 0 ) + { + SCIP_Bool isIntoHeadOrOutTail = isInto(previousType) == isHead(previousType); + SCIP_Bool isGood = isIntoHeadOrOutTail == ( sourceReversed == passesForwards ); + if( !isGood ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + } + redMem->pathBackwards = !passesForwards; + if( SPQRarcIsValid(target) ) + { + SCIP_Bool targetReversed = arcIsReversedNonRigid(dec, target); + SCIP_Bool inSameDirection = sourceReversed == targetReversed; + + MemberPathType currentType; + switch( previousType ) + { + case INTO_HEAD: + { + currentType = inSameDirection ? INTO_TAIL : INTO_HEAD; + break; + } + case INTO_TAIL: + { + currentType = inSameDirection ? INTO_HEAD : INTO_TAIL; + break; + } + case OUT_HEAD: + { + currentType = inSameDirection ? OUT_TAIL : OUT_HEAD; + break; + } + case OUT_TAIL: + { + currentType = inSameDirection ? OUT_HEAD : OUT_TAIL; + break; + } + default: + { + assert(FALSE); + } + } + redMem->pathType = currentType; + return; + } + //If we are in the last skeleton, we only have a source, so nothing further to do + assert(countedPathArcs > 0); + //Strictly speaking below are no-ops within the algorithm, but help with debugging + if( isInto(previousType)) + { + redMem->pathType = INTO_HEAD; + } + else + { + redMem->pathType = OUT_HEAD; + } +} + +/** Determines the path type of a parallel member. */ +static +void determinePathParallelType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMember, /**< The reduced member to check */ + spqr_member member, /**< The reduced member's member id */ + MemberPathType previousType, /**< Type of the previous reduced member in the path */ + spqr_arc source, /**< The marker connecting to the previous reduced member in the path */ + spqr_arc target /**< The marker connecting to the next reduced member in the path */ +) +{ + assert(dec); + assert(newCol); + assert(reducedMemberIsValid(reducedMember)); + assert(SPQRmemberIsValid(member) && memberIsRepresentative(dec, member)); + assert(getMemberType(dec, member) == SPQR_MEMBERTYPE_PARALLEL); + + //Parallel members must always be of degree two; if they are a leaf, they must contain an edge, which could have been propagated + assert(SPQRarcIsValid(source) && SPQRarcIsValid(target)); + SCIP_Bool sourceReversed = arcIsReversedNonRigid(dec, source); + SCIP_Bool targetReversed = arcIsReversedNonRigid(dec, target); + SCIP_Bool inSameDirection = sourceReversed == targetReversed; + + SPQRColReducedMember* redMem = &newCol->reducedMembers[reducedMember]; + + path_arc_id pathArc = redMem->firstPathArc; + + SCIP_Bool arcPresent = FALSE; + if( pathArcIsValid(pathArc)) + { + SCIP_Bool pathArcReversed = + newCol->pathArcs[pathArc].reversed != arcIsReversedNonRigid(dec, newCol->pathArcs[pathArc].arc); + SCIP_Bool intoHeadOrOutTail = isInto(previousType) == isHead(previousType); + SCIP_Bool good = intoHeadOrOutTail == ( pathArcReversed != sourceReversed ); + if( !good ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + arcPresent = TRUE; + } + + SCIP_Bool swapHeadTail = arcPresent != inSameDirection; + switch( previousType ) + { + case INTO_HEAD: + { + redMem->pathType = swapHeadTail ? INTO_HEAD : INTO_TAIL; + break; + } + case INTO_TAIL: + { + redMem->pathType = swapHeadTail ? INTO_TAIL : INTO_HEAD; + break; + } + case OUT_HEAD: + { + redMem->pathType = swapHeadTail ? OUT_HEAD : OUT_TAIL; + break; + } + case OUT_TAIL: + { + redMem->pathType = swapHeadTail ? OUT_TAIL : OUT_HEAD; + break; + } + } +} + +/** Determines the path type of a rigid member. */ +static +void determinePathRigidType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMember, /**< The reduced member to check */ + MemberPathType previousType, /**< Type of the previous reduced member in the path */ + spqr_arc source, /**< The marker connecting to the previous reduced member in the path */ + spqr_arc target /**< The marker connecting to the next reduced member in the path */ +) +{ + + SPQRColReducedMember* redMem = &newCol->reducedMembers[reducedMember]; + if( pathArcIsInvalid(redMem->firstPathArc)) + { + assert(SPQRarcIsValid(source)); + assert(SPQRarcIsValid(target)); + //In this case, we need to check if the source and target are adjacent in any node + + spqr_node sourceTail = findEffectiveArcTail(dec, source); + spqr_node sourceHead = findEffectiveArcHead(dec, source); + spqr_node targetTail = findEffectiveArcTail(dec, target); + spqr_node targetHead = findEffectiveArcHead(dec, target); + SCIP_Bool sourceHeadIsTargetHead = sourceHead == targetHead; + SCIP_Bool sourceTailIsTargetHead = sourceTail == targetHead; + SCIP_Bool sourceHeadIsTargetTail = sourceHead == targetTail; + SCIP_Bool sourceTailIsTargetTail = sourceTail == targetTail; + + if( !(sourceHeadIsTargetHead || sourceTailIsTargetHead || sourceHeadIsTargetTail || sourceTailIsTargetTail) ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + assert( + ( sourceHeadIsTargetHead ? 1 : 0 ) + ( sourceHeadIsTargetTail ? 1 : 0 ) + ( sourceTailIsTargetHead ? 1 : 0 ) + + ( sourceTailIsTargetTail ? 1 : 0 ) == 1); + //assert simplicity; they can be adjacent in only exactly one node + SCIP_Bool isSourceHead = sourceHeadIsTargetHead || sourceHeadIsTargetTail; + SCIP_Bool isTargetTail = sourceHeadIsTargetTail || sourceTailIsTargetTail; + if( isHead(previousType) == isSourceHead ) + { + //no need to reflect + redMem->reverseArcs = FALSE; + if( isInto(previousType)) + { + if( isTargetTail ) + { + redMem->pathType = INTO_TAIL; + } + else + { + redMem->pathType = INTO_HEAD; + } + } + else + { + if( isTargetTail ) + { + redMem->pathType = OUT_TAIL; + } + else + { + redMem->pathType = OUT_HEAD; + } + } + } + else + { + redMem->reverseArcs = TRUE; + //because of the reversal, all the heads/tails are switched below + if( isInto(previousType)) + { + if( isTargetTail ) + { + redMem->pathType = INTO_HEAD; + } + else + { + redMem->pathType = INTO_TAIL; + } + } + else + { + if( isTargetTail ) + { + redMem->pathType = OUT_HEAD; + } + else + { + redMem->pathType = OUT_TAIL; + } + } + } + assert(isInto(redMem->pathType) == isInto(previousType)); + return; + } + determineRigidPath(dec, newCol, redMem); + if( redMem->type == REDUCEDMEMBER_TYPE_NOT_NETWORK ) + { + return; + } + if( SPQRarcIsInvalid(source) ) + { + assert(SPQRarcIsValid(target)); + spqr_node targetTail = findEffectiveArcTail(dec, target); + spqr_node targetHead = findEffectiveArcHead(dec, target); + redMem->reverseArcs = FALSE; + if( redMem->rigidPathEnd == targetHead ) + { + redMem->pathType = INTO_HEAD; + } + else if( redMem->rigidPathEnd == targetTail ) + { + redMem->pathType = INTO_TAIL; + } + else if( redMem->rigidPathStart == targetHead ) + { + redMem->pathType = OUT_HEAD; + } + else if( redMem->rigidPathStart == targetTail ) + { + redMem->pathType = OUT_TAIL; + } + else + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + } + return; + } + assert(SPQRarcIsValid(source)); + spqr_node sourceTail = findEffectiveArcTail(dec, source); + spqr_node sourceHead = findEffectiveArcHead(dec, source); + + SCIP_Bool startsAtHead = sourceHead == redMem->rigidPathStart; + SCIP_Bool endsAtTail = sourceTail == redMem->rigidPathEnd; + SCIP_Bool startsAtTail = sourceTail == redMem->rigidPathStart; + SCIP_Bool endsAtHead = sourceHead == redMem->rigidPathEnd; + + SCIP_Bool isIntoHeadOrOutTail = isInto(previousType) == isHead(previousType); + if( isIntoHeadOrOutTail ) + {//into head or outTail + //Check if path starts at head or ends at tail + if( !startsAtHead && !endsAtTail ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + assert(startsAtHead || endsAtTail);//both can hold; they can form cycle but other components can not be reduced + // redMem->reverseArcs = isInto(previousType) != startsAtHead; //Reverse only if there is no path starting at head + } + else + {//Into tail or outHead + //Check if path starts at tail or ends at head + if( !startsAtTail && !endsAtHead ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + assert(startsAtTail || endsAtHead);// both can hold; they can form cycle but other components can not be reduced + // redMem->reverseArcs = isInto(previousType) != startsAtTail; //Reverse only if there is no path starting at tail + } + + if( SPQRarcIsValid(target) ) + { + spqr_node targetTail = findEffectiveArcTail(dec, target); + spqr_node targetHead = findEffectiveArcHead(dec, target); + + //Check if they are not parallel; (below logic relies on this fact) + assert(!(( targetHead == sourceHead && targetTail == sourceTail ) || + ( targetHead == sourceTail && targetTail == sourceHead ))); + + SCIP_Bool startsAtTargetHead = redMem->rigidPathStart == targetHead; + SCIP_Bool startsAtTargetTail = redMem->rigidPathStart == targetTail; + SCIP_Bool endsAtTargetHead = redMem->rigidPathEnd == targetHead; + SCIP_Bool endsAtTargetTail = redMem->rigidPathEnd == targetTail; + + if( !(startsAtTargetHead || startsAtTargetTail || endsAtTargetHead || endsAtTargetTail) ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + SCIP_Bool outReverse = FALSE; + SCIP_Bool outHead = FALSE; + + if( isInto(previousType) == isHead(previousType) ) + { + if( startsAtHead && endsAtTail ) + { + outReverse = ( startsAtTargetHead || startsAtTargetTail ) == isInto(previousType); + } + else if( startsAtHead ) + { + outReverse = !isInto(previousType); + } + else + { + assert(endsAtTail); + outReverse = isInto(previousType); + } + } + else + { + if( startsAtTail && endsAtHead ) + { + outReverse = ( startsAtTargetHead || startsAtTargetTail ) == isInto(previousType); + } + else if( startsAtTail ) + { + outReverse = !isInto(previousType); + } + else + { + assert(endsAtHead); + outReverse = isInto(previousType); + } + } + + //TODO: this if-else tree to compute two booleans can probably be simplified significantly + SCIP_Bool isBad = FALSE; + if( isInto(previousType) == isHead(previousType) ) + { + if( startsAtHead && endsAtTail ) + { + outHead = ( startsAtTargetTail || endsAtTargetHead ) == isInto(previousType); + } + else if( startsAtHead ) + { + if( endsAtTargetHead ) + { + outHead = isInto(previousType); + } + else if( endsAtTargetTail ) + { + outHead = !isInto(previousType); + } + else + { + isBad = TRUE; + } + } + else + { + assert(endsAtTail); + if( startsAtTargetTail ) + { + outHead = isInto(previousType); + } + else if( startsAtTargetHead ) + { + outHead = !isInto(previousType); + } + else + { + isBad = TRUE; + } + } + } + else + { + if( startsAtTail && endsAtHead ) + { + outHead = ( startsAtTargetTail || endsAtTargetHead ) == isInto(previousType); + } + else if( startsAtTail ) + { + if( endsAtTargetHead ) + { + outHead = isInto(previousType); + } + else if( endsAtTargetTail ) + { + outHead = !isInto(previousType); + } + else + { + isBad = TRUE; + } + } + else + { + assert(endsAtHead); + if( startsAtTargetTail ) + { + outHead = isInto(previousType); + } + else if( startsAtTargetHead ) + { + outHead = !isInto(previousType); + } + else + { + isBad = TRUE; + } + } + } + if( isBad ) + { + redMem->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + return; + } + + redMem->reverseArcs = outReverse; + if( isInto(previousType) ) + { + redMem->pathType = outHead ? INTO_HEAD : INTO_TAIL; + } + else + { + redMem->pathType = outHead ? OUT_HEAD : OUT_TAIL; + } + return; + } + + //TODO: is this simplifyable? + if( isInto(previousType) == isHead(previousType) ) + { + redMem->reverseArcs = startsAtHead != isInto(previousType); + } + else + { + redMem->reverseArcs = startsAtTail != isInto(previousType); + } + //last member of the path. Since we already checked the source, + //Below is technically no op, but helps with debugging + if( isInto(previousType) ) + { + redMem->pathType = INTO_HEAD; + } + else + { + redMem->pathType = OUT_HEAD; + } +} + +/** Determines the path type of a single member. */ +static +void determinePathMemberType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMember, /**< The reduced member to check */ + spqr_member member, /**< The reduced member's member id */ + MemberPathType previousType, /**< Type of the previous reduced member in the path */ + spqr_arc source, /**< The marker connecting to the previous reduced member in the path */ + spqr_arc target /**< The marker connecting to the next reduced member in the path */ +) +{ + newCol->reducedMembers[reducedMember].pathSourceArc = source; + newCol->reducedMembers[reducedMember].pathTargetArc = target; + //Check if the marked edges with the given signs + //form a (reverse) directed path from one of the source's end nodes to one of the target's end nodes + switch( getMemberType(dec, member) ) + { + case SPQR_MEMBERTYPE_RIGID: + { + determinePathRigidType(dec, newCol, reducedMember, previousType, source, target); + break; + } + case SPQR_MEMBERTYPE_PARALLEL: + { + determinePathParallelType(dec, newCol, reducedMember, member, previousType, source, target); + break; + } + case SPQR_MEMBERTYPE_SERIES: + { + determinePathSeriesType(dec, newCol, reducedMember, member, previousType, source, target); + break; + } + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_UNASSIGNED: + { + //In release + newCol->remainsNetwork = FALSE; + assert(FALSE); + break; + } + } +} + +/** Determines the path types of all reduced members. + * + * The reduced members themselves also form a path in the reduced decomposition. + */ +static +void determinePathTypes( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + SPQRColReducedComponent* component /**< The component to determine the path types for */ +) +{ + assert(dec); + assert(newCol); + assert(component); + assert(component->numPathEndMembers == 2); + + assert(component->pathEndMembers[0] != component->root); + + //We check the path by going from end to end. We start at the leaf and process every component, + //walking down until we hit the root. + //Then, we walk up from the root and process each component in the same manner. + reduced_member_id reducedStart = component->pathEndMembers[0]; + spqr_arc toPrevious = SPQR_INVALID_ARC; + spqr_arc toNext = SPQR_INVALID_ARC; + MemberPathType previousType = INTO_HEAD;//Arbitrary, is ignored in the first call + + spqr_member member = newCol->reducedMembers[reducedStart].member; + reduced_member_id reducedMember = reducedStart; + reduced_member_id previousReducedMember = reducedStart; + while( reducedMember != component->root ) + { + toNext = markerToParent(dec, member); + determinePathMemberType(dec, newCol, reducedMember, member, previousType, toPrevious, toNext); + if( !newCol->remainsNetwork ) + { + return; + } + previousType = newCol->reducedMembers[reducedMember].pathType; + toPrevious = markerOfParent(dec, member); + member = findMemberParent(dec, newCol->reducedMembers[reducedMember].member); + previousReducedMember = reducedMember; + reducedMember = newCol->memberInformation[member].reducedMember; + newCol->reducedMembers[previousReducedMember].nextPathMember = reducedMember; + newCol->reducedMembers[previousReducedMember].nextPathMemberIsParent = TRUE; + } + + while( reducedMember != component->pathEndMembers[1] ) + { + //Search the (other) child node + reduced_member_id child = INVALID_REDUCED_MEMBER; + //a bit ugly linear search, but not a problem for time complexity + for( children_idx i = newCol->reducedMembers[reducedMember].firstChild; + i < + newCol->reducedMembers[reducedMember].firstChild + newCol->reducedMembers[reducedMember].numChildren; ++i ) + { + reduced_member_id childReduced = newCol->childrenStorage[i]; + if( newCol->reducedMembers[childReduced].type != REDUCEDMEMBER_TYPE_CYCLE && + childReduced != previousReducedMember ) + { + child = childReduced; + break; + } + } + assert(reducedMemberIsValid(child)); + + spqr_member childMember = newCol->reducedMembers[child].member; + toNext = markerOfParent(dec, childMember); + + determinePathMemberType(dec, newCol, reducedMember, member, previousType, toPrevious, toNext); + if( !newCol->remainsNetwork ) + { + return; + } + previousType = newCol->reducedMembers[reducedMember].pathType; + toPrevious = markerToParent(dec, childMember); + member = childMember; + previousReducedMember = reducedMember; + reducedMember = child; + newCol->reducedMembers[previousReducedMember].nextPathMember = reducedMember; + newCol->reducedMembers[previousReducedMember].nextPathMemberIsParent = FALSE; + } + //The last iteration is not performed by the loops above. + //We explicitly set the target arc to invalid in order to indicate that this is the last iteration. + toNext = SPQR_INVALID_ARC; + determinePathMemberType(dec, newCol, reducedMember, member, previousType, toPrevious, toNext); + newCol->reducedMembers[reducedMember].nextPathMember = INVALID_REDUCED_MEMBER; + //since we return anyways, no need to check newCol->remainsNetwork explicitly +} + +/** Check if a rigid leaf closes a cycle with its child. + * + * If so, we can propagate this cycle to a virtual arc of the child node member and shrink the decomposition. + */ +static +void checkRigidLeaf( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id leaf, /**< The leaf node of the reduced SPQR tree */ + spqr_arc toParent, /**< The virtual arc to the leaf node's neighbour */ + reduced_member_id parent, /**< The neighbouring member of the leaf node. */ + spqr_arc toChild /**< The virtual arc from the neighbouring member pointing to the leaf. */ +) +{ + SPQRColReducedMember* leafMember = &newCol->reducedMembers[leaf]; + determineRigidPath(dec, newCol, leafMember); + if( leafMember->type == REDUCEDMEMBER_TYPE_NOT_NETWORK ) + { + return; + } + spqr_node targetHead = findEffectiveArcHead(dec, toParent); + spqr_node targetTail = findEffectiveArcTail(dec, toParent); + SCIP_Bool matches = leafMember->rigidPathStart == targetTail && leafMember->rigidPathEnd == targetHead; + SCIP_Bool opposite = leafMember->rigidPathStart == targetHead && leafMember->rigidPathEnd == targetTail; + if( matches || opposite ) + { + leafMember->type = REDUCEDMEMBER_TYPE_CYCLE; + createPathArc(dec, newCol, toChild, parent, opposite); + return; + } + leafMember->type = REDUCEDMEMBER_TYPE_MERGED; +} + +/** Check if a leaf node closes a cycle with its child. + * + * If so, we can propagate this cycle to a virtual arc of the child node member and shrink the decomposition. + */ +static +ReducedMemberType checkLeaf( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id leaf, /**< The leaf node of the reduced SPQR tree */ + spqr_arc toParent, /**< The virtual arc to the leaf node's neighbour */ + reduced_member_id parent, /**< The neighbouring member of the leaf node. */ + spqr_arc toChild /**< The virtual arc from the neighbouring member pointing to the leaf. */ +) +{ + assert(dec); + assert(newCol); + assert(SPQRarcIsValid(toParent)); + assert(SPQRarcIsValid(toChild)); + assert(!arcIsTree(dec, toParent)); + assert(reducedMemberIsValid(leaf)); + assert(reducedMemberIsValid(parent)); + + switch( getMemberType(dec, newCol->reducedMembers[leaf].member) ) + { + case SPQR_MEMBERTYPE_RIGID: + { + checkRigidLeaf(dec, newCol, leaf, toParent, parent, toChild); + break; + } + case SPQR_MEMBERTYPE_PARALLEL: + { + SPQRColReducedMember* reducedMember = &newCol->reducedMembers[leaf]; + assert(pathArcIsValid(reducedMember->firstPathArc)); + reducedMember->type = REDUCEDMEMBER_TYPE_CYCLE; + + SCIP_Bool pathArcReversed = newCol->pathArcs[reducedMember->firstPathArc].reversed; + SCIP_Bool arcPathArcIsReverse = arcIsReversedNonRigid(dec, newCol->pathArcs[reducedMember->firstPathArc].arc); + SCIP_Bool parentReversed = arcIsReversedNonRigid(dec, toParent); + createPathArc(dec, newCol, toChild, parent, ( arcPathArcIsReverse == parentReversed ) == pathArcReversed); + break; + } + case SPQR_MEMBERTYPE_SERIES: + case SPQR_MEMBERTYPE_LOOP: + { + SPQRColReducedMember* reducedMember = &newCol->reducedMembers[leaf]; + int countedPathArcs = 0; + SCIP_Bool good = TRUE; + SCIP_Bool passesForwards = TRUE; + for( path_arc_id pathArc = reducedMember->firstPathArc; pathArcIsValid(pathArc); + pathArc = newCol->pathArcs[pathArc].nextMember ) + { + if( countedPathArcs == 0 ) + { + passesForwards = + newCol->pathArcs[pathArc].reversed != arcIsReversedNonRigid(dec, newCol->pathArcs[pathArc].arc); + } + else if( + (newCol->pathArcs[pathArc].reversed != arcIsReversedNonRigid(dec, newCol->pathArcs[pathArc].arc)) != + passesForwards ) + { + good = FALSE; + break; + } + ++countedPathArcs; + } + if( !good ) + { + reducedMember->type = REDUCEDMEMBER_TYPE_NOT_NETWORK; + newCol->remainsNetwork = FALSE; + break; + } + + reducedMember->pathBackwards = !passesForwards; + if( countedPathArcs == getNumMemberArcs(dec, findMember(dec, reducedMember->member)) - 1 ) + { + //Type -> Cycle; + //Propagate arc + reducedMember->type = REDUCEDMEMBER_TYPE_CYCLE; + + SCIP_Bool firstArcReversed = arcIsReversedNonRigid(dec, newCol->pathArcs[reducedMember->firstPathArc].arc); + SCIP_Bool firstArcInPathReverse = newCol->pathArcs[reducedMember->firstPathArc].reversed; + SCIP_Bool parentReversed = arcIsReversedNonRigid(dec, toParent); + createPathArc(dec, newCol, toChild, parent, + ( parentReversed == firstArcReversed ) != firstArcInPathReverse); + + } + else + { + //Type -> single_end + reducedMember->type = REDUCEDMEMBER_TYPE_MERGED; + } + + break; + } + case SPQR_MEMBERTYPE_UNASSIGNED: + { + assert(FALSE); + break; + } + } + return newCol->reducedMembers[leaf].type; +} + +/** Recursively removes leaf nodes whose path forms cycles with the virtual arc to its children. */ +static +void propagateCycles( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol /**< The network matrix column addition data structure */ +) +{ + assert(dec); + assert(newCol); + + int leafArrayIndex = 0; + while( leafArrayIndex != newCol->numLeafMembers ) + { + reduced_member_id leaf = newCol->leafMembers[leafArrayIndex]; + //next is invalid if the member is not in the reduced decomposition. + reduced_member_id next = newCol->reducedMembers[leaf].parent; + spqr_arc parentMarker = markerToParent(dec, newCol->reducedMembers[leaf].member); + if( reducedMemberIsValid(next) && !arcIsTree(dec, parentMarker)) + { + assert(reducedMemberIsValid(next)); + assert(SPQRarcIsValid(parentMarker)); + ReducedMemberType type = checkLeaf(dec, newCol, leaf, parentMarker, next, + markerOfParent(dec, newCol->reducedMembers[leaf].member)); + if( type == REDUCEDMEMBER_TYPE_CYCLE ) + { + ++newCol->reducedMembers[next].numPropagatedChildren; + if( newCol->reducedMembers[next].numPropagatedChildren == newCol->reducedMembers[next].numChildren ) + { + newCol->leafMembers[leafArrayIndex] = next; + } + else + { + ++leafArrayIndex; + } + } + else if( type == REDUCEDMEMBER_TYPE_NOT_NETWORK ) + { + return; + } + else + { + assert(type == REDUCEDMEMBER_TYPE_MERGED); + ++leafArrayIndex; + int component = newCol->reducedMembers[leaf].componentIndex; + if( newCol->reducedComponents[component].numPathEndMembers >= 2 ) + { + newCol->remainsNetwork = FALSE; + return; + } + assert(newCol->reducedComponents[component].numPathEndMembers < 2); + newCol->reducedComponents[component].pathEndMembers[newCol->reducedComponents[component].numPathEndMembers] = leaf; + ++newCol->reducedComponents[component].numPathEndMembers; + } + } + else + { + ++leafArrayIndex; + int component = newCol->reducedMembers[leaf].componentIndex; + if( newCol->reducedComponents[component].numPathEndMembers >= 2 ) + { + newCol->remainsNetwork = FALSE; + return; + } + assert(newCol->reducedComponents[component].numPathEndMembers < 2); + newCol->reducedComponents[component].pathEndMembers[newCol->reducedComponents[component].numPathEndMembers] = leaf; + ++newCol->reducedComponents[component].numPathEndMembers; + } + } + + for( int j = 0; j < newCol->numReducedComponents; ++j ) + { + //The reduced root might be a leaf as well: we propagate it last + reduced_member_id root = newCol->reducedComponents[j].root; + + while( TRUE ) /*lint !e716*/ + { + if( newCol->reducedMembers[root].numPropagatedChildren != newCol->reducedMembers[root].numChildren - 1 ) + { + break; + } + //TODO: bit ugly, have to do a linear search for the child + reduced_member_id child = INVALID_REDUCED_MEMBER; + spqr_arc markerToChild = SPQR_INVALID_ARC; + for( children_idx i = newCol->reducedMembers[root].firstChild; + i < newCol->reducedMembers[root].firstChild + newCol->reducedMembers[root].numChildren; ++i ) + { + reduced_member_id childReduced = newCol->childrenStorage[i]; + if( newCol->reducedMembers[childReduced].type != REDUCEDMEMBER_TYPE_CYCLE ) + { + child = childReduced; + markerToChild = markerOfParent(dec, newCol->reducedMembers[child].member); + break; + } + } + assert(SPQRmemberIsValid(newCol->reducedMembers[child].member)); + assert(SPQRarcIsValid(markerToChild)); + if( !arcIsTree(dec, markerToChild) ) + { + ReducedMemberType type = checkLeaf(dec, newCol, root, markerToChild, child, + markerToParent(dec, newCol->reducedMembers[child].member)); + if( type == REDUCEDMEMBER_TYPE_CYCLE ) + { + root = child; + continue; + } + else if( type == REDUCEDMEMBER_TYPE_NOT_NETWORK ) + { + return; + } + } + //If the root has exactly one neighbour and is not contained, it is also considered a path end member + int component = newCol->reducedMembers[root].componentIndex; + SCIP_Bool rootPresent = FALSE; + for( int i = 0; i < newCol->reducedComponents[component].numPathEndMembers; ++i ) + { + rootPresent = rootPresent || ( newCol->reducedComponents[component].pathEndMembers[i] == root ); + } + if( !rootPresent ) + { + if( newCol->reducedComponents[component].numPathEndMembers >= 2 ) + { + newCol->remainsNetwork = FALSE; + return; + } + newCol->reducedComponents[component].pathEndMembers[newCol->reducedComponents[component].numPathEndMembers] = root; + ++newCol->reducedComponents[component].numPathEndMembers; + } + break; + } + + newCol->reducedComponents[j].root = root; + newCol->reducedMembers[root].parent = INVALID_REDUCED_MEMBER; + } +} + +/** Determine the type of a single component. */ +static +void determineComponentTypes( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + SPQRColReducedComponent* component /**< The component to determine the types for */ +) +{ + assert(dec); + assert(newCol); + assert(component); + + if( component->numPathEndMembers == 1 ) + { + assert(component->root == component->pathEndMembers[0]); + determineSingleComponentType(dec, newCol, component->root); + } + else + { + assert(component->numPathEndMembers == 2); + determinePathTypes(dec, newCol, component); + } +} + +/** Checks if the given column can be added to the network matrix decomposition. + * + * See header for more info. + */ +static +SCIP_RETCODE netcoladdCheck( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* coladd, /**< The network matrix column addition data structure */ + int column, /**< The column to add */ + const int* nonzrows, /**< The column's nonzero row indices */ + const double* nonzvals, /**< The column's nonzero entries */ + int nnonzs /**< The number of nonzeros in the column */ +) +{ + assert(dec); + assert(coladd); + assert(nnonzs == 0 || ( nonzrows && nonzvals )); + + /* A column can only be added once */ + if( netMatDecDataContainsColumn(dec,column) ) + { + return SCIP_INVALIDDATA; + } + coladd->remainsNetwork = TRUE; + cleanupPreviousIteration(dec, coladd); + //assert that previous iteration was cleaned up + + //Store call data + SCIP_CALL( newColUpdateColInformation(dec, coladd, column, nonzrows, nonzvals, nnonzs) ); + + //compute reduced decomposition + SCIP_CALL( constructReducedDecomposition(dec, coladd) ); + //initialize path arcs in reduced decomposition + SCIP_CALL( createPathArcs(dec, coladd) ); + SCIP_CALL( computeLeafMembers(dec, coladd) ); + propagateCycles(dec, coladd); + //determine types + if( coladd->remainsNetwork ) + { + for( int i = 0; i < coladd->numReducedComponents; ++i ) + { + determineComponentTypes(dec, coladd, &coladd->reducedComponents[i]); + } + } + //clean up memberInformation + cleanUpMemberInformation(coladd); + + return SCIP_OKAY; +} + +/** Struct that contains the data that tells us how to add the new column after the graph has been modified. + * + * In the case the member is a series or parallel node, the new column and rows are placed in series or parallel, + * respectively. Otherwise, the edge can be added between head and tail in a rigid member. + */ +typedef struct +{ + spqr_member member; /**< The member where the new column should be added */ + spqr_node head; /**< The head node of the new column (rigid members only) */ + spqr_node tail; /**< The tail node of the new column (rigid members only) */ + spqr_arc representative; /**< The representative arc of the new column */ + SCIP_Bool reversed; /**< Is the new column reversed? */ +} NewColInformation; + +#define NEWCOLINFORMATION_EMPTY { SPQR_INVALID_MEMBER, SPQR_INVALID_NODE, SPQR_INVALID_NODE, SPQR_INVALID_ARC, FALSE } + +/** Set the head node of the new column edge to be added. */ +static +void setTerminalHead( + NewColInformation* info, /**< The new column information */ + spqr_node node /**< The head node of the new column edge */ +) +{ + assert(SPQRnodeIsValid(node)); + assert(SPQRnodeIsInvalid(info->head)); + assert(info); + + info->head = node; +} + +/** Set the tail node of the new column edge to be added. */ +static +void setTerminalTail( + NewColInformation* info, /**< The new column information */ + spqr_node node /**< The tail node of the new column edge */ +) +{ + assert(SPQRnodeIsValid(node)); + assert(info); + assert(SPQRnodeIsInvalid(info->tail)); + + info->tail = node; +} + +/** Set whether the new column arc should be reversed. */ +static +void setTerminalReversed( + NewColInformation* info, /**< The new column information */ + SCIP_Bool reversed /**< Should the new column arc be reversed? */ +) +{ + assert(info); + + info->reversed = reversed; +} + +/** Set the member that contains the new column arc. */ +static +void setTerminalMember( + NewColInformation* info, /**< The new column information */ + spqr_member member /**< The decomposition member that contains the new column arc */ +) +{ + assert(info); + + info->member = member; +} + +/** Set the representative arc of the new column arc. */ +static +void setTerminalRepresentative( + NewColInformation* info, /**< The new column information */ + spqr_arc representative /**< The representative arc of the new column arc */ +) +{ + assert(info); + + info->representative = representative; +} + +/** Splits a parallel member into two adjacent parallel members connected by a virtual edge pair. + * + * One member keeps the two arcs passed to this function, the other member gets all other arcs. + */ +static +SCIP_RETCODE splitParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + spqr_member parallel, /**< The parallel member */ + spqr_arc arc1, /**< First arc to keep. */ + spqr_arc arc2, /**< Second arc to keep. */ + spqr_member* childParallel /**< Out pointer to the new child parallel member. */ +) +{ + assert(dec); + assert(SPQRarcIsValid(arc1)); + assert(SPQRarcIsValid(arc2)); + assert(SPQRmemberIsValid(parallel)); + + SCIP_Bool childContainsTree = arcIsTree(dec, arc1) || arcIsTree(dec, arc2); + spqr_arc toParent = markerToParent(dec, parallel); + SCIP_Bool parentMoved = toParent == arc1 || toParent == arc2; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, childParallel) ); + + moveArcToNewMember(dec, arc1, parallel, *childParallel); + moveArcToNewMember(dec, arc2, parallel, *childParallel); + + if( parentMoved ) + { + SCIP_CALL( createMarkerPair(dec, *childParallel, parallel, !childContainsTree, FALSE, FALSE) ); + } + else + { + SCIP_CALL( createMarkerPair(dec, parallel, *childParallel, childContainsTree, FALSE, FALSE) ); + } + + return SCIP_OKAY; +} + +/** Very elaborate function that splits a series member into multiple members based on the structure of the path arcs. + * + * The updated member reflects the structure of the updated SPQR tree after the new column arc is added. + */ +static +SCIP_RETCODE splitSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + SPQRColReducedMember* reducedMember, /**< The reduced member of the series member to split. */ + spqr_member member, /**< The series member to split. */ + spqr_member* loopMember, /**< Out-pointer to a new loop member that may be created */ + NewColInformation* newColInfo /**< New column information struct */ +) +{ + assert(dec); + assert(reducedMember); + assert(SPQRmemberIsValid(member)); + assert(memberIsRepresentative(dec, member)); + + assert(reducedMember->numPathArcs != 0); + SCIP_Bool createPathSeries = reducedMember->numPathArcs > 1; + SCIP_Bool convertOriginal = reducedMember->numPathArcs == getNumMemberArcs(dec, member) - 1; + + //TODO: for now very elaborate to first get logic correct. Can probably merge some branches later... + if( !createPathSeries && !convertOriginal ) + { + spqr_member parallel; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, ¶llel) ); + path_arc_id pathArcId = reducedMember->firstPathArc; + assert(pathArcIsValid(pathArcId)); + spqr_arc marked = newCol->pathArcs[pathArcId].arc; + assert(marked != markerToParent(dec, member));//TODO: handle this case later + moveArcToNewMember(dec, marked, member, parallel); + SCIP_Bool reversed = arcIsReversedNonRigid(dec, marked); + SCIP_CALL( createMarkerPair(dec, member, parallel, TRUE, reversed, reversed) ); + *loopMember = parallel; + + if( reversed == reducedMember->pathBackwards ) + { + setTerminalReversed(newColInfo, !reversed); + } + else + { + setTerminalReversed(newColInfo, reversed); + } + + setTerminalMember(newColInfo, *loopMember); + return SCIP_OKAY; + } + if( !createPathSeries && convertOriginal ) + { + //only one path arc; we are in a loop; no need to change anything + assert(getNumMemberArcs(dec, member) == 2); + assert(reducedMember->numPathArcs == 1); + *loopMember = member; + changeLoopToParallel(dec, member); + + path_arc_id pathArcId = reducedMember->firstPathArc; + spqr_arc marked = newCol->pathArcs[pathArcId].arc; + //The 'reversed' field has different meaning for parallels, so we need to change orientation when converting to a parallel + arcFlipReversed(dec, marked); + + setTerminalReversed(newColInfo, reducedMember->pathBackwards); + setTerminalMember(newColInfo, *loopMember); + return SCIP_OKAY; + } + if( createPathSeries && !convertOriginal ) + { + spqr_member pathMember; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &pathMember) ); + + path_arc_id pathArcId = reducedMember->firstPathArc; + SCIP_Bool parentMoved = FALSE; + while( pathArcIsValid(pathArcId)) + { + spqr_arc pathArc = newCol->pathArcs[pathArcId].arc; + pathArcId = newCol->pathArcs[pathArcId].nextMember; + if( pathArc == markerToParent(dec, member)) + { + parentMoved = TRUE; + } + moveArcToNewMember(dec, pathArc, member, pathMember); + } + + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, loopMember) ); + + if( !parentMoved ) + { + SCIP_CALL( createMarkerPair(dec, member, *loopMember, TRUE, FALSE, FALSE) ); + SCIP_CALL( createMarkerPair(dec, *loopMember, pathMember, TRUE, FALSE, TRUE) ); + } + else + { + SCIP_CALL( createMarkerPair(dec, pathMember, *loopMember, FALSE, FALSE, TRUE) ); + SCIP_CALL( createMarkerPair(dec, *loopMember, member, FALSE, FALSE, FALSE) ); + } + + setTerminalReversed(newColInfo, !reducedMember->pathBackwards); + setTerminalMember(newColInfo, *loopMember); + return SCIP_OKAY; + } + assert(createPathSeries && convertOriginal); + //There's one exception in this case + //if the single unmarked (column) marker is a parent or child marker to a parallel member, we add the edge there + { + spqr_member adjacentMember = SPQR_INVALID_MEMBER; + spqr_arc adjacentMarker = SPQR_INVALID_ARC; + spqr_arc memberMarker = SPQR_INVALID_ARC; + spqr_arc firstArc = getFirstMemberArc(dec, reducedMember->member); + spqr_arc arc = firstArc; + do + { + if( !newCol->arcInPath[arc] ) + { + if( arc == markerToParent(dec, reducedMember->member)) + { + adjacentMember = findMemberParent(dec, reducedMember->member); + adjacentMarker = markerOfParent(dec, reducedMember->member); + memberMarker = arc; + } + else if( arcIsChildMarker(dec, arc)) + { + adjacentMember = findArcChildMember(dec, arc); + adjacentMarker = markerToParent(dec, adjacentMember); + memberMarker = arc; + } + + break;//There is only a singular such arc + } + arc = getNextMemberArc(dec, arc); + } + while( arc != firstArc ); + + if( SPQRmemberIsValid(adjacentMember)) + { + SPQRMemberType adjacentType = getMemberType(dec, adjacentMember); + if( adjacentType == SPQR_MEMBERTYPE_PARALLEL ) + { + //Figure out if the markers are the same or opposite orientations + //If they are the same, we can proceed as normal, otherwise, we need to flip the placed edge + SCIP_Bool markersHaveSameOrientation = + arcIsReversedNonRigid(dec, adjacentMarker) == arcIsReversedNonRigid(dec, memberMarker); + setTerminalReversed(newColInfo, reducedMember->pathBackwards == markersHaveSameOrientation); + setTerminalMember(newColInfo, adjacentMember); + return SCIP_OKAY; + } + } + } + + spqr_member pathMember; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &pathMember) ); + + path_arc_id pathArcId = reducedMember->firstPathArc; + SCIP_Bool parentMoved = FALSE; + while( pathArcIsValid(pathArcId)) + { + spqr_arc pathArc = newCol->pathArcs[pathArcId].arc; + pathArcId = newCol->pathArcs[pathArcId].nextMember; + if( pathArc == markerToParent(dec, member)) + { + parentMoved = TRUE; + } + moveArcToNewMember(dec, pathArc, member, pathMember); + } + if( parentMoved ) + { + SCIP_CALL( createMarkerPair(dec, pathMember, member, FALSE, FALSE, FALSE) ); + } + else + { + SCIP_CALL( createMarkerPair(dec, member, pathMember, TRUE, FALSE, FALSE) ); + } + + changeLoopToParallel(dec, member); + + *loopMember = member; + setTerminalReversed(newColInfo, reducedMember->pathBackwards); + setTerminalMember(newColInfo, *loopMember); + return SCIP_OKAY; +} + +/** Very elaborate function that splits a series member into multiple members based on the structure of the path arcs. + * + * The updated member reflects the structure of the updated SPQR tree after the new column arc is added. + * This variant is only used on series members that are part of a reduced tree that is not a single member. + */ +static +SCIP_RETCODE splitSeriesMerging( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + SPQRColReducedMember* reducedMember, /**< The reduced member of the series member to split. */ + spqr_member member, /**< The series member to split. */ + spqr_arc* pathRepresentative, /**< Out pointer pointing to the tree arc in the final series node */ + spqr_arc* nonPathRepresentative, /**< Out pointer pointing to the non-tree arc in the final series node*/ + spqr_arc exceptionArc1, /**< The first exception arc. Set to SPQR_INVALID_ARC to ignore */ + spqr_arc exceptionArc2 /**< The second exception arc. Set to SPQR_INVALID_ARC to ignore */ +) +{ + assert(dec); + assert(reducedMember); + assert(SPQRmemberIsValid(member)); + assert(memberIsRepresentative(dec, member)); + + int numExceptionArcs = ( exceptionArc1 == SPQR_INVALID_ARC ? 0 : 1 ) + ( exceptionArc2 == SPQR_INVALID_ARC ? 0 : 1 ); + int numNonPathArcs = getNumMemberArcs(dec, member) - reducedMember->numPathArcs - numExceptionArcs; + SCIP_Bool createPathSeries = reducedMember->numPathArcs > 1; + //If this holds, there are 2 or more non-parent marker non-path arcs + SCIP_Bool createNonPathSeries = numNonPathArcs > 1; + assert(exceptionArc1 == SPQR_INVALID_ARC || !newCol->arcInPath[exceptionArc1]); + assert(exceptionArc2 == SPQR_INVALID_ARC || !newCol->arcInPath[exceptionArc2]); + + if( createPathSeries ) + { + spqr_member pathMember; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &pathMember) ); + + path_arc_id pathArcId = reducedMember->firstPathArc; + SCIP_Bool parentMoved = FALSE; + while( pathArcIsValid(pathArcId) ) + { + spqr_arc pathArc = newCol->pathArcs[pathArcId].arc; + pathArcId = newCol->pathArcs[pathArcId].nextMember; + assert(pathArc != exceptionArc1 && pathArc != exceptionArc2); + parentMoved = parentMoved || markerToParent(dec, member) == pathArc; + moveArcToNewMember(dec, pathArc, member, pathMember); + } + assert(getNumMemberArcs(dec, pathMember) >= 2); + + spqr_arc ignored; + SCIP_Bool inOldReversed = TRUE; + SCIP_Bool inNewReversed = FALSE; + if( parentMoved ) + { + SCIP_CALL( createMarkerPairWithReferences(dec, pathMember, member, FALSE, inNewReversed, inOldReversed, + &ignored, pathRepresentative) ); + } + else + { + SCIP_CALL( createMarkerPairWithReferences(dec, member, pathMember, TRUE, inOldReversed, inNewReversed, + pathRepresentative, &ignored) ); + } + } + else + { + if( pathArcIsValid(reducedMember->firstPathArc) ) + { + *pathRepresentative = newCol->pathArcs[reducedMember->firstPathArc].arc; + } + else + { + *pathRepresentative = SPQR_INVALID_ARC; + } + } + + if( createNonPathSeries ) + { + spqr_member nonPathMember; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &nonPathMember) ); + + spqr_arc arc = getFirstMemberArc(dec, member); + SCIP_Bool parentMoved = FALSE; + SCIP_Bool canStop = FALSE;//hack when the first arc is moved in the below loop to prevent that we immediately terminate + do + { + spqr_arc nextArc = getNextMemberArc(dec, arc); + if( arc != *pathRepresentative && arc != exceptionArc1 && arc != exceptionArc2 ) + { + parentMoved = parentMoved || markerToParent(dec, member) == arc; + moveArcToNewMember(dec, arc, member, nonPathMember); + } + else + { + canStop = TRUE; + } + arc = nextArc; + if( canStop && arc == getFirstMemberArc(dec, member)) + { + break; + } + } + while( TRUE ); /*lint !e506*/ + assert(getNumMemberArcs(dec, nonPathMember) >= 2); + SCIP_Bool representativeIsTree = !arcIsTree(dec, exceptionArc1); + if( SPQRarcIsValid(exceptionArc2) ) + { + representativeIsTree = representativeIsTree || !arcIsTree(dec, exceptionArc2); + } + spqr_arc ignored; + SCIP_Bool inOldReversed = TRUE; + SCIP_Bool inNewReversed = FALSE; + if( parentMoved ) + { + SCIP_CALL( createMarkerPairWithReferences(dec, nonPathMember, member, !representativeIsTree, + inNewReversed, inOldReversed, &ignored, nonPathRepresentative) ); + } + else + { + SCIP_CALL( createMarkerPairWithReferences(dec, member, nonPathMember, representativeIsTree, + inOldReversed, inNewReversed, nonPathRepresentative, &ignored) ); + } + } + else + { + *nonPathRepresentative = SPQR_INVALID_ARC; + if( numNonPathArcs != 0 ) + { + spqr_arc firstArc = getFirstMemberArc(dec, member); + spqr_arc arc = firstArc; + do + { + if( arc != *pathRepresentative && arc != exceptionArc1 && arc != exceptionArc2 ) + { + *nonPathRepresentative = arc; + break; + } + arc = getNextMemberArc(dec, arc); + } + while( arc != firstArc ); + assert(*nonPathRepresentative != SPQR_INVALID_ARC); + } + } + + return SCIP_OKAY; +} + +/** Transforms the first member in the path of members to reflect the new column update. */ +static +SCIP_RETCODE transformFirstPathMember( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMember, /**< The reduced member to transform */ + NewColInformation* newColInfo, /**< The new column information */ + spqr_arc* representativeArc, /**< Pointer to the representative of the member, needed for merging.*/ + spqr_member* mergedMember /**< Pointer to the merged member */ +) +{ + spqr_member member = newCol->reducedMembers[reducedMember].member; + SPQRMemberType type = getMemberType(dec, member); + if( type == SPQR_MEMBERTYPE_RIGID ) + { + //The nodes are already created, we only need to assign the correct start/end node + switch( newCol->reducedMembers[reducedMember].pathType ) + { + case INTO_HEAD: + case INTO_TAIL: + setTerminalTail(newColInfo, newCol->reducedMembers[reducedMember].rigidPathStart); + break; + case OUT_HEAD: + case OUT_TAIL: + setTerminalHead(newColInfo, newCol->reducedMembers[reducedMember].rigidPathEnd); + break; + } + *representativeArc = findArcSign(dec, newCol->reducedMembers[reducedMember].pathTargetArc).representative; + *mergedMember = member; + + return SCIP_OKAY; + } + assert(type == SPQR_MEMBERTYPE_SERIES); + //Split off sets of multiple path non-path edges so that the series has exactly 3 edges + + spqr_arc target = newCol->reducedMembers[reducedMember].pathTargetArc; + SPQRColReducedMember* redMem = &newCol->reducedMembers[reducedMember]; + spqr_arc pathRepresentative = SPQR_INVALID_ARC; + spqr_arc nonPathRepresentative = SPQR_INVALID_ARC; + SCIP_CALL( splitSeriesMerging(dec, newCol, redMem, member, &pathRepresentative, &nonPathRepresentative, target, + SPQR_INVALID_ARC) ); + + assert( + target != pathRepresentative && target != nonPathRepresentative && pathRepresentative != nonPathRepresentative); + assert(SPQRarcIsValid(pathRepresentative) && SPQRarcIsValid(nonPathRepresentative) && SPQRarcIsValid(target)); + assert(getNumMemberArcs(dec, member) == 3); + + //Create nodes + spqr_node a = SPQR_INVALID_NODE; + spqr_node b = SPQR_INVALID_NODE; + spqr_node c = SPQR_INVALID_NODE; + SCIP_CALL( createNode(dec, &a) ); + SCIP_CALL( createNode(dec, &b) ); + SCIP_CALL( createNode(dec, &c) ); + + // a -- b + // \ / + // c + + //Set arc nodes + //Set target from b to c, + SCIP_Bool targetReversed = arcIsReversedNonRigid(dec, target); + setArcHeadAndTail(dec, target, c, b); + + MemberPathType pathType = newCol->reducedMembers[reducedMember].pathType; + assert(pathType == INTO_HEAD || pathType == OUT_HEAD); + if( arcIsReversedNonRigid(dec, pathRepresentative) == targetReversed ) + { + setArcHeadAndTail(dec, pathRepresentative, a, c); + } + else + { + setArcHeadAndTail(dec, pathRepresentative, c, a); + } + if( arcIsReversedNonRigid(dec, nonPathRepresentative) == targetReversed ) + { + setArcHeadAndTail(dec, nonPathRepresentative, b, a); + } + else + { + setArcHeadAndTail(dec, nonPathRepresentative, a, b); + } + //setup signed union find; all arcs are placed are not reversed. We pick an arbitrary arc as 'root' arc for this skeleton + arcSetReversed(dec, target, FALSE); + arcSetReversed(dec, pathRepresentative, FALSE); + arcSetReversed(dec, nonPathRepresentative, FALSE); + arcSetRepresentative(dec, target, SPQR_INVALID_ARC); + arcSetRepresentative(dec, pathRepresentative, target); + arcSetRepresentative(dec, nonPathRepresentative, target); + *representativeArc = target; + + if( pathType == INTO_HEAD ) + { + setTerminalTail(newColInfo, a); + } + else + { + setTerminalHead(newColInfo, a); + } + + *mergedMember = member; + + return SCIP_OKAY; +} + +/** Transforms the next parallel member in the path of members and merge it into the current member. */ +static +SCIP_RETCODE transformAndMergeParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id current, /**< The current reduced member id */ + reduced_member_id next, /**< The next reduced member */ + spqr_member nextMember, /**< The member of the next reduced member in the path */ + SCIP_Bool nextIsParent, /**< Is the next reduced member the parent of the current member? */ + spqr_arc* representativeArc, /**< Pointer to the representative of the member, needed for merging.*/ + spqr_member* mergedMember /**< Pointer to the merged member */ +) +{ + //Split off edges not in current subtree to form one parallel (or one edge) + spqr_member childParallel = INVALID_REDUCED_MEMBER; + spqr_arc source = newCol->reducedMembers[next].pathSourceArc; + spqr_arc target = newCol->reducedMembers[next].pathTargetArc; + if( getNumMemberArcs(dec, nextMember) > 3 ) + { + SCIP_CALL( splitParallel(dec, nextMember, source, target, &childParallel) ); + nextMember = childParallel; + newCol->reducedMembers[next].member = childParallel; + } + assert(getNumMemberArcs(dec, nextMember) == 3); + + spqr_node sourceHead = SPQR_INVALID_NODE; + spqr_node sourceTail = SPQR_INVALID_NODE; + SCIP_CALL( createNode(dec, &sourceHead) ); + SCIP_CALL( createNode(dec, &sourceTail) ); + + //set edge nodes and arc union-find data + { + spqr_arc firstArc = getFirstMemberArc(dec, nextMember); + spqr_arc arc = firstArc; + + SCIP_Bool sourceReversed = arcIsReversedNonRigid(dec, source); + do + { + if( arcIsReversedNonRigid(dec, arc) == sourceReversed ) + { + setArcHeadAndTail(dec, arc, sourceHead, sourceTail); + } + else + { + setArcHeadAndTail(dec, arc, sourceTail, sourceHead); + } + arcSetRepresentative(dec, arc, source); + arcSetReversed(dec, arc, FALSE); + + arc = getNextMemberArc(dec, arc); + } + while( arc != firstArc ); + + arcSetRepresentative(dec, source, SPQR_INVALID_ARC); + } + + //fix arc orientations of members; we cannot reflect for parallels. + *representativeArc = mergeArcSigns(dec, *representativeArc, source, FALSE); + + spqr_member newMergedMember = SPQR_INVALID_MEMBER; + if( nextIsParent ) + { + SCIP_CALL( mergeGivenMemberIntoParent(dec, *mergedMember, nextMember, + source, newCol->reducedMembers[current].pathTargetArc, TRUE, + &newMergedMember) ); + } + else + { + SCIP_CALL( mergeGivenMemberIntoParent(dec, nextMember, *mergedMember, + newCol->reducedMembers[current].pathTargetArc, source, TRUE, + &newMergedMember) ); + } + *mergedMember = newMergedMember; + + return SCIP_OKAY; +} + +/** Transforms the next series member in the path of members and merge it into the current member. */ +static +SCIP_RETCODE transformAndMergeSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id current, /**< The current reduced member id */ + reduced_member_id next, /**< The next reduced member */ + spqr_member nextMember, /**< The member of the next reduced member in the path */ + SCIP_Bool nextIsParent, /**< Is the next reduced member the parent of the current member? */ + spqr_arc* representativeArc, /**< Pointer to the representative of the member, needed for merging.*/ + spqr_member* mergedMember, /**< Pointer to the merged member */ + NewColInformation* info /**< The new column information */ +) +{ + SPQRColReducedMember* redMem = &newCol->reducedMembers[next]; + spqr_arc source = redMem->pathSourceArc; + spqr_arc target = redMem->pathTargetArc; + + spqr_arc pathRepresentative = SPQR_INVALID_ARC; + spqr_arc nonPathRepresentative = SPQR_INVALID_ARC; + SCIP_CALL( + splitSeriesMerging(dec, newCol, redMem, nextMember, &pathRepresentative, &nonPathRepresentative, source, target) ); + //After splitting there is the following possibilities for nodes a-d: + //(a)-source-(b)-path-(c)-target-(d)-nonpath-(a) + //(a)-source-(b)-path-(c)-target-(d==a) + //(a)-source-(b)=(c)-target-(d)-nonpath-(a) + //(a)-source-(b)-path-(c)=(d) -nonpath-(a) + //Note that the given arc is always between the same nodes + assert(getNumMemberArcs(dec, nextMember) == 3 || getNumMemberArcs(dec, nextMember) == 4); + assert(pathRepresentative != source && nonPathRepresentative != source && + ( SPQRarcIsInvalid(target) || ( target != pathRepresentative && target != nonPathRepresentative ))); + spqr_node a = SPQR_INVALID_NODE; + spqr_node b = SPQR_INVALID_NODE; + spqr_node c = SPQR_INVALID_NODE; + spqr_node d = SPQR_INVALID_NODE; + SCIP_CALL( createNode(dec, &a) ); + SCIP_CALL( createNode(dec, &b) ); + if( SPQRarcIsValid(pathRepresentative) ) + { + SCIP_CALL( createNode(dec, &c) ); + } + else + { + c = b; + } + SCIP_Bool hasNonPath = SPQRarcIsValid(nonPathRepresentative); + SCIP_Bool hasTarget = SPQRarcIsValid(target); + if( hasNonPath && hasTarget ) + { + SCIP_CALL( createNode(dec, &d) ); + } + else + { + if( hasNonPath ) + { + d = c; + } + else + { + d = a; + } + } + + SCIP_Bool sourceReversed = arcIsReversedNonRigid(dec, source); + SCIP_Bool pathStartInHead = isHead(newCol->reducedMembers[current].pathType); + if( pathStartInHead ) + { + setArcHeadAndTail(dec, source, b, a); + } + else + { + setArcHeadAndTail(dec, source, a, b); + } + if( SPQRarcIsValid(pathRepresentative)) + { + if( (arcIsReversedNonRigid(dec, pathRepresentative) == sourceReversed) == pathStartInHead ) + { + setArcHeadAndTail(dec, pathRepresentative, c, b); + } + else + { + setArcHeadAndTail(dec, pathRepresentative, b, c); + } + arcSetReversed(dec, pathRepresentative, FALSE); + arcSetRepresentative(dec, pathRepresentative, source); + } + if( hasTarget ) + { + if( (arcIsReversedNonRigid(dec, target) == sourceReversed) == pathStartInHead ) + { + setArcHeadAndTail(dec, target, d, c); + } + else + { + setArcHeadAndTail(dec, target, c, d); + } + arcSetReversed(dec, target, FALSE); + arcSetRepresentative(dec, target, source); + } + if( hasNonPath ) + { + if( (arcIsReversedNonRigid(dec, nonPathRepresentative) == sourceReversed) == pathStartInHead ) + { + setArcHeadAndTail(dec, nonPathRepresentative, a, d); + } + else + { + setArcHeadAndTail(dec, nonPathRepresentative, d, a); + } + arcSetReversed(dec, nonPathRepresentative, FALSE); + arcSetRepresentative(dec, nonPathRepresentative, source); + } + + arcSetReversed(dec, source, FALSE); + arcSetRepresentative(dec, source, SPQR_INVALID_ARC); + + //fix arc orientations of members; we cannot reflect for series + + spqr_member newMergedMember = SPQR_INVALID_MEMBER; + if( nextIsParent ) + { + SCIP_CALL( mergeGivenMemberIntoParent(dec, *mergedMember, nextMember, + source, newCol->reducedMembers[current].pathTargetArc, TRUE, + &newMergedMember) ); + } + else + { + SCIP_CALL( mergeGivenMemberIntoParent(dec, nextMember, *mergedMember, + newCol->reducedMembers[current].pathTargetArc, source, TRUE, + &newMergedMember) ); + } + *mergedMember = newMergedMember; + + *representativeArc = mergeArcSigns(dec, *representativeArc, source, FALSE); + if( !hasTarget ) + { + //We are in the last node; finish the path + setTerminalReversed(info, FALSE); + if( isInto(newCol->reducedMembers[current].pathType)) + { + setTerminalHead(info, c); + } + else + { + setTerminalTail(info, c); + } + setTerminalMember(info, *mergedMember); + setTerminalRepresentative(info, *representativeArc); + } + + return SCIP_OKAY; +} + +/** Transforms the next rigid member in the path of members and merge it into the current member. */ +static +SCIP_RETCODE transformAndMergeRigid( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id current, /**< The current reduced member id */ + reduced_member_id next, /**< The next reduced member */ + spqr_member nextMember, /**< The member of the next reduced member in the path */ + SCIP_Bool nextIsParent, /**< Is the next reduced member the parent of the current member? */ + spqr_arc* representativeArc, /**< Pointer to the representative of the member, needed for merging.*/ + spqr_member* mergedMember, /**< Pointer to the merged member */ + NewColInformation* info /**< The new column information */ +) +{ + SPQRColReducedMember* redMem = &newCol->reducedMembers[next]; + spqr_arc source = redMem->pathSourceArc; + spqr_arc sourceRepresentative = findArcSign(dec, source).representative; + + spqr_member newMergedMember = SPQR_INVALID_MEMBER; + + if( nextIsParent ) + { + SCIP_CALL( mergeGivenMemberIntoParent(dec, *mergedMember, nextMember, + source, newCol->reducedMembers[current].pathTargetArc, !redMem->reverseArcs, + &newMergedMember) ); + } + else + { + SCIP_CALL( mergeGivenMemberIntoParent(dec, nextMember, *mergedMember, + newCol->reducedMembers[current].pathTargetArc, source, !redMem->reverseArcs, + &newMergedMember) ); + } + + *mergedMember = newMergedMember; + + *representativeArc = mergeArcSigns(dec, *representativeArc, sourceRepresentative, redMem->reverseArcs); + + if( SPQRarcIsInvalid(redMem->pathTargetArc) ) + { + //We are in the last node; finish the path + setTerminalReversed(info, FALSE); + if( isInto(newCol->reducedMembers[current].pathType) ) + { + if( redMem->reverseArcs ) + { + setTerminalHead(info, redMem->rigidPathStart); + } + else + { + setTerminalHead(info, redMem->rigidPathEnd); + } + } + else + { + if( redMem->reverseArcs ) + { + setTerminalTail(info, redMem->rigidPathEnd); + } + else + { + setTerminalTail(info, redMem->rigidPathStart); + } + } + setTerminalMember(info, *mergedMember); + setTerminalRepresentative(info, *representativeArc); + } + + return SCIP_OKAY; +} + +/** Transforms the next member in the path of members and merge it into the current member. */ +static +SCIP_RETCODE transformAndMerge( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id current, /**< The current reduced member id */ + reduced_member_id next, /**< The next reduced member */ + spqr_arc* representativeArc, /**< Pointer to the representative of the member, needed for merging.*/ + spqr_member* mergedMember, /**< Pointer to the merged member */ + SCIP_Bool nextIsParent, /**< Is the next reduced member the parent of the current member? */ + NewColInformation* info /**< The new column information */ +) +{ + spqr_member nextMember = newCol->reducedMembers[next].member; + switch( getMemberType(dec, nextMember) ) + { + case SPQR_MEMBERTYPE_RIGID: + { + SCIP_CALL( transformAndMergeRigid(dec, newCol, current, next, nextMember, nextIsParent, + representativeArc, mergedMember, info) ); + break; + } + case SPQR_MEMBERTYPE_PARALLEL: + { + SCIP_CALL( transformAndMergeParallel(dec, newCol, current, next, nextMember, nextIsParent, + representativeArc, mergedMember) ); + break; + } + case SPQR_MEMBERTYPE_SERIES: + { + SCIP_CALL( transformAndMergeSeries(dec, newCol, current, next, nextMember, nextIsParent, + representativeArc, mergedMember, info) ); + break; + } + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_UNASSIGNED: + { + assert(FALSE); + break; + } + } + return SCIP_OKAY; +} + +/** Transforms a single component when it contains multiple reduced members. */ +static +SCIP_RETCODE transformPath( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + SPQRColReducedComponent* component, /**< The component to transform */ + NewColInformation* newColInfo /**< The new column information */ +) +{ + //Realize first member + reduced_member_id firstPathMember = component->pathEndMembers[0]; + + spqr_arc representativeArc = SPQR_INVALID_ARC; + spqr_member mergedMember = SPQR_INVALID_MEMBER; + SCIP_CALL( transformFirstPathMember(dec, newCol, firstPathMember, newColInfo, &representativeArc, &mergedMember) ); + //Iteratively call function which realizes next member and merges them together. + reduced_member_id current = firstPathMember; + reduced_member_id next = newCol->reducedMembers[current].nextPathMember; + SCIP_Bool nextIsParent = newCol->reducedMembers[current].nextPathMemberIsParent; + while( reducedMemberIsValid(next) ) + { + SCIP_CALL( + transformAndMerge(dec, newCol, current, next, &representativeArc, &mergedMember, nextIsParent, newColInfo)); + current = next; + next = newCol->reducedMembers[current].nextPathMember; + nextIsParent = newCol->reducedMembers[current].nextPathMemberIsParent; + } + + return SCIP_OKAY; +} + +/** Transform a single parallel member to add the new column. */ +static +SCIP_RETCODE columnTransformSingleParallel( + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMemberId, /**< The reduced member to transform */ + spqr_member member, /**< The member to transform */ + NewColInformation* newColInfo /**< The new column information */ +) +{ + SPQRColReducedMember* reducedMember = &newCol->reducedMembers[reducedMemberId]; + assert(pathArcIsValid(reducedMember->firstPathArc) && reducedMember->numPathArcs == 1); + //The new arc can be placed in parallel; just add it to this member + setTerminalReversed(newColInfo, reducedMember->pathBackwards); + setTerminalMember(newColInfo, member); + + return SCIP_OKAY; +} + +/** Transform a single series member to add the new column. */ +static +SCIP_RETCODE columnTransformSingleSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMemberId, /**< The reduced member to transform */ + spqr_member member, /**< The member to transform */ + NewColInformation* newColInfo /**< The new column information */ +) +{ + if( getNumMemberArcs(dec, member) == 1 ) + { + newColInfo->member = member; + newColInfo->reversed = newCol->arcInPathReversed[getFirstMemberArc(dec, member)]; + return SCIP_OKAY; + } + //Isolated single cycle + spqr_member loopMember; + SPQRColReducedMember* reducedMember = &newCol->reducedMembers[reducedMemberId]; + SCIP_CALL( splitSeries(dec, newCol, reducedMember, member, &loopMember, newColInfo) ); + + return SCIP_OKAY; +} + +/** Transform a single rigid member to add the new column. */ +static +SCIP_RETCODE columnTransformSingleRigid( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + reduced_member_id reducedMemberId, /**< The reduced member to transform */ + spqr_member member, /**< The member to transform */ + NewColInformation* newColInfo /**< The new column information */ +) +{ + assert(dec); + assert(newCol); + assert(newColInfo); + assert(reducedMemberIsValid(reducedMemberId)); + + //The path is already computed, so we can simply take the start and end nodes. + //However, there is one exception, which is that an arc connecting these two nodes already exists in the member + //If so, we create a new parallel member with the new arc and this member, unless the existing arc already points + //to a parallel member + SPQRColReducedMember* reducedMember = &newCol->reducedMembers[reducedMemberId]; + assert(SPQRnodeIsValid(reducedMember->rigidPathStart) && SPQRnodeIsValid(reducedMember->rigidPathEnd)); + { + spqr_arc existingArcWithPath = SPQR_INVALID_ARC; + spqr_arc firstArc = getFirstNodeArc(dec, reducedMember->rigidPathStart); + spqr_arc arc = firstArc; + SCIP_Bool pathInSameDirection = FALSE; + do + { + spqr_node head = findArcHead(dec, arc); + spqr_node tail = findArcTail(dec, arc); + spqr_node other = head == reducedMember->rigidPathStart ? tail : head; + if( other == reducedMember->rigidPathEnd ) + { + existingArcWithPath = arc; + pathInSameDirection = ( head == other ) != findArcSign(dec, existingArcWithPath).reversed; + break; + } + arc = getNextNodeArc(dec, arc, reducedMember->rigidPathStart); + } + while( arc != firstArc ); + if( SPQRarcIsValid(existingArcWithPath) ) + { + SCIP_Bool isParent = FALSE; + spqr_member adjacentMember = arcIsChildMarker(dec, existingArcWithPath) ? + findArcChildMember(dec, existingArcWithPath) : SPQR_INVALID_MEMBER; + if( existingArcWithPath == markerToParent(dec, member) ) + { + adjacentMember = findMemberParent(dec, member); + isParent = TRUE; + } + if( SPQRmemberIsValid(adjacentMember) && getMemberType(dec, adjacentMember) == SPQR_MEMBERTYPE_PARALLEL ) + { + spqr_arc parallelMarker = isParent ? markerOfParent(dec, member) : markerToParent(dec, adjacentMember); + SCIP_Bool markerReversed = arcIsReversedNonRigid(dec, parallelMarker); + setTerminalMember(newColInfo, adjacentMember); + setTerminalReversed(newColInfo, markerReversed == pathInSameDirection); + } + else + { + //create a new parallel and move the edge there + //This is a bit painful, because we cannot actually remove edges because of the union-find data structure + //So what we do instead, is convert the current edge to a marker edge, and 'duplicate' + //it in the new parallel member, and add the new marker there too, manually + spqr_member adjacentParallel = SPQR_INVALID_MEMBER; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, &adjacentParallel) ); + //'duplicate' a new arc in the parallel to be the current arc + spqr_arc duplicate = SPQR_INVALID_ARC; + spqr_element element = arcGetElement(dec, existingArcWithPath); + if( element != MARKER_COLUMN_ELEMENT && element != MARKER_ROW_ELEMENT ) + { + if( SPQRelementIsColumn(element) ) + { + SCIP_CALL( createColumnArc(dec, adjacentParallel, &duplicate, SPQRelementToColumn(element), FALSE) ); + } + else + { + SCIP_CALL( createRowArc(dec, adjacentParallel, &duplicate, SPQRelementToRow(element), FALSE) ); + } + } + else if( isParent ) + { + SCIP_CALL( createParentMarker(dec, adjacentParallel, arcIsTree(dec, existingArcWithPath), adjacentMember, + markerOfParent(dec, member), &duplicate, FALSE) ); + } + else + { + SCIP_CALL( createChildMarker(dec, adjacentParallel, adjacentMember, arcIsTree(dec, existingArcWithPath), + &duplicate, FALSE) ); + dec->members[adjacentMember].parentMember = adjacentParallel; + dec->members[adjacentMember].markerOfParent = duplicate; + } + //Create the other marker edge + spqr_arc parallelMarker = SPQR_INVALID_ARC; + if( isParent ) + { + SCIP_CALL( createChildMarker(dec, adjacentParallel, member, !arcIsTree(dec, existingArcWithPath), + ¶llelMarker, FALSE) ); + } + else + { + SCIP_CALL( createParentMarker(dec, adjacentParallel, !arcIsTree(dec, existingArcWithPath), + member, existingArcWithPath, ¶llelMarker, FALSE) ); + } + + //Change the existing edge to a marker + if( isParent ) + { + assert(markerToParent(dec, member) == existingArcWithPath); + dec->arcs[markerOfParent(dec, member)].childMember = adjacentParallel; + dec->members[member].parentMember = adjacentParallel; + dec->members[member].markerToParent = existingArcWithPath; + dec->members[member].markerOfParent = parallelMarker; + dec->arcs[existingArcWithPath].element = arcIsTree(dec, existingArcWithPath) ? MARKER_ROW_ELEMENT + : MARKER_COLUMN_ELEMENT; + dec->arcs[existingArcWithPath].childMember = adjacentParallel; + + } + else + { + dec->arcs[existingArcWithPath].element = arcIsTree(dec, existingArcWithPath) ? MARKER_ROW_ELEMENT + : MARKER_COLUMN_ELEMENT; + dec->arcs[existingArcWithPath].childMember = adjacentParallel; + } + + setTerminalMember(newColInfo, adjacentParallel); + setTerminalReversed(newColInfo, !pathInSameDirection); + } + return SCIP_OKAY; + } + } + + setTerminalMember(newColInfo, member); + setTerminalReversed(newColInfo, FALSE); + setTerminalTail(newColInfo, reducedMember->rigidPathStart); + setTerminalHead(newColInfo, reducedMember->rigidPathEnd); + setTerminalRepresentative(newColInfo, + findArcSign(dec, newCol->pathArcs[reducedMember->firstPathArc].arc).representative); + + return SCIP_OKAY; +} + +/** Transform a component to reflect the new column. */ +static +SCIP_RETCODE transformComponent( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol, /**< The network matrix column addition data structure */ + SPQRColReducedComponent* component, /**< The component to transform */ + NewColInformation* newColInfo /**< The new column information */ +) +{ + assert(dec); + assert(newCol); + assert(component); + assert(newColInfo); + + if( newCol->reducedMembers[component->root].numChildren == + newCol->reducedMembers[component->root].numPropagatedChildren ) + { + //No merging necessary, only a single component + reduced_member_id reducedMember = component->root; + assert(reducedMemberIsValid(reducedMember)); + spqr_member member = newCol->reducedMembers[reducedMember].member; + SPQRMemberType type = getMemberType(dec, member); + + switch( type ) + { + case SPQR_MEMBERTYPE_RIGID: + { + SCIP_CALL( columnTransformSingleRigid(dec, newCol, reducedMember, member, newColInfo) ); + break; + } + case SPQR_MEMBERTYPE_PARALLEL: + { + SCIP_CALL( columnTransformSingleParallel(newCol, reducedMember, member, newColInfo) ); + break; + } + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_SERIES: + { + SCIP_CALL( columnTransformSingleSeries(dec, newCol, reducedMember, member, newColInfo) ); + break; + } + case SPQR_MEMBERTYPE_UNASSIGNED: + default: + { + assert(FALSE); + break; + } + } + return SCIP_OKAY; + } + // Otherwise, the reduced members form a path which can be merged into a single component of type R + SCIP_CALL( transformPath(dec, newCol, component, newColInfo) ); + + return SCIP_OKAY; +} + +/** Check if the submatrix stored remains a network matrix with the new column update. */ +static +SCIP_Bool netcoladdRemainsNetwork( + const SCIP_NETCOLADD* newCol /**< The network matrix column addition data structure */ + ) +{ + return newCol->remainsNetwork; +} + +/** Add the new column to the network decomposition as an arc. + * + * Only use this function after SCIPnetcoladdCheck() has been called. + */ +static +SCIP_RETCODE netcoladdAdd( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETCOLADD* newCol /**< The network matrix column addition data structure */ +) +{ + assert(dec); + assert(newCol); + assert(netcoladdRemainsNetwork(newCol)); + + if( newCol->numReducedComponents == 0 ) + { + spqr_member member; + SCIP_CALL( createStandaloneSeries(dec, newCol->newRowArcs, newCol->newRowArcReversed, + newCol->numNewRowArcs, newCol->newColIndex, &member) ); + } + else if( newCol->numReducedComponents == 1 ) + { + NewColInformation information = NEWCOLINFORMATION_EMPTY; + SCIP_CALL( transformComponent(dec, newCol, &newCol->reducedComponents[0], &information) ); + assert(memberIsRepresentative(dec, information.member)); + if( newCol->numNewRowArcs == 0 ) + { + spqr_arc colArc = SPQR_INVALID_ARC; + SCIP_CALL( createColumnArc(dec, information.member, &colArc, newCol->newColIndex, information.reversed) ); + if( SPQRnodeIsValid(information.head) ) + { + assert(SPQRnodeIsValid(information.tail)); + assert(SPQRarcIsValid(information.representative)); + setArcHeadAndTail(dec, colArc, findNode(dec, information.head), findNode(dec, information.tail)); + arcSetRepresentative(dec, colArc, information.representative); + arcSetReversed(dec, colArc, information.reversed != arcIsReversedNonRigid(dec, information.representative)); + } + } + else + { + spqr_member newSeries = SPQR_INVALID_MEMBER; + SCIP_CALL( createConnectedSeries(dec, newCol->newRowArcs, newCol->newRowArcReversed, newCol->numNewRowArcs, + newCol->newColIndex, &newSeries) ); + spqr_arc markerArc = SPQR_INVALID_ARC; + spqr_arc ignore = SPQR_INVALID_ARC; + SCIP_CALL( createMarkerPairWithReferences(dec, information.member, newSeries, FALSE, information.reversed, TRUE, + &markerArc, &ignore) ); + if( SPQRnodeIsValid(information.head) ) + { + assert(SPQRnodeIsValid(information.tail)); + assert(SPQRarcIsValid(information.representative)); + setArcHeadAndTail(dec, markerArc, findNode(dec, information.head), findNode(dec, information.tail)); + arcSetRepresentative(dec, markerArc, information.representative); + arcSetReversed(dec, markerArc, + information.reversed != arcIsReversedNonRigid(dec, information.representative)); + } + } + if( getMemberType(dec, information.member) == SPQR_MEMBERTYPE_LOOP ) + { + assert(getNumMemberArcs(dec, information.member) == 2 || getNumMemberArcs(dec, information.member) == 3); + if( getNumMemberArcs(dec, information.member) == 3 ) + { + changeLoopToParallel(dec, information.member); + } + } + } + else + { +#ifndef NDEBUG + int numDecComponentsBefore = numConnectedComponents(dec); +#endif + spqr_member newSeries = SPQR_INVALID_MEMBER; + SCIP_CALL( createConnectedSeries(dec, newCol->newRowArcs, newCol->newRowArcReversed, + newCol->numNewRowArcs, newCol->newColIndex, &newSeries) ); + for( int i = 0; i < newCol->numReducedComponents; ++i ) + { + NewColInformation information = NEWCOLINFORMATION_EMPTY; + SCIP_CALL( transformComponent(dec, newCol, &newCol->reducedComponents[i], &information) ); + if( getMemberType(dec, information.member) == SPQR_MEMBERTYPE_LOOP ) + { + assert(getNumMemberArcs(dec, information.member) == 1); + spqr_arc arc = getFirstMemberArc(dec, information.member); + assert(newCol->arcInPath[arc]); + moveArcToNewMember(dec, arc, information.member, newSeries); + arcSetReversed(dec, arc, !newCol->arcInPathReversed[arc]); + dec->members[information.member].type = SPQR_MEMBERTYPE_UNASSIGNED; + } + else + { + reorderComponent(dec, information.member);//reorder the subtree so that the newly series member is a parent + spqr_arc markerArc = SPQR_INVALID_ARC; + spqr_arc ignore = SPQR_INVALID_ARC; + SCIP_CALL( createMarkerPairWithReferences(dec, newSeries, information.member, TRUE, information.reversed, + TRUE, &ignore, &markerArc) ); + if( SPQRnodeIsValid(information.head) ) + { + assert(SPQRnodeIsValid(information.tail)); + assert(SPQRarcIsValid(information.representative)); + setArcHeadAndTail(dec, markerArc, findNode(dec, information.head), findNode(dec, information.tail)); + arcSetRepresentative(dec, markerArc, information.representative); + arcSetReversed(dec, markerArc, + information.reversed == arcIsReversedNonRigid(dec, information.representative)); + } + } + } + decreaseNumConnectedComponents(dec, newCol->numReducedComponents - 1); + assert(numConnectedComponents(dec) == ( numDecComponentsBefore - newCol->numReducedComponents + 1 )); + } + + return SCIP_OKAY; +} + +/* ---------- END functions for column addition --------------------------------------------------------------------- */ + +/* ---------- START functions for row addition ---------------------------------------------------------------------- */ + +/** Index type for the nonzeros of the new row, e.g. the columns whose tree path must be elongated with the new row */ +typedef int cut_arc_id; +#define INVALID_CUT_ARC (-1) + +/** Checks if the given cut arc is invalid (negative). */ +static +SCIP_Bool cutArcIsInvalid( + const cut_arc_id arc /**< The cut arc to check */ + ) +{ + return arc < 0; +} + +/** Checks if the given cut arc is valid (nonnegative). */ +static SCIP_Bool cutArcIsValid( + const cut_arc_id arc /**< The cut arc to check */ + ) +{ + return !cutArcIsInvalid(arc); +} + +/** Linked list node for the cut arcs. + * + * Contains links to the next cut arc in the whole decomposition and the next cut arc in the current member. + */ +typedef struct +{ + spqr_arc arc; /**< The arc id */ + spqr_node arcHead; /**< The arc's head node */ + spqr_node arcTail; /**< The arc's tail node */ + cut_arc_id nextMember; /**< Index to next linked list node of the linked list containing the cut arcs + * of the arcs member.*/ + cut_arc_id nextOverall; /**< Index to next linked list node of the linked list containing the cut arcs + * of the complete decomposition.*/ + SCIP_Bool arcReversed; /**< Should the new row have reverse direction in the cut arcs path? */ +} CutArcListNode; + +/** Type of the reduced member + * + * Indicates whether the member is processed, and whether it should be merged or left the same. + */ +typedef enum +{ + TYPE_UNDETERMINED = 0, /**< The type of this member has not yet been determined */ + TYPE_PROPAGATED = 1, /**< The member contains cut arcs, but should not be merged */ + TYPE_MERGED = 2, /**< This member should be merged into a rigid member */ + TYPE_NOT_NETWORK = 3 /**< This member implies that the addition of the new row + * does not create a network matrix */ +} RowReducedMemberType; + +/** Stores for every vertex information needed for computing articulation nodes in rigid members. */ +typedef struct +{ + int low; /**< What is the lowest discovery time vertex that can be reached from this + * subtree?*/ + int discoveryTime; /**< When was the vertex first discovered? */ +} ArticulationNodeInformation; + +/** Call stack data structure for performing DFS on the rigid member graphs. */ +typedef struct +{ + spqr_node node; /**< The current node of the DFS call */ + spqr_arc nodeArc; /**< The current arc of the node that is being processed */ +} DFSCallData; + +/** Call stack data structure for merging the SPQR tree into a single rigid member. */ +typedef struct +{ + children_idx currentChild; /**< The index of the current child that is being merged */ + reduced_member_id id; /**< The index of the current member */ +} MergeTreeCallData; + +/** Call stack data structure that determines whether a bipartition of the nodes exists that satisfies the requirements. */ +typedef struct +{ + spqr_node node; /**< The current node of the call */ + spqr_arc arc; /**< The current arc that is being processed */ +} ColorDFSCallData; + +/** Call stack data structure for recursive algorithm to find articulation point in rigid member graphs (Tarjan). */ +typedef struct +{ + spqr_arc arc; /**< The arc that is currently being processed */ + spqr_node node; /**< The node that is currently being processed */ + spqr_node parent; /**< The node's parent */ + SCIP_Bool isAP; /**< Is the current node an articulation point? */ +} ArticulationPointCallStack; + +/** Colors assigned to the node in the partitioning algorithm. + * + * It must either be in the source or the sink partition. + */ +typedef enum +{ + UNCOLORED = 0, /**< The current node has not yet been processed */ + COLOR_SOURCE = 1, /**< The current node belongs to the source partition */ + COLOR_SINK = 2 /**< The current node belongs to the sink partition */ +} COLOR_STATUS; + +/** Struct that stores the data of a single reduced member. */ +typedef struct +{ + spqr_member member; /**< The id of the decomposition member */ + spqr_member rootMember; /**< The decomposition member that is the root node of the arborescence + * containing this member */ + int depth; /**< The depth of this member in the arborescence */ + RowReducedMemberType type; /**< The type of the member */ + reduced_member_id parent; /**< The reduced member id of the parent of this reduced member */ + + children_idx firstChild; /**< The index of the first child in the children array. */ + children_idx numChildren; /**< The number of children in the arborescence of this reduced member */ + children_idx numPropagatedChildren; /**< Counts the number of children that are propagated to this reduced + * member */ + + cut_arc_id firstCutArc; /**< Head of the linked list containing the cut arcs */ + int numCutArcs; /**< The number of cut arcs in the linked list */ + + spqr_arc splitArc; /**< An arc adjacent to the split node. */ + SCIP_Bool splitHead; /**< Is the head or the tail of the split arc split in the realization? */ + SCIP_Bool otherIsSource; /**< Is the nonsplit node of the split arc in the source or the sink + * partition? In rigid members this refers to the articulation arc. */ + + /* Rigid member fields */ + spqr_node otherNode; /**< The other nonsplit node adjacent to the virtual edge */ + spqr_node splitNode; /**< The node to be split */ + SCIP_Bool allHaveCommonNode; /**< Do all cut edges share a common node? */ + SCIP_Bool otherNodeSplit; /**< Is the other node a split node, too? */ + SCIP_Bool willBeReversed; /**< Will all the arcs in this component be reversed? */ + spqr_arc articulationArc; /**< Indicates the arc between the split node and the other ndoe, if both + * are splittable. */ + spqr_node coloredNode; /**< Points to a colored node so that we can efficiently zero out colors + * by backtracking our DFS */ +} SPQRRowReducedMember; + +/** Keeps track of the data relevant for each SPQR tree in the SPQR forest. */ +typedef struct +{ + int rootDepth; /**< The depth of the root node of the subtree in the arborescence */ + reduced_member_id root; /**< The reduced member id of the root */ +} SPQRRowReducedComponent; + +/** The main datastructure that manages all the data for row-addition in network matrices */ +typedef struct +{ + SCIP_Bool remainsNetwork; /**< Does the addition of the current row give a network matrix? */ + + SPQRRowReducedMember* reducedMembers; /**< The array of reduced members, that form the subtree containing the + * rows of the current column.*/ + int memReducedMembers; /**< Number of allocated slots in the reduced member array */ + int numReducedMembers; /**< Number of used slots in the reduced member array */ + + SPQRRowReducedComponent* reducedComponents; /**< The array of reduced components, + * that represent the SPQR trees in the SPQR forest */ + int memReducedComponents; /**< Number of allocated slots in the reduced component array */ + int numReducedComponents; /**< Number of used slots in the reduced component array */ + + MemberInfo* memberInformation; /**< Array with member information; tracks the reduced member id that + * corresponds to every member in the decomposition. */ + int memMemberInformation; /**< Number of allocated slots in the member information array */ + int numMemberInformation; /**< Number of used slots in the member information array */ + + reduced_member_id* childrenStorage; /**< Array that stores the children of the reduced member arborescences. + * Each reduced member has a 'firstChild' field and a length, that points + * to the subarray within this array with its children. This array is + * shared here in order to minimize allocations across iterations. */ + int memChildrenStorage; /**< Number of allocated slots for the children storage array */ + int numChildrenStorage; /**< Number of used slots for the children storage array */ + + CutArcListNode* cutArcs; /**< Array containing the linked list nodes of the cut arcs */ + int memCutArcs; /**< Number of allocated entries in cutArcs */ + int numCutArcs; /**< Number of used entries in cutArcs */ + cut_arc_id firstOverallCutArc; /**< Index of the head node of the linked list containing all cut arcs */ + + spqr_row newRowIndex; /**< The index of the new row to be added */ + + spqr_col* newColumnArcs; /**< The nonzero columns in the new row that do not yet occur in the + * decomposition */ + SCIP_Bool* newColumnReversed; /**< True if the nonzero entry of the new column is -1, False otherwise */ + int memColumnArcs; /**< Number of allocated slots in newColumnArcs/newColumnReversed */ + int numColumnArcs; /**< Number of new columns in the row to be added */ + + reduced_member_id* leafMembers; /**< Array that stores the leaf members of the SPQR forest */ + int numLeafMembers; /**< Number of used slots in leafMembers array */ + int memLeafMembers; /**< Number of allocated slots in leafMembers array */ + + spqr_arc* decompositionColumnArcs; /**< For each nonzero column of the new row that is in the decomposition, + * stores the corresponding decomposition arc */ + SCIP_Bool* decompositionColumnArcReversed; /**< For each nonzero column of the new row that is in the decomposition, + * stores whether the corresponding decomposition arc is reversed */ + int memDecompositionColumnArcs; /**< Number of allocated slots in decompositionColumnArcs(Reversed) */ + int numDecompositionColumnArcs; /**< Number of used slots in decompositionColumnArcs(Reversed) */ + + SCIP_Bool* isArcCut; /**< Stores for each arc, if the arc a cut arc? */ + SCIP_Bool* isArcCutReversed; /**< Is the new row in reverse direction on the arcs cycle? */ + int memIsArcCut; /**< The allocated size of the isArcCut(Reversed) arrays */ + + COLOR_STATUS* nodeColors; /**< Stores the color of each node */ + int memNodeColors; /**< The allocated size of the nodeColors array */ + + spqr_node* articulationNodes; /**< Temp. array for storing articulation nodes of member graph-cut arcs */ + int numArticulationNodes; /**< Number of used slots in articulation nodes array */ + int memArticulationNodes; /**< Number of allocated slots in articulation nodes array */ + + ArticulationNodeInformation* articulationNodeSearchInfo; /**< Stores for each node information necessary to find + * articulation nodes. */ + int memNodeSearchInfo; /**< The number of allocated entries in articulationNodeSearchInfo array*/ + + int* crossingPathCount; /**< Stores for each arc, how many cut arc cycles contain it */ + int memCrossingPathCount; /**< The number of allocated entries for the crossingPathCount array */ + + DFSCallData* intersectionDFSData; /**< Call stack for computing the intersection of all cut arc paths */ + int memIntersectionDFSData; /**< Number of allocated entries for intersectionDFSData */ + + ColorDFSCallData* colorDFSData; /**< Call stack for computing source/sink coloring */ + int memColorDFSData; /**< Number of allocated entries for colorDFSData */ + + ArticulationPointCallStack* artDFSData; /**< Call stack for computing articulation points */ + int memArtDFSData; /**< Number of allocated entries for artDFSData */ + + CreateReducedMembersCallstack* createReducedMembersCallstack; /**< Call stack for createReducedMembers() */ + int memCreateReducedMembersCallstack; /**< Number of allocated entries for createReducedMembersCallStack */ + + int* intersectionPathDepth; /**< Tracks depth of each node in the intersection of all paths algorithm */ + int memIntersectionPathDepth; /**< Number of allocated entries in intersectionPathDepth array */ + + spqr_node* intersectionPathParent; /**< Tracks the parents of each node in the intersection of all paths + * algorithm. */ + int memIntersectionPathParent; /**< Number of allocated entries in intersectionPathParent array */ + + MergeTreeCallData* mergeTreeCallData; /**< Call stack for mergeTree */ + int memMergeTreeCallData; /**< Number of allocated elements for mergeTreeCallData */ + + COLOR_STATUS* temporaryColorArray; /**< A temporary array used for saving some colors */ + int memTemporaryColorArray; /**< The number of allocated elements in temporaryColorArray */ +} SCIP_NETROWADD; + +/** Struct that contains information on how to place the new row arc in the decomposition */ +typedef struct +{ + spqr_member member; /**< The member to place the new row in */ + spqr_node head; /**< The head node of the new row arc (only used for rigid members) */ + spqr_node tail; /**< The tail node of the new row arc (only used for rigid members) */ + spqr_arc representative; /**< The representative arc of the new row arc */ + SCIP_Bool reversed; /**< Orientation of the arc w.r.t. the representative */ +} NewRowInformation; + +#define NEWROWINFORMATION_EMPTY { SPQR_INVALID_MEMBER, SPQR_INVALID_NODE, SPQR_INVALID_NODE, SPQR_INVALID_ARC, FALSE } + +/** Saves the information of the current row and partitions it based on whether or not the given columns are + * already part of the decomposition. + */ +static +SCIP_RETCODE newRowUpdateRowInformation( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition datastructure */ + const spqr_row row, /**< The row to check */ + const spqr_col* columns, /**< The column indices of the nonzeros in the row */ + const double* columnValues, /**< The matrix entries of the nonzeros in the row */ + const int numColumns /**< The number of nonzeros in the row */ +) +{ + newRow->newRowIndex = row; + + newRow->numDecompositionColumnArcs = 0; + newRow->numColumnArcs = 0; + + for( int i = 0; i < numColumns; ++i ) + { + spqr_arc columnArc = getDecompositionColumnArc(dec, columns[i]); + SCIP_Bool reversed = columnValues[i] < 0.0; + if( SPQRarcIsValid(columnArc) ) + {//If the arc is the current decomposition: save it in the array + if( newRow->numDecompositionColumnArcs == newRow->memDecompositionColumnArcs ) + { + int newNumArcs = newRow->memDecompositionColumnArcs == 0 ? 8 : 2 * newRow->memDecompositionColumnArcs; + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->decompositionColumnArcs, + newRow->memDecompositionColumnArcs, newNumArcs) ); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->decompositionColumnArcReversed, + newRow->memDecompositionColumnArcs, newNumArcs) ); + newRow->memDecompositionColumnArcs = newNumArcs; + } + newRow->decompositionColumnArcs[newRow->numDecompositionColumnArcs] = columnArc; + newRow->decompositionColumnArcReversed[newRow->numDecompositionColumnArcs] = reversed; + ++newRow->numDecompositionColumnArcs; + } + else + { + //Not in the decomposition: add it to the set of arcs which are newly added with this row. + if( newRow->numColumnArcs == newRow->memColumnArcs ) + { + int newNumArcs = newRow->memColumnArcs == 0 ? 8 : 2 * newRow->memColumnArcs; + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->newColumnArcs, + newRow->memColumnArcs, newNumArcs) ); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->newColumnReversed, + newRow->memColumnArcs, newNumArcs) ); + newRow->memColumnArcs = newNumArcs; + } + newRow->newColumnArcs[newRow->numColumnArcs] = columns[i]; + newRow->newColumnReversed[newRow->numColumnArcs] = reversed; + newRow->numColumnArcs++; + } + } + + return SCIP_OKAY; +} + +/** Adds members to the reduced member tree, by starting at the given member, jumping up to the parent, repeating this + * procedure until the root has been added. + */ +static +reduced_member_id createRowReducedMembersToRoot( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition datastructure */ + const spqr_member firstMember /**< The member to create a reduced member for */ +) +{ + assert(SPQRmemberIsValid(firstMember)); + + CreateReducedMembersCallstack* callstack = newRow->createReducedMembersCallstack; + callstack[0].member = firstMember; + int callDepth = 0; + + while( callDepth >= 0 ) + { + spqr_member member = callstack[callDepth].member; + reduced_member_id reducedMember = newRow->memberInformation[member].reducedMember; + + SCIP_Bool reducedValid = reducedMemberIsValid(reducedMember); + if( !reducedValid ) + { + //reduced member was not yet created; we create it + reducedMember = newRow->numReducedMembers; + + SPQRRowReducedMember* reducedMemberData = &newRow->reducedMembers[reducedMember]; + ++newRow->numReducedMembers; + + reducedMemberData->member = member; + reducedMemberData->numChildren = 0; + reducedMemberData->numCutArcs = 0; + reducedMemberData->firstCutArc = INVALID_CUT_ARC; + reducedMemberData->type = TYPE_UNDETERMINED; + reducedMemberData->numPropagatedChildren = 0; + reducedMemberData->articulationArc = SPQR_INVALID_ARC; + reducedMemberData->splitNode = SPQR_INVALID_NODE; + reducedMemberData->otherNode = SPQR_INVALID_NODE; + reducedMemberData->splitArc = SPQR_INVALID_ARC; + reducedMemberData->splitHead = FALSE; + reducedMemberData->allHaveCommonNode = FALSE; + reducedMemberData->otherNodeSplit = FALSE; + reducedMemberData->willBeReversed = FALSE; + reducedMemberData->coloredNode = SPQR_INVALID_NODE; + + newRow->memberInformation[member].reducedMember = reducedMember; + assert(memberIsRepresentative(dec, member)); + spqr_member parentMember = findMemberParent(dec, member); + + if( SPQRmemberIsValid(parentMember) ) + { + //recursive call to parent member + ++callDepth; + assert(callDepth < newRow->memCreateReducedMembersCallstack); + callstack[callDepth].member = parentMember; + continue; + } + else + { + //we found a new reduced decomposition component + + reducedMemberData->parent = INVALID_REDUCED_MEMBER; + reducedMemberData->depth = 0; + reducedMemberData->rootMember = member; + + assert(newRow->numReducedComponents < newRow->memReducedComponents); + newRow->reducedComponents[newRow->numReducedComponents].root = reducedMember; + ++newRow->numReducedComponents; + } + } + if( reducedValid ) + { + assert(reducedMember < newRow->numReducedMembers); + //Reduced member was already created in earlier call + //update the depth of the root if appropriate + reduced_member_id* depthMinimizer = &newRow->memberInformation[newRow->reducedMembers[reducedMember].rootMember].rootDepthMinimizer; + if( reducedMemberIsInvalid(*depthMinimizer) || + newRow->reducedMembers[reducedMember].depth < newRow->reducedMembers[*depthMinimizer].depth ) + { + *depthMinimizer = reducedMember; + } + } + while( TRUE ) /*lint !e716*/ + { + --callDepth; + if( callDepth < 0 ) + break; + spqr_member parentMember = callstack[callDepth + 1].member; + reduced_member_id parentReducedMember = newRow->memberInformation[parentMember].reducedMember; + spqr_member currentMember = callstack[callDepth].member; + reduced_member_id currentReducedMember = newRow->memberInformation[currentMember].reducedMember; + + SPQRRowReducedMember* parentReducedMemberData = &newRow->reducedMembers[parentReducedMember]; + SPQRRowReducedMember* reducedMemberData = &newRow->reducedMembers[currentReducedMember]; + + reducedMemberData->parent = parentReducedMember; + reducedMemberData->depth = parentReducedMemberData->depth + 1; + reducedMemberData->rootMember = parentReducedMemberData->rootMember; + + newRow->reducedMembers[parentReducedMember].numChildren++; + } + } + + reduced_member_id returnedMember = newRow->memberInformation[callstack[0].member].reducedMember; + return returnedMember; +} + + +/** Construct the reduced decomposition, which is the smallest subtree containing all members cut arcs. */ +static +SCIP_RETCODE constructRowReducedDecomposition( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow /**< The network matrix row addition datastructure */ +) +{ +#ifndef NDEBUG + for( int i = 0; i < newRow->memMemberInformation; ++i ) + { + assert(reducedMemberIsInvalid(newRow->memberInformation[i].reducedMember)); + } +#endif + + newRow->numReducedComponents = 0; + newRow->numReducedMembers = 0; + if( newRow->numDecompositionColumnArcs == 0 ) + {//Early return in case the reduced decomposition will be empty + return SCIP_OKAY; + } + assert(newRow->numReducedMembers == 0); + assert(newRow->numReducedComponents == 0); + + int newSize = largestMemberID(dec);//Is this sufficient? + if( newSize > newRow->memReducedMembers ) + { + int updatedSize = MAX(2 * newRow->memReducedMembers, newSize); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->reducedMembers, newRow->memReducedMembers, updatedSize) ); + newRow->memReducedMembers = updatedSize; + } + if( newSize > newRow->memMemberInformation ) + { + int updatedSize = MAX(2 * newRow->memMemberInformation, newSize); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->memberInformation, newRow->memMemberInformation, updatedSize) ); + for( int i = newRow->memMemberInformation; i < updatedSize; ++i ) + { + newRow->memberInformation[i].reducedMember = INVALID_REDUCED_MEMBER; + newRow->memberInformation[i].rootDepthMinimizer = INVALID_REDUCED_MEMBER; + } + newRow->memMemberInformation = updatedSize; + } + + int numComponents = numConnectedComponents(dec); + if( numComponents > newRow->memReducedComponents ) + { + int updatedSize = MAX(2 * newRow->memReducedComponents, numComponents); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->reducedComponents, newRow->memReducedComponents, updatedSize) ); + newRow->memReducedComponents = updatedSize; + } + + int numMembers = getNumMembers(dec); + if( newRow->memCreateReducedMembersCallstack < numMembers ) + { + int updatedSize = MAX(2 * newRow->memCreateReducedMembersCallstack, numMembers); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->createReducedMembersCallstack, + newRow->memCreateReducedMembersCallstack, updatedSize) ); + newRow->memCreateReducedMembersCallstack = updatedSize; + } + + //Create the reduced members (recursively) + for( int i = 0; i < newRow->numDecompositionColumnArcs; ++i ) + { + assert(i < newRow->memDecompositionColumnArcs); + spqr_arc arc = newRow->decompositionColumnArcs[i]; + spqr_member arcMember = findArcMember(dec, arc); + reduced_member_id reducedMember = createRowReducedMembersToRoot(dec, newRow, arcMember); + reduced_member_id* depthMinimizer = &newRow->memberInformation[newRow->reducedMembers[reducedMember].rootMember].rootDepthMinimizer; + if( reducedMemberIsInvalid(*depthMinimizer) ) + { + *depthMinimizer = reducedMember; + } + } + + //Set the reduced roots according to the root depth minimizers + for( int i = 0; i < newRow->numReducedComponents; ++i ) + { + SPQRRowReducedComponent* component = &newRow->reducedComponents[i]; + spqr_member rootMember = newRow->reducedMembers[component->root].member; + reduced_member_id reducedMinimizer = newRow->memberInformation[rootMember].rootDepthMinimizer; + component->rootDepth = newRow->reducedMembers[reducedMinimizer].depth; + component->root = reducedMinimizer; + + //This simplifies code further down which does not need to be component-aware; just pretend that the reduced member is the new root. + newRow->reducedMembers[component->root].parent = INVALID_REDUCED_MEMBER; + assert(memberIsRepresentative(dec, rootMember)); + } + + //update the children array + int numTotalChildren = 0; + for( int i = 0; i < newRow->numReducedMembers; ++i ) + { + SPQRRowReducedMember* reducedMember = &newRow->reducedMembers[i]; + reduced_member_id minimizer = newRow->memberInformation[reducedMember->rootMember].rootDepthMinimizer; + if( reducedMember->depth >= newRow->reducedMembers[minimizer].depth ) + { + reducedMember->firstChild = numTotalChildren; + numTotalChildren += reducedMember->numChildren; + reducedMember->numChildren = 0; + } + } + + if( newRow->memChildrenStorage < numTotalChildren ) + { + int newMemSize = MAX(newRow->memChildrenStorage * 2, numTotalChildren); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->childrenStorage, newRow->memChildrenStorage, + newMemSize) ); + newRow->memChildrenStorage = newMemSize; + } + newRow->numChildrenStorage = numTotalChildren; + + //Fill up the children array` + for( reduced_member_id reducedMember = 0; reducedMember < newRow->numReducedMembers; ++reducedMember ) + { + SPQRRowReducedMember* reducedMemberData = &newRow->reducedMembers[reducedMember]; + if( reducedMemberData->depth <= + newRow->reducedMembers[newRow->memberInformation[reducedMemberData->rootMember].rootDepthMinimizer].depth ) + { + continue; + } + spqr_member parentMember = findMemberParent(dec, reducedMemberData->member); + reduced_member_id parentReducedMember = SPQRmemberIsValid(parentMember) + ? newRow->memberInformation[parentMember].reducedMember + : INVALID_REDUCED_MEMBER; + if( reducedMemberIsValid(parentReducedMember) ) + { + SPQRRowReducedMember* parentReducedMemberData = &newRow->reducedMembers[parentReducedMember]; + newRow->childrenStorage[parentReducedMemberData->firstChild + + parentReducedMemberData->numChildren] = reducedMember; + ++parentReducedMemberData->numChildren; + } + } + + //Clean up the root depth minimizers. + for( int i = 0; i < newRow->numReducedMembers; ++i ) + { + SPQRRowReducedMember* reducedMember = &newRow->reducedMembers[i]; + assert(reducedMember); + spqr_member rootMember = reducedMember->rootMember; + assert(rootMember >= 0); + assert(rootMember < dec->memMembers); + newRow->memberInformation[rootMember].rootDepthMinimizer = INVALID_REDUCED_MEMBER; + } + + return SCIP_OKAY; +} + +/** Marks an arc as 'cut'. + * + * This implies that its cycle in the decomposition must be elongated. + */ +static +void createCutArc( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition datastructure */ + const spqr_arc arc, /**< The arc to mark as a cut arc */ + const reduced_member_id reducedMember, /**< The reduced member containing the arc */ + SCIP_Bool reversed /**< Indicates if the new row entry has +1 or -1 (TRUE) for the arc */ +) +{ + cut_arc_id cut_arc = newRow->numCutArcs; + + CutArcListNode* listNode = &newRow->cutArcs[cut_arc]; + listNode->arc = arc; + + listNode->nextMember = newRow->reducedMembers[reducedMember].firstCutArc; + newRow->reducedMembers[reducedMember].firstCutArc = cut_arc; + + listNode->nextOverall = newRow->firstOverallCutArc; + newRow->firstOverallCutArc = cut_arc; + + newRow->numCutArcs++; + newRow->reducedMembers[reducedMember].numCutArcs++; + assert(newRow->numCutArcs <= newRow->memCutArcs); + + assert(arc < newRow->memIsArcCut); + newRow->isArcCut[arc] = TRUE; + newRow->isArcCutReversed[arc] = reversed; + + assert(memberIsRepresentative(dec, newRow->reducedMembers[reducedMember].member)); + if( getMemberType(dec, newRow->reducedMembers[reducedMember].member) == SPQR_MEMBERTYPE_RIGID ) + { + + listNode->arcHead = findEffectiveArcHead(dec, arc); + listNode->arcTail = findEffectiveArcTail(dec, arc); + if( reversed ) + { + SCIPswapInts(&listNode->arcHead, &listNode->arcTail); + } + assert(SPQRnodeIsValid(listNode->arcHead) && SPQRnodeIsValid(listNode->arcTail)); + } + else + { + listNode->arcHead = SPQR_INVALID_NODE; + listNode->arcTail = SPQR_INVALID_NODE; + } + + listNode->arcReversed = reversed; +} + +/** Creates all cut arcs within the decomposition for the new row. + * + * Note this preallocates memory for cut arcs which may be created by propagation. + */ +static +SCIP_RETCODE createReducedDecompositionCutArcs( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow /**< The network matrix row addition datastructure */ +) +{ + //Allocate memory for cut arcs + spqr_arc maxArcID = largestArcID(dec); + if( maxArcID > newRow->memIsArcCut ) + { + int newSize = MAX(maxArcID, 2 * newRow->memIsArcCut); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->isArcCut, newRow->memIsArcCut, newSize) ); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->isArcCutReversed, newRow->memIsArcCut, newSize) ); + for( int i = newRow->memIsArcCut; i < newSize; ++i ) + { + newRow->isArcCut[i] = FALSE; + newRow->isArcCutReversed[i] = FALSE; + } + newRow->memIsArcCut = newSize; + } +#ifndef NDEBUG + for( int i = 0; i < newRow->memIsArcCut; ++i ) + { + assert(!newRow->isArcCut[i]); + assert(!newRow->isArcCutReversed[i]); + } +#endif + + int numNeededArcs = newRow->numDecompositionColumnArcs * 4; + if( numNeededArcs > newRow->memCutArcs ) + { + int newSize = MAX(newRow->memCutArcs * 2, numNeededArcs); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->cutArcs, newRow->memCutArcs, newSize) ); + newRow->memCutArcs = newSize; + } + newRow->numCutArcs = 0; + newRow->firstOverallCutArc = INVALID_CUT_ARC; + for( int i = 0; i < newRow->numDecompositionColumnArcs; ++i ) + { + spqr_arc arc = newRow->decompositionColumnArcs[i]; + spqr_member member = findArcMember(dec, arc); + reduced_member_id reduced_member = newRow->memberInformation[member].reducedMember; + assert(reducedMemberIsValid(reduced_member)); + createCutArc(dec, newRow, arc, reduced_member, newRow->decompositionColumnArcReversed[i]); + } + + return SCIP_OKAY; +} + +/** Determines the members of the reduced decomposition which are leafs. + * + * This is used in propagation to ensure + * propagation is only checked for components which have at most one neighbour that is not propagated. + */ +static +SCIP_RETCODE determineLeafReducedMembers( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow /**< The network matrix row addition datastructure */ +) +{ + if( newRow->numDecompositionColumnArcs > newRow->memLeafMembers ) + { + int updatedSize = MAX(newRow->numDecompositionColumnArcs, 2 * newRow->memLeafMembers); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->leafMembers, newRow->memLeafMembers, updatedSize) ); + newRow->memLeafMembers = updatedSize; + } + newRow->numLeafMembers = 0; + + for( reduced_member_id reducedMember = 0; reducedMember < newRow->numReducedMembers; ++reducedMember ) + { + if( newRow->reducedMembers[reducedMember].numChildren == 0 ) + { + newRow->leafMembers[newRow->numLeafMembers] = reducedMember; + ++newRow->numLeafMembers; + } + } + + return SCIP_OKAY; +} + +/** Preallocates memory arrays necessary for searching rigid components. */ +static +SCIP_RETCODE allocateRigidSearchMemory( + const SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow /**< The network matrix row addition datastructure */ +) +{ + int totalNumNodes = getNumNodes(dec); + int maxNumNodes = 2 * dec->numArcs; + if( maxNumNodes > newRow->memNodeColors ) + { + int newSize = MAX(2 * newRow->memNodeColors, maxNumNodes); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->nodeColors, newRow->memNodeColors, newSize) ); + for( int i = newRow->memNodeColors; i < newSize; ++i ) + { + newRow->nodeColors[i] = UNCOLORED; + } + newRow->memNodeColors = newSize; + } + + if( totalNumNodes > newRow->memArticulationNodes ) + { + int newSize = MAX(2 * newRow->memArticulationNodes, totalNumNodes); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->articulationNodes, newRow->memArticulationNodes, newSize) ); + newRow->memArticulationNodes = newSize; + } + if( totalNumNodes > newRow->memNodeSearchInfo ) + { + int newSize = MAX(2 * newRow->memNodeSearchInfo, totalNumNodes); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->articulationNodeSearchInfo, newRow->memNodeSearchInfo, + newSize) ); + newRow->memNodeSearchInfo = newSize; + } + if( totalNumNodes > newRow->memCrossingPathCount ) + { + int newSize = MAX(2 * newRow->memCrossingPathCount, totalNumNodes); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->crossingPathCount, newRow->memCrossingPathCount, newSize) ); + newRow->memCrossingPathCount = newSize; + } + if( totalNumNodes > newRow->memTemporaryColorArray ) + { + int newSize = MAX(2 * newRow->memTemporaryColorArray, totalNumNodes); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->temporaryColorArray, + newRow->memTemporaryColorArray, newSize) ); + newRow->memTemporaryColorArray = newSize; + } + + //TODO: see if tradeoff for performance bound by checking max # of nodes of rigid is worth it to reduce size + //of the following allocations + int largestID = largestNodeID(dec); + //TODO: only update the stack sizes of the following when needed? The preallocation may not be worth it. + if( largestID > newRow->memIntersectionDFSData ) + { + int newSize = MAX(2 * newRow->memIntersectionDFSData, largestID); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->intersectionDFSData, newRow->memIntersectionDFSData, newSize) ); + newRow->memIntersectionDFSData = newSize; + } + if( largestID > newRow->memColorDFSData ) + { + int newSize = MAX(2 * newRow->memColorDFSData, largestID); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->colorDFSData, newRow->memColorDFSData, newSize) ); + newRow->memColorDFSData = newSize; + } + if( largestID > newRow->memArtDFSData ) + { + int newSize = MAX(2 * newRow->memArtDFSData, largestID); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->artDFSData, newRow->memArtDFSData, newSize) ); + newRow->memArtDFSData = newSize; + } + + for( int i = 0; i < newRow->memIntersectionPathDepth; ++i ) + { + newRow->intersectionPathDepth[i] = -1; + } + + if( largestID > newRow->memIntersectionPathDepth ) + { + int newSize = MAX(2 * newRow->memIntersectionPathDepth, largestID); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->intersectionPathDepth, newRow->memIntersectionPathDepth, + newSize) ); + for( int i = newRow->memIntersectionPathDepth; i < newSize; ++i ) + { + newRow->intersectionPathDepth[i] = -1; + } + newRow->memIntersectionPathDepth = newSize; + } + for( int i = 0; i < newRow->memIntersectionPathParent; ++i ) + { + newRow->intersectionPathParent[i] = SPQR_INVALID_NODE; + } + if( largestID > newRow->memIntersectionPathParent ) + { + int newSize = MAX(2 * newRow->memIntersectionPathParent, largestID); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->intersectionPathParent, newRow->memIntersectionPathParent, + newSize) ); + for( int i = newRow->memIntersectionPathParent; i < newSize; ++i ) + { + newRow->intersectionPathParent[i] = SPQR_INVALID_NODE; + } + newRow->memIntersectionPathParent = newSize; + } + + return SCIP_OKAY; +} + +/** Clears the colors for all nodes. + * + * We do DFS in reverse (reverse exploration order), as zeroing out the entire + * array is more expensive. + */ +static +void zeroOutColors( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition datastructure */ + const spqr_node firstRemoveNode /**< The first node that was colored in the original DFS */ +) +{ + assert(firstRemoveNode < newRow->memNodeColors); + + newRow->nodeColors[firstRemoveNode] = UNCOLORED; + ColorDFSCallData* data = newRow->colorDFSData; + if( newRow->colorDFSData == NULL ) + { + return; + } + + data[0].node = firstRemoveNode; + data[0].arc = getFirstNodeArc(dec, firstRemoveNode); + if( SPQRarcIsInvalid(data[0].arc) ) + { + return; + } + + int depth = 0; + + while( depth >= 0 ) + { + assert(depth < newRow->memColorDFSData); + ColorDFSCallData* callData = &data[depth]; + spqr_node head = findArcHead(dec, callData->arc); + spqr_node tail = findArcTail(dec, callData->arc); + spqr_node otherNode = callData->node == head ? tail : head; + assert(otherNode < newRow->memNodeColors); + if( newRow->nodeColors[otherNode] != UNCOLORED ) + { + callData->arc = getNextNodeArc(dec, callData->arc, callData->node); + + newRow->nodeColors[otherNode] = UNCOLORED; + ++depth; + data[depth].node = otherNode; + data[depth].arc = getFirstNodeArc(dec, otherNode); + continue; + } + + callData->arc = getNextNodeArc(dec, callData->arc, callData->node); + while( depth >= 0 && data[depth].arc == getFirstNodeArc(dec, data[depth].node) ) + { + --depth; + } + } +} + +/** Cleans up various arrays used in previous iterations. + * + * This is cheaper than reallocating empty memory. + */ +static +void cleanUpPreviousIteration( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow /**< The network matrix row addition datastructure */ +) +{ + //zero out coloring information from previous check + for( int i = 0; i < newRow->numReducedMembers; ++i ) + { + if( SPQRnodeIsValid(newRow->reducedMembers[i].coloredNode)) + { + zeroOutColors(dec, newRow, newRow->reducedMembers[i].coloredNode); + } + } + +#ifndef NDEBUG + for( int i = 0; i < newRow->memNodeColors; ++i ) + { + assert(newRow->nodeColors[i] == UNCOLORED); + } +#endif + + //For cut arcs: clear them from the array from previous iteration + cut_arc_id cutArcIdx = newRow->firstOverallCutArc; + while( cutArcIsValid(cutArcIdx) ) + { + spqr_arc cutArc = newRow->cutArcs[cutArcIdx].arc; + cutArcIdx = newRow->cutArcs[cutArcIdx].nextOverall; + newRow->isArcCut[cutArc] = FALSE; + newRow->isArcCutReversed[cutArc] = FALSE; + } +#ifndef NDEBUG + for( int i = 0; i < newRow->memIsArcCut; ++i ) + { + assert(!newRow->isArcCut[i]); + assert(!newRow->isArcCutReversed[i]); + } +#endif +} + +/** Finds all the star nodes, i.e. nodes that are adjacent to all cut arcs, in a rigid member. */ +static +void rigidFindStarNodes( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id toCheck /**< The reduced member to check */ +) +{ + //4 cases: + //Only a single edge; both head/tail are okay => network + //All are adjacent to a single node, but do not have it as head or tail => not network + //All are adjacent to a single node, and have it as head or tail => network + //Not all are adjacent to a single node => check articulation nodes + assert(newRow->reducedMembers[toCheck].numCutArcs > 0);//calling this function otherwise is nonsensical + + cut_arc_id cutArcIdx = newRow->reducedMembers[toCheck].firstCutArc; + spqr_arc cutArc = newRow->cutArcs[cutArcIdx].arc; + spqr_node head = findArcHead(dec, cutArc); + spqr_node tail = findArcTail(dec, cutArc); + + SCIP_Bool reverse = findArcSign(dec, cutArc).reversed != newRow->cutArcs[cutArcIdx].arcReversed; + spqr_node cutArcsHead = reverse ? tail : head; + spqr_node cutArcsTail = reverse ? head : tail; + + if( newRow->reducedMembers[toCheck].numCutArcs == 1 ) + { + newRow->reducedMembers[toCheck].articulationArc = cutArc; + newRow->reducedMembers[toCheck].splitNode = cutArcsHead; + newRow->reducedMembers[toCheck].otherNode = cutArcsTail; + newRow->reducedMembers[toCheck].otherIsSource = TRUE; + newRow->reducedMembers[toCheck].allHaveCommonNode = TRUE; + return;// Only a single cut arc + } + + spqr_node intersectionNodes[2] = {head, tail}; + + while( cutArcIsValid(newRow->cutArcs[cutArcIdx].nextMember) ) + { + cutArcIdx = newRow->cutArcs[cutArcIdx].nextMember; + cutArc = newRow->cutArcs[cutArcIdx].arc; + head = findArcHead(dec, cutArc); + tail = findArcTail(dec, cutArc); + reverse = findArcSign(dec, cutArc).reversed != newRow->cutArcs[cutArcIdx].arcReversed; + spqr_node effectiveHead = reverse ? tail : head; + spqr_node effectiveTail = reverse ? head : tail; + if( effectiveHead != cutArcsHead ) + { + cutArcsHead = SPQR_INVALID_NODE; + } + if( effectiveTail != cutArcsTail ) + { + cutArcsTail = SPQR_INVALID_NODE; + } + + //intersection between intersectionNodes and head and tail + for( int i = 0; i < 2; ++i ) + { + if( intersectionNodes[i] != head && intersectionNodes[i] != tail ) + { + intersectionNodes[i] = SPQR_INVALID_NODE; + } + } + if( SPQRnodeIsInvalid(intersectionNodes[0]) && SPQRnodeIsInvalid(intersectionNodes[1]) ) + { + newRow->reducedMembers[toCheck].splitNode = SPQR_INVALID_NODE; + newRow->reducedMembers[toCheck].allHaveCommonNode = FALSE; + return;//not all arcs are adjacent to a single node, need to check articulation nodes + } + } + if( SPQRnodeIsInvalid(cutArcsHead) && SPQRnodeIsInvalid(cutArcsTail) ) + { + //All arcs adjacent to a single node, but not in same direction; not network + newRow->remainsNetwork = FALSE; + newRow->reducedMembers[toCheck].type = TYPE_NOT_NETWORK; + return; + } + SCIP_Bool headSplittable = SPQRnodeIsValid(cutArcsHead); + //Check if the n arcs are in a n+1 degree node; if so, the other endpoint of this non split arc is also splittable + //By virtue of the spanning tree, this arc must be a tree arc. + spqr_node splitNode = headSplittable ? cutArcsHead : cutArcsTail; + newRow->reducedMembers[toCheck].splitNode = splitNode; + newRow->reducedMembers[toCheck].otherIsSource = headSplittable; + newRow->reducedMembers[toCheck].allHaveCommonNode = TRUE; + if( newRow->reducedMembers[toCheck].numCutArcs == nodeDegree(dec, splitNode) - 1 ) + { + spqr_arc firstNodeArc = getFirstNodeArc(dec, splitNode); + spqr_arc neighbourArc = firstNodeArc; + do + { + if( arcIsTree(dec, neighbourArc)) + { + break; + } + neighbourArc = getNextNodeArc(dec, neighbourArc, splitNode); + } while( neighbourArc != firstNodeArc ); + + newRow->reducedMembers[toCheck].articulationArc = neighbourArc; + spqr_arc arcHead = findArcHead(dec, neighbourArc); + newRow->reducedMembers[toCheck].otherNode = arcHead == splitNode ? findArcTail(dec, neighbourArc) : arcHead; + } +} + +/** Clears the colors for all nodes, but the neighbours. + * + * We do DFS in reverse (reverse exploration order), as zeroing out the entire array is more expensive. + */ +static +void zeroOutColorsExceptNeighbourhood( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const spqr_node articulationNode, /**< The node whose neighbours we want to keep */ + const spqr_node startRemoveNode /**< The node where DFS started */ +) +{ + COLOR_STATUS* neighbourColors = newRow->temporaryColorArray; + assert(nodeDegree(dec, articulationNode) <= newRow->memTemporaryColorArray); + { + int i = 0; + spqr_arc artFirstArc = getFirstNodeArc(dec, articulationNode); + spqr_arc artItArc = artFirstArc; + do + { + spqr_node head = findArcHead(dec, artItArc); + spqr_node tail = findArcTail(dec, artItArc); + spqr_node otherNode = articulationNode == head ? tail : head; + neighbourColors[i] = newRow->nodeColors[otherNode]; + i++; + assert(i <= nodeDegree(dec, articulationNode)); + artItArc = getNextNodeArc(dec, artItArc, articulationNode); + } + while( artItArc != artFirstArc ); + } + zeroOutColors(dec, newRow, startRemoveNode); + + { + int i = 0; + spqr_arc artFirstArc = getFirstNodeArc(dec, articulationNode); + spqr_arc artItArc = artFirstArc; + do + { + spqr_node head = findArcHead(dec, artItArc); + spqr_node tail = findArcTail(dec, artItArc); + spqr_node otherNode = articulationNode == head ? tail : head; + newRow->nodeColors[otherNode] = neighbourColors[i]; + i++; + assert(i <= nodeDegree(dec, articulationNode)); + artItArc = getNextNodeArc(dec, artItArc, articulationNode); + } + while( artItArc != artFirstArc ); + } +} + +/** Find the intersection of all cut arc paths in the given rigid member. + * + * Theoretically, there is a faster algorithm using lowest common ancestor queries, but it is quite complicated. + * Thus, we stick with the 'simple' version below, which is typically also faster in practice. + */ +static +void intersectionOfAllPaths( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id toCheck, /**< The rigid member to check */ + int* const nodeNumPaths /**< Tracks for each node, how many cut arc paths cross it */ +) +{ + int* intersectionPathDepth = newRow->intersectionPathDepth; + spqr_node* intersectionPathParent = newRow->intersectionPathParent; + + //First do a dfs over the tree, storing all the tree-parents and depths for each node + //TODO: maybe cache this tree and also update it so we can prevent this DFS call? + + //pick an arbitrary node as root; we just use the first cutArc here + { + spqr_node root = findArcHead(dec, newRow->cutArcs[newRow->reducedMembers[toCheck].firstCutArc].arc); + DFSCallData* pathSearchCallStack = newRow->intersectionDFSData; + + assert(intersectionPathDepth[root] == -1); + assert(intersectionPathParent[root] == SPQR_INVALID_NODE); + + int pathSearchCallStackSize = 0; + + intersectionPathDepth[root] = 0; + intersectionPathParent[root] = SPQR_INVALID_NODE; + + pathSearchCallStack[0].node = root; + pathSearchCallStack[0].nodeArc = getFirstNodeArc(dec, root); + pathSearchCallStackSize++; + while( pathSearchCallStackSize > 0 ) + { + assert(pathSearchCallStackSize <= newRow->memIntersectionDFSData); + DFSCallData* dfsData = &pathSearchCallStack[pathSearchCallStackSize - 1]; + //cannot be a tree arc which is its parent + if( arcIsTree(dec, dfsData->nodeArc) && + ( pathSearchCallStackSize <= 1 || + dfsData->nodeArc != pathSearchCallStack[pathSearchCallStackSize - 2].nodeArc ) ) + { + spqr_node head = findArcHeadNoCompression(dec, dfsData->nodeArc); + spqr_node tail = findArcTailNoCompression(dec, dfsData->nodeArc); + spqr_node other = head == dfsData->node ? tail : head; + assert(other != dfsData->node); + + //We go up a level: add new node to the call stack + pathSearchCallStack[pathSearchCallStackSize].node = other; + pathSearchCallStack[pathSearchCallStackSize].nodeArc = getFirstNodeArc(dec, other); + //Every time a new node is discovered/added, we update its parent and depth information + assert(intersectionPathDepth[other] == -1); + assert(intersectionPathParent[other] == SPQR_INVALID_NODE); + intersectionPathParent[other] = dfsData->node; + intersectionPathDepth[other] = pathSearchCallStackSize; + ++pathSearchCallStackSize; + continue; + } + do + { + dfsData->nodeArc = getNextNodeArc(dec, dfsData->nodeArc, dfsData->node); + if( dfsData->nodeArc == getFirstNodeArc(dec, dfsData->node) ) + { + --pathSearchCallStackSize; + dfsData = &pathSearchCallStack[pathSearchCallStackSize - 1]; + } + else + { + break; + } + } + while( pathSearchCallStackSize > 0 ); + } + } + + //For each cut arc, trace back both ends until they meet + cut_arc_id cutArc = newRow->reducedMembers[toCheck].firstCutArc; + do + { + spqr_arc arc = newRow->cutArcs[cutArc].arc; + cutArc = newRow->cutArcs[cutArc].nextMember; + + //Iteratively jump up to the parents until they reach a common parent + spqr_node source = findArcHead(dec, arc); + spqr_node target = findArcTail(dec, arc); + int sourceDepth = intersectionPathDepth[source]; + int targetDepth = intersectionPathDepth[target]; + nodeNumPaths[source]++; + nodeNumPaths[target]++; + + while( sourceDepth > targetDepth ) + { + assert(source != target); + source = intersectionPathParent[source]; + nodeNumPaths[source]++; + --sourceDepth; + } + while( targetDepth > sourceDepth ) + { + assert(source != target); + target = intersectionPathParent[target]; + nodeNumPaths[target]++; + --targetDepth; + } + while( source != target && targetDepth >= 0 ) + { + source = intersectionPathParent[source]; + target = intersectionPathParent[target]; + nodeNumPaths[source]++; + nodeNumPaths[target]++; + --targetDepth; + } + //In all the above, the lowest common ancestor is increased twice, so we correct for it ad-hoc + nodeNumPaths[source]--; + assert(SPQRnodeIsValid(source) && SPQRnodeIsValid(target)); + assert(source == target); + } + while( cutArcIsValid(cutArc)); +} + +/** Add a node to array of articulation nodes. */ +static +void addArticulationNode( + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + spqr_node articulationNode /**< The node to add */ +) +{ +#ifndef NDEBUG + for( int i = 0; i < newRow->numArticulationNodes; ++i ) + { + assert(newRow->articulationNodes[i] != articulationNode); + } +#endif + newRow->articulationNodes[newRow->numArticulationNodes] = articulationNode; + ++newRow->numArticulationNodes; +} + +/** Find all the articulation points of the rigid member graph where the cut arcs are removed. + * + * Articulation points are nodes whose removal disconnects the remaining graph. + */ +static +void articulationPoints( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + ArticulationNodeInformation* nodeInfo, /**< Stores information about the found articulation nodes */ + reduced_member_id reducedMember /**< The reduced member to find the articulation nodes for */ +) +{ + const SCIP_Bool* arcRemoved = newRow->isArcCut; + + int rootChildren = 0; + spqr_node root_node = findArcHead(dec, getFirstMemberArc(dec, newRow->reducedMembers[reducedMember].member)); + + ArticulationPointCallStack* callStack = newRow->artDFSData; + + int depth = 0; + int time = 1; + + callStack[depth].arc = getFirstNodeArc(dec, root_node); + callStack[depth].node = root_node; + callStack[depth].parent = SPQR_INVALID_NODE; + callStack[depth].isAP = FALSE; + + nodeInfo[root_node].low = time; + nodeInfo[root_node].discoveryTime = time; + + while( depth >= 0 ) + { + if( !arcRemoved[callStack[depth].arc] ) + { + spqr_node node = callStack[depth].node; + spqr_node head = findArcHead(dec, callStack[depth].arc); + spqr_node tail = findArcTail(dec, callStack[depth].arc); + spqr_node otherNode = node == head ? tail : head; + if( otherNode != callStack[depth].parent ) + { + if( nodeInfo[otherNode].discoveryTime == 0 ) + { + if( depth == 0 ) + { + rootChildren++; + } + //recursive call + ++depth; + assert(depth < newRow->memArtDFSData); + callStack[depth].parent = node; + callStack[depth].node = otherNode; + callStack[depth].arc = getFirstNodeArc(dec, otherNode); + callStack[depth].isAP = FALSE; + + ++time; + nodeInfo[otherNode].low = time; + nodeInfo[otherNode].discoveryTime = time; + continue; + + } + else + { + nodeInfo[node].low = MIN(nodeInfo[node].low, nodeInfo[otherNode].discoveryTime); + } + } + } + + while( TRUE ) /*lint !e716*/ + { + callStack[depth].arc = getNextNodeArc(dec, callStack[depth].arc, callStack[depth].node); + if( callStack[depth].arc != getFirstNodeArc(dec, callStack[depth].node)) break; + --depth; + if( depth < 0 ) break; + + spqr_node current_node = callStack[depth].node; + spqr_node other_node = callStack[depth + 1].node; + nodeInfo[current_node].low = MIN(nodeInfo[current_node].low, nodeInfo[other_node].low); + if( depth != 0 && + !callStack[depth].isAP && + nodeInfo[current_node].discoveryTime <= nodeInfo[other_node].low ) + { + addArticulationNode(newRow, current_node); + callStack[depth].isAP = TRUE; + } + } + } + if( rootChildren > 1 ) + { + addArticulationNode(newRow, root_node); + } +} + +/** For a given articulation node, partitions the rigid member's graph where it is removed into a SOURCE and SINK + * partition. + * + * All cut arcs must point (after reversal) from the source partition to the sink partition. This is akin + * to finding a 2-coloring, and uses a DFS to do so. This function specifically executes the DFS/recursive part. + */ +static +void rigidConnectedColoringRecursive( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + spqr_node articulationNode, /**< The articulation node to obtain the coloring for */ + spqr_node firstProcessNode, /**< The first node to process for the DFS */ + COLOR_STATUS firstColor, /**< The partition that the first node lies in. */ + SCIP_Bool* isGood /**< Returns whether the coloring was consistent */ +) +{ + const SCIP_Bool* isArcCut = newRow->isArcCut; + COLOR_STATUS* nodeColors = newRow->nodeColors; + ColorDFSCallData* data = newRow->colorDFSData; + + data[0].node = firstProcessNode; + data[0].arc = getFirstNodeArc(dec, firstProcessNode); + newRow->nodeColors[firstProcessNode] = firstColor; + + int depth = 0; + while( depth >= 0 ) + { + assert(depth < newRow->memColorDFSData); + assert(newRow->nodeColors[articulationNode] == UNCOLORED); + + ColorDFSCallData* callData = &data[depth]; + spqr_node head = findArcHead(dec, callData->arc); + spqr_node tail = findArcTail(dec, callData->arc); + spqr_node otherNode = callData->node == head ? tail : head; + COLOR_STATUS currentColor = nodeColors[callData->node]; + COLOR_STATUS otherColor = nodeColors[otherNode]; + //Checks the direction of the arc; in the rest of the algorithm, we just need to check partition + if( isArcCut[callData->arc] && currentColor != otherColor ) + { + SCIP_Bool otherIsTail = callData->node == head; + SCIP_Bool arcReversed = findArcSign(dec, callData->arc).reversed != newRow->isArcCutReversed[callData->arc]; + SCIP_Bool good = ( currentColor == COLOR_SOURCE ) == ( otherIsTail == arcReversed ); + if( !good ) + { + *isGood = FALSE; + break; + } + } + if( otherNode != articulationNode ) + { + if( otherColor == UNCOLORED ) + { + if( isArcCut[callData->arc] ) + { + nodeColors[otherNode] = currentColor == COLOR_SOURCE ? COLOR_SINK : COLOR_SOURCE;//reverse the colors + } + else + { + nodeColors[otherNode] = currentColor; + } + callData->arc = getNextNodeArc(dec, callData->arc, callData->node); + + depth++; + assert(depth < newRow->memColorDFSData); + data[depth].node = otherNode; + data[depth].arc = getFirstNodeArc(dec, otherNode); + continue; + } + if( isArcCut[callData->arc] != ( currentColor != otherColor ) ) + { + *isGood = FALSE; + break; + } + } + callData->arc = getNextNodeArc(dec, callData->arc, callData->node); + while( depth >= 0 && data[depth].arc == getFirstNodeArc(dec, data[depth].node) ) + { + --depth; + } + } +} + +/** For a given articulation node, partitions the rigid member's graph where it is removed into a SOURCE and SINK + * partition. + * + * All cut arcs must point (after reversal) from the source partition to the sink partition. This is akin + * to finding a 2-coloring, and uses a DFS to do so. + */ +static +void rigidConnectedColoring( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id reducedMember, /**< The rigid member whose graph to color */ + const spqr_node node, /**< The articulation node to obtain the coloring for */ + SCIP_Bool* const isGood /**< Returns whether the coloring was consistent */ +) +{ + //we should only perform this function if there's more than one cut arc + assert(newRow->reducedMembers[reducedMember].numCutArcs > 1); +#ifndef NDEBUG + { + spqr_member member = newRow->reducedMembers[reducedMember].member; + spqr_arc firstArc = getFirstMemberArc(dec, member); + spqr_arc memberArc = firstArc; + do + { + assert(newRow->nodeColors[findArcHeadNoCompression(dec, memberArc)] == UNCOLORED); + assert(newRow->nodeColors[findArcTailNoCompression(dec, memberArc)] == UNCOLORED); + memberArc = getNextMemberArc(dec, memberArc); + } + while( firstArc != memberArc ); + } +#endif + + spqr_node firstProcessNode; + COLOR_STATUS firstColor; + { + cut_arc_id cutArc = newRow->reducedMembers[reducedMember].firstCutArc; + spqr_arc arc = newRow->cutArcs[cutArc].arc; + assert(SPQRarcIsValid(arc)); + spqr_node head = findArcHead(dec, arc); + spqr_node tail = findArcTail(dec, arc); + if( findArcSign(dec, arc).reversed != newRow->cutArcs[cutArc].arcReversed ) + { + spqr_node temp = head; + head = tail; + tail = temp; + } + if( tail != node ) + { + firstProcessNode = tail; + firstColor = COLOR_SOURCE; + } + else + { + assert(head != node); + firstProcessNode = head; + firstColor = COLOR_SINK; + } + } + assert(SPQRnodeIsValid(firstProcessNode) && firstProcessNode != node); + *isGood = TRUE; + newRow->reducedMembers[reducedMember].coloredNode = firstProcessNode; + rigidConnectedColoringRecursive(dec, newRow, node, firstProcessNode, firstColor, isGood); + + // Need to zero all colors for next attempts if we failed + if( !( *isGood ) ) + { + zeroOutColors(dec, newRow, firstProcessNode); + newRow->reducedMembers[reducedMember].coloredNode = SPQR_INVALID_NODE; + } + else + { + //Otherwise, we zero out all colors but the ones which we need + zeroOutColorsExceptNeighbourhood(dec, newRow, node, firstProcessNode); + newRow->reducedMembers[reducedMember].coloredNode = node; + } +} + +/** Given a coloring for an articulation node, we can prove that a neighbouring node is splittable if and only if it is + * the unique node in the neighbourhood of the articulation node within the SOURCE or SINK partition. In this function, + * we check for a splittable articulation node if such an adjacent node exists, and return it if possible. + */ +static +spqr_node checkNeighbourColoringArticulationNode( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const spqr_node articulationNode, /**< The articulation whose neighbours are to be checked */ + spqr_arc* const adjacentSplittingArc/**< The arc connecting the two splittable nodes. */ +) +{ + spqr_node firstSideCandidate = SPQR_INVALID_NODE; + spqr_node secondSideCandidate = SPQR_INVALID_NODE; + spqr_arc firstSideArc = SPQR_INVALID_ARC; + spqr_arc secondSideArc = SPQR_INVALID_ARC; + int numFirstSide = 0; + int numSecondSide = 0; + + spqr_arc firstArc = getFirstNodeArc(dec, articulationNode); + spqr_arc moveArc = firstArc; + do + { + spqr_node head = findArcHead(dec, moveArc); + spqr_node tail = findArcTail(dec, moveArc); + spqr_node otherNode = articulationNode == head ? tail : head; + assert(newRow->nodeColors[otherNode] != UNCOLORED); + if(( newRow->nodeColors[otherNode] == COLOR_SOURCE ) != newRow->isArcCut[moveArc] ) + { + if( numFirstSide == 0 && arcIsTree(dec, moveArc)) + { + firstSideCandidate = otherNode; + firstSideArc = moveArc; + } + else if( numFirstSide > 0 ) + { + firstSideCandidate = SPQR_INVALID_NODE; + } + ++numFirstSide; + } + else + { + if( numSecondSide == 0 && arcIsTree(dec, moveArc)) + { + secondSideCandidate = otherNode; + secondSideArc = moveArc; + } + else if( numSecondSide > 0 ) + { + secondSideCandidate = SPQR_INVALID_NODE; + } + ++numSecondSide; + } + moveArc = getNextNodeArc(dec, moveArc, articulationNode); + } + while( moveArc != firstArc ); + + if( numFirstSide == 1 ) + { + *adjacentSplittingArc = firstSideArc; + return firstSideCandidate; + } + else if( numSecondSide == 1 ) + { + *adjacentSplittingArc = secondSideArc; + return secondSideCandidate; + } + return SPQR_INVALID_NODE; +} + +/** Find all the splittable articulation points that lie on the intersection of all cut arc cycles. + * + * firstNode and secondNode can be set to limit the nodes that this function checks to the given nodes. + */ +static +void rigidGetSplittableArticulationPointsOnPath( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id toCheck, /**< The rigid member to check */ + spqr_node firstNode, /**< Optional: the first given node that should be checked */ + spqr_node secondNode /**< Optional: the second given node that should be checked */ +) +{ + assert(newRow->reducedMembers[toCheck].numCutArcs > 1); + + int totalNumNodes = getNumNodes(dec); + int* nodeNumPaths = newRow->crossingPathCount; + + for( int i = 0; i < totalNumNodes; ++i ) + { + nodeNumPaths[i] = 0; + } + + intersectionOfAllPaths(dec, newRow, toCheck, nodeNumPaths); + + newRow->numArticulationNodes = 0; + + ArticulationNodeInformation* artNodeInfo = newRow->articulationNodeSearchInfo; + //TODO: ugly; we clean up over all decomposition nodes for every component + //clean up can not easily be done in the search, unfortunately + for( int i = 0; i < totalNumNodes; ++i ) + { + artNodeInfo[i].low = 0; + artNodeInfo[i].discoveryTime = 0; + } + + articulationPoints(dec, newRow, artNodeInfo, toCheck); + + int numCutArcs = newRow->reducedMembers[toCheck].numCutArcs; + for( int i = 0; i < newRow->numArticulationNodes; i++ ) + { + spqr_node articulationNode = newRow->articulationNodes[i]; + assert(nodeIsRepresentative(dec, articulationNode)); + SCIP_Bool isOnPath = nodeNumPaths[articulationNode] == numCutArcs; + if( isOnPath && + (( SPQRnodeIsInvalid(firstNode) && SPQRnodeIsInvalid(secondNode)) || articulationNode == firstNode || + articulationNode == secondNode ) ) + { + SCIP_Bool isGood = TRUE; + rigidConnectedColoring(dec, newRow, toCheck, articulationNode, &isGood); + if( !isGood ) + { + continue; + } + newRow->reducedMembers[toCheck].splitNode = articulationNode; + + //Once we have found one node, we can (possibly) find another by looking at the coloring of the neighbourhood of it. + + spqr_arc adjacentSplittingArc = SPQR_INVALID_ARC; + spqr_node adjacentSplittingNode = checkNeighbourColoringArticulationNode(dec, newRow, articulationNode, + &adjacentSplittingArc); + + //If the neighbour-coloring works, we still need to check that the adjacent node + //is also an articulation node + if( SPQRnodeIsValid(adjacentSplittingNode) && + (( SPQRnodeIsInvalid(firstNode) && SPQRnodeIsInvalid(secondNode)) || adjacentSplittingNode == firstNode || + adjacentSplittingNode == secondNode ) ) + { + SCIP_Bool isArticulationNode = FALSE; + for( int j = 0; j < newRow->numArticulationNodes; ++j ) + { + if( newRow->articulationNodes[j] == adjacentSplittingNode ) + { + isArticulationNode = TRUE; + break; + } + } + if( isArticulationNode ) + { + newRow->reducedMembers[toCheck].articulationArc = adjacentSplittingArc; + newRow->reducedMembers[toCheck].otherNode = adjacentSplittingNode; + newRow->reducedMembers[toCheck].otherIsSource = + newRow->nodeColors[adjacentSplittingNode] == COLOR_SOURCE; + + //Cleaning up the colors + { + spqr_arc firstNodeArc = getFirstNodeArc(dec, articulationNode); + spqr_arc itArc = firstNodeArc; + do + { + spqr_node head = findArcHead(dec, itArc); + spqr_node tail = findArcTail(dec, itArc); + spqr_node otherNode = articulationNode == head ? tail : head; + newRow->nodeColors[otherNode] = UNCOLORED; + itArc = getNextNodeArc(dec, itArc, articulationNode); + } + while( itArc != firstNodeArc ); + } + } + } + return; + } + } + + newRow->reducedMembers[toCheck].type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; +} + +/** Checks if a leaf parallel member can be propagated away from the reduced decomposition. */ +static +void determineParallelType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id toCheckMember, /**< The parallel leaf member to check */ + const spqr_arc markerToOther, /**< Marker to the non-leaf member */ + const reduced_member_id otherMember, /**< The connected non-leaf member */ + const spqr_arc markerToCheck /**< Marker from the non-leaf to the leaf parallel member */ +) +{ + SPQRRowReducedMember* member = &newRow->reducedMembers[toCheckMember]; + assert(cutArcIsValid(member->firstCutArc)); + + SCIP_Bool good = TRUE; + SCIP_Bool isReversed = TRUE; + int countedCutArcs = 0; + for( cut_arc_id cutArc = member->firstCutArc; cutArcIsValid(cutArc); + cutArc = newRow->cutArcs[cutArc].nextMember ) + { + spqr_arc arc = newRow->cutArcs[cutArc].arc; + SCIP_Bool arcIsReversed = arcIsReversedNonRigid(dec, arc) != newRow->cutArcs[cutArc].arcReversed; + if( countedCutArcs == 0 ) + { + isReversed = arcIsReversed; + } + else if( arcIsReversed != isReversed ) + { + good = FALSE; + break; + } + ++countedCutArcs; + } + if( !good ) + { + member->type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + return; + } + //we can only propagate if the marker arc is a tree arc and all other arcs are cut + if( !arcIsTree(dec, markerToOther) || + countedCutArcs != ( getNumMemberArcs(dec, newRow->reducedMembers[toCheckMember].member) - 1 ) ) + { + //In all other cases, the bond can be split so that the result will be okay! + newRow->reducedMembers[toCheckMember].type = TYPE_MERGED; + } + else + { + SCIP_Bool markerIsReversed = arcIsReversedNonRigid(dec, markerToOther); + createCutArc(dec, newRow, markerToCheck, otherMember, markerIsReversed != isReversed); + newRow->reducedMembers[toCheckMember].type = TYPE_PROPAGATED; + } +} + +/** Checks if a leaf series member can be propagated away from the reduced decomposition. */ +static +void determineSeriesType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id toCheckMember, /**< The series leaf member to check */ + const spqr_arc markerToOther, /**< Marker to the non-leaf member */ + const reduced_member_id otherMember, /**< The connected non-leaf member */ + const spqr_arc markerToCheck /**< Marker from the non-leaf to the leaf series member */ +) +{ + //Propagation only calls this function if the arc is tree already, so we do not check it here. + assert(arcIsTree(dec, markerToOther)); + assert(newRow->reducedMembers[toCheckMember].numCutArcs == 1); + assert(cutArcIsValid(newRow->reducedMembers[toCheckMember].firstCutArc)); + spqr_arc cutArc = newRow->reducedMembers[toCheckMember].firstCutArc; + spqr_arc arc = newRow->cutArcs[cutArc].arc; + newRow->reducedMembers[toCheckMember].type = TYPE_PROPAGATED; + createCutArc(dec, newRow, markerToCheck, otherMember, + ( arcIsReversedNonRigid(dec, arc) == arcIsReversedNonRigid(dec, markerToOther)) != + newRow->cutArcs[cutArc].arcReversed); +} + +/** Checks if a leaf rigid member can be propagated away from the reduced decomposition. */ +static +void determineRigidType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id toCheckMember, /**< The rigid leaf member to check */ + const spqr_arc markerToOther, /**< Marker to the non-leaf member */ + const reduced_member_id otherMember, /**< The connected non-leaf member */ + const spqr_arc markerToCheck /**< Marker from the non-leaf to the rigid leaf member */ +) +{ + //Checking for propagation only makes sense if there is at least one cut arc + assert(newRow->reducedMembers[toCheckMember].numCutArcs > 0); + + rigidFindStarNodes(dec, newRow, toCheckMember); + if( !newRow->remainsNetwork ) + { + return; + } + spqr_node markerHead = findArcHead(dec, markerToOther); + spqr_node markerTail = findArcTail(dec, markerToOther); + if( findArcSign(dec, markerToOther).reversed ) + { + spqr_node temp = markerHead; + markerHead = markerTail; + markerTail = temp; + } + if( SPQRnodeIsInvalid(newRow->reducedMembers[toCheckMember].splitNode) ) + { + //not a star => attempt to find splittable nodes using articulation node algorithms + //We save some work by telling the methods that only the marker nodes should be checked + rigidGetSplittableArticulationPointsOnPath(dec, newRow, toCheckMember, markerHead, markerTail); + } + if( !newRow->remainsNetwork ) + { + return; + } + + if( SPQRarcIsValid(newRow->reducedMembers[toCheckMember].articulationArc) && + newRow->reducedMembers[toCheckMember].articulationArc == markerToOther ) + { + newRow->reducedMembers[toCheckMember].type = TYPE_PROPAGATED; + SCIP_Bool reverse = ( markerHead == newRow->reducedMembers[toCheckMember].splitNode ) != + newRow->reducedMembers[toCheckMember].otherIsSource; + + createCutArc(dec, newRow, markerToCheck, otherMember, reverse); + } + else if( newRow->reducedMembers[toCheckMember].splitNode == markerHead || + newRow->reducedMembers[toCheckMember].splitNode == markerTail ) + { + newRow->reducedMembers[toCheckMember].type = TYPE_MERGED; + } + else if( SPQRarcIsValid(newRow->reducedMembers[toCheckMember].articulationArc) && + ( newRow->reducedMembers[toCheckMember].otherNode == markerHead || + newRow->reducedMembers[toCheckMember].otherNode == markerTail ) ) + { + newRow->reducedMembers[toCheckMember].type = TYPE_MERGED; + } + else + { + //Found source or sinks, but not adjacent to the marker + newRow->reducedMembers[toCheckMember].type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + } +} + +/** Checks if a leaf member can be propagated away from the reduced decomposition. */ +static +RowReducedMemberType determineType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id toCheckMember, /**< The leaf member to check */ + const spqr_arc markerToOther, /**< Marker to the non-leaf member */ + const reduced_member_id otherMember, /**< The connected non-leaf member */ + const spqr_arc markerToCheck /**< Marker from the non-leaf to the leaf member */ +) +{ + assert(newRow->reducedMembers[toCheckMember].type == TYPE_UNDETERMINED); + switch( getMemberType(dec, newRow->reducedMembers[toCheckMember].member) ) + { + case SPQR_MEMBERTYPE_RIGID: + { + determineRigidType(dec, newRow, toCheckMember, markerToOther, otherMember, markerToCheck); + break; + } + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_PARALLEL: + { + determineParallelType(dec, newRow, toCheckMember, markerToOther, otherMember, markerToCheck); + break; + } + case SPQR_MEMBERTYPE_SERIES: + { + determineSeriesType(dec, newRow, toCheckMember, markerToOther, otherMember, markerToCheck); + break; + } + case SPQR_MEMBERTYPE_UNASSIGNED: + default: + newRow->reducedMembers[toCheckMember].type = TYPE_NOT_NETWORK; + assert(FALSE); + } + + return newRow->reducedMembers[toCheckMember].type; +} + +/** Propagates away all leaf members that are propagatable to shrink the reduced decomposition. */ +static +void propagateComponents( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow /**< The network matrix row addition data structure */ +) +{ + int leafArrayIndex = 0; + + reduced_member_id leaf; + reduced_member_id next; + + while( leafArrayIndex != newRow->numLeafMembers ) + { + leaf = newRow->leafMembers[leafArrayIndex]; + //next is invalid if the member is not in the reduced decomposition. + next = newRow->reducedMembers[leaf].parent; + spqr_arc parentMarker = markerToParent(dec, newRow->reducedMembers[leaf].member); + if( next != INVALID_REDUCED_MEMBER && arcIsTree(dec, parentMarker) ) + { + assert(reducedMemberIsValid(next)); + assert(SPQRarcIsValid(parentMarker)); + RowReducedMemberType type = determineType(dec, newRow, leaf, parentMarker, next, + markerOfParent(dec, newRow->reducedMembers[leaf].member)); + if( type == TYPE_PROPAGATED ) + { + ++newRow->reducedMembers[next].numPropagatedChildren; + if( newRow->reducedMembers[next].numPropagatedChildren == newRow->reducedMembers[next].numChildren ) + { + newRow->leafMembers[leafArrayIndex] = next; + } + else + { + ++leafArrayIndex; + } + } + else if( type == TYPE_NOT_NETWORK ) + { + return; + } + else + { + assert(type == TYPE_MERGED); + ++leafArrayIndex; + } + } + else + { + ++leafArrayIndex; + } + } + + for( int j = 0; j < newRow->numReducedComponents; ++j ) + { + //The reduced root might be a leaf as well: we propagate it last + reduced_member_id root = newRow->reducedComponents[j].root; + + while( TRUE ) /*lint !e716*/ + { + if( newRow->reducedMembers[root].numPropagatedChildren == newRow->reducedMembers[root].numChildren - 1 ) + { + //TODO: bit ugly, have to do a linear search for the child + reduced_member_id child = INVALID_REDUCED_MEMBER; + spqr_arc markerToChild = SPQR_INVALID_ARC; + for( children_idx i = newRow->reducedMembers[root].firstChild; + i < newRow->reducedMembers[root].firstChild + newRow->reducedMembers[root].numChildren; ++i ) + { + reduced_member_id childReduced = newRow->childrenStorage[i]; + if( newRow->reducedMembers[childReduced].type != TYPE_PROPAGATED ) + { + child = childReduced; + markerToChild = markerOfParent(dec, newRow->reducedMembers[child].member); + break; + } + } + assert(SPQRmemberIsValid(newRow->reducedMembers[child].member)); + assert(SPQRarcIsValid(markerToChild)); + if( !arcIsTree(dec, markerToChild) ) + { + break; + } + RowReducedMemberType type = determineType(dec, newRow, root, markerToChild, child, + markerToParent(dec, newRow->reducedMembers[child].member)); + if( type == TYPE_PROPAGATED ) + { + root = child; + } + else if( type == TYPE_NOT_NETWORK ) + { + return; + } + else + { + break; + } + } + else + { + break; + } + } + newRow->reducedComponents[j].root = root; + newRow->reducedMembers[root].parent = INVALID_REDUCED_MEMBER; + } +} + +/** A data structure representing a node pair. */ +typedef struct +{ + spqr_node first; + spqr_node second; +} Nodes; + +/** Computes the intersection nodes of all markers that point to reduced tree members. + * + * These are the only nodes that may become Y-splittable, after propagation. + */ +static +Nodes rigidDetermineCandidateNodesFromAdjacentComponents( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id toCheck /**< The rigid member to check */ +) +{ + Nodes pair; + pair.first = SPQR_INVALID_NODE; + pair.second = SPQR_INVALID_NODE; + + //take union of children's arcs nodes to find one or two candidates + for( children_idx i = newRow->reducedMembers[toCheck].firstChild; + i < newRow->reducedMembers[toCheck].firstChild + newRow->reducedMembers[toCheck].numChildren; ++i ) + { + reduced_member_id reducedChild = newRow->childrenStorage[i]; + if( newRow->reducedMembers[reducedChild].type != TYPE_PROPAGATED ) + { + spqr_arc arc = markerOfParent(dec, newRow->reducedMembers[reducedChild].member); + spqr_node head = findArcHead(dec, arc); + spqr_node tail = findArcTail(dec, arc); + if( SPQRnodeIsInvalid(pair.first) && SPQRnodeIsInvalid(pair.second) ) + { + pair.first = head; + pair.second = tail; + } + else + { + if( pair.first != head && pair.first != tail ) + { + pair.first = SPQR_INVALID_NODE; + } + if( pair.second != head && pair.second != tail ) + { + pair.second = SPQR_INVALID_NODE; + } + } + if( SPQRnodeIsInvalid(pair.first) && SPQRnodeIsInvalid(pair.second) ) + { + return pair; + } + } + } + if( reducedMemberIsValid(newRow->reducedMembers[toCheck].parent) && + newRow->reducedMembers[newRow->reducedMembers[toCheck].parent].type != TYPE_PROPAGATED ) + { + + spqr_arc arc = markerToParent(dec, newRow->reducedMembers[toCheck].member); + spqr_node head = findArcHead(dec, arc); + spqr_node tail = findArcTail(dec, arc); + if( SPQRnodeIsInvalid(pair.first) && SPQRnodeIsInvalid(pair.second) ) + { + pair.first = head; + pair.second = tail; + } + else + { + if( pair.first != head && pair.first != tail ) + { + pair.first = SPQR_INVALID_NODE; + } + if( pair.second != head && pair.second != tail ) + { + pair.second = SPQR_INVALID_NODE; + } + } + } + return pair; +} + +/** Allocates memory for various procedures that need to do tree-search for rigid members (e.g. DFS or BFS). */ +static +SCIP_RETCODE allocateTreeSearchMemory( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow /**< The network matrix row addition data structure */ +) +{ + int necessarySpace = newRow->numReducedMembers; + if( necessarySpace > newRow->memMergeTreeCallData ) + { + int updatedSize = MAX(2 * newRow->memMergeTreeCallData, necessarySpace); + SCIP_ALLOC( BMSreallocBlockMemoryArray(dec->env, &newRow->mergeTreeCallData, newRow->memMergeTreeCallData, + updatedSize) ); + newRow->memMergeTreeCallData = updatedSize; + } + return SCIP_OKAY; +} + +/** Determine the type of a rigid member when it is the only member in the reduced decomposition. */ +static +void determineSingleRowRigidType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedMember /**< The rigid member to determine the type for */ +) +{ + //Checking for propagation only makes sense if there is at least one cut arc + assert(newRow->reducedMembers[reducedMember].numCutArcs > 0); + + rigidFindStarNodes(dec, newRow, reducedMember); + if( !newRow->remainsNetwork ) + { + return; + } + + if( SPQRnodeIsInvalid(newRow->reducedMembers[reducedMember].splitNode) ) + { + //not a star => attempt to find splittable nodes using articulation node algorithms + rigidGetSplittableArticulationPointsOnPath(dec, newRow, reducedMember, SPQR_INVALID_NODE, SPQR_INVALID_NODE); + } + if( SPQRnodeIsValid(newRow->reducedMembers[reducedMember].splitNode) ) + { + newRow->reducedMembers[reducedMember].type = TYPE_MERGED; + } + else + { + newRow->reducedMembers[reducedMember].type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + } +} + +/** Determine the type of a parallel member when it is the only member in the reduced decomposition. */ +static +void determineSingleParallelType( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedMember /**< The parallel member to determine the type for */ + ) +{ + SPQRRowReducedMember* redMember = &newRow->reducedMembers[reducedMember]; + assert(cutArcIsValid(redMember->firstCutArc)); + + SCIP_Bool good = TRUE; + SCIP_Bool isReversed = TRUE; + int countedCutArcs = 0; + for( cut_arc_id cutArc = redMember->firstCutArc; cutArcIsValid(cutArc); + cutArc = newRow->cutArcs[cutArc].nextMember ) + { + spqr_arc arc = newRow->cutArcs[cutArc].arc; + SCIP_Bool arcIsReversed = arcIsReversedNonRigid(dec, arc) != newRow->cutArcs[cutArc].arcReversed; + if( countedCutArcs == 0 ) + { + isReversed = arcIsReversed; + } + else if( arcIsReversed != isReversed ) + { + good = FALSE; + break; + } + ++countedCutArcs; + } + if( !good ) + { + redMember->type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + } + else + { + redMember->type = TYPE_MERGED; + } +} + +/** Determine the type of a series member when it is the only member in the reduced decomposition. */ +static +void determineSingleSeriesType( + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedMember /**< The series member to determine the type for */ +) +{ + assert(newRow->reducedMembers[reducedMember].numCutArcs == 1); + assert(cutArcIsValid(newRow->reducedMembers[reducedMember].firstCutArc)); + newRow->reducedMembers[reducedMember].type = TYPE_MERGED; +} + +/** Sets the given split nodes; performs bookkeeping so that we have an easier time updating the graph in many + * edge cases. + * + * Algorithmically speaking, does nothing important. + */ +static +spqr_node determineAndColorSplitNode( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id id, /**< The reduced member that we compute the split node for */ + spqr_node candidateOne, /**< The first candidate node */ + spqr_node candidateTwo /**< The second candidate node */ +) +{ + if( SPQRnodeIsInvalid(newRow->reducedMembers[id].splitNode)) + { + return SPQR_INVALID_NODE; + } + spqr_node splitNode = newRow->reducedMembers[id].splitNode; + if( splitNode == candidateOne || splitNode == candidateTwo ) + { + if( newRow->reducedMembers[id].allHaveCommonNode ) + { + spqr_arc firstNodeArc = getFirstNodeArc(dec, splitNode); + spqr_arc iterArc = firstNodeArc; + COLOR_STATUS color = newRow->reducedMembers[id].otherIsSource ? COLOR_SOURCE : COLOR_SINK; + do + { + spqr_node head = findArcHead(dec, iterArc); + spqr_node other = head == splitNode ? findArcTail(dec, iterArc) : head; + newRow->nodeColors[other] = color; + iterArc = getNextNodeArc(dec, iterArc, splitNode); + } + while( iterArc != firstNodeArc ); + newRow->reducedMembers[id].coloredNode = splitNode; + } + return splitNode; + } + splitNode = newRow->reducedMembers[id].otherNode; + if( SPQRnodeIsInvalid(splitNode) || ( splitNode != candidateOne && splitNode != candidateTwo ) ) + { + return SPQR_INVALID_NODE; + } + assert(SPQRarcIsValid(newRow->reducedMembers[id].articulationArc)); + if( newRow->reducedMembers[id].allHaveCommonNode ) + { + spqr_arc firstNodeArc = getFirstNodeArc(dec, splitNode); + spqr_arc iterArc = firstNodeArc; + COLOR_STATUS color; + if( newRow->reducedMembers[id].numCutArcs == 1 ) + { + color = newRow->reducedMembers[id].otherIsSource ? COLOR_SINK : COLOR_SOURCE; + } + else + { + color = newRow->reducedMembers[id].otherIsSource ? COLOR_SOURCE : COLOR_SINK; + } + do + { + spqr_node head = findArcHead(dec, iterArc); + spqr_node other = head == splitNode ? findArcTail(dec, iterArc) : head; + newRow->nodeColors[other] = color; + iterArc = getNextNodeArc(dec, iterArc, splitNode); + } + while( iterArc != firstNodeArc ); + newRow->nodeColors[newRow->reducedMembers[id].splitNode] = newRow->reducedMembers[id].otherIsSource ? COLOR_SINK + : COLOR_SOURCE; + + } + else + { + COLOR_STATUS splitColor = newRow->nodeColors[splitNode]; + + spqr_arc firstNodeArc = getFirstNodeArc(dec, splitNode); + spqr_arc iterArc = firstNodeArc; + do + { + spqr_node head = findArcHead(dec, iterArc); + spqr_node other = head == splitNode ? findArcTail(dec, iterArc) : head; + newRow->nodeColors[other] = splitColor; + iterArc = getNextNodeArc(dec, iterArc, splitNode); + } + while( iterArc != firstNodeArc ); + newRow->nodeColors[newRow->reducedMembers[id].splitNode] = splitColor == COLOR_SOURCE ? COLOR_SINK : COLOR_SOURCE; + newRow->nodeColors[splitNode] = UNCOLORED; + } + + newRow->reducedMembers[id].coloredNode = splitNode; + return splitNode; +} + +/** After propagation, determines the split type of the first leaf node of the reduced decomposition. + * + * The leaf nodes of the decomposition after propagation can only be of type P or R. + */ +static +void determineSplitTypeFirstLeaf( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedId /**< The member to determine the split type for */ +) +{ + spqr_member member = newRow->reducedMembers[reducedId].member; + SPQRMemberType type = getMemberType(dec, member); + assert(type == SPQR_MEMBERTYPE_PARALLEL || type == SPQR_MEMBERTYPE_RIGID); + assert(cutArcIsValid(newRow->reducedMembers[reducedId].firstCutArc)); + SPQRRowReducedMember* redMember = &newRow->reducedMembers[reducedId]; + + if( type == SPQR_MEMBERTYPE_PARALLEL ) + { + //TODO: duplicate-ish + + SCIP_Bool good = TRUE; + SCIP_Bool isReversed = TRUE; + int countedCutArcs = 0; + for( cut_arc_id cutArc = redMember->firstCutArc; cutArcIsValid(cutArc); + cutArc = newRow->cutArcs[cutArc].nextMember ) + { + spqr_arc arc = newRow->cutArcs[cutArc].arc; + SCIP_Bool arcIsReversed = arcIsReversedNonRigid(dec, arc) != newRow->cutArcs[cutArc].arcReversed; + if( countedCutArcs == 0 ) + { + isReversed = arcIsReversed; + } + else if( arcIsReversed != isReversed ) + { + good = FALSE; + break; + } + ++countedCutArcs; + } + if( !good ) + { + redMember->type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + } + else + { + spqr_arc marker = markerToParent(dec, member); + redMember->type = TYPE_MERGED; + redMember->splitArc = marker; + redMember->splitHead = TRUE; + redMember->otherIsSource = arcIsReversedNonRigid(dec, marker) == isReversed; + } + return; + } + assert(type == SPQR_MEMBERTYPE_RIGID); + + spqr_arc marker = markerToParent(dec, member); + spqr_node markerHead = findArcHead(dec, marker); + spqr_node markerTail = findArcTail(dec, marker); + if( findArcSign(dec, marker).reversed ) + { + spqr_node temp = markerHead; + markerHead = markerTail; + markerTail = temp; + } + + if( !SPQRnodeIsValid(newRow->reducedMembers[reducedId].splitNode) ) + { + //Checking for propagation only makes sense if there is at least one cut arc + assert(newRow->reducedMembers[reducedId].numCutArcs > 0); + + rigidFindStarNodes(dec, newRow, reducedId); + if( !newRow->remainsNetwork ) + { + return; + } + + if( SPQRnodeIsInvalid(newRow->reducedMembers[reducedId].splitNode) ) + { + //not a star => attempt to find splittable nodes using articulation node algorithms + //We save some work by telling the methods that only the marker nodes should be checked + rigidGetSplittableArticulationPointsOnPath(dec, newRow, reducedId, markerHead, markerTail); + } + if( !newRow->remainsNetwork ) + { + return; + } + if( SPQRnodeIsInvalid(newRow->reducedMembers[reducedId].splitNode) ) + { + redMember->type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + return; + } + } + + spqr_node splitNode = newRow->reducedMembers[reducedId].splitNode; +#ifndef NDEBUG + spqr_node otherNode = newRow->reducedMembers[reducedId].otherNode; +#endif + //We cannot have both splittable (should have been propagated) + assert(!(( splitNode == markerTail || splitNode == markerHead ) && + ( otherNode == markerTail || otherNode == markerHead ))); + + splitNode = determineAndColorSplitNode(dec, newRow, reducedId, markerHead, markerTail); + if( SPQRnodeIsInvalid(splitNode) ) + { + redMember->type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + return; + } + assert(splitNode == markerHead || splitNode == markerTail); + + newRow->reducedMembers[reducedId].splitNode = splitNode; + newRow->reducedMembers[reducedId].willBeReversed = FALSE; + redMember->type = TYPE_MERGED; +} + +/** A data structure that tells us if the head or tail of a marked arc is split, and if the other node is in the + * source or the sink partition. + */ +typedef struct +{ + SCIP_Bool headSplit; /**< Is the head or tail of the marked arc split?*/ + SCIP_Bool otherIsSource; /**< Is the non-split node in the source or sink partition? */ +} SplitOrientation; + +/** Get the split orientation for a given rigid member and marked arc. */ +static +SplitOrientation getRelativeOrientationRigid( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedId, /**< The member to determine the split orientation for */ + spqr_arc arcToNext /**< The marked arc to determine the orientation for */ +) +{ + assert(findArcMemberNoCompression(dec, arcToNext) == newRow->reducedMembers[reducedId].member); + assert(SPQRnodeIsValid(newRow->reducedMembers[reducedId].splitNode)); + + SplitOrientation orientation; + if( newRow->reducedMembers[reducedId].numCutArcs == 0 ) + { + spqr_node splitNode = newRow->reducedMembers[reducedId].splitNode; + spqr_node head = findEffectiveArcHead(dec, arcToNext); + + assert(head == splitNode || splitNode == findEffectiveArcTailNoCompression(dec, arcToNext)); + + orientation.headSplit = newRow->reducedMembers[reducedId].willBeReversed == ( head != splitNode ); + orientation.otherIsSource = newRow->reducedMembers[reducedId].otherIsSource; + return orientation; + } + spqr_node splitNode = newRow->reducedMembers[reducedId].splitNode; + spqr_node arcHead = findArcHead(dec, arcToNext); + spqr_node arcTail = findArcTail(dec, arcToNext); + if( findArcSign(dec, arcToNext).reversed ) + { + spqr_node temp = arcHead; + arcHead = arcTail; + arcTail = temp; + } + assert(arcHead == splitNode || arcTail == splitNode); + spqr_node other = arcHead == splitNode ? arcTail : arcHead; + + assert(newRow->nodeColors[other] == COLOR_SOURCE || newRow->nodeColors[other] == COLOR_SINK); + + if( newRow->reducedMembers[reducedId].willBeReversed ) + { + orientation.headSplit = arcHead != splitNode; + orientation.otherIsSource = newRow->nodeColors[other] != COLOR_SOURCE; + } + else + { + orientation.headSplit = arcHead == splitNode; + orientation.otherIsSource = newRow->nodeColors[other] == COLOR_SOURCE; + } + return orientation; +} + +/** Get the split orientation for a given parallel member and marked arc. */ +static +SplitOrientation getRelativeOrientationParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedId, /**< The member to determine the split orientation for */ + spqr_arc arcToNext /**< The marked arc to determine the orientaiton for */ +) +{ + assert(findArcMemberNoCompression(dec, arcToNext) == newRow->reducedMembers[reducedId].member); + assert(SPQRarcIsValid(newRow->reducedMembers[reducedId].splitArc) && SPQRarcIsValid(arcToNext)); + + SplitOrientation orientation; + orientation.otherIsSource = newRow->reducedMembers[reducedId].otherIsSource; + if( arcIsReversedNonRigid(dec, arcToNext) == arcIsReversedNonRigid(dec, newRow->reducedMembers[reducedId].splitArc)) + { + orientation.headSplit = newRow->reducedMembers[reducedId].splitHead; + } + else + { + orientation.headSplit = !newRow->reducedMembers[reducedId].splitHead; + } + return orientation; +} + +/** Get the split orientation for a given series member and marked arc. */ +static +SplitOrientation getRelativeOrientationSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedId, /**< The member to determine the split orientation for */ + spqr_arc arcToNext /**< The marked arc to determine the orientaiton for */ +) +{ + assert(findArcMemberNoCompression(dec, arcToNext) == newRow->reducedMembers[reducedId].member); + assert(SPQRarcIsValid(newRow->reducedMembers[reducedId].splitArc) && SPQRarcIsValid(arcToNext)); + + SplitOrientation orientation; + orientation.otherIsSource = newRow->reducedMembers[reducedId].otherIsSource; + if( arcIsReversedNonRigid(dec, arcToNext) == arcIsReversedNonRigid(dec, newRow->reducedMembers[reducedId].splitArc)) + { + orientation.headSplit = !newRow->reducedMembers[reducedId].splitHead; + } + else + { + orientation.headSplit = newRow->reducedMembers[reducedId].splitHead; + } + return orientation; +} + +/** Get the split orientation for a given member and marked arc. */ +static +SplitOrientation getRelativeOrientation( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedId, /**< The member to determine the split orientation for */ + spqr_arc arcToNext /**< The marked arc to determine the orientaiton for */ +) +{ + switch( getMemberType(dec, newRow->reducedMembers[reducedId].member) ) + { + case SPQR_MEMBERTYPE_RIGID: + return getRelativeOrientationRigid(dec, newRow, reducedId, arcToNext); + case SPQR_MEMBERTYPE_PARALLEL: + return getRelativeOrientationParallel(dec, newRow, reducedId, arcToNext); + case SPQR_MEMBERTYPE_SERIES: + return getRelativeOrientationSeries(dec, newRow, reducedId, arcToNext); + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_UNASSIGNED: + default: + { + assert(FALSE); + SplitOrientation orientation; /*lint !e527*/ + orientation.headSplit = FALSE; /*lint !e527*/ + orientation.otherIsSource = FALSE; + return orientation; + } + } +} + +/** Determine the split type of a series node when the SPQR tree is not a singular member. */ +static +void determineSplitTypeSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedId, /**< The member to determine the split orientation for */ + spqr_arc marker, /**< The marker to the previous member whose type was already determined */ + SplitOrientation previousOrientation /**< The orientation to the previous member */ +) +{ + int numAdjacentMembers = + newRow->reducedMembers[reducedId].numChildren - newRow->reducedMembers[reducedId].numPropagatedChildren; + if( reducedMemberIsValid(newRow->reducedMembers[reducedId].parent) && + newRow->reducedMembers[newRow->reducedMembers[reducedId].parent].type != TYPE_PROPAGATED ) + { + ++numAdjacentMembers; + } + assert(numAdjacentMembers > 1); + if( numAdjacentMembers > 2 ) + { + newRow->remainsNetwork = FALSE; + newRow->reducedMembers[reducedId].type = TYPE_NOT_NETWORK; + return; + } + cut_arc_id cutArc = newRow->reducedMembers[reducedId].firstCutArc; + if( cutArcIsValid(cutArc) ) + { + spqr_arc arc = newRow->cutArcs[cutArc].arc; + SCIP_Bool good = ((( arcIsReversedNonRigid(dec, arc) == arcIsReversedNonRigid(dec, marker)) == + newRow->cutArcs[cutArc].arcReversed ) == previousOrientation.headSplit ) == + previousOrientation.otherIsSource; + if( !good ) + { + newRow->remainsNetwork = FALSE; + newRow->reducedMembers[reducedId].type = TYPE_NOT_NETWORK; + return; + } + newRow->reducedMembers[reducedId].splitArc = marker; + newRow->reducedMembers[reducedId].splitHead = previousOrientation.headSplit; + newRow->reducedMembers[reducedId].otherIsSource = !previousOrientation.otherIsSource; + newRow->reducedMembers[reducedId].type = TYPE_MERGED; + return; + } + + newRow->reducedMembers[reducedId].splitArc = marker; + newRow->reducedMembers[reducedId].splitHead = previousOrientation.headSplit; + newRow->reducedMembers[reducedId].otherIsSource = previousOrientation.otherIsSource; + newRow->reducedMembers[reducedId].type = TYPE_MERGED; +} + +/** Determine the split type of a parallel node when the SPQR tree is not a singular member. */ +static +void determineSplitTypeParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedId, /**< The member to determine the split orientation for */ + spqr_arc marker, /**< The marker to the previous member whose type was already determined */ + SplitOrientation previousOrientation /**< The orientation to the previous member */ +) +{ + SPQRRowReducedMember* redMember = &newRow->reducedMembers[reducedId]; + + SCIP_Bool good = TRUE; + SCIP_Bool isReversed = TRUE; + int countedCutArcs = 0; + for( cut_arc_id cutArc = redMember->firstCutArc; cutArcIsValid(cutArc); + cutArc = newRow->cutArcs[cutArc].nextMember ) + { + spqr_arc arc = newRow->cutArcs[cutArc].arc; + SCIP_Bool arcIsReversed = arcIsReversedNonRigid(dec, arc) != newRow->cutArcs[cutArc].arcReversed; + if( countedCutArcs == 0 ) + { + isReversed = arcIsReversed; + } + else if( arcIsReversed != isReversed ) + { + good = FALSE; + break; + } + ++countedCutArcs; + } + if( !good ) + { + redMember->type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + return; + } + if( countedCutArcs == 0 ) + { + redMember->splitArc = marker; + redMember->splitHead = previousOrientation.headSplit; + redMember->otherIsSource = previousOrientation.otherIsSource; + redMember->type = TYPE_MERGED; + return; + } + SCIP_Bool isHeadSourceOrTailTarget = previousOrientation.headSplit == previousOrientation.otherIsSource; + SCIP_Bool isOkay = isHeadSourceOrTailTarget == ( isReversed == arcIsReversedNonRigid(dec, marker)); + if( !isOkay ) + { + redMember->type = TYPE_NOT_NETWORK; + newRow->remainsNetwork = FALSE; + return; + } + redMember->splitArc = marker; + redMember->splitHead = previousOrientation.headSplit; + redMember->otherIsSource = previousOrientation.otherIsSource; + redMember->type = TYPE_MERGED; +} + +/** Determine the split type of a rigid node when the SPQR tree is not a singular member. */ +static +void determineSplitTypeRigid( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedId, /**< The reduced member to determine the split orientation for */ + spqr_member member, /**< The member belonging to the reduced member */ + spqr_arc marker, /**< The marker to the previous member whose type was already determined */ + SplitOrientation previousOrientation /**< The orientation to the previous member */ +) +{ + assert(dec); + assert(newRow); + assert(getMemberType(dec, member) == SPQR_MEMBERTYPE_RIGID); + + Nodes nodes = rigidDetermineCandidateNodesFromAdjacentComponents(dec, newRow, reducedId); + if( SPQRnodeIsInvalid(nodes.first) && SPQRnodeIsInvalid(nodes.second) ) + { + newRow->remainsNetwork = FALSE; + newRow->reducedMembers[reducedId].type = TYPE_NOT_NETWORK; + return; + } + if( SPQRnodeIsInvalid(nodes.first) && SPQRnodeIsValid(nodes.second) ) + { + nodes.first = nodes.second; + nodes.second = SPQR_INVALID_NODE; + } + + spqr_node markerHead = findArcHead(dec, marker); + spqr_node markerTail = findArcTail(dec, marker); + if( findArcSign(dec, marker).reversed ) + { + spqr_node temp = markerHead; + markerHead = markerTail; + markerTail = temp; + } + + if( newRow->reducedMembers[reducedId].numCutArcs == 0 ) + { + assert(SPQRnodeIsInvalid(nodes.second));//There must be at least two adjacent components + if( nodes.first != markerHead && nodes.first != markerTail ) + { + newRow->remainsNetwork = FALSE; + newRow->reducedMembers[reducedId].type = TYPE_NOT_NETWORK; + return; + } + SCIP_Bool reverse = previousOrientation.headSplit == ( nodes.first == markerTail ); + newRow->reducedMembers[reducedId].splitNode = nodes.first; + newRow->reducedMembers[reducedId].otherIsSource = previousOrientation.otherIsSource; + newRow->reducedMembers[reducedId].willBeReversed = reverse; + newRow->reducedMembers[reducedId].type = TYPE_MERGED; + return; + } + if( !SPQRnodeIsValid(newRow->reducedMembers[reducedId].splitNode) ) + { + //Checking for propagation only makes sense if there is at least one cut arc + assert(newRow->reducedMembers[reducedId].numCutArcs > 0); + + rigidFindStarNodes(dec, newRow, reducedId); + if( !newRow->remainsNetwork ) + { + return; + } + + if( SPQRnodeIsInvalid(newRow->reducedMembers[reducedId].splitNode) ) + { + //not a star => attempt to find splittable nodes using articulation node algorithms + //We save some work by telling the methods that only the marker nodes should be checked + rigidGetSplittableArticulationPointsOnPath(dec, newRow, reducedId, nodes.first, nodes.second); + } + if( !newRow->remainsNetwork ) + { + return; + } + if( SPQRnodeIsInvalid(newRow->reducedMembers[reducedId].splitNode) ) + { + newRow->remainsNetwork = FALSE; + newRow->reducedMembers[reducedId].type = TYPE_NOT_NETWORK; + return; + } + } + spqr_node splitNode = determineAndColorSplitNode(dec, newRow, reducedId, nodes.first, nodes.second); + + if( SPQRnodeIsInvalid(splitNode) ) + { + newRow->remainsNetwork = FALSE; + newRow->reducedMembers[reducedId].type = TYPE_NOT_NETWORK; + return; + } + assert(splitNode == nodes.first || splitNode == nodes.second); + assert(splitNode == markerHead || splitNode == markerTail); + + spqr_node otherNode = splitNode == markerHead ? markerTail : markerHead; + SCIP_Bool headsMatch = previousOrientation.headSplit == ( splitNode == markerHead ); + + COLOR_STATUS otherColor = newRow->nodeColors[otherNode]; + assert(otherColor == COLOR_SOURCE || otherColor == COLOR_SINK); + SCIP_Bool otherIsSource = otherColor == COLOR_SOURCE; + + SCIP_Bool good = headsMatch == ( previousOrientation.otherIsSource == otherIsSource ); + + if( !good ) + { + newRow->remainsNetwork = FALSE; + newRow->reducedMembers[reducedId].type = TYPE_NOT_NETWORK; + return; + } + + newRow->reducedMembers[reducedId].splitNode = splitNode; + newRow->reducedMembers[reducedId].willBeReversed = !headsMatch; + newRow->reducedMembers[reducedId].type = TYPE_MERGED; +} + +/** Determine the split type of a node when the SPQR tree is not a singular member. + * + * This function is used for all the nodes that are not first in the given ordering. + */ +static +void determineSplitTypeNext( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id current, /**< The current node, whose type has already been determined */ + reduced_member_id next, /**< The next node to determine the type of, adjacent to the current node */ + SCIP_Bool currentIsParent /**< Is the current node a parent of the next node? */ +) +{ + spqr_member member = newRow->reducedMembers[next].member; + SPQRMemberType type = getMemberType(dec, member); + spqr_arc nextArc = currentIsParent ? markerToParent(dec, member) : markerOfParent(dec, + newRow->reducedMembers[current].member); + spqr_arc currentArc = currentIsParent ? markerOfParent(dec, member) : markerToParent(dec, + newRow->reducedMembers[current].member); + SplitOrientation orientation = getRelativeOrientation(dec, newRow, current, currentArc); + assert(type == SPQR_MEMBERTYPE_PARALLEL || type == SPQR_MEMBERTYPE_RIGID || type == SPQR_MEMBERTYPE_SERIES); + switch( type ) + { + case SPQR_MEMBERTYPE_RIGID: + { + determineSplitTypeRigid(dec, newRow, next, member, nextArc, orientation); + break; + } + case SPQR_MEMBERTYPE_PARALLEL: + { + determineSplitTypeParallel(dec, newRow, next, nextArc, orientation); + break; + } + case SPQR_MEMBERTYPE_SERIES: + { + determineSplitTypeSeries(dec, newRow, next, nextArc, orientation); + break; + } + + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_UNASSIGNED: + default: + newRow->remainsNetwork = FALSE; + assert(FALSE); + } +} + +/** For the given root reduced member of a component, determine if the component is updateable/transformable. */ +static +void determineMergeableTypes( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id root /**< The root of the given component */ +) +{ + assert(newRow->numReducedMembers <= newRow->memMergeTreeCallData); + if( newRow->reducedMembers[root].numPropagatedChildren == newRow->reducedMembers[root].numChildren ) + { + //Determine single component; + if( newRow->reducedMembers[root].type == TYPE_UNDETERMINED ) + { + spqr_member member = newRow->reducedMembers[root].member; + switch( getMemberType(dec, member) ) + { + case SPQR_MEMBERTYPE_RIGID: + determineSingleRowRigidType(dec, newRow, root); + break; + case SPQR_MEMBERTYPE_PARALLEL: + determineSingleParallelType(dec, newRow, root); + break; + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_SERIES: + determineSingleSeriesType( newRow, root); + break; + case SPQR_MEMBERTYPE_UNASSIGNED: + default: + newRow->remainsNetwork = FALSE; + assert(FALSE); + } + } + return; + } + + //Go to a leaf. We need to start in a leaf to avoid the ambiguity of choosing an orientation + //in members which have no cut arcs; otherwise, we might choose the wrong one + reduced_member_id leaf = root; + while( newRow->reducedMembers[leaf].numChildren != newRow->reducedMembers[leaf].numPropagatedChildren ) + { + for( int i = 0; i < newRow->reducedMembers[leaf].numChildren; ++i ) + { + children_idx idx = newRow->reducedMembers[leaf].firstChild + i; + reduced_member_id child = newRow->childrenStorage[idx]; + if( newRow->reducedMembers[child].type != TYPE_PROPAGATED ) + { + leaf = child; + break; + } + } + } + determineSplitTypeFirstLeaf(dec, newRow, leaf); + + if( !newRow->remainsNetwork ) + { + return; + } + reduced_member_id baseNode = leaf; + reduced_member_id nextNode = newRow->reducedMembers[baseNode].parent; + + while( reducedMemberIsValid(nextNode) ) + { + //check this node + determineSplitTypeNext(dec, newRow, baseNode, nextNode, FALSE); + if( !newRow->remainsNetwork ) + { + return; + } + //Add other nodes in the subtree + //use a while loop to avoid recursion; we may get stack overflows for large graphs + MergeTreeCallData* data = newRow->mergeTreeCallData; + + data[0].id = nextNode; + data[0].currentChild = newRow->reducedMembers[nextNode].firstChild ; + int depth = 0; + while( depth >= 0 ) + { + reduced_member_id id = data[depth].id; + children_idx childidx = data[depth].currentChild; + if( childidx == newRow->reducedMembers[id].firstChild + newRow->reducedMembers[id].numChildren ) + { + --depth; + continue; + } + reduced_member_id currentchild = newRow->childrenStorage[childidx]; + data[depth].currentChild += 1; + //skip this child if we already processed it or it is not merged + if( currentchild == baseNode || newRow->reducedMembers[currentchild].type == TYPE_PROPAGATED ) + { + continue; + } + determineSplitTypeNext(dec,newRow,id,currentchild,TRUE); + if( !newRow->remainsNetwork ) + { + return; + } + //recursively process the child + depth += 1; + assert(depth < newRow->memMergeTreeCallData); + data[depth].id = currentchild; + data[depth].currentChild = newRow->reducedMembers[currentchild].firstChild; + } + //Move up one layer + baseNode = nextNode; + nextNode = newRow->reducedMembers[nextNode].parent; + } +} + +/** Empty the new member information array. */ +static +void cleanUpRowMemberInformation( + SCIP_NETROWADD* newRow /**< The network matrix row addition data structure */ + ) +{ + //This loop is at the end as memberInformation is also used to assign the cut arcs during propagation + //Clean up the memberInformation array + for( int i = 0; i < newRow->numReducedMembers; ++i ) + { + newRow->memberInformation[newRow->reducedMembers[i].member].reducedMember = INVALID_REDUCED_MEMBER; + } +#ifndef NDEBUG + for( int i = 0; i < newRow->memMemberInformation; ++i ) + { + assert(reducedMemberIsInvalid(newRow->memberInformation[i].reducedMember)); + } +#endif +} + +/** Transforms a rigid arc by putting it in series with the new column arc. + * + * We need to do some magic to keep our the internal datastructures consistent in this case. + */ +static +SCIP_RETCODE rigidTransformArcIntoCycle( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + const spqr_member member, /**< The rigid member that contains the arc that is moved */ + const spqr_arc arc, /**< The arc that is moved to a new series member */ + const SCIP_Bool reverseArcDirection,/**< Is the given arc reversed? */ + NewRowInformation* const newRowInfo /**< Stores information on how to add the new row */ +) +{ + //If a cycle already exists, just expand it with the new arc. + spqr_member markerCycleMember = SPQR_INVALID_MEMBER; + spqr_arc markerCycleArc = SPQR_INVALID_ARC; + SCIP_Bool isParent = arc == markerToParent(dec, member); + spqr_member adjacentMember = SPQR_INVALID_MEMBER; + if( isParent ) + { + adjacentMember = findMemberParent(dec, member); + if( getMemberType(dec, adjacentMember) == SPQR_MEMBERTYPE_SERIES ) + { + markerCycleMember = adjacentMember; + markerCycleArc = markerOfParent(dec, member); + } + } + else if( arcIsChildMarker(dec, arc) ) + { + adjacentMember = findArcChildMember(dec, arc); + if( getMemberType(dec, adjacentMember) == SPQR_MEMBERTYPE_SERIES ) + { + markerCycleMember = adjacentMember; + markerCycleArc = markerToParent(dec, adjacentMember); + } + } + if( markerCycleMember != SPQR_INVALID_MEMBER ) + { + newRowInfo->member = markerCycleMember; + if( arcIsReversedNonRigid(dec, markerCycleArc) ) + { + newRowInfo->reversed = reverseArcDirection; + } + else + { + newRowInfo->reversed = !reverseArcDirection; + } + + return SCIP_OKAY; + } + + //Otherwise, we create a new cycle + spqr_member newCycle; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &newCycle) ); + //We would like to move the edge but unfortunately cannot do so without destroying the arc union-find datastructure. + //Thus, we 'convert' the arc into a marker and add the new series + + spqr_arc duplicate = SPQR_INVALID_ARC; + spqr_element element = arcGetElement(dec, arc); + if( element != MARKER_COLUMN_ELEMENT && element != MARKER_ROW_ELEMENT ) + { + if( SPQRelementIsColumn(element) ) + { + SCIP_CALL( createColumnArc(dec, newCycle, &duplicate, SPQRelementToColumn(element), TRUE) ); + } + else + { + SCIP_CALL( createRowArc(dec, newCycle, &duplicate, SPQRelementToRow(element), TRUE) ); + } + } + else if( isParent ) + { + //create parent marker + SCIP_CALL( createParentMarker(dec, newCycle, arcIsTree(dec, arc), adjacentMember, + markerOfParent(dec, member), &duplicate, TRUE) ); + } + else + { + //create child marker + SCIP_CALL( createChildMarker(dec, newCycle, adjacentMember, arcIsTree(dec, arc), &duplicate, TRUE) ); + dec->members[adjacentMember].parentMember = newCycle; + dec->members[adjacentMember].markerOfParent = duplicate; + } + //Create the other marker edge + spqr_arc cycleMarker = SPQR_INVALID_ARC; + if( isParent ) + { + SCIP_CALL( createChildMarker(dec, newCycle, member, !arcIsTree(dec, arc), + &cycleMarker, FALSE) ); + } + else + { + SCIP_CALL( createParentMarker(dec, newCycle, !arcIsTree(dec, arc), + member, arc, &cycleMarker, FALSE) ); + } + //Change the existing edge to a marker + if( isParent ) + { + assert(markerToParent(dec, member) == arc); + dec->arcs[markerOfParent(dec, member)].childMember = newCycle; + dec->members[member].parentMember = newCycle; + dec->members[member].markerToParent = arc; + dec->members[member].markerOfParent = cycleMarker; + dec->arcs[arc].element = arcIsTree(dec, arc) ? MARKER_ROW_ELEMENT : MARKER_COLUMN_ELEMENT; + dec->arcs[arc].childMember = SPQR_INVALID_MEMBER; + } + else + { + dec->arcs[arc].element = arcIsTree(dec, arc) ? MARKER_ROW_ELEMENT : MARKER_COLUMN_ELEMENT; + dec->arcs[arc].childMember = newCycle; + } + newRowInfo->member = newCycle; + newRowInfo->reversed = !reverseArcDirection; + + return SCIP_OKAY; +} + +/** Updates a single rigid member to reflect the new row. */ +static +SCIP_RETCODE transformSingleRigid( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id reducedMember, /**< The reduced member to transform */ + const spqr_member member, /**< The member belonging to the reduced member */ + NewRowInformation* const newRowInfo /**< Stores information about the new row placement */ +) +{ + if( SPQRarcIsValid(newRow->reducedMembers[reducedMember].articulationArc) ) + { + spqr_arc arc = newRow->reducedMembers[reducedMember].articulationArc; + //Cut arc is propagated into a cycle with new arc + assert(newRow->reducedMembers[reducedMember].splitNode == findEffectiveArcHeadNoCompression(dec, arc) || + newRow->reducedMembers[reducedMember].splitNode == findEffectiveArcTailNoCompression(dec, arc)); + + SCIP_Bool reversed; + + if( newRow->isArcCut[arc] ) + { + reversed = ( newRow->reducedMembers[reducedMember].splitNode == findEffectiveArcHead(dec, arc)) == + newRow->reducedMembers[reducedMember].otherIsSource; + } + else + { + reversed = ( newRow->reducedMembers[reducedMember].splitNode == findEffectiveArcHead(dec, arc)) != + newRow->reducedMembers[reducedMember].otherIsSource; + } + + SCIP_CALL( rigidTransformArcIntoCycle(dec, member, newRow->reducedMembers[reducedMember].articulationArc, + reversed, newRowInfo) ); + + return SCIP_OKAY; + } + //Single splittable node + assert(SPQRnodeIsValid(newRow->reducedMembers[reducedMember].splitNode)); + + spqr_node splitNode = newRow->reducedMembers[reducedMember].splitNode; + if( newRow->reducedMembers[reducedMember].allHaveCommonNode ) + { + //Create a new node; move all cut arcs end of split node to it and add new arc between new node and split node + spqr_node newNode = SPQR_INVALID_NODE; + SCIP_CALL( createNode(dec, &newNode) ); + + cut_arc_id cutArcIdx = newRow->reducedMembers[reducedMember].firstCutArc; + do + { + spqr_arc cutArc = newRow->cutArcs[cutArcIdx].arc; + spqr_node arcHead = findArcHead(dec, cutArc); + if( arcHead == splitNode ) + { + changeArcHead(dec, cutArc, arcHead, newNode); + } + else + { + changeArcTail(dec, cutArc, findArcTail(dec, cutArc), newNode); + } + + cutArcIdx = newRow->cutArcs[cutArcIdx].nextMember; + } + while( cutArcIsValid(cutArcIdx) ); + + newRowInfo->member = member; + if( newRow->reducedMembers[reducedMember].otherIsSource ) + { + newRowInfo->head = newNode; + newRowInfo->tail = splitNode; + } + else + { + newRowInfo->head = splitNode; + newRowInfo->tail = newNode; + } + newRowInfo->representative = findArcSign(dec, + newRow->cutArcs[newRow->reducedMembers[reducedMember].firstCutArc].arc).representative; + newRowInfo->reversed = FALSE; + + return SCIP_OKAY; + } + //Articulation point was split (based on coloring) + + spqr_node newNode = SPQR_INVALID_NODE; + SCIP_CALL( createNode(dec, &newNode) ); + + spqr_arc firstNodeArc = getFirstNodeArc(dec, splitNode); + spqr_arc iterArc = firstNodeArc; + + do + { + SCIP_Bool isCut = newRow->isArcCut[iterArc]; + spqr_node otherHead = findArcHead(dec, iterArc); + spqr_node otherTail = findArcTail(dec, iterArc); + spqr_node otherEnd = otherHead == splitNode ? otherTail : otherHead; + SCIP_Bool isMoveColor = newRow->nodeColors[otherEnd] == COLOR_SOURCE; + spqr_arc nextArc = getNextNodeArc(dec, iterArc, splitNode);//Need to do this before we modify the arc :) + + SCIP_Bool changeArcEnd = isCut == isMoveColor; + if( changeArcEnd ) + { + if( otherHead == splitNode ) + { + changeArcHead(dec, iterArc, otherHead, newNode); + } + else + { + changeArcTail(dec, iterArc, otherTail, newNode); + } + } + newRow->nodeColors[otherEnd] = UNCOLORED;//Clean up + + //Ugly hack to make sure we can iterate neighbourhood whilst changing arc ends. + spqr_arc previousArc = iterArc; + iterArc = nextArc; + if( iterArc == firstNodeArc ) + { + break; + } + if( changeArcEnd && previousArc == firstNodeArc ) + { + firstNodeArc = iterArc; + } + } + while( TRUE ); /*lint !e506*/ + newRow->reducedMembers[reducedMember].coloredNode = SPQR_INVALID_NODE; + + newRowInfo->member = member; + newRowInfo->head = newNode; + newRowInfo->tail = splitNode; + newRowInfo->representative = findArcSign(dec, + newRow->cutArcs[newRow->reducedMembers[reducedMember].firstCutArc].arc).representative; + newRowInfo->reversed = FALSE; + + return SCIP_OKAY; +} + +/** Splits a single parallel member into two adjacent ones, where the cut arcs and non-cut arcs get their own member. */ +static +SCIP_RETCODE splitParallelRowAddition( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id reducedMember, /**< The reduced member to transform */ + const spqr_member member, /**< The member belonging to the reduced member */ + NewRowInformation* const newRowInfo /**< Stores information about the new row placement */ +) +{ + assert(newRow->reducedMembers[reducedMember].numCutArcs > 0); + + int numCutArcs = newRow->reducedMembers[reducedMember].numCutArcs; + int numParallelArcs = getNumMemberArcs(dec, member); + + SCIP_Bool createCutParallel = numCutArcs > 1; + SCIP_Bool convertOriginalParallel = ( numCutArcs + 1 ) == numParallelArcs; + + //Do linear search to find non-marked arc + spqr_arc treeArc = getFirstMemberArc(dec, member); + do + { + if( arcIsTree(dec, treeArc)) + { + break; + } + treeArc = getNextMemberArc(dec, treeArc); + } + while( treeArc != getFirstMemberArc(dec, member) ); + assert(arcIsTree(dec, treeArc)); + + SCIP_Bool treeReversed = arcIsReversedNonRigid(dec, treeArc); + + assert(!( !createCutParallel && + convertOriginalParallel ));//This can only happen if the parallel member is actually a loop, which means it is mislabeled + if( createCutParallel ) + { + if( convertOriginalParallel ) + { + spqr_member adjacentMember = SPQR_INVALID_MEMBER; + spqr_arc adjacentArc = SPQR_INVALID_ARC; + if( treeArc == markerToParent(dec, member) ) + { + adjacentMember = findMemberParent(dec, member); + adjacentArc = markerOfParent(dec, member); + } + else if( arcIsChildMarker(dec, treeArc) ) + { + adjacentMember = findArcChildMember(dec, treeArc); + adjacentArc = markerToParent(dec, adjacentMember); + assert(markerOfParent(dec, adjacentMember) == treeArc); + } + cut_arc_id firstCut = newRow->reducedMembers[reducedMember].firstCutArc; + SCIP_Bool firstReversed = + newRow->cutArcs[firstCut].arcReversed != arcIsReversedNonRigid(dec, newRow->cutArcs[firstCut].arc); + + if( SPQRmemberIsValid(adjacentMember) && getMemberType(dec, adjacentMember) == SPQR_MEMBERTYPE_SERIES ) + { + newRowInfo->member = adjacentMember; + if( arcIsReversedNonRigid(dec, treeArc) == arcIsReversedNonRigid(dec, adjacentArc) ) + { + newRowInfo->reversed = !firstReversed; + } + else + { + newRowInfo->reversed = firstReversed; + } + return SCIP_OKAY; + } + spqr_member cutMember = SPQR_INVALID_MEMBER; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, &cutMember) ); + + cut_arc_id cutArcIdx = newRow->reducedMembers[reducedMember].firstCutArc; + assert(cutArcIsValid(cutArcIdx)); + SCIP_Bool parentCut = FALSE; + + while( cutArcIsValid(cutArcIdx) ) + { + spqr_arc cutArc = newRow->cutArcs[cutArcIdx].arc; + cutArcIdx = newRow->cutArcs[cutArcIdx].nextMember; + moveArcToNewMember(dec, cutArc, member, cutMember); + if( cutArc == markerToParent(dec, member) ) + { + parentCut = TRUE; + } + } + if( parentCut ) + { + SCIP_CALL( createMarkerPair(dec, cutMember, member, TRUE, FALSE, TRUE) ); + } + else + { + SCIP_CALL( createMarkerPair(dec, member, cutMember, FALSE, TRUE, FALSE) ); + } + changeLoopToSeries(dec, member); + newRowInfo->member = member; + if( treeReversed ) + { + newRowInfo->reversed = firstReversed == arcIsReversedNonRigid(dec, treeArc); + } + else + { + newRowInfo->reversed = firstReversed != arcIsReversedNonRigid(dec, treeArc); + } + } + else + { + spqr_member cutMember = SPQR_INVALID_MEMBER; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, &cutMember) ); + + cut_arc_id cutArcIdx = newRow->reducedMembers[reducedMember].firstCutArc; + assert(cutArcIsValid(cutArcIdx)); + SCIP_Bool parentCut = FALSE; + + while( cutArcIsValid(cutArcIdx) ) + { + spqr_arc cutArc = newRow->cutArcs[cutArcIdx].arc; + cutArcIdx = newRow->cutArcs[cutArcIdx].nextMember; + moveArcToNewMember(dec, cutArc, member, cutMember); + if( cutArc == markerToParent(dec, member) ) + { + parentCut = TRUE; + } + } + spqr_member newSeries; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &newSeries) ); + if( parentCut ) + { + SCIP_CALL( createMarkerPair(dec, newSeries, member, TRUE, FALSE, FALSE) ); + SCIP_CALL( createMarkerPair(dec, cutMember, newSeries, TRUE, FALSE, TRUE) ); + } + else + { + SCIP_CALL( createMarkerPair(dec, member, newSeries, FALSE, FALSE, FALSE) ); + SCIP_CALL( createMarkerPair(dec, newSeries, cutMember, FALSE, TRUE, FALSE) ); + } + newRowInfo->member = newSeries; + cut_arc_id firstCut = newRow->reducedMembers[reducedMember].firstCutArc; + SCIP_Bool firstReversed = + newRow->cutArcs[firstCut].arcReversed != arcIsReversedNonRigid(dec, newRow->cutArcs[firstCut].arc); + + newRowInfo->reversed = firstReversed; + } + + return SCIP_OKAY; + } + + assert(!createCutParallel && !convertOriginalParallel); + assert(numCutArcs == 1); +#ifndef NDEBUG + spqr_arc arc = newRow->cutArcs[newRow->reducedMembers[reducedMember].firstCutArc].arc; + spqr_member adjacentMember = SPQR_INVALID_MEMBER; + if( arc == markerToParent(dec, member) ) + { + adjacentMember = findMemberParent(dec, member); + } + else if( arcIsChildMarker(dec, arc) ) + { + adjacentMember = findArcChildMember(dec, arc); + } + if( SPQRmemberIsValid(adjacentMember) ) + { + assert(getMemberType(dec, adjacentMember) != SPQR_MEMBERTYPE_SERIES); + } +#endif + spqr_member newSeries; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &newSeries) ); + cut_arc_id cutArcIdx = newRow->reducedMembers[reducedMember].firstCutArc; + assert(cutArcIsValid(cutArcIdx)); + SCIP_Bool parentCut = FALSE; + + while( cutArcIsValid(cutArcIdx) ) + { + spqr_arc cutArc = newRow->cutArcs[cutArcIdx].arc; + cutArcIdx = newRow->cutArcs[cutArcIdx].nextMember; + moveArcToNewMember(dec, cutArc, member, newSeries); + if( cutArc == markerToParent(dec, member) ) + { + parentCut = TRUE; + } + } + if( parentCut ) + { + SCIP_CALL( createMarkerPair(dec, newSeries, member, TRUE, TRUE, FALSE) ); + } + else + { + SCIP_CALL( createMarkerPair(dec, member, newSeries, FALSE, FALSE, TRUE) ); + } + newRowInfo->member = newSeries; + cut_arc_id cutArcId = newRow->reducedMembers[reducedMember].firstCutArc; + newRowInfo->reversed = + newRow->cutArcs[cutArcId].arcReversed == arcIsReversedNonRigid(dec, newRow->cutArcs[cutArcId].arc); + + return SCIP_OKAY; +} + +/** Updates a single rigid member to reflect the new row. */ +static +SCIP_RETCODE transformSingleParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id reducedMember, /**< The reduced member to transform */ + const spqr_member member, /**< The member belonging to the reduced member */ + NewRowInformation* info /**< Stores information about the new row placement */ +) +{ + SCIP_CALL( splitParallelRowAddition(dec, newRow, reducedMember, member, info) ); + return SCIP_OKAY; +} + +/** Split a series member into multiple series members for merging. */ +static +SCIP_RETCODE splitSeriesMergingRowAddition( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + const reduced_member_id reducedMember, /**< The reduced series member */ + const spqr_member member, /**< The member belonging to the reduced member */ + spqr_member* const mergingMember, /**< The member that contains the arcs that are to be merged */ + SCIP_Bool* const isCut, /**< Array that contains whether each arc is Cut */ + spqr_arc* representativeEdge /**< Pointer to the representative arc for the split off arcs */ +) +{ + assert(getNumMemberArcs(dec, member) >= 3); + *isCut = newRow->reducedMembers[reducedMember].numCutArcs > 0; + + if( getNumMemberArcs(dec, member) == 3 ) + { + spqr_arc splitArc = newRow->reducedMembers[reducedMember].splitArc; + spqr_arc otherArc = SPQR_INVALID_ARC; + for( children_idx i = newRow->reducedMembers[reducedMember].firstChild; + i < + newRow->reducedMembers[reducedMember].firstChild + newRow->reducedMembers[reducedMember].numChildren; ++i ) + { + reduced_member_id child = newRow->childrenStorage[i]; + if( newRow->reducedMembers[child].type == TYPE_MERGED ) + { + spqr_arc testArc = markerOfParent(dec, findMember(dec, newRow->reducedMembers[child].member)); + if( testArc != splitArc ) + { + otherArc = testArc; + break; + } + } + } + if( SPQRarcIsInvalid(otherArc) ) + { +#ifndef NDEBUG + reduced_member_id parent = newRow->reducedMembers[reducedMember].parent; +#endif + assert(newRow->reducedMembers[parent].type == TYPE_MERGED || + newRow->reducedMembers[parent].type == TYPE_PROPAGATED); + assert(reducedMemberIsValid(parent) && newRow->reducedMembers[parent].type == TYPE_MERGED); + otherArc = markerToParent(dec, member); + } + spqr_arc firstArc = getFirstMemberArc(dec, member); + spqr_arc arc = firstArc; + do + { + if( arc != splitArc && arc != otherArc ) + { + *representativeEdge = arc; + break; + } + arc = getNextMemberArc(dec, arc); + } + while( arc != firstArc ); + *mergingMember = member; + return SCIP_OKAY; + } + //Split off the relevant part of the series member + spqr_member mergingSeries = SPQR_INVALID_MEMBER; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_SERIES, &mergingSeries) ); + //Move all marker arcs which point to another component in the reduced decomposition to the new member + //This should be exactly 2, as with 3 the result is not network anymore + //move all mergeable children and parent arcs to the mergingMember + + SCIP_Bool coTreeToMergingMember = FALSE; + SCIP_Bool parentToMergingMember = FALSE; + for( children_idx i = newRow->reducedMembers[reducedMember].firstChild; + i < newRow->reducedMembers[reducedMember].firstChild + newRow->reducedMembers[reducedMember].numChildren; ++i ) + { + reduced_member_id child = newRow->childrenStorage[i]; + assert( + newRow->reducedMembers[child].type == TYPE_MERGED || newRow->reducedMembers[child].type == TYPE_PROPAGATED); + if( newRow->reducedMembers[child].type == TYPE_MERGED ) + { + spqr_arc moveArc = markerOfParent(dec, findMember(dec, newRow->reducedMembers[child].member)); + moveArcToNewMember(dec, moveArc, member, mergingSeries); + if( !arcIsTree(dec, moveArc) ) + { + coTreeToMergingMember = TRUE; + } + } + } + + reduced_member_id parent = newRow->reducedMembers[reducedMember].parent; + assert(reducedMemberIsInvalid(parent) || ( newRow->reducedMembers[parent].type == TYPE_MERGED || + newRow->reducedMembers[parent].type == TYPE_PROPAGATED )); + + if( reducedMemberIsValid(parent) && + newRow->reducedMembers[parent].type == TYPE_MERGED ) + { + spqr_arc moveArc = markerToParent(dec, member); + moveArcToNewMember(dec, moveArc, member, mergingSeries); + parentToMergingMember = TRUE; + if( !arcIsTree(dec, moveArc) ) + { + coTreeToMergingMember = TRUE; + } + } + spqr_arc ignoreArc = SPQR_INVALID_ARC; + if( parentToMergingMember ) + { + SCIP_CALL( createMarkerPairWithReferences(dec, mergingSeries, member, coTreeToMergingMember, TRUE, FALSE, + representativeEdge, &ignoreArc) ); + } + else + { + SCIP_CALL( + createMarkerPairWithReferences(dec, member, mergingSeries, !coTreeToMergingMember, FALSE, TRUE, &ignoreArc, + representativeEdge) ); + } + + *mergingMember = mergingSeries; + assert(getNumMemberArcs(dec, mergingSeries) == 3); + assert(getNumMemberArcs(dec, member) >= 3); + + return SCIP_OKAY; +} + +/** Split a parallel member into multiple parallel members for merging. */ +static +SCIP_RETCODE splitParallelMerging( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id reducedMember, /**< The reduced parallel member */ + spqr_member member, /**< The member belonging to the reduced member */ + spqr_member* const pMergeMember, /**< The member that contains the arcs that are to be merged */ + spqr_arc* const cutRepresentative /**< Pointer to the representative arc for all cut arcs */ +) +{ + //When merging, we cannot have propagated members; + assert(newRow->reducedMembers[reducedMember].numCutArcs < ( getNumMemberArcs(dec, member) - 1 )); + + int numMergeableAdjacent = + newRow->reducedMembers[reducedMember].numChildren - newRow->reducedMembers[reducedMember].numPropagatedChildren; + if( reducedMemberIsValid(newRow->reducedMembers[reducedMember].parent) && + newRow->reducedMembers[newRow->reducedMembers[reducedMember].parent].type == TYPE_MERGED ) + { + numMergeableAdjacent++; + } + + int numCutArcs = newRow->reducedMembers[reducedMember].numCutArcs; + //All arcs which are not in the mergeable decomposition or cut + int numBaseSplitAwayArcs = getNumMemberArcs(dec, member) - numMergeableAdjacent - numCutArcs; + + SCIP_Bool createCutParallel = numCutArcs > 1; + SCIP_Bool keepOriginalParallel = numBaseSplitAwayArcs <= 1; + + spqr_member cutMember = SPQR_INVALID_MEMBER; + //The below cases can probably be aggregated in some way, but for now we first focus on the correct logic + if( createCutParallel && keepOriginalParallel ) + { + SCIP_Bool parentCut = FALSE; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, &cutMember) ); + + cut_arc_id cutArcIdx = newRow->reducedMembers[reducedMember].firstCutArc; + assert(cutArcIsValid(cutArcIdx)); + + while( cutArcIsValid(cutArcIdx) ) + { + spqr_arc cutArc = newRow->cutArcs[cutArcIdx].arc; + cutArcIdx = newRow->cutArcs[cutArcIdx].nextMember; + moveArcToNewMember(dec, cutArc, member, cutMember); + if( cutArc == markerToParent(dec, member) ) + { + parentCut = TRUE; + } + } + spqr_arc ignoreArc = SPQR_INVALID_ARC; + if( parentCut ) + { + SCIP_CALL( + createMarkerPairWithReferences(dec, cutMember, member, TRUE, FALSE, FALSE, &ignoreArc, cutRepresentative) ); + } + else + { + SCIP_CALL( + createMarkerPairWithReferences(dec, member, cutMember, FALSE, FALSE, FALSE, cutRepresentative, &ignoreArc) ); + } + + *pMergeMember = member; + } + else if( createCutParallel ) + { + assert(!keepOriginalParallel); + + SCIP_Bool parentCut = FALSE; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, &cutMember) ); + + cut_arc_id cutArcIdx = newRow->reducedMembers[reducedMember].firstCutArc; + assert(cutArcIsValid(cutArcIdx)); + + while( cutArcIsValid(cutArcIdx) ) + { + spqr_arc cutArc = newRow->cutArcs[cutArcIdx].arc; + cutArcIdx = newRow->cutArcs[cutArcIdx].nextMember; + moveArcToNewMember(dec, cutArc, member, cutMember); + if( cutArc == markerToParent(dec, member) ) + { + parentCut = TRUE; + } + } + spqr_arc ignoreArc = SPQR_INVALID_ARC; + if( parentCut ) + { + SCIP_CALL( + createMarkerPairWithReferences(dec, cutMember, member, TRUE, FALSE, FALSE, &ignoreArc, cutRepresentative) ); + } + else + { + SCIP_CALL( + createMarkerPairWithReferences(dec, member, cutMember, FALSE, FALSE, FALSE, cutRepresentative, &ignoreArc) ); + } + + + spqr_arc noCutRepresentative = SPQR_INVALID_ARC; + spqr_member mergingMember = member; + SCIP_Bool parentToMergingMember = FALSE; + SCIP_Bool treeToMergingMember = FALSE; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, &mergingMember) ); + //move all mergeable children and parent arcs to the mergingMember + for( children_idx i = newRow->reducedMembers[reducedMember].firstChild; + i < newRow->reducedMembers[reducedMember].firstChild + + newRow->reducedMembers[reducedMember].numChildren; + ++i ) + { + reduced_member_id child = newRow->childrenStorage[i]; + assert( + newRow->reducedMembers[child].type == TYPE_MERGED || newRow->reducedMembers[child].type == TYPE_PROPAGATED); + if( newRow->reducedMembers[child].type == TYPE_MERGED ) + { + spqr_arc moveArc = markerOfParent(dec, findMember(dec, newRow->reducedMembers[child].member)); + moveArcToNewMember(dec, moveArc, member, mergingMember); + if( arcIsTree(dec, moveArc) ) + { + treeToMergingMember = TRUE; + } + } + } + reduced_member_id parent = newRow->reducedMembers[reducedMember].parent; + if( reducedMemberIsValid(parent) && + newRow->reducedMembers[parent].type == TYPE_MERGED ) + { + spqr_arc moveArc = markerToParent(dec, member); + moveArcToNewMember(dec, moveArc, member, mergingMember); + parentToMergingMember = TRUE; + if( arcIsTree(dec, moveArc) ) + { + treeToMergingMember = TRUE; + } + } + //If there is only one cut arc, we also move it. + if( SPQRarcIsValid(*cutRepresentative) ) + { + if( *cutRepresentative == markerToParent(dec, member) ) + { + parentToMergingMember = TRUE; + } + moveArcToNewMember(dec, *cutRepresentative, member, mergingMember); + } + spqr_arc ignoreArgument = SPQR_INVALID_ARC; + if( parentToMergingMember || parentCut ) + { + SCIP_CALL( createMarkerPairWithReferences(dec, mergingMember, member, !treeToMergingMember, FALSE, FALSE, + &ignoreArgument, &noCutRepresentative) ); + } + else + { + SCIP_CALL( createMarkerPairWithReferences(dec, member, mergingMember, treeToMergingMember, FALSE, FALSE, + &noCutRepresentative, &ignoreArgument) ); + } + *pMergeMember = mergingMember; + } + else if( keepOriginalParallel ) + { + assert(!createCutParallel); + if( cutArcIsValid(newRow->reducedMembers[reducedMember].firstCutArc) ) + { + *cutRepresentative = newRow->cutArcs[newRow->reducedMembers[reducedMember].firstCutArc].arc; + } + *pMergeMember = member; + } + else + { + assert(!keepOriginalParallel && !createCutParallel); + + if( cutArcIsValid(newRow->reducedMembers[reducedMember].firstCutArc) ) + { + *cutRepresentative = newRow->cutArcs[newRow->reducedMembers[reducedMember].firstCutArc].arc; + } + + spqr_arc noCutRepresentative = SPQR_INVALID_ARC; + spqr_member mergingMember = member; + SCIP_Bool parentToMergingMember = FALSE; + SCIP_Bool treeToMergingMember = FALSE; + SCIP_CALL( createMember(dec, SPQR_MEMBERTYPE_PARALLEL, &mergingMember) ); + //move all mergeable children and parent arcs to the mergingMember + for( children_idx i = newRow->reducedMembers[reducedMember].firstChild; + i < newRow->reducedMembers[reducedMember].firstChild + + newRow->reducedMembers[reducedMember].numChildren; + ++i ) + { + reduced_member_id child = newRow->childrenStorage[i]; + assert( + newRow->reducedMembers[child].type == TYPE_MERGED || newRow->reducedMembers[child].type == TYPE_PROPAGATED); + if( newRow->reducedMembers[child].type == TYPE_MERGED ) + { + spqr_arc moveArc = markerOfParent(dec, findMember(dec, newRow->reducedMembers[child].member)); + moveArcToNewMember(dec, moveArc, member, mergingMember); + if( arcIsTree(dec, moveArc) ) + { + treeToMergingMember = TRUE; + } + } + } + reduced_member_id parent = newRow->reducedMembers[reducedMember].parent; + if( reducedMemberIsValid(parent) && + newRow->reducedMembers[parent].type == TYPE_MERGED ) + { + spqr_arc moveArc = markerToParent(dec, member); + moveArcToNewMember(dec, moveArc, member, mergingMember); + parentToMergingMember = TRUE; + if( arcIsTree(dec, moveArc) ) + { + treeToMergingMember = TRUE; + } + } + //If there is only one cut arc, we also move it. + if( SPQRarcIsValid(*cutRepresentative) ) + { + if( *cutRepresentative == markerToParent(dec, member) ) + { + parentToMergingMember = TRUE; + } + moveArcToNewMember(dec, *cutRepresentative, member, mergingMember); + } + spqr_arc ignoreArgument = SPQR_INVALID_ARC; + if( parentToMergingMember ) + { + SCIP_CALL( createMarkerPairWithReferences(dec, mergingMember, member, !treeToMergingMember, FALSE, FALSE, + &ignoreArgument, &noCutRepresentative) ); + } + else + { + SCIP_CALL( createMarkerPairWithReferences(dec, member, mergingMember, treeToMergingMember, FALSE, FALSE, + &noCutRepresentative, &ignoreArgument) ); + } + *pMergeMember = mergingMember; + } + + return SCIP_OKAY; +} + +/** Update the first leaf to reflect the addition of the new row */ +static +SCIP_RETCODE splitFirstLeaf( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id leaf, /**< The leaf to split the first node for */ + NewRowInformation* const newRowInfo /**< Stores how to add the new row */ +) +{ + assert(cutArcIsValid(newRow->reducedMembers[leaf].firstCutArc)); + assert(newRow->reducedMembers[leaf].numCutArcs > 0); + spqr_member member = newRow->reducedMembers[leaf].member; + if( getMemberType(dec, member) == SPQR_MEMBERTYPE_PARALLEL ) + { + spqr_member mergeMember = SPQR_INVALID_MEMBER; + spqr_arc cutRepresentative = SPQR_INVALID_ARC; + SCIP_CALL( splitParallelMerging(dec, newRow, leaf, member, &mergeMember, &cutRepresentative) ); + newRow->reducedMembers[leaf].member = mergeMember; + + spqr_node firstNode = SPQR_INVALID_NODE; + spqr_node secondNode = SPQR_INVALID_NODE; + spqr_node thirdNode = SPQR_INVALID_NODE; + SCIP_CALL( createNode(dec, &firstNode) ); + SCIP_CALL( createNode(dec, &secondNode) ); + SCIP_CALL( createNode(dec, &thirdNode) ); + + spqr_arc splitArc = newRow->reducedMembers[leaf].splitArc; + assert(findArcMemberNoCompression(dec, splitArc) == mergeMember); + SCIP_Bool splitArcReversed = arcIsReversedNonRigid(dec, splitArc); + SCIP_Bool splitHead = newRow->reducedMembers[leaf].splitHead; + spqr_node splitArcHead = splitArcReversed ? secondNode : firstNode; + spqr_node splitArcTail = splitArcReversed ? firstNode : secondNode; + spqr_node splitNode = splitHead ? splitArcHead : splitArcTail; + spqr_node otherNode = splitHead ? splitArcTail : splitArcHead; + + spqr_arc first_arc = getFirstMemberArc(dec, mergeMember); + spqr_arc arc = first_arc; + + do + { + if( arc != cutRepresentative ) + { + if( arcIsReversedNonRigid(dec, arc) ) + { + setArcHeadAndTail(dec, arc, secondNode, firstNode); + } + else + { + setArcHeadAndTail(dec, arc, firstNode, secondNode); + } + } + else + { + if( (arcIsReversedNonRigid(dec, arc) == splitArcReversed) == splitHead ) + { + setArcHeadAndTail(dec, arc, thirdNode, otherNode); + } + else + { + setArcHeadAndTail(dec, arc, otherNode, thirdNode); + } + } + arcSetReversed(dec, arc, FALSE); + if( arc == splitArc ) + { + arcSetRepresentative(dec, arc, SPQR_INVALID_ARC); + } + else + { + arcSetRepresentative(dec, arc, splitArc); + } + arc = getNextMemberArc(dec, arc); + } + while( arc != first_arc ); + + updateMemberType(dec, mergeMember, SPQR_MEMBERTYPE_RIGID); + newRowInfo->member = mergeMember; + if( newRow->reducedMembers[leaf].otherIsSource ) + { + newRowInfo->head = thirdNode; + newRowInfo->tail = splitNode; + } + else + { + newRowInfo->head = splitNode; + newRowInfo->tail = thirdNode; + } + + newRowInfo->reversed = FALSE; + newRowInfo->representative = splitArc; + + return SCIP_OKAY; + } + assert(getMemberType(dec, member) == SPQR_MEMBERTYPE_RIGID); + + spqr_node newNode = SPQR_INVALID_NODE;//Sink node + SCIP_CALL( createNode(dec, &newNode) ); + + spqr_node splitNode = newRow->reducedMembers[leaf].splitNode; + + spqr_arc firstNodeArc = getFirstNodeArc(dec, splitNode); + spqr_arc iterArc = firstNodeArc; + do + { + spqr_node otherHead = findArcHead(dec, iterArc); + spqr_node otherTail = findArcTail(dec, iterArc); + spqr_node otherEnd = otherHead == splitNode ? otherTail : otherHead; + spqr_arc nextArc = getNextNodeArc(dec, iterArc, splitNode);//Need to do this before we modify the arc + + SCIP_Bool isCut = newRow->isArcCut[iterArc]; + SCIP_Bool isMoveColor = newRow->nodeColors[otherEnd] == COLOR_SOURCE; + SCIP_Bool changeArcEnd = isCut == isMoveColor; + if( changeArcEnd ) + { + if( otherHead == splitNode ) + { + changeArcHead(dec, iterArc, otherHead, newNode); + } + else + { + changeArcTail(dec, iterArc, otherTail, newNode); + } + } + //Ugly hack to make sure we can iterate neighbourhood whilst changing arc ends. + newRow->nodeColors[otherEnd] = UNCOLORED; + spqr_arc previousArc = iterArc; + iterArc = nextArc; + if( iterArc == firstNodeArc ) + { + break; + } + if( changeArcEnd && previousArc == firstNodeArc ) + { + firstNodeArc = iterArc; + } + } + while( TRUE ); /*lint !e506*/ + newRowInfo->head = newNode; + newRowInfo->tail = splitNode; + newRowInfo->member = member; + newRowInfo->reversed = FALSE; + newRowInfo->representative = findArcSign(dec, iterArc).representative; + + return SCIP_OKAY; +} + +/** Merge an (updated) member into its parent. + * + * This function is mainly there to prevent duplication. + */ +static +SCIP_RETCODE mergeSplitMemberIntoParent( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + spqr_member member, /**< The member to merge */ + spqr_member parent, /**< The member's parent */ + spqr_arc parentToChild, /**< The marker pointing from the parent to child */ + spqr_arc childToParent, /**< The marker pointing from the child to the parent */ + SCIP_Bool headToHead, /**< Should the marker heads be identified with one another? */ + spqr_node parentNode, /**< A node in the parent that is not adjacent to the marker */ + spqr_node childNode, /**< A node in the child that is not adjacent to the marker */ + spqr_member* mergedMember, /**< Pointer to the member that contains both members after merging */ + spqr_node* arcNodeOne, /**< The first identified node of the two marker arcs */ + spqr_node* arcNodeTwo, /**< The second identified node of the two marker arcs */ + spqr_node* thirdNode /**< The node that parentNode and childNode are identified into */ +) +{ + assert(dec); + assert(SPQRmemberIsValid(member)); + assert(memberIsRepresentative(dec, member)); + assert(SPQRmemberIsValid(parent)); + assert(memberIsRepresentative(dec, parent)); + assert(findMemberParentNoCompression(dec, member) == parent); + assert(markerOfParent(dec, member) == parentToChild); + assert(markerToParent(dec, member) == childToParent); + + removeArcFromMemberArcList(dec, parentToChild, parent); + removeArcFromMemberArcList(dec, childToParent, member); + + int parentTail = findEffectiveArcTail(dec, parentToChild); + int parentHead = findEffectiveArcHead(dec, parentToChild); + int childTail = findEffectiveArcTail(dec, childToParent); + int childHead = findEffectiveArcHead(dec, childToParent); + spqr_node parentArcNodes[2] = { parentTail, parentHead }; + spqr_node childArcNodes[2] = { childTail, childHead }; + + clearArcHeadAndTail(dec, parentToChild); + clearArcHeadAndTail(dec, childToParent); + + spqr_node first = childArcNodes[headToHead ? 0 : 1]; + spqr_node second = childArcNodes[headToHead ? 1 : 0]; + { + spqr_node newNode = mergeNodes(dec, parentArcNodes[0], first); + spqr_node toRemoveFrom = newNode == first ? parentArcNodes[0] : first; + mergeNodeArcList(dec, newNode, toRemoveFrom); + *arcNodeOne = newNode; + newRow->nodeColors[toRemoveFrom] = UNCOLORED; + } + { + spqr_node newNode = mergeNodes(dec, parentArcNodes[1], second); + spqr_node toRemoveFrom = newNode == second ? parentArcNodes[1] : second; + mergeNodeArcList(dec, newNode, toRemoveFrom); + *arcNodeTwo = newNode; + newRow->nodeColors[toRemoveFrom] = UNCOLORED; + } + { + spqr_node newNode = mergeNodes(dec, parentNode, childNode); + spqr_node toRemoveFrom = newNode == childNode ? parentNode : childNode; + mergeNodeArcList(dec, newNode, toRemoveFrom); + *thirdNode = newNode; + newRow->nodeColors[toRemoveFrom] = UNCOLORED; + } + + spqr_member newMember = mergeMembers(dec, member, parent); + spqr_member toRemoveFrom = newMember == member ? parent : member; + mergeMemberArcList(dec, newMember, toRemoveFrom); + if( toRemoveFrom == parent ) + { + updateMemberParentInformation(dec, newMember, toRemoveFrom); + } + updateMemberType(dec, newMember, SPQR_MEMBERTYPE_RIGID); + *mergedMember = newMember; + + return SCIP_OKAY; +} + +/** Update a series member to reflect the addition of the new row, and merge it into the previous members that were + * updated. + */ +static +SCIP_RETCODE splitAndMergeSeries( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id smallMember, /**< The reduced series member */ + SCIP_Bool largeIsParent, /**< Whether the large member containing previously updated members is + * the parent of this member */ + NewRowInformation* const newRowInfo, /**< Stores the information on how to add the new row */ + spqr_member member /**< The member belonging to the reduced series member */ +) +{ + SCIP_Bool isCut = FALSE; + spqr_member mergingMember = SPQR_INVALID_MEMBER; + spqr_arc nonVirtualArc = SPQR_INVALID_ARC; + + SCIP_CALL( splitSeriesMergingRowAddition(dec, newRow, smallMember, member, &mergingMember, &isCut, &nonVirtualArc) ); + assert(getNumMemberArcs(dec, mergingMember) == 3); + + //create the split series. There's two possible configurations, based on whether it contains a cut edge or not + spqr_node a = SPQR_INVALID_NODE; + spqr_node b = SPQR_INVALID_NODE; + spqr_node c = SPQR_INVALID_NODE; + spqr_node d = SPQR_INVALID_NODE; + SCIP_CALL( createNode(dec, &a) ); + SCIP_CALL( createNode(dec, &b) ); + SCIP_CALL( createNode(dec, &c) ); + SCIP_CALL( createNode(dec, &d) ); + + spqr_arc splitArc = newRow->reducedMembers[smallMember].splitArc; + + { + SCIP_Bool splitHead = newRow->reducedMembers[smallMember].splitHead; + spqr_arc firstArc = getFirstMemberArc(dec, mergingMember); + spqr_arc arc = firstArc; + SCIP_Bool splitReversed = arcIsReversedNonRigid(dec, splitArc); + do + { + if( arc == splitArc ) + { + if( splitHead ) + { + setArcHeadAndTail(dec, splitArc, b, a); + } + else + { + setArcHeadAndTail(dec, splitArc, a, b); + } + } + else if( arc == nonVirtualArc ) + { + if( (arcIsReversedNonRigid(dec, arc) == splitReversed) == splitHead ) + { + setArcHeadAndTail(dec, arc, a, d); + } + else + { + setArcHeadAndTail(dec, arc, d, a); + } + } + else + { + spqr_node otherNode = cutArcIsValid(newRow->reducedMembers[smallMember].firstCutArc) ? c : b; + if( (arcIsReversedNonRigid(dec, arc) == splitReversed) == splitHead ) + { + setArcHeadAndTail(dec, arc, d, otherNode); + } + else + { + setArcHeadAndTail(dec, arc, otherNode, d); + } + } + arcSetReversed(dec, arc, FALSE); + arcSetRepresentative(dec, arc, splitArc); + arc = getNextMemberArc(dec, arc); + } + while( arc != firstArc ); + arcSetRepresentative(dec, splitArc, SPQR_INVALID_ARC); + } + + spqr_member otherMember = newRowInfo->member; + spqr_arc otherMarker = largeIsParent ? markerOfParent(dec, mergingMember) : markerToParent(dec, otherMember); + + assert(nodeIsRepresentative(dec, newRowInfo->tail)); + assert(nodeIsRepresentative(dec, newRowInfo->head)); + spqr_node splitNode = newRow->reducedMembers[smallMember].splitHead ? findEffectiveArcHead(dec, otherMarker) + : findEffectiveArcTail(dec, otherMarker); + + spqr_node otherNode = splitNode == newRowInfo->head ? newRowInfo->tail : newRowInfo->head; + assert(splitNode == newRowInfo->head || splitNode == newRowInfo->tail); + newRowInfo->representative = mergeArcSigns(dec, newRowInfo->representative, splitArc, FALSE); + + spqr_member mergedMember = SPQR_INVALID_MEMBER; + spqr_node arcNodeOne; + spqr_node arcNodeTwo; + spqr_node thirdNode; + if( largeIsParent ) + { + SCIP_CALL( mergeSplitMemberIntoParent(dec, newRow, mergingMember, otherMember, otherMarker, splitArc, TRUE, + otherNode, c, &mergedMember, + &arcNodeOne, + &arcNodeTwo, + &thirdNode) ); + } + else + { + SCIP_CALL( mergeSplitMemberIntoParent(dec, newRow, otherMember, mergingMember, splitArc, otherMarker, TRUE, + c, otherNode, &mergedMember, + &arcNodeOne, + &arcNodeTwo, + &thirdNode) ); + } + newRow->reducedMembers[smallMember].member = mergedMember; + + newRowInfo->member = mergedMember; + SCIP_Bool splitIsReferenceHead = newRow->reducedMembers[smallMember].splitHead; + SCIP_Bool splitIsNewRowHead = splitNode == newRowInfo->head; + if( !splitIsReferenceHead && !splitIsNewRowHead ) + { + newRowInfo->head = thirdNode; + newRowInfo->tail = arcNodeOne; + } + else if( !splitIsReferenceHead && splitIsNewRowHead ) + { + newRowInfo->head = arcNodeOne; + newRowInfo->tail = thirdNode; + } + else if( splitIsReferenceHead && !splitIsNewRowHead ) + { + newRowInfo->head = thirdNode; + newRowInfo->tail = arcNodeTwo; + } + else if( splitIsReferenceHead && splitIsNewRowHead ) + { + newRowInfo->head = arcNodeTwo; + newRowInfo->tail = thirdNode; + } + + return SCIP_OKAY; +} + +/** Update a parallel member to reflect the addition of the new row, and merge it into the previous members that were + * updated. + */ +static +SCIP_RETCODE splitAndMergeParallel( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id smallMember, /**< The reduced parallel member */ + SCIP_Bool largeIsParent, /**< Whether the large member containing previously updated members is + * the parent of this member */ + NewRowInformation* const newRowInfo, /**< Stores the information on how to add the new row */ + spqr_member member /**< The member belonging to the reduced parallel member */ +) +{ + spqr_member mergeMember = SPQR_INVALID_MEMBER; + spqr_arc cutRepresentative = SPQR_INVALID_ARC; + SCIP_CALL( splitParallelMerging(dec, newRow, smallMember, member, &mergeMember, &cutRepresentative) ); + newRow->reducedMembers[smallMember].member = mergeMember; + + spqr_node firstNode = SPQR_INVALID_NODE; + spqr_node secondNode = SPQR_INVALID_NODE; + spqr_node thirdNode = SPQR_INVALID_NODE; + SCIP_CALL( createNode(dec, &firstNode) ); + SCIP_CALL( createNode(dec, &secondNode) ); + SCIP_CALL( createNode(dec, &thirdNode) ); + + spqr_arc splitArc = newRow->reducedMembers[smallMember].splitArc; + assert(findArcMemberNoCompression(dec, splitArc) == mergeMember); + SCIP_Bool splitArcReversed = arcIsReversedNonRigid(dec, splitArc); + SCIP_Bool splitHead = newRow->reducedMembers[smallMember].splitHead; + + spqr_node splitArcHead = splitArcReversed ? secondNode : firstNode; + spqr_node splitArcTail = splitArcReversed ? firstNode : secondNode; + spqr_node otherNode = splitHead ? splitArcTail : splitArcHead; + + spqr_arc first_arc = getFirstMemberArc(dec, mergeMember); + spqr_arc arc = first_arc; + + do + { + if( arc != cutRepresentative ) + { + if( arcIsReversedNonRigid(dec, arc) ) + { + setArcHeadAndTail(dec, arc, secondNode, firstNode); + } + else + { + setArcHeadAndTail(dec, arc, firstNode, secondNode); + } + } + else + { + if( (arcIsReversedNonRigid(dec, arc) == splitArcReversed) == splitHead ) + { + setArcHeadAndTail(dec, arc, thirdNode, otherNode); + } + else + { + setArcHeadAndTail(dec, arc, otherNode, thirdNode); + } + } + arcSetReversed(dec, arc, FALSE); + arcSetRepresentative(dec, arc, splitArc); + arc = getNextMemberArc(dec, arc); + } + while( arc != first_arc ); + arcSetRepresentative(dec, splitArc, SPQR_INVALID_ARC); + + spqr_member otherMember = newRowInfo->member; + spqr_arc otherMarker = largeIsParent ? markerOfParent(dec, mergeMember) : markerToParent(dec, otherMember); + + assert(nodeIsRepresentative(dec, newRowInfo->tail)); + assert(nodeIsRepresentative(dec, newRowInfo->head)); + spqr_node largeSplitNode = newRow->reducedMembers[smallMember].splitHead ? findEffectiveArcHead(dec, otherMarker) + : findEffectiveArcTail(dec, otherMarker); + + spqr_node largeOtherNode = + largeSplitNode == newRowInfo->head ? newRowInfo->tail : newRowInfo->head; + assert(largeSplitNode == newRowInfo->head || largeSplitNode == newRowInfo->tail); + + newRowInfo->representative = mergeArcSigns(dec, newRowInfo->representative, splitArc, FALSE); + + spqr_member mergedMember = SPQR_INVALID_MEMBER; + spqr_node arcNodeOne; + spqr_node arcNodeTwo; + spqr_node mergeNodeThree; + if( largeIsParent ) + { + SCIP_CALL( mergeSplitMemberIntoParent(dec, newRow, mergeMember, otherMember, otherMarker, splitArc, TRUE, + largeOtherNode, thirdNode, &mergedMember, + &arcNodeOne, + &arcNodeTwo, + &mergeNodeThree) ); + } + else + { + SCIP_CALL( mergeSplitMemberIntoParent(dec, newRow, otherMember, mergeMember, splitArc, otherMarker, TRUE, + thirdNode, largeOtherNode, &mergedMember, + &arcNodeOne, + &arcNodeTwo, + &mergeNodeThree) ); + } + + newRowInfo->member = mergedMember; + + SCIP_Bool splitIsReferenceHead = newRow->reducedMembers[smallMember].splitHead; + SCIP_Bool splitIsNewRowHead = largeSplitNode == newRowInfo->head; + if( !splitIsReferenceHead && !splitIsNewRowHead ) + { + newRowInfo->head = mergeNodeThree; + newRowInfo->tail = arcNodeOne; + } + else if( !splitIsReferenceHead && splitIsNewRowHead ) + { + newRowInfo->head = arcNodeOne; + newRowInfo->tail = mergeNodeThree; + } + else if( splitIsReferenceHead && !splitIsNewRowHead ) + { + newRowInfo->head = mergeNodeThree; + newRowInfo->tail = arcNodeTwo; + } + else if( splitIsReferenceHead && splitIsNewRowHead ) + { + newRowInfo->head = arcNodeTwo; + newRowInfo->tail = mergeNodeThree; + } + + return SCIP_OKAY; +} + +/** Update a rigid member to reflect the addition of the new row, and merge it into the previous members that were + * updated. + */ +static +SCIP_RETCODE splitAndMergeRigid( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id smallMember, /**< The reduced rigid member */ + SCIP_Bool largeIsParent, /**< Whether the large member containing previously updated members is + * the parent of this member */ + NewRowInformation* const newRowInfo, /**< Stores the information on how to add the new row */ + spqr_member member /**< The member belonging to the reduced rigid member */ +) +{ + spqr_node newNode = SPQR_INVALID_NODE;//Sink node + SCIP_CALL( createNode(dec, &newNode) ); + + spqr_member smallMemberMember = member; + spqr_member largeMemberMember = newRowInfo->member; + + spqr_arc smallMarker = largeIsParent ? markerToParent(dec, smallMemberMember) : markerOfParent(dec, + largeMemberMember); + spqr_arc largeMarker = largeIsParent ? markerOfParent(dec, smallMemberMember) : markerToParent(dec, + largeMemberMember); + + spqr_node splitNode = newRow->reducedMembers[smallMember].splitNode; + spqr_node smallOtherNode = newNode; + + if( newRow->reducedMembers[smallMember].numCutArcs != 0 ) + { + spqr_arc firstNodeArc = getFirstNodeArc(dec, splitNode); + spqr_arc iterArc = firstNodeArc; + do + { + spqr_node otherHead = findArcHead(dec, iterArc); + spqr_node otherTail = findArcTail(dec, iterArc); + spqr_node otherEnd = otherHead == splitNode ? otherTail : otherHead; + spqr_arc nextArc = getNextNodeArc(dec, iterArc, splitNode);//Need to do this before we modify the arc + + SCIP_Bool isCut = newRow->isArcCut[iterArc]; + SCIP_Bool isMoveColor = newRow->nodeColors[otherEnd] == COLOR_SOURCE; + SCIP_Bool changeArcEnd = isCut == isMoveColor; + if( changeArcEnd ) + { + if( otherHead == splitNode ) + { + changeArcHead(dec, iterArc, otherHead, newNode); + } + else + { + changeArcTail(dec, iterArc, otherTail, newNode); + } + if( iterArc == smallMarker ) + { + smallOtherNode = splitNode; + } + } + newRow->nodeColors[otherEnd] = UNCOLORED; + //Ugly hack to make sure we can iterate neighbourhood whilst changing arc ends. + spqr_arc previousArc = iterArc; + iterArc = nextArc; + if( iterArc == firstNodeArc ) + { + break; + } + if( changeArcEnd && previousArc == firstNodeArc ) + { + firstNodeArc = iterArc; + } + } + while( TRUE ); /*lint !e506*/ + } + + spqr_arc representative = findArcSign(dec, smallMarker).representative; + + newRowInfo->representative = mergeArcSigns(dec, newRowInfo->representative, representative, + newRow->reducedMembers[smallMember].willBeReversed); + + spqr_node largeMarkerHead = findArcHead(dec, largeMarker); + spqr_node largeMarkerTail = findArcTail(dec, largeMarker); + if( findArcSign(dec, largeMarker).reversed ) + { + spqr_node temp = largeMarkerHead; + largeMarkerHead = largeMarkerTail; + largeMarkerTail = temp; + } + assert(newRowInfo->head == largeMarkerHead || newRowInfo->head == largeMarkerTail || + newRowInfo->tail == largeMarkerHead || newRowInfo->tail == largeMarkerTail); + spqr_node largeOtherNode = ( newRowInfo->head == largeMarkerHead || newRowInfo->head == largeMarkerTail ) + ? newRowInfo->tail : newRowInfo->head; + + spqr_member mergedMember = SPQR_INVALID_MEMBER; + spqr_node arcNodeOne; + spqr_node arcNodeTwo; + spqr_node mergeNodeThree; + if( largeIsParent ) + { + SCIP_CALL( + mergeSplitMemberIntoParent(dec, newRow, smallMemberMember, largeMemberMember, largeMarker, smallMarker, TRUE, + largeOtherNode, smallOtherNode, &mergedMember, + &arcNodeOne, + &arcNodeTwo, + &mergeNodeThree) ); + } + else + { + SCIP_CALL( + mergeSplitMemberIntoParent(dec, newRow, largeMemberMember, smallMemberMember, smallMarker, largeMarker, TRUE, + smallOtherNode, largeOtherNode, &mergedMember, + &arcNodeOne, + &arcNodeTwo, + &mergeNodeThree) ); + } + newRowInfo->member = mergedMember; + + SCIP_Bool otherIsHead = largeOtherNode == newRowInfo->head; + SCIP_Bool adjacentToMarkerHead = ( newRowInfo->tail == largeMarkerHead || + newRowInfo->head == largeMarkerHead ); + if( adjacentToMarkerHead ) + { + if( otherIsHead ) + { + newRowInfo->head = mergeNodeThree; + newRowInfo->tail = arcNodeTwo; + } + else + { + newRowInfo->head = arcNodeTwo; + newRowInfo->tail = mergeNodeThree; + } + } + else + { + if( otherIsHead ) + { + newRowInfo->head = mergeNodeThree; + newRowInfo->tail = arcNodeOne; + } + else + { + newRowInfo->head = arcNodeOne; + newRowInfo->tail = mergeNodeThree; + } + } + + return SCIP_OKAY; +} + +/** Update a member to reflect the addition of the new row, and merge it into the previous members that were updated. */ +static +SCIP_RETCODE splitAndMerge( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id smallMember, /**< The reduced rigid member */ + SCIP_Bool largeIsParent, /**< Whether the large member containing previously updated members is + * the parent of this member */ + NewRowInformation* const newRowInfo /**< Stores the information on how to add the new row */ +) +{ + spqr_member member = newRow->reducedMembers[smallMember].member; + switch( getMemberType(dec, member)) + { + case SPQR_MEMBERTYPE_RIGID: + { + SCIP_CALL( splitAndMergeRigid(dec, newRow, smallMember, largeIsParent, newRowInfo, member) ); + break; + } + case SPQR_MEMBERTYPE_PARALLEL: + { + SCIP_CALL( splitAndMergeParallel(dec, newRow, smallMember, largeIsParent, newRowInfo, member) ); + break; + } + case SPQR_MEMBERTYPE_SERIES: + { + SCIP_CALL( splitAndMergeSeries(dec, newRow, smallMember, largeIsParent, newRowInfo, member) ); + break; + } + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_UNASSIGNED: + default: + newRow->remainsNetwork = FALSE; + assert(FALSE); + } + return SCIP_OKAY; +} + +/** Update an SPQR tree with multiple members to reflect the addition of a new row, + * merging it into one big rigid node. + */ +static +SCIP_RETCODE mergeTree( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + reduced_member_id root, /**< The root node of the SPQR tree that is to be merged */ + NewRowInformation* const newRowInfo /**< Stores the information on how to add the new row */ +) +{ + //We use the same ordering as when finding + //go to a leaf. We need to start in a leaf to avoid the ambiguity of choosing an orientation + //in members which have no cut arcs; otherwise, we might choose the wrong one + reduced_member_id leaf = root; + while( newRow->reducedMembers[leaf].numChildren != newRow->reducedMembers[leaf].numPropagatedChildren ) + { + for( int i = 0; i < newRow->reducedMembers[leaf].numChildren; ++i ) + { + children_idx idx = newRow->reducedMembers[leaf].firstChild + i; + reduced_member_id child = newRow->childrenStorage[idx]; + if( newRow->reducedMembers[child].type != TYPE_PROPAGATED ) + { + leaf = child; + break; + } + } + } + SCIP_CALL( splitFirstLeaf(dec, newRow, leaf, newRowInfo) ); + + reduced_member_id baseNode = leaf; + reduced_member_id nextNode = newRow->reducedMembers[baseNode].parent; + + while( reducedMemberIsValid(nextNode) ) + { + //check this node + SCIP_CALL( splitAndMerge(dec, newRow, nextNode, FALSE, newRowInfo) ); + + //Recursively merge the children + //use a while loop to avoid recursion; we may get stack overflows for large graphs + MergeTreeCallData * data = newRow->mergeTreeCallData; + + data[0].id = nextNode; + data[0].currentChild = newRow->reducedMembers[nextNode].firstChild ; + int depth = 0; + while( depth >= 0 ) + { + reduced_member_id id = data[depth].id; + children_idx childidx = data[depth].currentChild; + if( childidx == newRow->reducedMembers[id].firstChild + newRow->reducedMembers[id].numChildren ) + { + --depth; + continue; + } + reduced_member_id currentchild = newRow->childrenStorage[childidx]; + data[depth].currentChild += 1; + //skip this child if we already processed it or it is not merged + if( currentchild == baseNode || newRow->reducedMembers[currentchild].type == TYPE_PROPAGATED ) + { + continue; + } + SCIP_CALL( splitAndMerge(dec, newRow, currentchild, TRUE, newRowInfo) ); + + //recursively process the child + depth += 1; + assert(depth < newRow->memMergeTreeCallData); + data[depth].id = currentchild; + data[depth].currentChild = newRow->reducedMembers[currentchild].firstChild; + } + //Move up one layer + baseNode = nextNode; + nextNode = newRow->reducedMembers[nextNode].parent; + } + + return SCIP_OKAY; +} + +/** Update an SPQR tree (a single component of the SPQR forest) to reflect addition of a new row. */ +static +SCIP_RETCODE transformComponentRowAddition( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* newRow, /**< The network matrix row addition data structure */ + SPQRRowReducedComponent* component, /**< The component to transform */ + NewRowInformation* const newRowInfo /**< Stores the information on how to add the new row */ +) +{ + assert(component); + if( newRow->reducedMembers[component->root].numChildren == + newRow->reducedMembers[component->root].numPropagatedChildren ) + { + //No merging necessary, only a single component + reduced_member_id reducedMember = component->root; + assert(reducedMemberIsValid(reducedMember)); + spqr_member member = newRow->reducedMembers[reducedMember].member; + SPQRMemberType type = getMemberType(dec, member); + + switch( type ) + { + case SPQR_MEMBERTYPE_RIGID: + SCIP_CALL( transformSingleRigid(dec, newRow, reducedMember, member, newRowInfo) ); + break; + case SPQR_MEMBERTYPE_PARALLEL: + { + SCIP_CALL( transformSingleParallel(dec, newRow, reducedMember, member, newRowInfo) ); + break; + } + case SPQR_MEMBERTYPE_LOOP: + case SPQR_MEMBERTYPE_SERIES: + { + + newRowInfo->member = member; + cut_arc_id cutArc = newRow->reducedMembers[reducedMember].firstCutArc; + spqr_arc arc = newRow->cutArcs[cutArc].arc; + newRowInfo->reversed = arcIsReversedNonRigid(dec, arc) == newRow->cutArcs[cutArc].arcReversed; + if( type == SPQR_MEMBERTYPE_LOOP ) + { + if( getNumMemberArcs(dec, member) == 2 ) + { + changeLoopToSeries(dec, member); + } + } + break; + } + case SPQR_MEMBERTYPE_UNASSIGNED: + default: + assert(FALSE); + break; + } + + return SCIP_OKAY; + } + + SCIP_CALL( mergeTree(dec, newRow, component->root, newRowInfo) ); + + return SCIP_OKAY; +} + +/** Create the network row addition data structure. */ +static +SCIP_RETCODE netrowaddCreate( + BMS_BLKMEM* blkmem, /**< Block memory */ + SCIP_NETROWADD** prowadd /**< Pointer to store the new row addition struct at */ +) +{ + assert(blkmem); + SCIP_ALLOC( BMSallocBlockMemory(blkmem, prowadd) ); + SCIP_NETROWADD* newRow = *prowadd; + + newRow->remainsNetwork = TRUE; + + newRow->reducedMembers = NULL; + newRow->memReducedMembers = 0; + newRow->numReducedMembers = 0; + + newRow->reducedComponents = NULL; + newRow->memReducedComponents = 0; + newRow->numReducedComponents = 0; + + newRow->memberInformation = NULL; + newRow->memMemberInformation = 0; + newRow->numMemberInformation = 0; + + newRow->cutArcs = NULL; + newRow->memCutArcs = 0; + newRow->numCutArcs = 0; + newRow->firstOverallCutArc = INVALID_CUT_ARC; + + newRow->childrenStorage = NULL; + newRow->memChildrenStorage = 0; + newRow->numChildrenStorage = 0; + + newRow->newRowIndex = SPQR_INVALID_ROW; + + newRow->newColumnArcs = NULL; + newRow->newColumnReversed = NULL; + newRow->memColumnArcs = 0; + newRow->numColumnArcs = 0; + + newRow->leafMembers = NULL; + newRow->numLeafMembers = 0; + newRow->memLeafMembers = 0; + + newRow->decompositionColumnArcs = NULL; + newRow->decompositionColumnArcReversed = NULL; + newRow->memDecompositionColumnArcs = 0; + newRow->numDecompositionColumnArcs = 0; + + newRow->isArcCut = NULL; + newRow->isArcCutReversed = NULL; + newRow->memIsArcCut = 0; + + newRow->nodeColors = NULL; + newRow->memNodeColors = 0; + + newRow->articulationNodes = NULL; + newRow->memArticulationNodes = 0; + newRow->numArticulationNodes = 0; + + newRow->articulationNodeSearchInfo = NULL; + newRow->memNodeSearchInfo = 0; + + newRow->crossingPathCount = NULL; + newRow->memCrossingPathCount = 0; + + newRow->intersectionDFSData = NULL; + newRow->memIntersectionDFSData = 0; + + newRow->colorDFSData = NULL; + newRow->memColorDFSData = 0; + + newRow->artDFSData = NULL; + newRow->memArtDFSData = 0; + + newRow->createReducedMembersCallstack = NULL; + newRow->memCreateReducedMembersCallstack = 0; + + newRow->intersectionPathDepth = NULL; + newRow->memIntersectionPathDepth = 0; + + newRow->intersectionPathParent = NULL; + newRow->memIntersectionPathParent = 0; + + newRow->mergeTreeCallData = NULL; + newRow->memMergeTreeCallData = 0; + + newRow->temporaryColorArray = NULL; + newRow->memTemporaryColorArray = 0; + + return SCIP_OKAY; +} + +/** Frees the network row addition data structure. */ +static +void netrowaddFree( + BMS_BLKMEM* blkmem, /**< Block memory */ + SCIP_NETROWADD** prowadd /**< Pointer to row addition struct to be freed */ +) +{ + assert(*prowadd); + + SCIP_NETROWADD* newRow = *prowadd; + + BMSfreeBlockMemoryArray(blkmem, &newRow->temporaryColorArray, newRow->memTemporaryColorArray); + BMSfreeBlockMemoryArray(blkmem, &newRow->createReducedMembersCallstack, newRow->memCreateReducedMembersCallstack); + BMSfreeBlockMemoryArray(blkmem, &newRow->artDFSData, newRow->memArtDFSData); + BMSfreeBlockMemoryArray(blkmem, &newRow->colorDFSData, newRow->memColorDFSData); + BMSfreeBlockMemoryArray(blkmem, &newRow->mergeTreeCallData, newRow->memMergeTreeCallData); + BMSfreeBlockMemoryArray(blkmem, &newRow->intersectionDFSData, newRow->memIntersectionDFSData); + BMSfreeBlockMemoryArray(blkmem, &newRow->intersectionPathParent, newRow->memIntersectionPathParent); + BMSfreeBlockMemoryArray(blkmem, &newRow->intersectionPathDepth, newRow->memIntersectionPathDepth); + BMSfreeBlockMemoryArray(blkmem, &newRow->crossingPathCount, newRow->memCrossingPathCount); + BMSfreeBlockMemoryArray(blkmem, &newRow->articulationNodeSearchInfo, newRow->memNodeSearchInfo); + BMSfreeBlockMemoryArray(blkmem, &newRow->articulationNodes, newRow->memArticulationNodes); + BMSfreeBlockMemoryArray(blkmem, &newRow->nodeColors, newRow->memNodeColors); + BMSfreeBlockMemoryArray(blkmem, &newRow->isArcCut, newRow->memIsArcCut); + BMSfreeBlockMemoryArray(blkmem, &newRow->isArcCutReversed, newRow->memIsArcCut); + BMSfreeBlockMemoryArray(blkmem, &newRow->decompositionColumnArcs, newRow->memDecompositionColumnArcs); + BMSfreeBlockMemoryArray(blkmem, &newRow->decompositionColumnArcReversed, newRow->memDecompositionColumnArcs); + BMSfreeBlockMemoryArray(blkmem, &newRow->newColumnArcs, newRow->memColumnArcs); + BMSfreeBlockMemoryArray(blkmem, &newRow->newColumnReversed, newRow->memColumnArcs); + BMSfreeBlockMemoryArray(blkmem, &newRow->childrenStorage, newRow->memChildrenStorage); + BMSfreeBlockMemoryArray(blkmem, &newRow->cutArcs, newRow->memCutArcs); + BMSfreeBlockMemoryArray(blkmem, &newRow->memberInformation, newRow->memMemberInformation); + BMSfreeBlockMemoryArray(blkmem, &newRow->reducedComponents, newRow->memReducedComponents); + BMSfreeBlockMemoryArray(blkmem, &newRow->reducedMembers, newRow->memReducedMembers); + BMSfreeBlockMemoryArray(blkmem, &newRow->leafMembers, newRow->memLeafMembers); + BMSfreeBlockMemory(blkmem, prowadd); +} + +/** Checks if the given row can be added to the network decomposition. */ +static +SCIP_RETCODE netrowaddCheck( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* rowadd, /**< The network matrix row addition data structure */ + int row, /**< The row to be checked */ + const int* nonzcols, /**< The column indices of the row's nonzero values */ + const double* nonzvals, /**< The matrix entries of the row's nonzeroes */ + int nnonzs /**< The number of nonzeroes in the row */ +) +{ + assert(dec); + assert(rowadd); + assert(nnonzs == 0 || nonzcols); + + /* A row can only be added once */ + if( netMatDecDataContainsRow(dec,row) ) + { + return SCIP_INVALIDDATA; + } + + rowadd->remainsNetwork = TRUE; + cleanUpPreviousIteration(dec, rowadd); + + SCIP_CALL( newRowUpdateRowInformation(dec, rowadd, row, nonzcols, nonzvals, nnonzs) ); + SCIP_CALL( constructRowReducedDecomposition(dec, rowadd) ); + SCIP_CALL( createReducedDecompositionCutArcs(dec, rowadd) ); + + SCIP_CALL( determineLeafReducedMembers(dec, rowadd) ); + SCIP_CALL( allocateRigidSearchMemory(dec, rowadd) ); + SCIP_CALL( allocateTreeSearchMemory(dec, rowadd) ); + //Check for each component if the cut arcs propagate through a row tree marker to a cut arc in another component + //From the leafs inward. + propagateComponents(dec, rowadd); + //It can happen that we are not graphic by some of the checked components. + //In that case, further checking may lead to errors as some invariants that the code assumes will be broken. + if( rowadd->remainsNetwork ) + { + for( int i = 0; i < rowadd->numReducedComponents; ++i ) + { + determineMergeableTypes(dec, rowadd, rowadd->reducedComponents[i].root); + //exit early if one is not graphic + if( !rowadd->remainsNetwork ) + { + break; + } + } + } + + cleanUpRowMemberInformation(rowadd); + + return SCIP_OKAY; +} + +/** Adds the last checked row to the network decomposition. */ +static +SCIP_RETCODE netrowaddAdd( + SCIP_NETMATDECDATA* dec, /**< The network matrix decomposition */ + SCIP_NETROWADD* rowadd /**< The network matrix row addition data structure */ +) +{ + assert(rowadd->remainsNetwork); + if( rowadd->numReducedComponents == 0 ) + { + spqr_member newMember = SPQR_INVALID_MEMBER; + SCIP_CALL( createStandaloneParallel(dec, rowadd->newColumnArcs, rowadd->newColumnReversed, + rowadd->numColumnArcs, rowadd->newRowIndex, &newMember) ); + } + else if( rowadd->numReducedComponents == 1 ) + { + NewRowInformation information = NEWROWINFORMATION_EMPTY; + SCIP_CALL( transformComponentRowAddition(dec, rowadd, &rowadd->reducedComponents[0], &information) ); + + if( rowadd->numColumnArcs == 0 ) + { + spqr_arc rowArc = SPQR_INVALID_ARC; + SCIP_CALL( createRowArc(dec, information.member, &rowArc, rowadd->newRowIndex, information.reversed) ); + if( SPQRnodeIsValid(information.head)) + { + assert(SPQRnodeIsValid(information.tail)); + assert(SPQRarcIsValid(information.representative)); + setArcHeadAndTail(dec, rowArc, findNode(dec, information.head), findNode(dec, information.tail)); + arcSetRepresentative(dec, rowArc, information.representative); + arcSetReversed(dec, rowArc, information.reversed != arcIsReversedNonRigid(dec, information.representative)); + } + } + else + { + spqr_member new_row_parallel = SPQR_INVALID_MEMBER; + SCIP_CALL( createConnectedParallel(dec, rowadd->newColumnArcs, rowadd->newColumnReversed, rowadd->numColumnArcs, + rowadd->newRowIndex, &new_row_parallel) ); + spqr_arc markerArc = SPQR_INVALID_ARC; + spqr_arc ignore = SPQR_INVALID_ARC; + SCIP_CALL( createMarkerPairWithReferences(dec, information.member, new_row_parallel, TRUE, + information.reversed, FALSE, + &markerArc, &ignore) ); + if( SPQRnodeIsValid(information.head)) + { + assert(SPQRnodeIsValid(information.tail)); + assert(SPQRarcIsValid(information.representative)); + setArcHeadAndTail(dec, markerArc, findNode(dec, information.head), findNode(dec, information.tail)); + arcSetRepresentative(dec, markerArc, information.representative); + arcSetReversed(dec, markerArc, + information.reversed != arcIsReversedNonRigid(dec, information.representative)); + } + } + if( getMemberType(dec, information.member) == SPQR_MEMBERTYPE_LOOP ) + { + assert(getNumMemberArcs(dec, information.member) == 2 || getNumMemberArcs(dec, information.member) == 3); + if( getNumMemberArcs(dec, information.member) == 3 ) + { + changeLoopToSeries(dec, information.member); + } + } + } + else + { +#ifndef NDEBUG + int numDecComponentsBefore = numConnectedComponents(dec); +#endif + spqr_member new_row_parallel = SPQR_INVALID_MEMBER; + SCIP_CALL( createConnectedParallel(dec, rowadd->newColumnArcs, rowadd->newColumnReversed, rowadd->numColumnArcs, + rowadd->newRowIndex, &new_row_parallel) ); + for( int i = 0; i < rowadd->numReducedComponents; ++i ) + { + NewRowInformation information = NEWROWINFORMATION_EMPTY; + + SCIP_CALL( transformComponentRowAddition(dec, rowadd, &rowadd->reducedComponents[i], &information) ); + if( getMemberType(dec, information.member) == SPQR_MEMBERTYPE_LOOP ) + { + assert(getNumMemberArcs(dec, information.member) == 1); + spqr_arc arc = getFirstMemberArc(dec, information.member); + assert(rowadd->isArcCut[arc]); + moveArcToNewMember(dec, arc, information.member, new_row_parallel); + arcSetReversed(dec, arc, rowadd->isArcCutReversed[arc]); + dec->members[information.member].type = SPQR_MEMBERTYPE_UNASSIGNED; + } + else + { + reorderComponent(dec, information.member);//Make sure the new component is the root of the local decomposition tree + spqr_arc markerArc = SPQR_INVALID_ARC; + spqr_arc ignore = SPQR_INVALID_ARC; + SCIP_CALL( createMarkerPairWithReferences(dec, new_row_parallel, information.member, FALSE, + FALSE, information.reversed, + &ignore, &markerArc) ); + if( SPQRnodeIsValid(information.head)) + { + assert(SPQRnodeIsValid(information.tail)); + assert(SPQRarcIsValid(information.representative)); + setArcHeadAndTail(dec, markerArc, findNode(dec, information.head), findNode(dec, information.tail)); + arcSetRepresentative(dec, markerArc, information.representative); + arcSetReversed(dec, markerArc, + information.reversed != arcIsReversedNonRigid(dec, information.representative)); + } + } + } + decreaseNumConnectedComponents(dec, rowadd->numReducedComponents - 1); + assert(numConnectedComponents(dec) == ( numDecComponentsBefore - rowadd->numReducedComponents + 1 )); + } + + return SCIP_OKAY; +} + +/** Returns whether the last checked row can be added to the network decomposition. */ +static +SCIP_Bool netrowaddRemainsNetwork( + const SCIP_NETROWADD* rowadd /**< The network matrix row addition data structure */ + ) +{ + return rowadd->remainsNetwork; +} + +/** A generic data structure that stores a decomposition and can perform both column and row additions. */ +struct SCIP_Netmatdec +{ + SCIP_NETMATDECDATA* dec; + SCIP_NETROWADD* rowadd; + SCIP_NETCOLADD* coladd; +}; + +SCIP_RETCODE SCIPnetmatdecCreate( + BMS_BLKMEM* blkmem, /**< Block memory */ + SCIP_NETMATDEC** pdec, /**< buffer to store pointer to created decomposition */ + int nrows, /**< The maximal number of rows that the decomposition can expect */ + int ncols /**< The maximal number of columns that the decomposition can expect */ +) +{ + SCIP_ALLOC( BMSallocBlockMemory(blkmem,pdec) ); + SCIP_NETMATDEC* dec = *pdec; + + dec->dec = NULL; + SCIP_CALL( netMatDecDataCreate(blkmem, &dec->dec, nrows, ncols) ); + dec->rowadd = NULL; + dec->coladd = NULL; + + return SCIP_OKAY; +} + +void SCIPnetmatdecFree( + SCIP_NETMATDEC** pdec /**< pointer to the network matrix decomposition to freed */ +) +{ + SCIP_NETMATDEC* dec = *pdec; + BMS_BLKMEM* blkmem = dec->dec->env; + + if( dec->coladd != NULL ) + { + netcoladdFree(blkmem, &dec->coladd); + } + if( dec->rowadd != NULL ) + { + netrowaddFree(blkmem, &dec->rowadd); + } + netMatDecDataFree(&dec->dec); + BMSfreeBlockMemory(blkmem,pdec); +} + +SCIP_RETCODE SCIPnetmatdecTryAddCol( + SCIP_NETMATDEC* dec, /**< Network matrix decomposition */ + int column, /**< The column to add */ + int* nonzrows, /**< The column's nonzero row indices */ + double* nonzvals, /**< The column's nonzero entries */ + int nnonzs, /**< The number of nonzeros in the column */ + SCIP_Bool* success /**< Buffer to store whether the column was added */ +) +{ + if( dec->coladd == NULL ) + { + SCIP_CALL( netcoladdCreate(dec->dec->env, &dec->coladd) ); + } + + SCIP_CALL( netcoladdCheck(dec->dec, dec->coladd, column, nonzrows, nonzvals, nnonzs) ); + *success = netcoladdRemainsNetwork(dec->coladd); + if( *success ) + { + SCIP_CALL( netcoladdAdd(dec->dec, dec->coladd) ); + } + + return SCIP_OKAY; +} + +SCIP_RETCODE SCIPnetmatdecTryAddRow( + SCIP_NETMATDEC* dec, /**< Network matrix decomposition */ + int row, /**< The row to add */ + int* nonzcols, /**< The row's nonzero column indices */ + double* nonzvals, /**< The row's nonzero entries */ + int nnonzs, /**< The number of nonzeros in the row */ + SCIP_Bool* success /**< Buffer to store whether the row was added */ +) +{ + if( dec->rowadd == NULL ) + { + SCIP_CALL( netrowaddCreate(dec->dec->env, &dec->rowadd) ); + } + + SCIP_CALL( netrowaddCheck(dec->dec, dec->rowadd, row, nonzcols, nonzvals, nnonzs) ); + *success = netrowaddRemainsNetwork(dec->rowadd); + if( *success ) + { + SCIP_CALL( netrowaddAdd(dec->dec, dec->rowadd) ); + } + + return SCIP_OKAY; +} + +SCIP_Bool SCIPnetmatdecContainsRow( + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + int row /**< The row index that is checked */ +) +{ + return netMatDecDataContainsRow(dec->dec, row); +} + +SCIP_Bool SCIPnetmatdecContainsColumn( + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + int column /**< The column index that is checked */ +) +{ + return netMatDecDataContainsColumn(dec->dec, column); +} + +void SCIPnetmatdecRemoveComponent( + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + int* componentrows, /**< Pointer to the array of rows to delete */ + int nrows, /**< The number of rows to delete */ + int* componentcols, /**< Pointer to the array of columns to delete */ + int ncols /**< The number of columns to delete */ +) +{ + netMatDecDataRemoveComponent(dec->dec, componentrows, nrows, componentcols, ncols); +} + +SCIP_Bool SCIPnetmatdecIsMinimal( + SCIP_NETMATDEC* dec /**< The network matrix decomposition */ +) +{ + return netMatDecDataIsMinimal(dec->dec); +} + +SCIP_Bool SCIPnetmatdecVerifyCycle( + BMS_BUFMEM* bufmem, /**< Buffer memory */ + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + int column, /**< The column to check */ + int* nonzrowidx, /**< Array with the column's nonzero row indices */ + double* nonzvals, /**< Array with the column's nonzero values */ + int nnonzs, /**< Number of nonzeros in the column */ + int* pathrowstorage, /**< A buffer to hold the computed path's rows. Should have size equal or + * greater than the number of rows in the decomposition. */ + SCIP_Bool* pathsignstorage /**< A buffer to store the computed path's row signs. Should have size + * equal or greater than the number of rows in the decomposition. */ +) +{ + return netMatDecDataVerifyCycle(bufmem, dec->dec, column, nonzrowidx, nonzvals, nnonzs, pathrowstorage, pathsignstorage); +} + +SCIP_RETCODE SCIPnetmatdecCreateDiGraph( + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + BMS_BLKMEM * blkmem, /**< The block memory to use for the created digraph */ + SCIP_DIGRAPH** pdigraph, /**< Pointer to the pointer to store the created digraph */ + SCIP_Bool createrowarcs /**< Should the row arcs be added to the created digraph? */ +) +{ + return netMatDecDataCreateDiGraph(dec->dec, blkmem, pdigraph, createrowarcs); +} diff --git a/src/scip/presol_implint.c b/src/scip/presol_implint.c new file mode 100644 index 0000000000..5fbda0ceef --- /dev/null +++ b/src/scip/presol_implint.c @@ -0,0 +1,870 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the program and library */ +/* SCIP --- Solving Constraint Integer Programs */ +/* */ +/* Copyright (c) 2002-2024 Zuse Institute Berlin (ZIB) */ +/* */ +/* Licensed under the Apache License, Version 2.0 (the "License"); */ +/* you may not use this file except in compliance with the License. */ +/* You may obtain a copy of the License at */ +/* */ +/* http://www.apache.org/licenses/LICENSE-2.0 */ +/* */ +/* Unless required by applicable law or agreed to in writing, software */ +/* distributed under the License is distributed on an "AS IS" BASIS, */ +/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ +/* See the License for the specific language governing permissions and */ +/* limitations under the License. */ +/* */ +/* You should have received a copy of the Apache-2.0 license */ +/* along with SCIP; see the file LICENSE. If not visit scipopt.org. */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/**@file presol_implint.c + * @ingroup DEFPLUGINS_PRESOL + * @brief Presolver that detects implicit integer variables + * @author Rolf van der Hulst + */ + +/* TODO: explore integer to implicit integer conversion */ +/* TODO: fix to work in MINLP context */ +/*---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0----+----1----+----2*/ + +#include + +#include "scip/presol_implint.h" +#include "scip/pub_matrix.h" +#include "scip/pub_message.h" +#include "scip/pub_misc.h" +#include "scip/pub_network.h" +#include "scip/pub_presol.h" +#include "scip/pub_var.h" +#include "scip/scip_general.h" +#include "scip/scip_message.h" +#include "scip/scip_mem.h" +#include "scip/scip_nlp.h" +#include "scip/scip_numerics.h" +#include "scip/scip_param.h" +#include "scip/scip_presol.h" +#include "scip/scip_pricer.h" +#include "scip/scip_prob.h" +#include "scip/scip_probing.h" +#include "scip/scip_timing.h" +#include "scip/scip_var.h" + +#define PRESOL_NAME "implint" +#define PRESOL_DESC "detects implicit integer variables" +#define PRESOL_PRIORITY 100 /**< priority of the presolver (>= 0: before, < 0: after constraint handlers); combined with propagators */ +#define PRESOL_MAXROUNDS 0 /**< maximal number of presolving rounds the presolver participates in (-1: no limit) */ +#define PRESOL_TIMING SCIP_PRESOLTIMING_EXHAUSTIVE /* timing of the presolver (fast, medium, or exhaustive) */ + +#define DEFAULT_COLUMNROWRATIO 50.0 +#define DEFAULT_NUMERICSLIMIT 1e6 + +/** presolver data */ +struct SCIP_PresolData +{ + SCIP_Real columnrowratio; /**< Use the network row addition algorithm when the column to row ratio + * becomes larger than this threshold. Otherwise, use column addition. */ + SCIP_Real numericslimit; /**< A row that contains variables with coefficients that are greater in + * absolute value than this limit is not considered for + * implied integrality detection. */ +}; + + +/** Struct that contains information about the blocks/components of the submatrix given by the continuous columns. + * + * Note that currently, the matrix represents exactly the SCIP_MATRIX created by SCIPmatrixCreate(), but this may change + * once MINLP problems are also accounted for. + * @todo extend the plugin to also work for MINLP problems. This changes the computed matrix. + */ +struct MatrixComponents +{ + int nmatrixrows; /**< Number of rows in the matrix for the linear part of the problem */ + int nmatrixcols; /**< Number of columns in the matrix for the linear part of the problem */ + + SCIP_VARTYPE* coltype; /**< SCIP_VARTYPE of the associated column */ + + int* rowcomponent; /**< Maps a row to the index of the component it belongs to */ + int* colcomponent; /**< Maps a column to the index of the component it belongs to */ + + int* componentrows; /**< Flattened array of arrays of rows that are in a given component. */ + int* componentcols; /**< Flattened array of arrays of columns that are in a given component. */ + int* componentrowend; /**< The index of componentrows where the given component ends. */ + int* componentcolend; /**< The index of componentcols where the given component ends. */ + int ncomponents; /**< The number of components. */ +}; +typedef struct MatrixComponents MATRIX_COMPONENTS; + +/** A temporary data structure that stores some statistics/data on the rows and columns. + * + * This is freed again after implied integer detection is finished. + */ +struct MatrixStatistics +{ + SCIP_Bool* rowintegral; /**< Are all row entries of non-continuous columns and the row sides integral? */ + SCIP_Bool* rowequality; /**< Is the row an equality? */ + SCIP_Bool* rowbadnumerics; /**< Does the row contain large entries that make numerics difficult? */ + int* rownnonz; /**< Number of nonzeros in the row */ + int* rowncontinuous; /**< The number of those nonzeros that are in continuous columns */ + int* rowncontinuouspmone; /**< The number of +-1 entries in continuous columns */ + SCIP_Bool* colintegralbounds; /**< Does the column have integral bounds? */ + +}; +typedef struct MatrixStatistics MATRIX_STATISTICS; + +/** Creates the matrix components data structure */ +static +SCIP_RETCODE createMatrixComponents( + SCIP* scip, /**< SCIP data structure */ + SCIP_MATRIX* matrix, /**< The constraint matrix */ + MATRIX_COMPONENTS** pmatrixcomponents /**< Pointer to create the matrix components data structure */ + ) +{ + SCIP_CALL( SCIPallocBuffer(scip, pmatrixcomponents) ); + MATRIX_COMPONENTS* comp = *pmatrixcomponents; + + int nrows = SCIPmatrixGetNRows(matrix); + int ncols = SCIPmatrixGetNColumns(matrix); + + comp->nmatrixrows = nrows; + comp->nmatrixcols = ncols; + + SCIP_CALL( SCIPallocBufferArray(scip, &comp->coltype, ncols) ); + for( int i = 0; i < ncols; ++i ) + { + SCIP_VAR* var = SCIPmatrixGetVar(matrix,i); + comp->coltype[i] = SCIPvarGetType(var); + } + + SCIP_CALL( SCIPallocBufferArray(scip, &comp->rowcomponent, nrows) ); + for( int i = 0; i < nrows; ++i ) + { + comp->rowcomponent[i] = -1; + } + SCIP_CALL( SCIPallocBufferArray(scip, &comp->colcomponent, ncols) ); + for( int i = 0; i < ncols; ++i ) + { + comp->colcomponent[i] = -1; + } + + SCIP_CALL( SCIPallocBufferArray(scip, &comp->componentrows, nrows) ); + SCIP_CALL( SCIPallocBufferArray(scip, &comp->componentcols, ncols) ); + /* There will be at most ncols components */ + SCIP_CALL( SCIPallocBufferArray(scip, &comp->componentrowend, ncols) ); + SCIP_CALL( SCIPallocBufferArray(scip, &comp->componentcolend, ncols) ); + + comp->ncomponents = 0; + + return SCIP_OKAY; +} + +/** Frees the matrix components data structure */ +static +void freeMatrixComponents( + SCIP* scip, /**< SCIP data structure */ + MATRIX_COMPONENTS** pmatrixcomponents /**< Pointer to the allocated matrix components data structure */ + ) +{ + MATRIX_COMPONENTS* comp = *pmatrixcomponents; + + /* Make sure to free in reverse */ + SCIPfreeBufferArray(scip, &comp->componentcolend); + SCIPfreeBufferArray(scip, &comp->componentrowend); + SCIPfreeBufferArray(scip, &comp->componentcols); + SCIPfreeBufferArray(scip, &comp->componentrows); + SCIPfreeBufferArray(scip, &comp->colcomponent); + SCIPfreeBufferArray(scip, &comp->rowcomponent); + SCIPfreeBufferArray(scip, &comp->coltype); + + SCIPfreeBuffer(scip, pmatrixcomponents); +} + +/** Finds the representative of an element in the disjoint set datastructure. + * + * Afterwards compresses the path to speed up subsequent queries. + */ +static +int disjointSetFind( + int* disjointset, /**< The array storing the disjoint set representatives */ + int ind /**< The index to find */ + ) +{ + assert(disjointset != NULL); + + int current = ind; + int next; + /* traverse down tree */ + while( (next = disjointset[current]) >= 0 ) + { + current = next; + } + int root = current; + + /* compress indices along path */ + current = ind; + while( (next = disjointset[current]) >= 0 ) + { + disjointset[current] = root; + current = next; + } + + return root; +} + +/** Merges two sets/elements into one set. Returns the index of the merged element. + * + * The provided elements to be merged must be representative (i.e. returned by disjointSetFind()). + */ +static +int disjointSetMerge( + int* disjointset, /**< The array storing the disjoint set representatives */ + int first, /**< The first index to merge */ + int second /**< The second index to merge */ + ) +{ + assert(disjointset); + assert(disjointset[first] <= -1); + assert(disjointset[second] <= -1); + assert(first != second); /* We cannot merge a node into itself */ + + /* The rank is stored as a negative number: we decrement it making the negative number larger. + * The rank is an upper bound on the height of the tree. We want the new root to be the one with 'largest' rank, + * so smallest number. This way, we ensure that the tree remains shallow. If they are equal, we decrement. + */ + int firstRank = disjointset[first]; + int secondRank = disjointset[second]; + if( firstRank > secondRank ) + { + SCIPswapInts(&first, &second); + } + /* first becomes representative */ + disjointset[second] = first; + if( firstRank == secondRank ) + { + --disjointset[first]; + } + + return first; +} + +/** Computes the continuous connected components, i.e. the connected components of the submatrix given by all + * continuous columns. + */ +static +SCIP_RETCODE computeContinuousComponents( + SCIP* scip, /**< SCIP data structure */ + SCIP_MATRIX* matrix, /**< The constraint matrix to compute the components for */ + MATRIX_COMPONENTS* comp /**< The connected components data structure to store the components in */ + ) +{ + /* We let rows and columns share an index by mapping row index i to artificial column index i + nmatrixcols */ + int* disjointset = NULL; + SCIP_CALL( SCIPallocBufferArray(scip, &disjointset, comp->nmatrixcols + comp->nmatrixrows) ); + /* First n entries belong to columns, last entries to rows */ + for( int i = 0; i < comp->nmatrixcols + comp->nmatrixrows; ++i ) + { + disjointset[i] = -1; + } + + for( int col = 0; col < comp->nmatrixcols; ++col ) + { + if( comp->coltype[col] != SCIP_VARTYPE_CONTINUOUS ) + { + continue; + } + int colnnonzs = SCIPmatrixGetColNNonzs(matrix, col); + int* colrows = SCIPmatrixGetColIdxPtr(matrix, col); + + int colrep = disjointSetFind(disjointset, col); + for( int i = 0; i < colnnonzs; ++i ) + { + int colrow = colrows[i]; + int ind = colrow + comp->nmatrixcols; + int rowrep = disjointSetFind(disjointset, ind); + if( colrep != rowrep ) + { + colrep = disjointSetMerge(disjointset, colrep, rowrep); + } + } + } + + /** Now, fill in the relevant data. */ + int* representativecomponent; + SCIP_CALL( SCIPallocBufferArray(scip, &representativecomponent, comp->nmatrixcols + comp->nmatrixrows) ); + + for( int i = 0; i < comp->nmatrixcols + comp->nmatrixrows; ++i ) + { + representativecomponent[i] = -1; + } + comp->ncomponents = 0; + for( int col = 0; col < comp->nmatrixcols; ++col ) + { + if( comp->coltype[col] != SCIP_VARTYPE_CONTINUOUS ) + { + continue; + } + int colroot = disjointSetFind(disjointset, col); + int component = representativecomponent[colroot]; + if( component < 0 ) + { + assert(component == -1); + /* add new component */ + component = comp->ncomponents; + representativecomponent[colroot] = component; + comp->componentcolend[component] = 0; + comp->componentrowend[component] = 0; + ++comp->ncomponents; + } + comp->colcomponent[col] = component; + ++comp->componentcolend[component]; + } + for( int row = 0; row < comp->nmatrixrows; ++row ) + { + int rowroot = disjointSetFind(disjointset, row + comp->nmatrixcols); + int component = representativecomponent[rowroot]; + if( component < 0 ) + { + assert(component == -1); + /* Any rows that have roots that we have not seen yet are rows that have no continuous columns + * We can safely skip these for finding the continuous connected components + */ + continue; + } + comp->rowcomponent[row] = component; + ++comp->componentrowend[component]; + } + if( comp->ncomponents >= 1 ) + { + for( int i = 1; i < comp->ncomponents; ++i ) + { + comp->componentrowend[i] += comp->componentrowend[i-1]; + comp->componentcolend[i] += comp->componentcolend[i-1]; + } + int * componentnextrowindex; + int * componentnextcolindex; + SCIP_CALL( SCIPallocBufferArray(scip, &componentnextrowindex, comp->ncomponents) ); + SCIP_CALL( SCIPallocBufferArray(scip, &componentnextcolindex, comp->ncomponents) ); + componentnextrowindex[0] = 0; + componentnextcolindex[0] = 0; + for( int i = 1; i < comp->ncomponents; ++i ) + { + componentnextcolindex[i] = comp->componentcolend[i-1]; + componentnextrowindex[i] = comp->componentrowend[i-1]; + } + + for( int col = 0; col < comp->nmatrixcols; ++col ) + { + int component = comp->colcomponent[col]; + if( component < 0 ) + { + assert(component == -1); + continue; + } + int ind = componentnextcolindex[component]; + comp->componentcols[ind] = col; + ++componentnextcolindex[component]; + } + for( int row = 0; row < comp->nmatrixrows; ++row ) + { + int component = comp->rowcomponent[row]; + if( component < 0 ) + { + assert(component == -1); + continue; + } + int ind = componentnextrowindex[component]; + comp->componentrows[ind] = row; + ++componentnextrowindex[component]; + } + +#ifndef NDEBUG + for( int i = 0; i < comp->ncomponents; ++i ) + { + assert(componentnextrowindex[i] == comp->componentrowend[i]); + assert(componentnextcolindex[i] == comp->componentcolend[i]); + } +#endif + + SCIPfreeBufferArray(scip, &componentnextcolindex); + SCIPfreeBufferArray(scip, &componentnextrowindex); + } + + SCIPfreeBufferArray(scip, &representativecomponent); + SCIPfreeBufferArray(scip, &disjointset); + + return SCIP_OKAY; +} + +/** Creates the matrix statistics data structure */ +static +SCIP_RETCODE computeMatrixStatistics( + SCIP* scip, /**< SCIP data structure */ + SCIP_MATRIX* matrix, /**< The constraint matrix to compute the statistics for */ + MATRIX_COMPONENTS* comp, /**< Datastructure that contains the components of the matrix */ + MATRIX_STATISTICS** pstats, /**< Pointer to allocate the statistics data structure at */ + SCIP_Real numericslimit /**< The limit beyond which we consider integrality of coefficients + * to be unreliable */ + ) +{ + SCIP_CALL( SCIPallocBuffer(scip, pstats) ); + MATRIX_STATISTICS* stats = *pstats; + + int nrows = SCIPmatrixGetNRows(matrix); + int ncols = SCIPmatrixGetNColumns(matrix); + + SCIP_CALL( SCIPallocBufferArray(scip, &stats->rowintegral, nrows) ); + SCIP_CALL( SCIPallocBufferArray(scip, &stats->rowequality, nrows) ); + SCIP_CALL( SCIPallocBufferArray(scip, &stats->rowbadnumerics, nrows) ); + + SCIP_CALL( SCIPallocBufferArray(scip, &stats->rownnonz, nrows) ); + SCIP_CALL( SCIPallocBufferArray(scip, &stats->rowncontinuous, nrows) ); + SCIP_CALL( SCIPallocBufferArray(scip, &stats->rowncontinuouspmone, nrows) ); + + SCIP_CALL( SCIPallocBufferArray(scip, &stats->colintegralbounds, ncols) ); + + for( int i = 0; i < nrows; ++i ) + { + SCIP_Real lhs = SCIPmatrixGetRowLhs(matrix, i); + SCIP_Real rhs = SCIPmatrixGetRowRhs(matrix, i); + int* cols = SCIPmatrixGetRowIdxPtr(matrix, i); + SCIP_Real* vals = SCIPmatrixGetRowValPtr(matrix, i); + int nnonz = SCIPmatrixGetRowNNonzs(matrix, i); + stats->rownnonz[i] = nnonz; + stats->rowequality[i] = !SCIPisInfinity(scip, -lhs) && !SCIPisInfinity(scip, rhs) && SCIPisEQ(scip, lhs, rhs); + + SCIP_Bool integral = ( SCIPisInfinity(scip, -lhs) || SCIPisIntegral(scip, lhs) ) + && ( SCIPisInfinity(scip, rhs) || SCIPisIntegral(scip, rhs) ); + SCIP_Bool badnumerics = FALSE; + + int ncontinuous = 0; + int ncontinuouspmone = 0; + for( int j = 0; j < nnonz; ++j ) + { + SCIP_Bool continuous = comp->coltype[cols[j]] == SCIP_VARTYPE_CONTINUOUS; + SCIP_Real value = vals[j]; + if( continuous ) + { + ++ncontinuous; + if( SCIPisEQ(scip, ABS(value), 1.0) ) + { + ++ncontinuouspmone; + } + } + else + { + integral = integral && SCIPisIntegral(scip, value); + } + if( ABS(value) > numericslimit ) + { + badnumerics = TRUE; + } + } + + stats->rowncontinuous[i] = ncontinuous; + stats->rowncontinuouspmone[i] = ncontinuouspmone; + stats->rowintegral[i] = integral; + stats->rowbadnumerics[i] = badnumerics; + } + + for( int i = 0; i < ncols; ++i ) + { + + SCIP_Real lb = SCIPmatrixGetColLb(matrix, i); + SCIP_Real ub = SCIPmatrixGetColUb(matrix, i); + stats->colintegralbounds[i] = ( SCIPisInfinity(scip, -lb) || SCIPisIntegral(scip, lb) ) + && ( SCIPisInfinity(scip, ub) || SCIPisIntegral(scip, ub) ); + + /* Check that integer variables have integer bounds, as expected. */ + assert(comp->coltype[i] == SCIP_VARTYPE_CONTINUOUS || stats->colintegralbounds[i]); + } + + return SCIP_OKAY; +} + +/** Frees the matrix statistics data structure */ +static +void freeMatrixStatistics( + SCIP* scip, /**< SCIP data structure */ + MATRIX_STATISTICS** pstats /**< Pointer to the statistics data structure to be freed */ +) +{ + MATRIX_STATISTICS* stats= *pstats; + + /* Make sure, for performance, that these frees occur in reverse */ + SCIPfreeBufferArray(scip, &stats->colintegralbounds); + SCIPfreeBufferArray(scip, &stats->rowncontinuouspmone); + SCIPfreeBufferArray(scip, &stats->rowncontinuous); + SCIPfreeBufferArray(scip, &stats->rownnonz); + SCIPfreeBufferArray(scip, &stats->rowbadnumerics); + SCIPfreeBufferArray(scip, &stats->rowequality); + SCIPfreeBufferArray(scip, &stats->rowintegral); + + SCIPfreeBuffer(scip, pstats); +} + +/** Given the continuous components and statistics on the matrix, detect components that have implied integer variables + * by checking if the component is a (transposed) network matrix and if all the bounds/sides/coefficients are integral. + * + * For every component, we detect if the associated matrix is either a network matrix or a transposed network matrix + * (or both, in which case it represents a planar graph). + * We choose to check if it is a (transposed) network matrix either in a row-wise or in a column-wise fashion, + * depending on the size of the component. + * Finally, every variable that is in a network matrix or transposed network matrix is changed to an implied integer. + */ +static +SCIP_RETCODE findImpliedIntegers( + SCIP* scip, /**< SCIP data structure */ + SCIP_PRESOLDATA* presoldata, /**< The data belonging to the presolver */ + SCIP_MATRIX* matrix, /**< The constraint matrix to compute implied integers for */ + MATRIX_COMPONENTS* comp, /**< The continuous connected components of the matrix */ + MATRIX_STATISTICS* stats, /**< Statistics of the matrix */ + int* nchgvartypes /**< Pointer to count the number of changed variable types */ + ) +{ + /* TODO: some checks to prevent expensive memory initialization if not necessary (e.g. there must be some candidates) */ + SCIP_NETMATDEC* dec = NULL; + SCIP_CALL( SCIPnetmatdecCreate(SCIPblkmem(scip), &dec, comp->nmatrixrows, comp->nmatrixcols) ); + + SCIP_NETMATDEC* transdec = NULL; + SCIP_CALL( SCIPnetmatdecCreate(SCIPblkmem(scip), &transdec, comp->nmatrixcols, comp->nmatrixrows) ); + + int ngoodcomponents = 0; + int nbadnumerics = 0; + int nbadintegrality = 0; + int nnonnetwork = 0; + + /* Because the rows may also contain non-continuous columns, we need to remove these from the array that we + * pass to the network matrix decomposition method. We use these working arrays for this purpose. + */ + SCIP_Real* tempValArray; + int* tempIdxArray; + SCIP_CALL(SCIPallocBufferArray(scip, &tempValArray, comp->nmatrixcols)); + SCIP_CALL(SCIPallocBufferArray(scip, &tempIdxArray, comp->nmatrixcols)); + + for( int component = 0; component < comp->ncomponents; ++component ) + { + int startrow = (component == 0) ? 0 : comp->componentrowend[component-1]; + int nrows = comp->componentrowend[component] - startrow; + SCIP_Bool componentokay = TRUE; + for( int i = startrow; i < startrow + nrows; ++i ) + { + int row = comp->componentrows[i]; + if( stats->rowncontinuous[row] != stats->rowncontinuouspmone[row] || !stats->rowintegral[row] ) + { + componentokay = FALSE; + ++nbadintegrality; + break; + } + if( stats->rowbadnumerics[row] ) + { + componentokay = FALSE; + ++nbadnumerics; + break; + } + } + int startcol = ( component == 0 ) ? 0 : comp->componentcolend[component-1]; + int ncols = comp->componentcolend[component] - startcol; + + for( int i = startcol; i < startcol + ncols && componentokay; ++i ) + { + int col = comp->componentcols[i]; + if( !stats->colintegralbounds[col] ) + { + componentokay = FALSE; + ++nbadintegrality; + break; + } + } + + if(!componentokay) + { + continue; + } + + /* Check if the component is a network matrix */ + SCIP_Bool componentnetwork = TRUE; + + /* We use the row-wise algorithm only if the number of columns is much larger than the number of rows. + * Generally, the column-wise algorithm will be faster, but in these extreme cases, the row algorithm is faster. + * Only very few instances should use the row-wise algorithm. + */ + if( nrows * presoldata->columnrowratio < ncols ) + { + for( int i = startrow; i < startrow + nrows && componentnetwork; ++i ) + { + int row = comp->componentrows[i]; + int nrownnoz = SCIPmatrixGetRowNNonzs(matrix, row); + int* rowcols = SCIPmatrixGetRowIdxPtr(matrix, row); + SCIP_Real* rowvals = SCIPmatrixGetRowValPtr(matrix, row); + int ncontnonz = 0; + for( int j = 0; j < nrownnoz; ++j ) + { + int col = rowcols[j]; + if( comp->coltype[col] == SCIP_VARTYPE_CONTINUOUS ) + { + tempIdxArray[ncontnonz] = col; + tempValArray[ncontnonz] = rowvals[j]; + ++ncontnonz; + assert(SCIPisEQ(scip, ABS(rowvals[j]), 1.0)); + } + } + + SCIP_CALL( SCIPnetmatdecTryAddRow(dec, row, tempIdxArray, tempValArray, ncontnonz, &componentnetwork) ); + } + } + else + { + for( int i = startcol; i < startcol + ncols && componentnetwork; ++i ) + { + int col = comp->componentcols[i]; + int ncolnnonz = SCIPmatrixGetColNNonzs(matrix, col); + int* colrows = SCIPmatrixGetColIdxPtr(matrix, col); + SCIP_Real* colvals = SCIPmatrixGetColValPtr(matrix, col); + SCIP_CALL( SCIPnetmatdecTryAddCol(dec, col, colrows, colvals, ncolnnonz, &componentnetwork) ); + } + } + + if( !componentnetwork ) + { + SCIPnetmatdecRemoveComponent(dec, &comp->componentrows[startrow], nrows, &comp->componentcols[startcol], ncols); + } + + SCIP_Bool componenttransnetwork = TRUE; + + /* For the transposed matrix, the situation is exactly reversed because the row/column algorithms are swapped + * We only run transposed network detection if network detection failed + */ + if( !componentnetwork ) + { + if( nrows <= ncols * presoldata->columnrowratio ) + { + for( int i = startrow; i < startrow + nrows && componenttransnetwork; ++i ) + { + int row = comp->componentrows[i]; + int nrownnoz = SCIPmatrixGetRowNNonzs(matrix, row); + int* rowcols = SCIPmatrixGetRowIdxPtr(matrix, row); + SCIP_Real* rowvals = SCIPmatrixGetRowValPtr(matrix, row); + int ncontnonz = 0; + for( int j = 0; j < nrownnoz; ++j ) + { + int col = rowcols[j]; + if( comp->coltype[col] == SCIP_VARTYPE_CONTINUOUS ) + { + tempIdxArray[ncontnonz] = col; + tempValArray[ncontnonz] = rowvals[j]; + ++ncontnonz; + assert(SCIPisEQ(scip, ABS(rowvals[j]), 1.0)); + } + } + + SCIP_CALL( SCIPnetmatdecTryAddCol(transdec, row, tempIdxArray, tempValArray, ncontnonz, + &componenttransnetwork) ); + } + } + else + { + for( int i = startcol; i < startcol + ncols && componenttransnetwork; ++i ) + { + int col = comp->componentcols[i]; + int ncolnnonz = SCIPmatrixGetColNNonzs(matrix, col); + int* colrows = SCIPmatrixGetColIdxPtr(matrix, col); + SCIP_Real* colvals = SCIPmatrixGetColValPtr(matrix, col); + SCIP_CALL( SCIPnetmatdecTryAddRow(transdec, col, colrows, colvals, ncolnnonz, &componenttransnetwork) ); + } + } + } + + if( !componenttransnetwork ) + { + SCIPnetmatdecRemoveComponent(transdec, &comp->componentcols[startcol], ncols, + &comp->componentrows[startrow], nrows); + } + + if( !componentnetwork && !componenttransnetwork) + { + ++nnonnetwork; + continue; + } + ++ngoodcomponents; + + for( int i = startcol; i < startcol + ncols; ++i ) + { + int col = comp->componentcols[i]; + SCIP_VAR* var = SCIPmatrixGetVar(matrix, col); + SCIP_Bool infeasible = FALSE; + SCIP_CALL(SCIPchgVarType(scip, var, SCIP_VARTYPE_IMPLINT, &infeasible)); + (*nchgvartypes)++; + assert(!infeasible); + } + } + if( *nchgvartypes == 0 ) + { + SCIPverbMessage(scip, SCIP_VERBLEVEL_FULL, NULL, "No implied integers detected \n"); + } + else + { + SCIPverbMessage(scip, SCIP_VERBLEVEL_FULL, NULL, + "%d implied integers in %d / %d components (disqualified: %d by integrality, %d by numerics, %d not network) \n", + *nchgvartypes, ngoodcomponents, comp->ncomponents, nbadintegrality, nbadnumerics, nnonnetwork); + } + + SCIPfreeBufferArray(scip, &tempIdxArray); + SCIPfreeBufferArray(scip, &tempValArray); + + SCIPnetmatdecFree(&transdec); + SCIPnetmatdecFree(&dec); + + return SCIP_OKAY; +} + +/* + * Callback methods of presolver + */ + +/** destructor of presolver to free user data (called when SCIP is exiting) */ +static +SCIP_DECL_PRESOLFREE(presolFreeImplint) +{ + SCIP_PRESOLDATA* presoldata; + + /* free presolver data */ + presoldata = SCIPpresolGetData(presol); + assert(presoldata != NULL); + + SCIPfreeBlockMemory(scip, &presoldata); + SCIPpresolSetData(presol, NULL); + + return SCIP_OKAY; +} + +/** execution method of presolver */ +static +SCIP_DECL_PRESOLEXEC(presolExecImplint) +{ /*lint --e{715}*/ + *result = SCIP_DIDNOTRUN; + + /* TODO: re-check these conditions again */ + /* Disable implicit integer detection if we are probing or in NLP context */ + if( ( SCIPgetStage(scip) != SCIP_STAGE_PRESOLVING ) || SCIPinProbing(scip) || SCIPisNLPEnabled(scip) ) + { + return SCIP_OKAY; + } + /* Since implied integer detection relies on rows being static, we disable it for branch-and-price applications*/ + if( SCIPisStopped(scip) || SCIPgetNActivePricers(scip) > 0 ) + { + return SCIP_OKAY; + } + + *result = SCIP_DIDNOTFIND; + + /* Exit early if there are no candidates variables to upgrade */ + SCIP_PRESOLDATA* presoldata = SCIPpresolGetData(presol); + if( SCIPgetNContVars(scip) == 0 ) + { + return SCIP_OKAY; + } + + SCIP_Real starttime = SCIPgetSolvingTime(scip); + SCIPverbMessage(scip, SCIP_VERBLEVEL_HIGH, NULL, + " (%.1fs) implied integer detection started\n", starttime); + + SCIP_Bool initialized; + SCIP_Bool complete; + SCIP_Bool infeasible; + SCIP_MATRIX* matrix = NULL; + SCIP_Bool onlyifcomplete = TRUE; + SCIP_CALL( SCIPmatrixCreate(scip, &matrix, onlyifcomplete, &initialized, &complete, &infeasible, + naddconss, ndelconss, nchgcoefs, nchgbds, nfixedvars) ); + /*If infeasibility was detected during matrix creation, we return. */ + if( infeasible ) + { + if( initialized ) + { + SCIPmatrixFree(scip, &matrix); + } + *result = SCIP_CUTOFF; + return SCIP_OKAY; + } + + /*For now, we only work on pure MILP's TODO; use uplocks/downlocks */ + if( !( initialized && complete ) ) + { + if( initialized ) + { + SCIPmatrixFree(scip, &matrix); + } + SCIPverbMessage(scip, SCIP_VERBLEVEL_FULL, NULL, + " (%.1fs) implied integer detection stopped because problem is not an MILP\n", + SCIPgetSolvingTime(scip)); + return SCIP_OKAY; + } + + int beforechanged = *nchgvartypes; + MATRIX_COMPONENTS* comp = NULL; + MATRIX_STATISTICS* stats = NULL; + SCIP_CALL( createMatrixComponents(scip, matrix, &comp) ); + SCIP_CALL( computeMatrixStatistics(scip, matrix, comp, &stats, presoldata->numericslimit) ); + SCIP_CALL( computeContinuousComponents(scip, matrix, comp) ); + SCIP_CALL( findImpliedIntegers(scip, presoldata, matrix, comp, stats, nchgvartypes) ); + int afterchanged = *nchgvartypes; + + SCIP_Real endtime = SCIPgetSolvingTime(scip); + if( afterchanged == beforechanged ) + { + SCIPverbMessage(scip, SCIP_VERBLEVEL_HIGH, NULL, + " (%.1fs) no implied integers detected (time: %.2fs)\n", endtime, endtime - starttime); + *result = SCIP_DIDNOTFIND; + } + else + { + SCIPverbMessage(scip, SCIP_VERBLEVEL_HIGH, NULL, + " (%.1fs) %d implied integers detected (time: %.2fs)\n",endtime,*nchgvartypes,endtime-starttime); + + *result = SCIP_SUCCESS; + } + freeMatrixStatistics(scip, &stats); + freeMatrixComponents(scip, &comp); + SCIPmatrixFree(scip, &matrix); + + return SCIP_OKAY; +} + + +/* + * presolver specific interface methods + */ + +/** creates the implint presolver and includes it in SCIP */ +SCIP_RETCODE SCIPincludePresolImplint( + SCIP* scip /**< SCIP data structure */ + ) +{ + SCIP_PRESOLDATA* presoldata; + SCIP_PRESOL* presol; + + /* create implint presolver data */ + SCIP_CALL( SCIPallocBlockMemory(scip, &presoldata) ); + + /* include implint presolver */ + SCIP_CALL( SCIPincludePresolBasic(scip, &presol, PRESOL_NAME, PRESOL_DESC, PRESOL_PRIORITY, PRESOL_MAXROUNDS, + PRESOL_TIMING, presolExecImplint, presoldata) ); + + assert(presol != NULL); + + /* set non fundamental callbacks via setter functions */ + SCIP_CALL( SCIPsetPresolFree(scip, presol, presolFreeImplint) ); + + SCIP_CALL( SCIPaddRealParam(scip, + "presolving/implint/columnrowratio", + "use the network row addition algorithm when the column to row ratio becomes larger than this threshold", + &presoldata->columnrowratio, TRUE, DEFAULT_COLUMNROWRATIO, 0.0, 1e98, NULL, NULL) ); + + SCIP_CALL( SCIPaddRealParam(scip, + "presolving/implint/numericslimit", + "a row that contains variables with coefficients that are greater in absolute value than this limit is not considered for implied integrality detection", + &presoldata->numericslimit, TRUE, DEFAULT_NUMERICSLIMIT, 1.0, 1e98, NULL, NULL) ); + return SCIP_OKAY; +} diff --git a/src/scip/presol_implint.h b/src/scip/presol_implint.h new file mode 100644 index 0000000000..f0206752a3 --- /dev/null +++ b/src/scip/presol_implint.h @@ -0,0 +1,79 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the program and library */ +/* SCIP --- Solving Constraint Integer Programs */ +/* */ +/* Copyright (c) 2002-2024 Zuse Institute Berlin (ZIB) */ +/* */ +/* Licensed under the Apache License, Version 2.0 (the "License"); */ +/* you may not use this file except in compliance with the License. */ +/* You may obtain a copy of the License at */ +/* */ +/* http://www.apache.org/licenses/LICENSE-2.0 */ +/* */ +/* Unless required by applicable law or agreed to in writing, software */ +/* distributed under the License is distributed on an "AS IS" BASIS, */ +/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ +/* See the License for the specific language governing permissions and */ +/* limitations under the License. */ +/* */ +/* You should have received a copy of the Apache-2.0 license */ +/* along with SCIP; see the file LICENSE. If not visit scipopt.org. */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/**@file presol_implint.h + * @ingroup PRESOLVERS + * @brief Presolver that detects implicit integer variables + * @author Rolf van der Hulst + * + * This presolver looks for implicit integer variables, which are variables whose integrality is implied. + * The linear constraint handler handles the simple (primal) case such as 2x + 2y + z = 3, where z is implied integer by + * x and y. It also handles a more complicated dual case, where we have 'dual' implied integrality if z occurs only in + * inequalities of the primal form (where the equality becomes an inequality), and has integral bounds. + * + * In this plugin we explicitly look for the following structure in the constraint matrix: + * \f[ + * \begin{array}{llll} + * A x & + B y & & \leq c\\ + * D x & & + E z & \leq f\\ + * + * & & x & \in Z^{p_1} \\ + * & & y & \in Z^{p_2} \times R^{n_2-p_2}\\ + * & & z & \in Z^{p_3} \times R^{n_3-p_3} + * \end{array} + * \f] + * where A and c are integral and B is totally unimodular. It is not difficult to see that after fixing the x variables, + * that the remaining problem on the y variables is an integral polyhedron (and independent of the z variables). + * Hence, y is implied integer by x. + * + * Note that this presolver only treats integral rows, where SCIPisIntegral() is used to check integrality. + */ + +#ifndef __SCIP_PRESOL_IMPLINT_H__ +#define __SCIP_PRESOL_IMPLINT_H__ + +#include "scip/def.h" +#include "scip/type_retcode.h" +#include "scip/type_scip.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** creates the implicit integer presolver and includes it in SCIP + * + * @ingroup PresolverIncludes + */ +SCIP_EXPORT +SCIP_RETCODE SCIPincludePresolImplint( + SCIP* scip /**< SCIP data structure */ + ); + + +#ifdef __cplusplus +} +#endif + +#endif /* __SCIP_PRESOL_IMPLINT_H__ */ diff --git a/src/scip/pub_network.h b/src/scip/pub_network.h new file mode 100644 index 0000000000..091eb2b032 --- /dev/null +++ b/src/scip/pub_network.h @@ -0,0 +1,228 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the program and library */ +/* SCIP --- Solving Constraint Integer Programs */ +/* */ +/* Copyright (c) 2002-2024 Zuse Institute Berlin (ZIB) */ +/* */ +/* Licensed under the Apache License, Version 2.0 (the "License"); */ +/* you may not use this file except in compliance with the License. */ +/* You may obtain a copy of the License at */ +/* */ +/* http://www.apache.org/licenses/LICENSE-2.0 */ +/* */ +/* Unless required by applicable law or agreed to in writing, software */ +/* distributed under the License is distributed on an "AS IS" BASIS, */ +/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ +/* See the License for the specific language governing permissions and */ +/* limitations under the License. */ +/* */ +/* You should have received a copy of the Apache-2.0 license */ +/* along with SCIP; see the file LICENSE. If not visit scipopt.org. */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/**@file pub_network.h + * @ingroup PUBLICCOREAPI + * @brief Methods for detecting network matrices + * @author Rolf van der Hulst + * + * This file contains algorithms for incrementally growing (augmenting) network matrices, + * which are a large subclass of totally unimodular matrices. + * + * @addtogroup NetworkMatrix + * + * @{ + * + * A matrix \f$M \in \{-1,0,1\}^{m\times n} \f$ is a network matrix if there exists a directed graph \f$G = (V,A)\f$ + * with \f$|A| = m+n\f$ arcs and a spanning forest \f$T\f$ with \f$|T| = m\f$ such that \f$M\f$'s rows index \f$T\f$ and + * \f$M\f$'s columns index \f$A\setminus T\f$, + * and for each arc \f$ a = (u,v) \in A\setminus T\f$ and each arc \f$t\in T\f$ + * \f[ + * M_{t,a} = \begin{cases} + * +1 & \textrm{if the unique } u-v \textrm{ path in } T \textrm{ passes through } a \textrm{ forwardly}, \\ + * -1 & \textrm{if the unique } u-v \textrm{ path in } T \textrm{ passes through } a \textrm{ backwardly}, \\ + * 0 & \textrm{if the unique } u-v \textrm{ path in } T \textrm{ does not pass through } a + * \end{cases} + * \f] + * holds. + * + * The main difficulty with detecting network matrices is that there may exist many pairs of a graph and a spanning tree + * that realize a matrix. The algorithms in this file maintain and update an SPQR forest, which is a graph decomposition + * that represents all graphs that correspond to a given network matrix. + * + * Note that all addition algorithms expect that each nonzero is given exactly once and not more often; in particular, + * it is up to the user to ensure this when interleaving column and row addition steps. + * + * More details can be found in: + * - R.P. van der Hulst and M. Walter "A Row-wise Algorithm for Graph Realization" + * - R.E. Bixby and D.K. Wagner "An almost linear-time algorithm for graph realization" + * + * Note that although these publications contain the methods for undirected graphs (and binary matrices), + * their ideas are relatively easily extended to directed graphs and ternary matrices. + * Implementation details are described in further detail in network.c + */ + +/* TODO add method that realizes a SCIP digraph from the decomposition */ +/* TODO add method that *cleanly* removes complete components of the SPQR tree */ +/* TODO add node-arc incidence matrix methods */ +/*---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0----+----1----+----2*/ + +#ifndef __SCIP_NETWORK_H__ +#define __SCIP_NETWORK_H__ + +#include "scip/def.h" +#include "scip/type_retcode.h" +#include "scip/type_mem.h" +#include "scip/type_misc.h" +#include "blockmemshell/memory.h" + +#ifdef cplusplus +extern "C" { +#endif + +/** this class stores a decomposition of the network matrix using SPQR trees */ +typedef struct SCIP_Netmatdec SCIP_NETMATDEC; + +/** create an empty network matrix decomposition that can store a matrix with at most the given dimensions + * + * Initially, the network matrix decomposition stores an empty submatrix + */ +SCIP_EXPORT +SCIP_RETCODE SCIPnetmatdecCreate( + BMS_BLKMEM* blkmem, /**< Block memory */ + SCIP_NETMATDEC** pdec, /**< buffer to store pointer to created decomposition */ + int nrows, /**< The maximal number of rows that the decomposition can expect */ + int ncols /**< The maximal number of columns that the decomposition can expect */ +); + +/** frees a network matrix decomposition */ +SCIP_EXPORT +void SCIPnetmatdecFree( + SCIP_NETMATDEC** pdec /**< pointer to the network matrix decomposition to free */ +); + +/** tries to add a given column to the network matrix. Any nonzero row indices in the column that are not yet in the + * network matrix decomposition are added, too. + * + * @note Each column and row may be added only once. Trying to add a column that is already in the decomposition will + * result in an error. + * + * Note that the first call to this method for a given decomposition may be a bit slower, + * due to memory initialization. + */ +SCIP_EXPORT +SCIP_RETCODE SCIPnetmatdecTryAddCol( + SCIP_NETMATDEC* dec, /**< Network matrix decomposition */ + int column, /**< The column to add */ + int* nonzrows, /**< The column's nonzero row indices */ + double* nonzvals, /**< The column's nonzero entries */ + int nnonzs, /**< The number of nonzeros in the column */ + SCIP_Bool* success /**< Buffer to store whether the column was added */ +); + +/** tries to add a given row to the network matrix. Any nonzero column indices in the row that are not yet in the + * network matrix decomposition are added, too. + * + * @note Each column and row may be added only once. Trying to add a row that is already in the decomposition will + * result in an error. + * + * Note that the first call to this method for a given decomposition may be a bit slower, + * due to memory initialization. + * + * If the user is only interested in determining if a certain (sub)matrix is network or not, using + * SCIPnetmatdecTryAddCol() will generally be faster, unless the (sub)matrix has many more columns than rows. + * + */ +SCIP_EXPORT +SCIP_RETCODE SCIPnetmatdecTryAddRow( + SCIP_NETMATDEC* dec, /**< Network matrix decomposition */ + int row, /**< The row to add */ + int* nonzcols, /**< The row's nonzero column indices */ + double* nonzvals, /**< The row's nonzero entries */ + int nnonzs, /**< The number of nonzeros in the row */ + SCIP_Bool* success /**< Buffer to store whether the row was added */ +); + +/** checks if the network matrix decomposition contains the given row */ +SCIP_EXPORT +SCIP_Bool SCIPnetmatdecContainsRow( + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + int row /**< The row index that is checked */ +); + +/** checks if the network matrix decomposition contains the given column */ +SCIP_EXPORT +SCIP_Bool SCIPnetmatdecContainsColumn( + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + int column /**< The column index that is checked */ +); + +/** removes a connected component of the matrix from the network decomposition + * + * @note This method is 'stupid', and does not delete the associated graph data structure in the decomposition. + * Moreover, it does not explicitly check if the rows/columns that the user provides are a connected + * component of the submatrix given by the decomposition. If this is not the case, this will lead to buggy behavior. + * Use with care! + */ +SCIP_EXPORT +void SCIPnetmatdecRemoveComponent( + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + int* componentrows, /**< Array of rows to delete */ + int nrows, /**< The number of rows to delete */ + int* componentcols, /**< Array of columns to delete */ + int ncols /**< The number of columns to delete */ +); + +/** checks if the network matrix decomposition is minimal. + * + * A network matrix decomposition is minimal if it does not contain adjacent parallel or series skeletons. + * The network matrix decomposition we compute should always be minimal. + * This method should only be used in tests or asserts. + */ +SCIP_EXPORT +SCIP_Bool SCIPnetmatdecIsMinimal( + SCIP_NETMATDEC* dec /**< The network matrix decomposition */ +); + +/** checks if the cycle stored in the Decomposition matches the given array + * + * This method should only be used in tests + */ +SCIP_EXPORT +SCIP_Bool SCIPnetmatdecVerifyCycle( + BMS_BUFMEM * bufmem, /**< Buffer memory */ + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + int column, /**< The column to check */ + int* nonzrowidx, /**< Array with the column's nonzero row indices */ + double* nonzvals, /**< Array with the column's nonzero values */ + int nnonzs, /**< Number of nonzeros in the column */ + int* pathrowstorage, /**< A buffer to hold the computed path's rows. Should have size equal or + * greater than the number of rows in the decomposition. */ + SCIP_Bool* pathsignstorage /**< A buffer to store the computed path's row signs. Should have size + * equal or greater than the number of rows in the decomposition. */ +); + +/** Constructs a realization of an underlying directed graph belonging to the network matrix. + * + * The arc data of the digraph contains the row/column indices of the graph. If index < nrows, then the index is the + * corresponding row. If the index >= nrows, then index-nrows is the column index. + * Since many different realizations are possible, we use the default orientation of the two-separations to associate + * pairs of nodes to each other. In particular, we choose to connect the nodes of different 2-connected components + * in node 0. This way, the rank of the underlying matrix is equal to m+1-c, where c is the number of undirected + * connected components of the graph where the row/tree edges have been left out. + */ +SCIP_EXPORT +SCIP_RETCODE SCIPnetmatdecCreateDiGraph( + SCIP_NETMATDEC* dec, /**< The network matrix decomposition */ + BMS_BLKMEM * blkmem, /**< The block memory to use for the created digraph */ + SCIP_DIGRAPH** pdigraph, /**< Pointer to the pointer to store the created digraph */ + SCIP_Bool createrowarcs /**< Should the row arcs be added to the created digraph? */ +); +/**@} */ + +#ifdef cplusplus +} +#endif + +#endif /*__SCIP_NETWORK_H__ */ diff --git a/src/scip/scipdefplugins.c b/src/scip/scipdefplugins.c index 3b54c64355..05c5161894 100644 --- a/src/scip/scipdefplugins.c +++ b/src/scip/scipdefplugins.c @@ -109,6 +109,7 @@ SCIP_RETCODE SCIPincludeDefaultPlugins( SCIP_CALL( SCIPincludePresolDualinfer(scip) ); SCIP_CALL( SCIPincludePresolGateextraction(scip) ); SCIP_CALL( SCIPincludePresolImplics(scip) ); + SCIP_CALL( SCIPincludePresolImplint(scip) ); SCIP_CALL( SCIPincludePresolInttobinary(scip) ); #ifdef SCIP_WITH_PAPILO SCIP_CALL( SCIPincludePresolMILP(scip) ); diff --git a/src/scip/scipdefplugins.h b/src/scip/scipdefplugins.h index f22eb3e355..734ce44c72 100644 --- a/src/scip/scipdefplugins.h +++ b/src/scip/scipdefplugins.h @@ -182,6 +182,7 @@ #include "scip/presol_dualinfer.h" #include "scip/presol_gateextraction.h" #include "scip/presol_implics.h" +#include "scip/presol_implint.h" #include "scip/presol_inttobinary.h" #include "scip/presol_milp.h" #include "scip/presol_redvub.h" diff --git a/src/scip/type_hypergraph.h b/src/scip/type_hypergraph.h index c2fa370434..5dffbc035d 100644 --- a/src/scip/type_hypergraph.h +++ b/src/scip/type_hypergraph.h @@ -64,8 +64,7 @@ typedef int SCIP_HYPERGRAPH_EDGE; typedef int SCIP_HYPERGRAPH_OVERLAP; -/** - * Called by \ref SCIPhypergraphOverlapFind, \ref SCIPhypergraphOverlapIntersect and \ref SCIPhypergraphComputeOverlaps +/** Called by \ref SCIPhypergraphOverlapFind, \ref SCIPhypergraphOverlapIntersectEdges and \ref SCIPhypergraphComputeOverlaps * whenever a new overlap set is created or an existing overlap is found. */ #define SCIP_DECL_HYPERGRAPH_OVERLAP(x) SCIP_RETCODE x (SCIP_HYPERGRAPH* hypergraph, SCIP_HYPERGRAPH_OVERLAP overlap, \ diff --git a/tests/src/network/network.c b/tests/src/network/network.c new file mode 100644 index 0000000000..930ddbfd1c --- /dev/null +++ b/tests/src/network/network.c @@ -0,0 +1,1990 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the program and library */ +/* SCIP --- Solving Constraint Integer Programs */ +/* */ +/* Copyright (c) 2002-2024 Zuse Institute Berlin (ZIB) */ +/* */ +/* Licensed under the Apache License, Version 2.0 (the "License"); */ +/* you may not use this file except in compliance with the License. */ +/* You may obtain a copy of the License at */ +/* */ +/* http://www.apache.org/licenses/LICENSE-2.0 */ +/* */ +/* Unless required by applicable law or agreed to in writing, software */ +/* distributed under the License is distributed on an "AS IS" BASIS, */ +/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ +/* See the License for the specific language governing permissions and */ +/* limitations under the License. */ +/* */ +/* You should have received a copy of the Apache-2.0 license */ +/* along with SCIP; see the file LICENSE. If not visit scipopt.org. */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/**@file network.c + * @brief unittests for network matrix detection methods + * @author Rolf van der Hulst + */ + +/*--+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0----+----1----+----2*/ + +#include "scip/pub_network.h" + +#include "include/scip_test.h" + +/** + * Because the algorithm and data structures used for detecting network matrices are rather complex, + * we extensively test them in this file. We do this by checking if the cycles of the graphs represented by + * the decomposition match the nonzero entries of the columns of the matrix that we supplied, for many different graphs. + * Most of the specific graphs tested either contain some special case or posed challenges during development. + */ + +static SCIP* scip; + +static +void setup(void) +{ + /* create scip */ + SCIP_CALL( SCIPcreate(&scip) ); +} + +static +void teardown(void) +{ + /* free scip */ + SCIP_CALL( SCIPfree(&scip) ); +} + +/* CSR/CSC matrix type to encode testing matrices */ +typedef struct +{ + int nrows; + int ncols; + int nnonzs; + + bool isRowWise; /* True -> CSR matrix, False -> CSC matrix */ + + int* firstIndex; /* Array containing the index of the first nonzero of the row (column) + * for the CSR (CSC) matrix */ + int* entryIndex; /* Array containing the entries columns (rows) for the CSR (CSC) matrix */ + double* entryValue; /* Array containing the entry values */ +} DirectedTestCase; + +/**< Create a testcase from a string */ +static +DirectedTestCase stringToTestCase( + const char* string, /**< The string to convert to a test case */ + int rows, /**< The number of rows of the matrix to create */ + int cols /**< The number of columns of the matrix to create */ + ) +{ + DirectedTestCase testCase; + testCase.nrows = rows; + testCase.ncols = cols; + testCase.nnonzs = 0; + testCase.isRowWise = true; + + + testCase.firstIndex = malloc(sizeof(int) * ( rows + 1 )); + + int nonzeroArraySize = 8; + testCase.entryIndex = malloc(sizeof(int) * nonzeroArraySize); + testCase.entryValue = malloc(sizeof(double) * nonzeroArraySize); + + + const char* current = string; + int i = 0; + + while(i < rows * cols && *current != '\0' ) + { + char* next = NULL; + double num = strtod(current, &next); + if( i % cols == 0 ) + { + testCase.firstIndex[i / cols] = testCase.nnonzs; + } + if( num != 0.0 ) + { + if( testCase.nnonzs == nonzeroArraySize ) + { + int newSize = nonzeroArraySize * 2; + testCase.entryValue = realloc(testCase.entryValue, sizeof(double) * newSize); + testCase.entryIndex = realloc(testCase.entryIndex, sizeof(int) * newSize); + nonzeroArraySize = newSize; + } + testCase.entryValue[testCase.nnonzs] = num; + testCase.entryIndex[testCase.nnonzs] = i % cols; + ++testCase.nnonzs; + } + current = next; + ++i; + } + testCase.firstIndex[testCase.nrows] = testCase.nnonzs; + + return testCase; +} + +/**< Transposes a testcase */ +static +void transposeMatrixStorage( + DirectedTestCase* testCase /**< The testcase to transpose (in place) */ + ) +{ + int numPrimaryDimension = testCase->isRowWise ? testCase->nrows : testCase->ncols; + int numSecondaryDimension = testCase->isRowWise ? testCase->ncols : testCase->nrows; + + int* transposedFirstIndex = malloc(sizeof(int) * ( numSecondaryDimension + 1 )); + int* transposedEntryIndex = malloc(sizeof(int) * testCase->nnonzs); + double* transposedEntryValue = malloc(sizeof(double) * testCase->nnonzs); + + for( int i = 0; i <= numSecondaryDimension; ++i ) + { + transposedFirstIndex[i] = 0; + } + for( int i = 0; i < testCase->nnonzs; ++i ) + { + ++( transposedFirstIndex[testCase->entryIndex[i] + 1] ); + } + + for( int i = 1; i < numSecondaryDimension; ++i ) + { + transposedFirstIndex[i] += transposedFirstIndex[i - 1]; + } + + for( int i = 0; i < numPrimaryDimension; ++i ) + { + int first = testCase->firstIndex[i]; + int beyond = testCase->firstIndex[i + 1]; + for( int entry = first; entry < beyond; ++entry ) + { + int index = testCase->entryIndex[entry]; + int transIndex = transposedFirstIndex[index]; + transposedEntryIndex[transIndex] = i; + transposedEntryValue[transIndex] = testCase->entryValue[entry]; + ++( transposedFirstIndex[index] ); + } + } + for( int i = numSecondaryDimension; i > 0; --i ) + { + transposedFirstIndex[i] = transposedFirstIndex[i - 1]; + } + transposedFirstIndex[0] = 0; + + free(testCase->entryIndex); + free(testCase->entryValue); + free(testCase->firstIndex); + + testCase->firstIndex = transposedFirstIndex; + testCase->entryIndex = transposedEntryIndex; + testCase->entryValue = transposedEntryValue; + + testCase->isRowWise = !testCase->isRowWise; +} + +/**< Copies a test case */ +static +DirectedTestCase copyTestCase( + DirectedTestCase* testCase /**< The test case to copy */ + ) +{ + DirectedTestCase copy; + copy.nrows = testCase->nrows; + copy.ncols = testCase->ncols; + copy.nnonzs = testCase->nnonzs; + copy.isRowWise = testCase->isRowWise; + + int size = ( testCase->isRowWise ? testCase->nrows : testCase->ncols ) + 1; + copy.firstIndex = malloc(sizeof(int) * size); + for( int i = 0; i < size; ++i ) + { + copy.firstIndex[i] = testCase->firstIndex[i]; + } + copy.entryIndex = malloc(sizeof(int) * testCase->nnonzs); + copy.entryValue = malloc(sizeof(double) * testCase->nnonzs); + + for( int i = 0; i < testCase->nnonzs; ++i ) + { + copy.entryIndex[i] = testCase->entryIndex[i]; + copy.entryValue[i] = testCase->entryValue[i]; + } + return copy; +} + +/**< Frees a testcase */ +static +void freeTestCase( + DirectedTestCase* testCase /**< The test case to free */ + ) +{ + free(testCase->firstIndex); + free(testCase->entryIndex); + free(testCase->entryValue); +} + +/**< Runs and checks whether a testcase is executed correctly with the network column addition algorithm. + * This checks the cycles of the network matrix at every step. If isExpected(Not)Network is set, we check if the + * complete matrix is (not) network, and return an error otherwise. + */ +static +SCIP_RETCODE runColumnTestCase( + DirectedTestCase* testCase, /**< The testcase to check */ + bool expectedNetwork, /**< If the complete matrix is not detected to be a network matrix, return an error */ + bool expectedNotNetwork /**< If the complete matrix is detected to be a network matrix, return an error */ + ) +{ + if( testCase->isRowWise ) + { + transposeMatrixStorage(testCase); + } + cr_assert(!testCase->isRowWise); + SCIP_NETMATDEC* dec = NULL; + BMS_BLKMEM* blkmem = SCIPblkmem(scip); + BMS_BUFMEM* bufmem = SCIPbuffer(scip); + SCIP_CALL( SCIPnetmatdecCreate(blkmem, &dec, testCase->nrows, testCase->ncols) ); + + SCIP_Bool isNetwork = TRUE; + + int* tempColumnStorage; + SCIP_Bool* tempSignStorage; + + SCIP_CALL( SCIPallocBufferArray(scip, &tempColumnStorage, testCase->nrows) ); + SCIP_CALL( SCIPallocBufferArray(scip, &tempSignStorage, testCase->nrows) ); + + for( int i = 0; i < testCase->ncols; ++i ) + { + int colEntryStart = testCase->firstIndex[i]; + int colEntryEnd = testCase->firstIndex[i + 1]; + int* nonzeroRows = &testCase->entryIndex[colEntryStart]; + double* nonzeroValues = &testCase->entryValue[colEntryStart]; + int nonzeros = colEntryEnd - colEntryStart; + cr_assert(nonzeros >= 0); + /* Check if adding the column preserves the network matrix */ + SCIP_CALL( SCIPnetmatdecTryAddCol(dec, i, nonzeroRows, nonzeroValues, nonzeros, &isNetwork) ); + if( !isNetwork ) + { + break; + } + cr_expect(SCIPnetmatdecIsMinimal(dec)); + /* Check if the computed network matrix indeed reflects the network matrix, + * by checking if the fundamental cycles are all correct + */ + for( int j = 0; j <= i; ++j ) + { + int jColEntryStart = testCase->firstIndex[j]; + int jColEntryEnd = testCase->firstIndex[j + 1]; + int* jNonzeroRows = &testCase->entryIndex[jColEntryStart]; + double* jNonzeroValues = &testCase->entryValue[jColEntryStart]; + int jNonzeros = jColEntryEnd - jColEntryStart; + SCIP_Bool cycleIsCorrect = SCIPnetmatdecVerifyCycle(bufmem, dec, j, + jNonzeroRows, jNonzeroValues, + jNonzeros, tempColumnStorage, + tempSignStorage); + + cr_expect(cycleIsCorrect); + } + } + + + if( expectedNetwork ) + { + /* We expect that the given matrix is a network matrix. If not, something went wrong */ + cr_expect(isNetwork); + } + if( expectedNotNetwork ) + { + /* We expect that the given matrix is not a network matrix. If not, something went wrong */ + cr_expect(!isNetwork); + } + SCIPfreeBufferArray(scip, &tempColumnStorage); + SCIPfreeBufferArray(scip, &tempSignStorage); + + SCIPnetmatdecFree(&dec); + cr_assert(dec == NULL); + + return SCIP_OKAY; +} + +/**< Runs and checks whether a testcase is executed correctly with the network row addition algorithm. + * This checks the cycles of the network matrix at every step. If isExpected(Not)Network is set, we check if the + * complete matrix is (not) network, and return an error otherwise. + */ +static +SCIP_RETCODE runRowTestCase( + DirectedTestCase* testCase, /**< The testcase to check */ + bool expectedNetwork, /**< If the complete matrix is not detected to be a network matrix, return an error */ + bool expectedNotNetwork /**< If the complete matrix is detected to be a network matrix, return an error */ + ) +{ + if( !testCase->isRowWise ) + { + transposeMatrixStorage(testCase); + } + cr_assert(testCase->isRowWise); + + /* We keep a column-wise copy to check the columns easily */ + DirectedTestCase colWiseCase = copyTestCase(testCase); + transposeMatrixStorage(&colWiseCase); + + BMS_BLKMEM* blkmem = SCIPblkmem(scip); + BMS_BUFMEM* bufmem = SCIPbuffer(scip); + + SCIP_NETMATDEC* dec = NULL; + SCIP_CALL( SCIPnetmatdecCreate(blkmem, &dec, testCase->nrows, testCase->ncols) ); + + SCIP_Bool isNetwork = TRUE; + + int* tempColumnStorage; + SCIP_Bool* tempSignStorage; + + SCIP_CALL( SCIPallocBufferArray(scip, &tempColumnStorage, testCase->nrows) ); + SCIP_CALL( SCIPallocBufferArray(scip, &tempSignStorage, testCase->nrows) ); + + for( int i = 0; i < testCase->nrows; ++i ) + { + int rowEntryStart = testCase->firstIndex[i]; + int rowEntryEnd = testCase->firstIndex[i + 1]; + int* nonzeroCols = &testCase->entryIndex[rowEntryStart]; + double* nonzeroValues = &testCase->entryValue[rowEntryStart]; + int nonzeros = rowEntryEnd - rowEntryStart; + cr_assert(nonzeros >= 0); + /* Check if adding the row preserves the network matrix */ + SCIP_CALL( SCIPnetmatdecTryAddRow(dec, i, nonzeroCols, nonzeroValues, nonzeros, &isNetwork) ); + if( !isNetwork ) + { + break; + } + cr_expect(SCIPnetmatdecIsMinimal(dec)); + /* Check if the computed network matrix indeed reflects the network matrix, + * by checking if the fundamental cycles are all correct + */ + for( int j = 0; j < colWiseCase.ncols; ++j ) + { + int jColEntryStart = colWiseCase.firstIndex[j]; + int jColEntryEnd = colWiseCase.firstIndex[j + 1]; + + /* Count the number of rows in the column that should be in the current decomposition */ + int finalEntryIndex = jColEntryStart; + for( int testEntry = jColEntryStart; testEntry < jColEntryEnd; ++testEntry ) + { + if( colWiseCase.entryIndex[testEntry] <= i ) + { + ++finalEntryIndex; + } + else + { + break; + } + } + + int* jNonzeroRows = &colWiseCase.entryIndex[jColEntryStart]; + double* jNonzeroValues = &colWiseCase.entryValue[jColEntryStart]; + + int jNonzeros = finalEntryIndex - jColEntryStart; + SCIP_Bool cycleIsCorrect = SCIPnetmatdecVerifyCycle(bufmem, dec, j, + jNonzeroRows, jNonzeroValues, + jNonzeros, tempColumnStorage, + tempSignStorage); + + cr_expect(cycleIsCorrect); + } + } + + if( expectedNetwork ) + { + /* We expect that the given matrix is a network matrix. If not, something went wrong */ + cr_expect(isNetwork); + } + if( expectedNotNetwork ) + { + /* We expect that the given matrix is not a network matrix. If not, something went wrong */ + cr_expect(!isNetwork); + } + + freeTestCase(&colWiseCase); + + SCIPfreeBufferArray(scip, &tempColumnStorage); + SCIPfreeBufferArray(scip, &tempSignStorage); + + SCIPnetmatdecFree(&dec); + cr_assert(dec == NULL); + + return SCIP_OKAY; +} + +/**< Runs the network row addition, and attempts to construct the graph. + * This functions main purpose is to check if SCIPnetmatdecCreateDiGraph() correctly creates the graph without errors. + */ +static +SCIP_RETCODE runRowTestCaseGraph( + DirectedTestCase* testCase /**< The testcase to check */ + ) +{ + if( !testCase->isRowWise ) + { + transposeMatrixStorage(testCase); + } + cr_assert(testCase->isRowWise); + + /* We keep a column-wise copy to check the columns easily */ + DirectedTestCase colWiseCase = copyTestCase(testCase); + transposeMatrixStorage(&colWiseCase); + + BMS_BLKMEM* blkmem = SCIPblkmem(scip); + + SCIP_NETMATDEC* dec = NULL; + SCIP_CALL( SCIPnetmatdecCreate(blkmem, &dec, testCase->nrows, testCase->ncols) ); + + SCIP_Bool isNetwork = TRUE; + + for( int i = 0; i < testCase->nrows; ++i ) + { + int rowEntryStart = testCase->firstIndex[i]; + int rowEntryEnd = testCase->firstIndex[i + 1]; + int* nonzeroCols = &testCase->entryIndex[rowEntryStart]; + double* nonzeroValues = &testCase->entryValue[rowEntryStart]; + int nonzeros = rowEntryEnd - rowEntryStart; + cr_assert(nonzeros >= 0); + /* Check if adding the row preserves the network matrix */ + SCIP_CALL( SCIPnetmatdecTryAddRow(dec, i, nonzeroCols, nonzeroValues, nonzeros, &isNetwork) ); + cr_assert(isNetwork); + } + SCIP_DIGRAPH* graph; + SCIP_CALL( SCIPnetmatdecCreateDiGraph(dec, blkmem, &graph, TRUE) ); + + SCIPdigraphPrint(graph, SCIPgetMessagehdlr(scip), stdout); + SCIPdigraphFree(&graph); + freeTestCase(&colWiseCase); + + SCIPnetmatdecFree(&dec); + cr_assert(dec == NULL); + + return SCIP_OKAY; +} + +TestSuite(network, .init = setup, .fini = teardown); + +Test(network, coladd_single_column, .description = "Try adding a single column") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 " + "+1 " + "-1 ", + 3, 1); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_doublecolumn_invalid_sign, .description = "Try adding a second column that has invalid signing") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 " + "+1 0 " + "-1 +1 ", + 3, 2); + runColumnTestCase(&testCase, false, true); + freeTestCase(&testCase); +} + +Test(network, coladd_doublecolumn_invalid_sign_2, .description = "Try adding a second column that has invalid signing") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "+1 0 " + "-1 -1 ", + 3, 2); + runColumnTestCase(&testCase, false, true); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_1, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "+1 0 " + " 0 0 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_2, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "+1 0 " + " 0 0 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_1r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 " + "+1 0 " + " 0 0 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_2r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 " + "+1 0 " + " 0 0 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_3, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "+1 0 " + " 0 +1 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_4, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 " + "+1 0 " + " 0 +1 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_3r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 " + "+1 0 " + " 0 +1 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_4r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 " + "+1 0 " + " 0 +1 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_5, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 " + " 0 0 " + " 0 0 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_6, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + " 0 0 " + " 0 0 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_7, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 " + " 0 0 " + " 0 +1 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_8, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + " 0 0 " + " 0 +1 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_5r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 " + " 0 0 " + " 0 0 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_6r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 " + " 0 0 " + " 0 0 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_7r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 " + " 0 0 " + " 0 +1 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_8r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 " + " 0 0 " + " 0 +1 ", + 3, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_9, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "+1 0 " + "-1 +1 " + " 0 0 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_10, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 " + "+1 0 " + "-1 -1 " + " 0 0 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_11, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 " + "+1 0 " + "-1 -1 " + " 0 +1 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_12, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "+1 0 " + "-1 +1 " + " 0 +1 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_9r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 " + "-1 0 " + "+1 +1 " + " 0 0 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_10r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 " + "-1 0 " + "+1 -1 " + " 0 0 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_11r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 " + "-1 0 " + "+1 -1 " + " 0 +1 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_12r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 " + "-1 0 " + "+1 +1 " + " 0 +1 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_13, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 " + "+1 +1 " + "-1 -1 " + " 0 0 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_14, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "+1 -1 " + "-1 +1 " + " 0 0 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_15, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 " + "+1 +1 " + "-1 -1 " + " 0 +1 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_16, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "+1 -1 " + "-1 +1 " + " 0 +1 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_13r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 " + "-1 +1 " + "+1 -1 " + " 0 0 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_14r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 " + "-1 -1 " + "+1 +1 " + " 0 0 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_15r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 " + "-1 +1 " + "+1 -1 " + " 0 +1 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_splitseries_16r, .description = "Split a series component") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 " + "-1 -1 " + "+1 +1 " + " 0 +1 ", + 4, 2); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_parallelsimple_1, .description = "Extending a parallel component") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 1 -1 ", + 1, 4); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_parallelsimple_2, .description = "Extending a parallel component") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 1 -1 " + "0 0 -1 0 ", + 2, 4); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_parallelsimple_3, .description = "Extending a parallel component") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 1 1 " + "0 0 1 0 ", + 2, 4); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_components_1, .description = "Merging multiple components into one") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 -1 " + "1 0 1 ", + 2, 3); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_components_2, .description = "Merging multiple components into one") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 -1 " + "1 0 1 ", + 2, 3); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_components_3, .description = "Merging multiple components into one") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 " + "-1 0 1 ", + 2, 3); + runColumnTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_1, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 " + "1 -1 -1 " + "-1 1 -1 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_2, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 -1 " + "1 0 1 " + "0 0 0 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_3, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 1 " + "1 -1 -1 " + "0 0 1 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_4, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 1 " + "-1 1 0 " + "0 1 -1 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_5, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 " + "0 1 0 " + "-1 -1 -1 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_6, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 1 -1 " + "-1 1 -1 " + "-1 0 -1 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_7, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 -1 " + "0 1 1 " + "-1 0 0 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_8, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 -1 " + "0 -1 1 " + "1 0 0 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_9, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 0 0 " + "-1 -1 -1 " + "-1 -1 -1 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_10, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 1 -1 " + "-1 1 -1 " + "1 0 -1 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by3_11, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 1 0 " + "-1 0 1 " + "0 1 1 ", + 3, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_6by3_1, .description = "A six by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 1 " + "-1 1 -1 " + "-1 1 0 " + "0 1 1 " + "1 0 -1 " + "0 -1 -1 ", + 6, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_6by3_2, .description = "A six by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 1 " + "-1 1 0 " + "0 1 -1 " + "0 -1 0 " + "0 1 0 " + "0 0 1 ", + 6, 3); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by4_1, .description = "A three by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 1 1 " + "-1 -1 0 0 " + "1 0 1 -1 ", + 3, 4); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_3by5_1, .description = "A three by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 0 1 " + "0 -1 -1 -1 -1 " + "1 -1 0 -1 -1 ", + 3, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_4by8_1, .description = "A four by eight case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 0 1 1 0 1 0 1 " + "0 0 0 0 0 -1 1 -1 " + "1 -1 0 0 1 1 0 0 " + "0 0 1 1 -1 0 -1 0 ", + 4, 8); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_4by8_2, .description = "A four by eight case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 0 -1 0 0 -1 0 " + "1 1 0 0 1 1 -1 1 " + "0 -1 0 0 -1 -1 0 -1 " + "0 1 1 -1 1 0 -1 -1 ", + 4, 8); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_4by8_3, .description = "A four by eight case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 1 -1 0 -1 0 -1 0 " + "-1 0 -1 0 1 -1 1 1 " + "0 0 -1 1 -1 -1 0 0 " + "-1 1 0 -1 0 -1 1 -1 ", + 4, 8); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_4by8_4, .description = "A four by eight case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 0 1 1 0 1 0 0 " + "0 -1 0 -1 1 0 -1 -1 " + "-1 -1 1 0 1 1 -1 -1 " + "0 0 0 -1 0 -1 1 -1 ", + 4, 8); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_4by8_5, .description = "A four by eight case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 0 0 -1 -1 -1 0 -1 " + "-1 -1 0 0 1 1 -1 0 " + "0 0 1 0 -1 -1 0 -1 " + "0 0 1 -1 -1 0 0 -1 ", + 4, 8); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_4by8_6, .description = "A four by eight case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 0 0 1 1 1 0 -1 " + "0 -1 -1 0 0 1 0 -1 " + "0 0 1 -1 0 0 1 1 " + "0 -1 -1 1 1 1 -1 0 ", + 4, 8); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_4by8_7, .description = "A four by eight case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 0 1 1 1 0 1 0 " + "0 -1 1 1 1 1 -1 0 " + "1 0 0 0 1 0 1 1 " + "1 -1 1 0 -1 -1 -1 -1 ", + 4, 8); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_4by4_1, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 +1 -1 0 " + "-1 0 -1 0 " + "0 0 -1 +1 " + "-1 +1 0 -1", + 4, 4); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_1, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 0 0 " + "0 -1 -1 1 0 " + "0 0 -1 1 0 " + "-1 -1 0 0 -1 " + "-1 -1 -1 1 0 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_2, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 0 1 1 -1 " + "-1 1 1 1 0 " + "0 0 1 1 1 " + "-1 1 0 -1 0 " + "-1 1 0 0 -1 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_3, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 0 -1 " + "0 -1 0 1 -1 " + "1 1 1 0 1 " + "0 0 1 1 0 " + "-1 0 -1 0 1 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_4, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 -1 1 0 " + "0 0 -1 1 -1 " + "1 -1 0 0 1 " + "0 1 1 -1 0 " + "0 -1 0 1 0 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_5, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 0 1 1 " + "-1 1 1 0 0 " + "0 0 0 -1 -1 " + "1 -1 0 0 -1 " + "-1 1 0 1 1 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_6, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 1 0 0 " + "-1 0 -1 0 0 " + "0 -1 1 1 -1 " + "-1 1 -1 -1 0 " + "1 0 0 -1 0 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_7, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 0 1 0 0 " + "0 -1 0 1 0 " + "0 1 -1 -1 0 " + "-1 0 -1 -1 -1 " + "-1 -1 0 1 -1 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_8, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 1 -1 0 " + "1 0 0 0 -1 " + "-1 0 -1 1 1 " + "0 0 0 -1 -1 " + "1 1 0 -1 0 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, coladd_5by5_9, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 0 -1 1 " + "1 -1 -1 -1 1 " + "0 0 -1 0 0 " + "0 -1 -1 0 0 " + "1 0 0 0 1 ", + 5, 5); + runColumnTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_1by2_1, .description = "A one by two case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 0 ", + 1, 2); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_1by2_2, .description = "A one by two case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 ", + 1, 2); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_1by2_3, .description = "A one by two case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 ", + 1, 2); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_2by3_1, .description = "A two by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 +1 " + "-1 +1 -1 ", + 2, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_2by3_2, .description = "A two by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 +1 " + "-1 +1 +1 ", + 2, 3); + runRowTestCase(&testCase, false, true); + freeTestCase(&testCase); +} + +Test(network, rowadd_2by3_3, .description = "A two by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 +1 " + "+1 0 +1 ", + 2, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_2by3_4, .description = "A two by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 0 " + "+1 0 0 ", + 2, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_2by3_5, .description = "A two by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 0 " + "+0 +1 0 ", + 2, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_2by3_6, .description = "A two by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 0 " + "+0 -1 0 ", + 2, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_2by3_7, .description = "A two by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 0 " + "+0 +1 +1 ", + 2, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_2by3_8, .description = "A two by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 0 " + "+0 -1 +1 ", + 2, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by6_1, .description = "A three by six case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 0 0 0 0 " + "0 0 +1 -1 0 0 " + "-1 +1 -1 0 0 0 ", + 3, 6); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by6_2, .description = "A three by six case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 0 0 0 0 " + "0 0 +1 -1 0 0 " + "-1 +1 -1 0 0 +1 ", + 3, 6); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by2_1, .description = "A three by two case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 -1 " + "-1 +1 " + "+1 -1 ", + 3, 2); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by1_1, .description = "A three by one case") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 " + "-1 " + "+1 ", + 3, 1); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_1, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 1 " + "-1 1 0 " + "0 1 -1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_2, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 " + "0 1 0 " + "-1 -1 -1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_3, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 0 " + "-1 0 -1 " + "-1 -1 0 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_4, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 0 1 " + "0 -1 1 " + "-1 -1 -1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_5, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 -1 " + "-1 -1 0 " + "0 1 -1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_6, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 " + "1 1 0 " + "-1 1 -1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_7, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 0 " + "0 1 -1 " + "-1 1 -1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_8, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 1 " + "-1 1 0 " + "-1 0 -1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by3_9, .description = "A three by three case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 -1 " + "-1 -1 -1 " + "1 0 1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by6_3, .description = "A three by six case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 0 0 0 -1 " + "0 0 0 -1 -1 -1 " + "-1 1 0 0 1 1 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_3by6_4, .description = "A three by six case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 -1 -1 0 0 " + "0 0 0 1 -1 -1 " + "0 -1 1 1 -1 0 ", + 3, 3); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_1, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 0 0 " + "-1 -1 -1 -1 " + "0 -1 -1 -1 " + "1 0 0 -1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_2, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 0 -1 -1 " + "1 1 -1 -1 " + "0 0 -1 -1 " + "0 -1 0 1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_3, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 1 0 1 " + "-1 0 -1 0 " + "0 0 1 1 " + "0 -1 -1 1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_4, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 0 -1 0 " + "0 1 0 0 " + "-1 -1 1 1 " + "0 -1 -1 1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_5, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 0 -1 1 " + "1 0 0 -1 " + "-1 0 -1 0 " + "1 1 1 0 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_6, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 0 -1 1 " + "0 1 -1 0 " + "0 -1 1 -1 " + "-1 -1 0 1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_7, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 0 -1 1 " + "0 1 -1 0 " + "0 -1 1 -1 " + "-1 -1 0 1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_8, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 0 1 1 " + "1 -1 -1 0 " + "-1 1 1 1 " + "1 0 0 -1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_9, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 0 0 " + "-1 1 0 -1 " + "0 1 1 -1 " + "-1 0 1 0 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_10, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 1 -1 " + "-1 0 0 -1 " + "0 1 0 1 " + "0 -1 0 0 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_11, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 1 0 " + "-1 -1 0 1 " + "-1 0 0 1 " + "0 0 1 1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_12, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 0 -1 " + "0 1 0 1 " + "-1 0 1 0 " + "0 0 -1 -1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_13, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 -1 1 " + "-1 0 0 1 " + "1 1 1 -1 " + "1 0 -1 -1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_14, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 1 1 1 " + "1 1 0 1 " + "0 1 1 0 " + "0 1 1 1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_15, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 -1 0 " + "1 1 0 1 " + "1 0 -1 0 " + "0 1 0 1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_16, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 1 0 " + "-1 0 0 -1 " + "0 -1 0 1 " + "-1 -1 0 0 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_17, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 -1 -1 0 " + "-1 -1 0 0 " + "0 -1 0 1 " + "0 1 1 -1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_4by4_18, .description = "A four by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 0 1 " + "1 0 -1 0 " + "-1 0 1 1 " + "-1 1 0 -1 ", + 4, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_5by5_1, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 1 -1 1 " + "0 0 1 -1 0 " + "-1 0 0 1 0 " + "0 0 1 0 1 " + "0 -1 0 1 1 ", + 5, 5); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_5by5_2, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "1 -1 0 -1 -1 " + "0 -1 0 -1 -1 " + "0 0 1 -1 0 " + "0 -1 0 -1 -1 " + "-1 1 1 0 0 ", + 5, 5); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_5by5_3, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "-1 0 1 0 -1 " + "0 1 1 0 -1 " + "1 0 -1 0 1 " + "-1 0 1 0 0 " + "1 0 -1 0 1 ", + 5, 5); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_5by5_4, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 -1 1 0 0 " + "0 1 -1 1 0 " + "0 -1 1 0 0 " + "1 -1 1 0 0 " + "0 1 0 1 1 ", + 5, 5); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_5by5_5, .description = "A five by five case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 0 1 0 1 " + "1 0 0 1 -1 " + "1 -1 1 1 0 " + "0 0 -1 0 -1 " + "0 0 1 0 1 ", + 5, 5); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_8by4, .description = "A eight by four case") +{ + DirectedTestCase testCase = stringToTestCase( + "0 0 0 0 " + "1 0 1 0 " + "-1 1 -1 -1 " + "1 0 1 1 " + "1 -1 1 0 " + "1 -1 0 0 " + "1 1 -1 1 " + "0 0 1 0 ", + 8, 4); + runRowTestCase(&testCase, false, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_singlerigid_1, .description = "Updating a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 0 +1 " + "+1 +1 0 " + "0 -1 +1 " + "+1 +1 0 ", + 4, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_singlerigid_2, .description = "Updating a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 0 +1 " + "+1 +1 0 " + "0 -1 +1 " + "-1 -1 0 ", + 4, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_singlerigid_3, .description = "Updating a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 0 +1 " + "+1 +1 0 " + "0 -1 +1 " + "-1 +1 0 ", + 4, 3); + runRowTestCase(&testCase, false, true); + freeTestCase(&testCase); +} + +Test(network, rowadd_singlerigid_4, .description = "Updating a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 0 +1 " + "-1 -1 -1 " + "0 +1 +1 " + "+1 +1 +1 ", + 4, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_singlerigid_5, .description = "Updating a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 0 +1 " + "-1 -1 -1 " + "0 +1 +1 " + "-1 -1 -1 ", + 4, 3); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_singlerigid_6, .description = "Updating a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 0 +1 " + "-1 -1 -1 " + "0 +1 +1 " + "-1 +1 -1 ", + 4, 3); + runRowTestCase(&testCase, false, true); + freeTestCase(&testCase); +} + +Test(network, rowadd_singlerigid_7, .description = "Updating a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 0 0 +1 " + "+1 0 +1 0 0 " + "0 -1 +1 +1 -1 " + "0 0 0 -1 +1 " + "+1 +1 0 0 0 ", + 5, 5); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} + +Test(network, rowadd_singlerigid_8, .description = "Updating a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 +1 0 0 +1 " + "+1 0 +1 0 0 " + "0 -1 +1 +1 -1 " + "0 0 0 -1 +1 " + "+1 +1 0 0 0 " + "+1 0 +1 +1 0 ", + 6, 5); + runRowTestCase(&testCase, true, false); + freeTestCase(&testCase); +} +/* TODO: test interleaved addition, test using random sampling + test erdos-renyi generated graphs */ + +Test(network, rowadd_singlerigid_graph, .description = "Computing the graph for a single rigid member") +{ + DirectedTestCase testCase = stringToTestCase( + "+1 0 +1 " + "+1 +1 0 " + "0 -1 +1 " + "+1 +1 0 ", + 4, 3); + runRowTestCaseGraph(&testCase); + freeTestCase(&testCase); +}