├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.rst ├── etc └── cmake │ ├── release-amd64.conf │ └── release-i386.conf ├── include └── netctrl │ ├── model.h │ ├── model │ ├── controllability.h │ ├── liu.h │ └── switchboard.h │ ├── netctrl.h │ ├── util.h │ ├── util │ └── directed_matching.h │ └── version.h.in ├── scripts └── release.sh └── src ├── CMakeLists.txt ├── lib ├── CMakeLists.txt ├── model │ ├── controllability.cpp │ ├── liu.cpp │ └── switchboard.cpp └── util │ └── directed_matching.cpp └── ui ├── CMakeLists.txt ├── SimpleOpt.h ├── cmd_arguments.cpp ├── cmd_arguments.h ├── graph_util.cpp ├── graph_util.h ├── logging.h ├── main.cpp └── walker_sampling.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | release/ 3 | tmp/ 4 | .*.swp -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/igraphpp"] 2 | path = vendor/igraphpp 3 | url = https://github.com/ntamas/igraphpp.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | ##################################################################### 4 | # General project properties 5 | ##################################################################### 6 | 7 | project(netctrl C CXX) 8 | 9 | ##################################################################### 10 | # Build options 11 | ##################################################################### 12 | 13 | # Whether we prefer static to shared libraries 14 | option(PREFER_STATIC_LIBRARIES 15 | "Decides whether we prefer static libraries to shared ones when they both exist" 16 | OFF) 17 | 18 | ##################################################################### 19 | # Version information 20 | ##################################################################### 21 | 22 | set(NETCTRL_VERSION_MAJOR 0) 23 | set(NETCTRL_VERSION_MINOR 2) 24 | set(NETCTRL_VERSION_PATCH 0) 25 | 26 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/netctrl/version.h.in 27 | ${CMAKE_CURRENT_BINARY_DIR}/include/netctrl/version.h) 28 | 29 | ##################################################################### 30 | # Tweaking CMake 31 | ##################################################################### 32 | 33 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/etc/cmake;${CMAKE_MODULE_PATH}) 34 | if(PREFER_STATIC_LIBRARIES) 35 | set(CMAKE_FIND_LIBRARY_SUFFIXES .a;.lib;${CMAKE_FIND_LIBRARY_SUFFIXES}) 36 | set(CMAKE_C_FLAGS "-static-libgcc ${CMAKE_C_FLAGS}") 37 | set(CMAKE_CXX_FLAGS "-static-libgcc ${CMAKE_CXX_FLAGS}") 38 | endif(PREFER_STATIC_LIBRARIES) 39 | 40 | ##################################################################### 41 | # Find dependencies 42 | ##################################################################### 43 | 44 | find_package(igraph REQUIRED) 45 | 46 | ##################################################################### 47 | # Compiler flags for different build configurations 48 | ##################################################################### 49 | 50 | set(CMAKE_C_FLAGS "${CMAKE_ARCH_FLAGS} -Wall") 51 | set(CMAKE_CXX_FLAGS "${CMAKE_ARCH_FLAGS} -Wall") 52 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_ARCH_FLAGS} -O0 -g") 53 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_ARCH_FLAGS} -O0 -g") 54 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_ARCH_FLAGS} -O3 -DNDEBUG -Wall -s") 55 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_ARCH_FLAGS} -O3 -DNDEBUG -Wall -s") 56 | set(CMAKE_C_FLAGS_PROFILING "${CMAKE_ARCH_FLAGS} -pg") 57 | set(CMAKE_CXX_FLAGS_PROFILING "${CMAKE_ARCH_FLAGS} -pg") 58 | 59 | include_directories(${igraph_INCLUDE_DIRS} 60 | ${CMAKE_CURRENT_SOURCE_DIR}/include 61 | ${CMAKE_CURRENT_BINARY_DIR}/include 62 | ${CMAKE_CURRENT_SOURCE_DIR}/vendor/igraphpp/include) 63 | 64 | ##################################################################### 65 | # Set up CPack 66 | ##################################################################### 67 | 68 | set(CPACK_PACKAGE_VERSION_MAJOR ${NETCTRL_VERSION_MAJOR}) 69 | set(CPACK_PACKAGE_VERSION_MINOR ${NETCTRL_VERSION_MINOR}) 70 | set(CPACK_PACKAGE_VERSION_PATCH ${NETCTRL_VERSION_PATCH}) 71 | set(CPACK_GENERATOR "TGZ") 72 | set(CPACK_STRIP_FILES "bin/netctrl") 73 | include(CPack) 74 | 75 | install(FILES README.rst 76 | PERMISSIONS OWNER_READ OWNER_WRITE 77 | GROUP_READ WORLD_READ 78 | DESTINATION .) 79 | 80 | ##################################################################### 81 | # Process subdirectories 82 | ##################################################################### 83 | 84 | add_subdirectory(src) 85 | add_subdirectory(vendor/igraphpp/src) 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Tamás Nepusz, Tamás Vicsek 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | netctrl 3 | ======= 4 | --------------------------------------------------------------- 5 | Controllability of complex networks with node and edge dynamics 6 | --------------------------------------------------------------- 7 | 8 | :Author: Tamas Nepusz 9 | :Version: 0.3.0 10 | :License: MIT 11 | 12 | This program implements algorithms that search for driver nodes in complex 13 | networks in order to make them (structurally) controllable. The program 14 | currently implements the controllability model of Liu et al [1]_ and the 15 | switchboard dynamics model of Nepusz and Vicsek [2]_. Other models might be 16 | added later. 17 | 18 | Precompiled binaries 19 | ==================== 20 | 21 | Follow `this link `_ for precompiled 22 | packages for Linux systems running on 32-bit or 64-bit processors. 23 | 24 | If you are running a different system (e.g., Windows or Mac OS X), you have to 25 | compile ``netctrl`` yourself; please proceed to the `Compiling from source 26 | code`_ section. You must also compile ``netctrl`` yourself if you need the 27 | bleeding edge version as the packages at the above URL are not guaranteed to be 28 | updated regularly. However, they could safely be used to check out ``netctrl`` 29 | quickly without having to go through all the hassle with compiling ``netctrl`` 30 | from source. 31 | 32 | If you are using a precompiled binary, please proceed to the Usage_ section 33 | for usage instructions. 34 | 35 | Compiling from source code 36 | ========================== 37 | 38 | Requirements 39 | ------------ 40 | 41 | - igraph_ version 0.10.3 or later. 42 | 43 | - CMake_ to generate the makefiles (or the project file if you are using 44 | Visual Studio). 45 | 46 | .. _igraph: http://igraph.sourceforge.net 47 | .. _Launchpad repository: http://launchpad.net/igraph/ 48 | .. _CMake: http://www.cmake.org 49 | 50 | Compiling using ``cmake`` and ``make`` 51 | -------------------------------------- 52 | 53 | These instructions are for Linux or Mac OS X and assume that igraph_ is 54 | installed in a way that CMake can figure out automatically where it is. 55 | (This usually involves using ``pkg-config``; if you run ``pkg-config --cflags igraph`` 56 | and it works, then it should work with CMake as well):: 57 | 58 | $ git submodule update --init 59 | $ mkdir build 60 | $ cd build 61 | $ cmake .. 62 | $ make 63 | 64 | The first command is required only after you have checked out the source code 65 | from GitHub for the first time. The command fetches the source code of the 66 | C++ interface of igraph_ from GitHub and adds it to the source tree. 67 | 68 | Usage 69 | ===== 70 | 71 | The program may operate in one of the following five modes at the moment: 72 | 73 | 1. Finding driver nodes (``--mode driver_nodes``; this is the default). This mode 74 | lists the driver nodes of the network being analyzed, one node per line. 75 | Note that the algorithm finds a single feasible control configuration and 76 | lists the driver nodes of this configuration only; in other words, if you do 77 | not see a node in the list of driver nodes, it does not mean that the node 78 | may not become a driver node in an *alternative* control configuration. E.g., 79 | if the network contains a Hamiltonian cycle and you are working with the 80 | linear nodal dynamics of Liu et al [1]_, *any* node may become a driver node. 81 | 82 | 2. Finding control paths (``--mode control_paths``). This mode is similar to 83 | ``driver_nodes``, but provides a more detailed output where each control 84 | path is listed. Control paths are stems and buds in the Liu et al [1]_ 85 | model and open/closed walks in the switchboard model [2]_; see the respective 86 | publications for more details. 87 | 88 | 3. Printing general statistics (``--mode statistics``). This mode prints 89 | the number/fraction of driver nodes and the different edge types 90 | (redundant, ordinary or critical for the linear nodal dynamics; 91 | distinguished, ordinary or critical for the switchboard dynamics). 92 | The first row contains the absolute numbers, the second row contains 93 | the relative fractions. The order of numbers within a row are as follows: 94 | driver nodes, distinguished edges, redundant edges, ordinary edges and 95 | critical edges. The linear nodal dynamics contains no distinguished edges; 96 | the switchboard dynamics contians no redundant edges. 97 | 98 | 4. Testing the significance of the observed fraction of driver nodes by 99 | comparing it to null models (``--mode significance``). This mode generates 100 | 100 random instances of different null models for the given network and 101 | calculates the fraction of driver nodes for all the randomized instances. 102 | The average values are then listed for each null model and for the actual 103 | network. The following null models are tested: 104 | 105 | - Erdos-Renyi random networks (``ER``). 106 | 107 | - Configuration model preserving the joint degree distribution 108 | (``Configuration``). 109 | 110 | - Configuration model that preserves the in- and out-degree sequences but 111 | not the joint degree distribution (``Configuration_no_joint``). 112 | 113 | 5. Annotating the edges and nodes of the input graph with several attributes. 114 | For each node, ``netctrl`` will determine whether the node is a driver node 115 | or not. For each edge, ``netctrl`` will determine whether the edge is 116 | distinguished, redundant, ordinary or critical (see also ``--mode statistcs`` 117 | above), indicate which control path it is a part of (if any), and also 118 | determines the position of each edge in its control path. The results are 119 | printed in either GraphML or GML format, depending on the value of the 120 | ``-F`` (or ``--output-format``) argument. 121 | 122 | The mode can be selected with the ``--mode`` (or ``-M``) command line option. 123 | You should also select the controllability model with the ``--model`` (or ``-m``) 124 | option as follows: 125 | 126 | - ``switchboard`` selects the switchboard model of Nepusz and Vicsek [2]_ 127 | (this is the default). 128 | 129 | - ``liu`` selects the linear nodal dynamic model of Liu et al [1]_. 130 | 131 | Finally, you may specify an output file (``--output``, ``-o``), suppress most 132 | of the output of the program (``--quiet``, ``-q``) or ask for the command 133 | line help (``--help``, ``-h``). 134 | 135 | Input formats 136 | ============= 137 | 138 | ``netctrl`` supports the following input formats: 139 | 140 | - Simple edge list format (``.txt``) where each line contains two *numbers* 141 | corresponding to the source and target vertex IDs. Vertex IDs must be from 142 | 0 to *n*-1, where *n* is the total number of vertices. 143 | 144 | - Symbolic edge list format (``.ncol``, also known as the NCOL_ format). In 145 | this format, each line contains the name of the source and target vertex. 146 | Names may be arbitrary strings that do not contain whitespace. 147 | 148 | - LGL_ format (``.lgl``) 149 | 150 | - GraphML_ format (``.graphml``) 151 | 152 | - GML_ format (``.gml``) 153 | 154 | .. _LGL: http://lgl.sourceforge.net/#FileFormat 155 | .. _NCOL: http://lgl.sourceforge.net/#FileFormat 156 | .. _GraphML: http://graphml.graphdrawing.org 157 | .. _GML: http://www.fim.uni-passau.de/en/fim/faculty/chairs/theoretische-informatik/projects.html 158 | 159 | The input format of the graph will be detected from the extension of the file 160 | name by deafult; see above for the recognised extensions. For the GraphML and GML 161 | formats, vertex names must be provided in the ``name`` vertex attribute. If no 162 | such attribute is present, vertices will use numeric IDs from 0 to *n*-1, where 163 | *n* is the total number of vertices. 164 | 165 | If the format autodetection fails (i.e. ``netctrl`` detects the format incorrectly 166 | or it is not able to decide on the format at all), you can help ``netctrl`` out 167 | by specifying the input format manually using the ``-f`` or ``--input-format`` 168 | option. 169 | 170 | Output formats 171 | ============== 172 | 173 | The output format is relevant only if ``netctrl`` is running with ``--mode graph``. 174 | In this case, you can choose between the GraphML_ and GML_ output formats; the 175 | annotated graph will be printed in whichever format you choose and the 176 | node and edge metadata will be attached as attributes in the chosen format. 177 | Note that the other formats listed in the `Input formats`_ section do not support 178 | node and edge attributes, hence they are not suitable as output formats. 179 | 180 | Bugs, questions? 181 | ================ 182 | 183 | Have you found a bug in the code? Do you have questions? Let me know. 184 | I think you are smart enough to figure out my email address by googling 185 | for my name. Or just drop me a message on GitHub. 186 | 187 | Bibliography 188 | ============ 189 | 190 | .. [1] Liu YY, Slotine JJ and Barabási AL: Controllability of complex 191 | networks. *Nature* **473**:167-173, 2011. 192 | 193 | .. [2] Nepusz T and Vicsek T: Controlling edge dynamics in complex 194 | networks. *Nature Physics*, **8**:568-573, 2012. 195 | 196 | -------------------------------------------------------------------------------- /etc/cmake/release-amd64.conf: -------------------------------------------------------------------------------- 1 | # Toolchain file for CMake to set up a release mode compilation 2 | # on an amd64 system 3 | # 4 | # To use this, run the following command: 5 | # 6 | # mkdir build-arm 7 | # cd build-arm 8 | # cmake -DCMAKE_TOOLCHAIN_FILE=../arm-angstrom.cmake .. 9 | SET(CPACK_SYSTEM_NAME amd64) 10 | 11 | SET(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) 12 | SET(CMAKE_ARCH_FLAGS "-static-libgcc -static-libstdc++" CACHE STRING "" FORCE) 13 | SET(PREFER_STATIC_LIBRARIES YES CACHE BOOL "" FORCE) 14 | SET(IGRAPH_LIBRARY /home/tamas/lib/libigraph.a CACHE FILEPATH "" FORCE) 15 | -------------------------------------------------------------------------------- /etc/cmake/release-i386.conf: -------------------------------------------------------------------------------- 1 | # Toolchain file for CMake to set up a release mode compilation 2 | # on an amd64 system 3 | # 4 | # To use this, run the following command: 5 | # 6 | # mkdir build-arm 7 | # cd build-arm 8 | # cmake -DCMAKE_TOOLCHAIN_FILE=../arm-angstrom.cmake .. 9 | SET(CPACK_SYSTEM_NAME i386) 10 | 11 | SET(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) 12 | SET(CMAKE_ARCH_FLAGS "-m32 -static-libgcc -static-libstdc++" CACHE STRING "" FORCE) 13 | SET(PREFER_STATIC_LIBRARIES YES CACHE BOOL "" FORCE) 14 | SET(IGRAPH_LIBRARY /home/tamas/bzr/igraph/0.6-matching/build-i386/src/.libs/libigraph.a CACHE FILEPATH "" FORCE) 15 | set(LIBXML2_LIBRARIES /home/tamas/src/libxml2-2.7.8.dfsg/build-i386/.libs/libxml2.a CACHE FILEPATH "" FORCE) 16 | SET(ZLIB_LIBRARY /home/tamas/src/zlib-1.2.3.4.dfsg/libz.a CACHE FILEPATH "" FORCE) 17 | -------------------------------------------------------------------------------- /include/netctrl/model.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef NETCTRL_MODEL_H 4 | #define NETCTRL_MODEL_H 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #endif 11 | 12 | -------------------------------------------------------------------------------- /include/netctrl/model/controllability.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef NETCTRL_MODEL_CONTROLLABILITY_H 4 | #define NETCTRL_MODEL_CONTROLLABILITY_H 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace netctrl { 11 | 12 | class ControlPath; 13 | 14 | /// Edge classes in controllability models 15 | typedef enum { 16 | EDGE_ORDINARY, 17 | EDGE_REDUNDANT, 18 | EDGE_CRITICAL, 19 | EDGE_DISTINGUISHED 20 | } EdgeClass; 21 | 22 | /// Function to convert an edge class into its name 23 | std::string edgeClassToString(EdgeClass klass); 24 | 25 | /// Abstract superclass for controllability models 26 | class ControllabilityModel { 27 | protected: 28 | /// The graph on which the controllability model will operate 29 | igraph::Graph* m_pGraph; 30 | 31 | public: 32 | /// Constructs a controllability model that will operate on the given graph 33 | ControllabilityModel(igraph::Graph* pGraph = 0) : m_pGraph(pGraph) { 34 | } 35 | 36 | /// Virtual destructor that does nothing 37 | virtual ~ControllabilityModel() {} 38 | 39 | /// Creates an exact copy of this model 40 | virtual ControllabilityModel* clone() = 0; 41 | 42 | /// Calculates the set of driver nodes and control paths 43 | /** 44 | * This is an abstract method that must be overridden in subclasses. 45 | */ 46 | virtual void calculate() = 0; 47 | 48 | /** 49 | * \brief Returns a vector which shows how would the number of driver nodes 50 | * change after the removal of a given edge. 51 | * 52 | * This is an abstract method that \em may be overridden in subclasses. 53 | * 54 | * \return a vector containing a number for each edge of the graph, showing 55 | * the difference in the number of driver nodes before and after the 56 | * removal of the edge. Positive numbers indicate edges whose removal 57 | * increases the number of driver nodes. The function may also return 58 | * an empty vector indicating that such a calculation is not implemented 59 | * or not feasible. 60 | */ 61 | virtual igraph::VectorInt changesInDriverNodesAfterEdgeRemoval() const; 62 | 63 | /// Returns the controllability measure of the model after a successful calculation 64 | virtual float controllability() const = 0; 65 | 66 | /// Returns a vector of control paths after a successful calculation 67 | /** 68 | * Pointers returned in this vector are owned by the model; they should 69 | * \em not be destroyed by the caller. 70 | */ 71 | virtual std::vector controlPaths() const = 0; 72 | 73 | /// Returns the set of driver nodes after a successful calculation 74 | virtual igraph::VectorInt driverNodes() const = 0; 75 | 76 | /** 77 | * \brief Returns a vector that classifies edges into four classes: redundant, 78 | * ordinary, critical or distinguished. 79 | * 80 | * An edge is redundant if its removal does not change the set of driver nodes 81 | * in \em any control configuration; critical if its removal always requires 82 | * us to add at least one extra driver node in \em any control configuration, 83 | * and \em distinguished if its removal decreases the number of driver nodes. 84 | * Otherwise it is ordinary. 85 | * 86 | * Note that the Liu controllability model contains no distinguished edges, 87 | * and the switchboard model contains no ordinary edges. 88 | * 89 | * \returns a vector classifying the edges into classes, or an empty vector 90 | * if the operation is not implemented for a given model. 91 | */ 92 | virtual std::vector edgeClasses() const; 93 | 94 | /// Returns the graph on which the controllability model will operate 95 | virtual igraph::Graph* graph() const { 96 | return m_pGraph; 97 | } 98 | 99 | /// Sets the graph on which the controllability model will operate 100 | virtual void setGraph(igraph::Graph* graph) { 101 | m_pGraph = graph; 102 | } 103 | }; 104 | 105 | 106 | /// Abstract superclass for control paths (stems and buds) 107 | class ControlPath { 108 | protected: 109 | igraph::VectorInt m_nodes; 110 | 111 | /// Creates an empty control path 112 | ControlPath() : m_nodes() {} 113 | 114 | /// Creates a control path with the given nodes 115 | explicit ControlPath(const igraph::VectorInt& nodes) : m_nodes(nodes) {} 116 | 117 | public: 118 | /// Virtual destructor that does nothing 119 | virtual ~ControlPath() {} 120 | 121 | /// Appends a new node to the control path 122 | void appendNode(long int node) { 123 | m_nodes.push_back(node); 124 | } 125 | 126 | /// Returns the edges involved in the control path 127 | virtual igraph::VectorInt edges(const igraph::Graph& graph) const = 0; 128 | 129 | /// Returns a user-friendly name for the control path type 130 | virtual std::string name() const = 0; 131 | 132 | /// Returns whether the control path needs an independent input signal 133 | virtual bool needsInputSignal() const = 0; 134 | 135 | /// Returns the nodes involved in the control path 136 | igraph::VectorInt& nodes() { 137 | return m_nodes; 138 | } 139 | 140 | /// Returns the nodes involved in the control path (const variant) 141 | const igraph::VectorInt& nodes() const { 142 | return m_nodes; 143 | } 144 | 145 | /// Prepends a node to the control path 146 | void prependNode(long int node) { 147 | m_nodes.insert(0, node); 148 | } 149 | 150 | /// Returns the number of nodes involved 151 | size_t size() const { 152 | return m_nodes.size(); 153 | } 154 | 155 | /// Returns a string representation of the control path 156 | virtual std::string toString() const = 0; 157 | }; 158 | 159 | } // end of namespace 160 | 161 | #endif // NETCTRL_MODEL_CONTROLLABILITY_H 162 | 163 | -------------------------------------------------------------------------------- /include/netctrl/model/liu.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef NETCTRL_MODEL_LIU_H 4 | #define NETCTRL_MODEL_LIU_H 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace netctrl { 11 | 12 | /// Controllability model of Liu et al 13 | class LiuControllabilityModel : public ControllabilityModel { 14 | private: 15 | /// The list of driver nodes that was calculated 16 | igraph::VectorInt m_driverNodes; 17 | 18 | /// The matching that corresponds to the current driver node configuration 19 | DirectedMatching m_matching; 20 | 21 | /// The list of control paths that was calculated 22 | std::vector m_controlPaths; 23 | 24 | public: 25 | /// Constructs a model that will operate on the given graph 26 | LiuControllabilityModel(igraph::Graph* pGraph = 0) 27 | : ControllabilityModel(pGraph), m_driverNodes(), m_matching(), 28 | m_controlPaths() { 29 | } 30 | 31 | /// Destroys the model 32 | virtual ~LiuControllabilityModel(); 33 | 34 | virtual void calculate(); 35 | virtual ControllabilityModel* clone(); 36 | virtual float controllability() const; 37 | virtual std::vector controlPaths() const; 38 | virtual igraph::VectorInt driverNodes() const; 39 | virtual std::vector edgeClasses() const; 40 | 41 | DirectedMatching* matching(); 42 | const DirectedMatching* matching() const; 43 | 44 | virtual void setGraph(igraph::Graph* graph); 45 | 46 | protected: 47 | /// Removes all the control paths from the previous run (if any) 48 | void clearControlPaths(); 49 | 50 | /// Constructs the bipartite graph on which the matching will be searched. 51 | /** 52 | * \param directed whether the bipartite graph should be directed. 53 | * If this is true, matched edges in the current matching 54 | * will be oriented from top to bottom and the rest will 55 | * be oriented from bottom to top. 56 | */ 57 | igraph::Graph constructBipartiteGraph(bool directed=false) const; 58 | }; 59 | 60 | /// Control path that represents a stem 61 | class Stem : public ControlPath { 62 | public: 63 | /// Creates an empty stem 64 | Stem() : ControlPath() {} 65 | 66 | /// Creates a stem with the given nodes 67 | explicit Stem(const igraph::VectorInt& nodes) : ControlPath(nodes) {} 68 | 69 | /// Returns the edges involved in the stem 70 | virtual igraph::VectorInt edges(const igraph::Graph& graph) const; 71 | 72 | /// Returns a user-friendly name for the control path type 73 | virtual std::string name() const { 74 | return "stem"; 75 | } 76 | 77 | /// Returns \c true since each stem needs an independent input signal 78 | virtual bool needsInputSignal() const { 79 | return true; 80 | } 81 | 82 | /// Returns the root of the stem (i.e. the first vertex) 83 | long int root() const { 84 | return m_nodes.front(); 85 | } 86 | 87 | /// Returns the tip of the stem (i.e. the last vertex) 88 | long int tip() const { 89 | return m_nodes.back(); 90 | } 91 | 92 | /// Returns a string representation of the stem 93 | virtual std::string toString() const; 94 | }; 95 | 96 | /// Control path that represents a bud 97 | class Bud : public ControlPath { 98 | protected: 99 | /// The stem this bud is attached to. 100 | /** 101 | * When this is null, it means that the bud is attached to an input node 102 | * directly. 103 | */ 104 | const Stem* m_pStem; 105 | 106 | public: 107 | /// Creates an empty bud 108 | Bud() : ControlPath(), m_pStem(0) {} 109 | 110 | /// Creates a bud with the given nodes 111 | explicit Bud(const igraph::VectorInt& nodes, const Stem* pStem = 0) 112 | : ControlPath(nodes), m_pStem(pStem) {} 113 | 114 | /// Returns the edges involved in the bud 115 | virtual igraph::VectorInt edges(const igraph::Graph& graph) const; 116 | 117 | /// Returns a user-friendly name for the control path type 118 | virtual std::string name() const { 119 | return "bud"; 120 | } 121 | 122 | /// Returns \c false since buds do not need independent input signals 123 | virtual bool needsInputSignal() const { 124 | return false; 125 | } 126 | 127 | /// Attaches the bud to a stem 128 | void setStem(Stem* pStem) { 129 | m_pStem = pStem; 130 | } 131 | 132 | /// Returns the stem the bud is attached to 133 | const Stem* stem() const { 134 | return m_pStem; 135 | } 136 | 137 | /// Returns a string representation of the bud 138 | virtual std::string toString() const; 139 | }; 140 | 141 | } // end of namespace 142 | 143 | #endif // NETCTRL_MODEL_LIU_H 144 | 145 | -------------------------------------------------------------------------------- /include/netctrl/model/switchboard.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef NETCTRL_MODEL_SWITCHBOARD_H 4 | #define NETCTRL_MODEL_SWITCHBOARD_H 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace netctrl { 11 | 12 | class SwitchboardControlPath; 13 | 14 | /// Switchboard controllability model 15 | class SwitchboardControllabilityModel : public ControllabilityModel { 16 | public: 17 | /// The different types of controllability measures in this model 18 | typedef enum { 19 | NODE_MEASURE, 20 | EDGE_MEASURE 21 | } ControllabilityMeasure; 22 | 23 | private: 24 | /// The list of driver nodes that was calculated 25 | igraph::VectorInt m_driverNodes; 26 | 27 | /// The list of control paths that was calculated 28 | std::vector m_controlPaths; 29 | 30 | /// Whether we are using the node-based or the edge-based measure 31 | ControllabilityMeasure m_controllabilityMeasure; 32 | 33 | public: 34 | /// Constructs a model that will operate on the given graph 35 | SwitchboardControllabilityModel(igraph::Graph* pGraph = 0) 36 | : ControllabilityModel(pGraph), m_driverNodes(), m_controlPaths(), 37 | m_controllabilityMeasure(NODE_MEASURE) 38 | { 39 | } 40 | 41 | /// Destroys the model 42 | virtual ~SwitchboardControllabilityModel(); 43 | 44 | virtual void calculate(); 45 | igraph::VectorInt changesInDriverNodesAfterEdgeRemoval() const; 46 | virtual ControllabilityModel* clone(); 47 | virtual float controllability() const; 48 | virtual std::vector controlPaths() const; 49 | virtual igraph::VectorInt driverNodes() const; 50 | virtual std::vector edgeClasses() const; 51 | virtual void setGraph(igraph::Graph* graph); 52 | 53 | /// Returns the controllability measure used by the model 54 | ControllabilityMeasure controllabilityMeasure() const; 55 | 56 | /// Sets the controllability measure used by the model 57 | /** 58 | * When using the node-based measure (\c SBD_NODE_MEASURE), the 59 | * controllability measure is the number of driver nodes divided by the 60 | * number of nodes. When using the edge-based measure (\c SBD_EDGE_MEASURE), 61 | * the controllability measure is the number of open control paths plus the 62 | * number of balanced components, divided by the number of edges. 63 | */ 64 | void setControllabilityMeasure(ControllabilityMeasure measure); 65 | 66 | protected: 67 | /// Removes all the control paths from the previous run (if any) 68 | void clearControlPaths(); 69 | 70 | private: 71 | /** 72 | * \brief Starts a walk from the given node following arbitrary edges and 73 | * creates a control path out of it. 74 | * 75 | * \param start the node to start the walk from 76 | * \param edgeUsed a vector where we can mark edges that have been used 77 | * up for the current walk (or previous ones) 78 | * \param outDegrees the number of unused outbound edges for each node. 79 | * Must be consistent with \c edgeUsed and is updated 80 | * accordingly. 81 | * \param inDegrees the number of unused inbound edges for each node. 82 | * Must be consistent with \c edgeUsed and is updated 83 | * accordingly. 84 | * \return a newly allocated control path whose ownership is transferred to 85 | * the caller 86 | */ 87 | std::unique_ptr createControlPathFromNode(long int start, 88 | igraph::VectorBool& edgeUsed, igraph::VectorInt& outDegrees, 89 | igraph::VectorInt& inDegrees) const; 90 | 91 | /** 92 | * Checks whether the given vertex v is part of a non-trivial 93 | * balanced component. 94 | */ 95 | bool isInBalancedComponent(long int v, const igraph::VectorInt& degreeDiffs) const; 96 | 97 | /** 98 | * Checks whether the given vertex v will be part of a non-trivial 99 | * balanced component after removing its edge to vertex u. 100 | */ 101 | bool isInBalancedComponentExcept(long int v, long int u, 102 | const igraph::VectorInt& degreeDiffs) const; 103 | }; 104 | 105 | class ClosedWalk; 106 | 107 | /// Superclass for all the control paths that occur in the switchboard dynamics 108 | class SwitchboardControlPath : public ControlPath { 109 | public: 110 | /// Creates an empty control path 111 | SwitchboardControlPath() : ControlPath() {} 112 | 113 | /// Creates a control path with the given nodes 114 | explicit SwitchboardControlPath(const igraph::VectorInt& nodes) 115 | : ControlPath(nodes) {} 116 | 117 | /// Extends the control path with a closed walk. 118 | /** 119 | * \param walk the closed walk to extend this path with 120 | * \throws runtime_error if the control path and the closed walk share no 121 | * common nodes 122 | */ 123 | void extendWith(const ClosedWalk* walk); 124 | }; 125 | 126 | /// Control path that represents a directed open walk 127 | class OpenWalk : public SwitchboardControlPath { 128 | public: 129 | /// Creates an empty open walk 130 | OpenWalk() : SwitchboardControlPath() {} 131 | 132 | /// Creates an open walk with the given nodes 133 | explicit OpenWalk(const igraph::VectorInt& nodes) : SwitchboardControlPath(nodes) {} 134 | 135 | /// Returns the edges involved in the open walk 136 | virtual igraph::VectorInt edges(const igraph::Graph& graph) const; 137 | 138 | /// Returns a user-friendly name for the control path type 139 | virtual std::string name() const { 140 | return "open walk"; 141 | } 142 | 143 | /// Returns \c true since each open walk needs an input signal 144 | virtual bool needsInputSignal() const { 145 | return true; 146 | } 147 | 148 | /// Returns a string representation of the open walk 149 | virtual std::string toString() const; 150 | }; 151 | 152 | /// Control path that represents a closed walk 153 | class ClosedWalk : public SwitchboardControlPath { 154 | public: 155 | /// Creates a closed walk 156 | ClosedWalk() : SwitchboardControlPath() {} 157 | 158 | /// Creates a closed walk with the given nodes 159 | explicit ClosedWalk(const igraph::VectorInt& nodes) : SwitchboardControlPath(nodes) {} 160 | 161 | /// Returns \c false since closed walks do not require independent input signals 162 | virtual bool needsInputSignal() const { 163 | return false; 164 | } 165 | 166 | /// Returns the edges involved in the closed walk 167 | virtual igraph::VectorInt edges(const igraph::Graph& graph) const; 168 | 169 | /// Returns a user-friendly name for the control path type 170 | virtual std::string name() const { 171 | return "closed walk"; 172 | } 173 | 174 | /// Returns a string representation of the closed walk 175 | virtual std::string toString() const; 176 | }; 177 | 178 | } // end of namespace 179 | 180 | #endif // NETCTRL_MODEL_SWITCHBOARD_H 181 | 182 | -------------------------------------------------------------------------------- /include/netctrl/netctrl.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef NETCTRL_NETCTRL_H 4 | #define NETCTRL_NETCTRL_H 5 | 6 | #include 7 | #include 8 | 9 | #endif 10 | 11 | 12 | -------------------------------------------------------------------------------- /include/netctrl/util.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef NETCTRL_UTIL_H 4 | #define NETCTRL_UTIL_H 5 | 6 | #include 7 | 8 | #endif 9 | 10 | 11 | -------------------------------------------------------------------------------- /include/netctrl/util/directed_matching.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef NETCTRL_UTIL_DIRECTED_MATCHING_H 4 | #define NETCTRL_UTIL_DIRECTED_MATCHING_H 5 | 6 | #include 7 | 8 | namespace netctrl { 9 | 10 | /// Represents a matching in a directed graph as defined by Liu et al 11 | class DirectedMatching { 12 | private: 13 | /// Stores the mapping from matching nodes to matched nodes 14 | igraph::VectorInt m_outMapping; 15 | 16 | /// Stores the mapping from matched nodes to matching nodes 17 | igraph::VectorInt m_inMapping; 18 | 19 | public: 20 | /// Enum for the constructor that denotes the format of the incoming vector 21 | enum Direction { DIRECTION_OUT, DIRECTION_IN, DIRECTION_OUT_IN, 22 | DIRECTION_IN_OUT }; 23 | 24 | /// Constructs an empty matching 25 | DirectedMatching() : m_outMapping(), m_inMapping() {} 26 | 27 | /// Constructs a matching 28 | /** 29 | * \param vector the vector that describes the matching 30 | * \param direction denotes the format of the vector. 31 | * \c DIRECTION_OUT means that element i of the 32 | * vector contains the index of the node that 33 | * vertex i is *matched to*. \c DIRECTION_IN means 34 | * that element i of the vector contains the 35 | * node that *matches* i. \c DIRECTION_OUT_IN and 36 | * \c DIRECTION_IN_OUT mean that both sides of the 37 | * mapping are provided in out-in or in-out order, 38 | * concatenated. 39 | */ 40 | DirectedMatching(const igraph::VectorInt& mapping, Direction direction); 41 | 42 | /** 43 | * Returns whether the given node is matched by another node. 44 | */ 45 | bool isMatched(long int v) const { 46 | return matchIn(v) != -1; 47 | } 48 | 49 | /** 50 | * Returns whether the given node matches another node. 51 | */ 52 | bool isMatching(long int u) const { 53 | return matchOut(u) != -1; 54 | } 55 | 56 | /** 57 | * Returns the index of the node a given node is matched by. 58 | * 59 | * \param v the index of the node we are interested in. 60 | * \returns the index of the node that matches node v, or -1 if node 61 | * v is unmatched. 62 | */ 63 | long int matchIn(long int v) const { 64 | return m_inMapping[v]; 65 | } 66 | 67 | /** 68 | * Returns the index of the node a given node is matched to. 69 | * 70 | * \param u the index of the node we are interested in. 71 | * \returns the index of the node that node u is matched to, or -1 if node 72 | * u is unmatched. 73 | */ 74 | long int matchOut(long int u) const { 75 | return m_outMapping[u]; 76 | } 77 | 78 | /** 79 | * Establishes a matching between the two given nodes. 80 | * 81 | * This method also takes care of erasing any existing matching 82 | * related to the nodes. 83 | */ 84 | void setMatch(long int u, long int v) { 85 | if (v == -1 || u == -1) 86 | return; 87 | 88 | if (m_outMapping[u] == v) 89 | return; 90 | 91 | unmatch(u, m_outMapping[u]); 92 | unmatch(m_inMapping[v], v); 93 | m_outMapping[u] = v; 94 | m_inMapping[v] = u; 95 | } 96 | 97 | /** 98 | * Destroys the matching between the two given nodes. 99 | * 100 | * It is \em not checked whether the two nodes are really matched 101 | * to each other. 102 | * 103 | * \param u the matching node that matches v. 104 | * \param v the node that is matched by u. 105 | */ 106 | void unmatch(long int u, long int v) { 107 | if (v == -1 || u == -1) 108 | return; 109 | assert(m_inMapping[v] == u); 110 | assert(m_outMapping[u] == v); 111 | m_inMapping[v] = -1; 112 | m_outMapping[u] = -1; 113 | } 114 | }; 115 | 116 | } // end of namespace 117 | 118 | #endif 119 | 120 | 121 | -------------------------------------------------------------------------------- /include/netctrl/version.h.in: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef NETCTRL_VERSION_H 4 | #define NETCTRL_VERSION_H 5 | 6 | #define NETCTRL_VERSION_MAJOR @NETCTRL_VERSION_MAJOR@ 7 | #define NETCTRL_VERSION_MINOR @NETCTRL_VERSION_MINOR@ 8 | #define NETCTRL_VERSION_PATCH @NETCTRL_VERSION_PATCH@ 9 | #define NETCTRL_VERSION @NETCTRL_VERSION_MAJOR@*10000+@NETCTRL_VERSION_MINOR@*100+@NETCTRL_VERSION_PATCH@ 10 | #define NETCTRL_VERSION_STRING "@NETCTRL_VERSION_MAJOR@.@NETCTRL_VERSION_MINOR@.@NETCTRL_VERSION_PATCH@" 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PLATFORMS="amd64 i386" 4 | 5 | set -e 6 | 7 | cd `dirname $0`/.. 8 | 9 | DIR=release/`date +%Y-%m-%d` 10 | rm -rf $DIR 11 | 12 | for PLATFORM in $PLATFORMS; do 13 | # amd64 build 14 | mkdir -p $DIR/$PLATFORM 15 | cd $DIR/$PLATFORM 16 | cmake -DCMAKE_TOOLCHAIN_FILE=etc/cmake/release-${PLATFORM}.conf ../../.. 17 | make package 18 | mv netctrl*.tar.gz ../ 19 | cd ../../.. 20 | done 21 | 22 | echo "Build completed." 23 | echo "The release tarballs are to be found in $DIR." 24 | echo "" 25 | echo "Libraries that the executables link to:" 26 | 27 | for PLATFORM in $PLATFORMS; do 28 | fname=$DIR/$PLATFORM/src/ui/netctrl 29 | echo $fname 30 | ldd $fname 31 | done 32 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(lib) 2 | add_subdirectory(ui) 3 | -------------------------------------------------------------------------------- /src/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(netctrl0 STATIC model/controllability.cpp 2 | model/liu.cpp 3 | model/switchboard.cpp 4 | util/directed_matching.cpp 5 | ) 6 | target_include_directories( 7 | netctrl0 PRIVATE 8 | $ 9 | ) 10 | 11 | -------------------------------------------------------------------------------- /src/lib/model/controllability.cpp: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #include 4 | 5 | namespace netctrl { 6 | 7 | igraph::VectorInt ControllabilityModel::changesInDriverNodesAfterEdgeRemoval() const { 8 | return igraph::VectorInt(); 9 | } 10 | 11 | std::vector ControllabilityModel::edgeClasses() const { 12 | return std::vector(); 13 | } 14 | 15 | std::string edgeClassToString(EdgeClass klass) { 16 | switch (klass) { 17 | case EDGE_ORDINARY: 18 | return "ordinary"; 19 | case EDGE_REDUNDANT: 20 | return "redundant"; 21 | case EDGE_CRITICAL: 22 | return "critical"; 23 | case EDGE_DISTINGUISHED: 24 | return "distinguished"; 25 | } 26 | 27 | return ""; 28 | } 29 | 30 | } // end of namespace 31 | -------------------------------------------------------------------------------- /src/lib/model/liu.cpp: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace netctrl { 16 | 17 | using namespace igraph; 18 | 19 | LiuControllabilityModel::~LiuControllabilityModel() { 20 | clearControlPaths(); 21 | } 22 | 23 | void LiuControllabilityModel::calculate() { 24 | // Check if we have a graph 25 | if (m_pGraph == 0) 26 | throw std::runtime_error("m_pGraph must not be null"); 27 | 28 | // Construct the bipartite graph on which we are going to work 29 | Graph bipartiteGraph = this->constructBipartiteGraph(false); 30 | 31 | // Construct the type vector 32 | long int i = 0, n = m_pGraph->vcount(), u; 33 | VectorBool types(2 * n); 34 | for (i = 0; i < n; i++) { 35 | types[i] = 1; 36 | } 37 | // Calculate the maximum bipartite matching 38 | VectorInt matching; 39 | maximum_bipartite_matching(bipartiteGraph, types, 0, 0, &matching, 0, 0); 40 | for (i = 0; i < n; i++) { 41 | if (matching[i] != -1) 42 | matching[i] -= n; 43 | } 44 | m_matching = DirectedMatching(matching, DirectedMatching::DIRECTION_IN_OUT); 45 | 46 | // Create the list of driver nodes 47 | m_driverNodes.clear(); 48 | for (i = 0; i < n; i++) { 49 | if (!m_matching.isMatched(i)) 50 | m_driverNodes.push_back(i); 51 | } 52 | 53 | // Clear the list of control paths 54 | clearControlPaths(); 55 | 56 | // Construct stems from each driver node. At the same time, create a vector that 57 | // maps vertices to the stems they belong to and another one that marks vertices 58 | // that have already been assigned to stems or buds. 59 | std::vector verticesToStems(n); 60 | VectorBool vertexUsed(n); 61 | for (VectorInt::const_iterator it = m_driverNodes.begin(); it != m_driverNodes.end(); it++) { 62 | Stem* stem = new Stem(); 63 | 64 | u = *it; 65 | while (u != -1) { 66 | stem->appendNode(u); 67 | verticesToStems[u] = stem; 68 | vertexUsed[u] = true; 69 | u = m_matching.matchOut(u); 70 | } 71 | 72 | m_controlPaths.push_back(stem); 73 | } 74 | 75 | // The remaining matched edges form buds 76 | for (u = 0; u < n; u++) { 77 | if (vertexUsed[u] || !m_matching.isMatched(u)) 78 | continue; 79 | 80 | Bud* bud = new Bud(); 81 | while (!vertexUsed[u]) { 82 | bud->appendNode(u); 83 | vertexUsed[u] = true; 84 | u = m_matching.matchOut(u); 85 | } 86 | if (bud->size() > 1 && bud->nodes().front() == bud->nodes().back()) { 87 | bud->nodes().pop_back(); 88 | } 89 | 90 | // Check whether we can attach the bud to a stem 91 | for (VectorInt::const_iterator it = bud->nodes().begin(), end = bud->nodes().end(); 92 | it != end && bud->stem() == 0; it++) { 93 | VectorInt neis = m_pGraph->neighbors(*it, IGRAPH_IN); 94 | for (VectorInt::const_iterator it2 = neis.begin(); it2 != neis.end(); it2++) { 95 | if (verticesToStems[*it2] != 0) { 96 | bud->setStem(verticesToStems[*it2]); 97 | break; 98 | } 99 | } 100 | } 101 | 102 | m_controlPaths.push_back(bud); 103 | } 104 | 105 | // Cleanup: if there is no driver node, we must provide at least one 106 | if (m_driverNodes.empty()) { 107 | m_driverNodes.push_back(0); 108 | } 109 | } 110 | 111 | void LiuControllabilityModel::clearControlPaths() { 112 | for (std::vector::const_iterator it = m_controlPaths.begin(); 113 | it != m_controlPaths.end(); it++) { 114 | delete *it; 115 | } 116 | } 117 | 118 | ControllabilityModel* LiuControllabilityModel::clone() { 119 | ControllabilityModel* result = new LiuControllabilityModel(m_pGraph); 120 | return result; 121 | } 122 | 123 | Graph LiuControllabilityModel::constructBipartiteGraph(bool directed) const { 124 | long int i, n = m_pGraph->vcount(), m = m_pGraph->ecount() * 2, u, v; 125 | Graph bipartiteGraph(2*n, directed); 126 | 127 | VectorInt edges = m_pGraph->getEdgelist(); 128 | 129 | if (directed) { 130 | for (i = 0; i < m; i += 2) { 131 | u = edges[i]; v = edges[i+1]; 132 | if (m_matching.matchOut(u) == v) { 133 | edges[i] = v; 134 | edges[i+1] = u+n; 135 | } else { 136 | edges[i] = u+n; 137 | } 138 | } 139 | } else { 140 | for (i = 0; i < m; i += 2) { 141 | edges[i] += n; 142 | } 143 | } 144 | bipartiteGraph.addEdges(edges); 145 | 146 | if (!m_pGraph->isDirected()) { 147 | if (directed) { 148 | for (i = 0; i < m; i += 2) { 149 | if (edges[i] >= n) { 150 | edges[i] -= n; 151 | edges[i+1] += n; 152 | } else { 153 | edges[i] += n; 154 | edges[i+1] -= n; 155 | } 156 | } 157 | } else { 158 | for (i = 0; i < m; i += 2) { 159 | edges[i] -= n; 160 | edges[i+1] += n; 161 | } 162 | } 163 | bipartiteGraph.addEdges(edges); 164 | } 165 | 166 | return bipartiteGraph; 167 | } 168 | 169 | float LiuControllabilityModel::controllability() const { 170 | return m_driverNodes.size() / static_cast(m_pGraph->vcount()); 171 | } 172 | 173 | std::vector LiuControllabilityModel::controlPaths() const { 174 | return m_controlPaths; 175 | } 176 | 177 | igraph::VectorInt LiuControllabilityModel::driverNodes() const { 178 | return m_driverNodes; 179 | } 180 | 181 | std::vector LiuControllabilityModel::edgeClasses() const { 182 | integer_t from, to, i, n = m_pGraph->vcount(), m = m_pGraph->ecount(); 183 | std::vector result(m); 184 | std::deque queue; 185 | 186 | // The algorithm implemented here is adapted from Algorithm 2 of the 187 | // following publication: 188 | // 189 | // Regin JC: A filtering algorithm for constraints of difference in CSPs. 190 | // In: AAAI '94 Proceedings of the 12th national conference on Artificial 191 | // intelligence (vol. 1), pp. 362-367, 1994. 192 | 193 | // (1) Initially, all the edges are REDUNDANT 194 | std::fill(result.begin(), result.end(), EDGE_REDUNDANT); 195 | 196 | // (2) Construct the directed bipartite graph where matched edges are 197 | // directed from top to bottom and unmatched edges are directed 198 | // from bottom to top 199 | Graph bipartiteGraph = this->constructBipartiteGraph(true); 200 | 201 | // (3a) Start a backward BFS from unmatched nodes, mark all traversed edges 202 | // as ORDINARY 203 | VectorBool seen(2*n); 204 | for (from = 0; from < n; from++) { 205 | if (!m_matching.isMatched(from)) { 206 | queue.push_back(from); 207 | seen[from] = true; 208 | } 209 | if (!m_matching.isMatching(from)) { 210 | queue.push_back(from+n); 211 | seen[from+n] = true; 212 | } 213 | } 214 | while (!queue.empty()) { 215 | to = queue.front(); queue.pop_front(); 216 | 217 | igraph::VectorInt edges = bipartiteGraph.incident(to, IGRAPH_IN); 218 | for (igraph::VectorInt::const_iterator it = edges.begin(); it != edges.end(); ++it) { 219 | long int eid = *it; 220 | if (eid >= m) // needed for undirected graphs only 221 | eid -= m; 222 | result[eid] = EDGE_ORDINARY; 223 | bipartiteGraph.edge(*it, &from, &to); 224 | if (!seen[from]) { 225 | seen[from] = true; 226 | queue.push_back(from); 227 | } 228 | } 229 | } 230 | // (3b) Start a forward BFS 231 | seen.fill(false); 232 | for (from = 0; from < n; from++) { 233 | if (!m_matching.isMatched(from)) { 234 | queue.push_back(from); 235 | seen[from] = true; 236 | } 237 | if (!m_matching.isMatching(from)) { 238 | queue.push_back(from+n); 239 | seen[from+n] = true; 240 | } 241 | } 242 | while (!queue.empty()) { 243 | from = queue.front(); queue.pop_front(); 244 | 245 | igraph::VectorInt edges = bipartiteGraph.incident(from, IGRAPH_OUT); 246 | for (igraph::VectorInt::const_iterator it = edges.begin(); it != edges.end(); ++it) { 247 | long int eid = *it; 248 | if (eid >= m) // needed for undirected graphs only 249 | eid -= m; 250 | result[eid] = EDGE_ORDINARY; 251 | bipartiteGraph.edge(*it, &from, &to); 252 | if (!seen[to]) { 253 | seen[to] = true; 254 | queue.push_back(to); 255 | } 256 | } 257 | } 258 | 259 | // (4) Compute the strongly connected components of the bipartite 260 | // directed graph, mark all edges inside the same component 261 | // as ORDINARY 262 | VectorInt membership(2*n); 263 | connected_components(bipartiteGraph, &membership, 0, 0, IGRAPH_STRONG); 264 | for (i = 0; i < m; i++) { 265 | bipartiteGraph.edge(i, &from, &to); 266 | if (membership[from] == membership[to]) { 267 | result[i] = EDGE_ORDINARY; 268 | } 269 | } 270 | 271 | // (5) For all edges in the matching: if they are still REDUNDANT, 272 | // then they should become CRITICAL 273 | for (from = 0; from < n; from++) { 274 | to = m_matching.matchOut(from); 275 | if (to < 0) 276 | continue; 277 | 278 | i = m_pGraph->getEid(from, to); 279 | if (result[i] == EDGE_REDUNDANT) { 280 | result[i] = EDGE_CRITICAL; 281 | } 282 | } 283 | 284 | return result; 285 | } 286 | 287 | const DirectedMatching* LiuControllabilityModel::matching() const { 288 | return &m_matching; 289 | } 290 | 291 | DirectedMatching* LiuControllabilityModel::matching() { 292 | return &m_matching; 293 | } 294 | 295 | void LiuControllabilityModel::setGraph(igraph::Graph* graph) { 296 | ControllabilityModel::setGraph(graph); 297 | m_driverNodes.clear(); 298 | clearControlPaths(); 299 | } 300 | 301 | 302 | /*************************************************************************/ 303 | 304 | 305 | igraph::VectorInt Stem::edges(const igraph::Graph& graph) const { 306 | igraph::VectorInt result; 307 | igraph::VectorInt::const_iterator it = m_nodes.begin(), it2 = it+1; 308 | igraph::VectorInt::const_iterator end = m_nodes.end(); 309 | 310 | while (it2 != end) { 311 | result.push_back(graph.getEid(*it, *it2)); 312 | it++; it2++; 313 | } 314 | 315 | return result; 316 | } 317 | 318 | std::string Stem::toString() const { 319 | std::ostringstream oss; 320 | 321 | oss << "Stem:"; 322 | for (igraph::VectorInt::const_iterator it = m_nodes.begin(); it != m_nodes.end(); it++) { 323 | oss << ' ' << *it; 324 | } 325 | 326 | return oss.str(); 327 | } 328 | 329 | igraph::VectorInt Bud::edges(const igraph::Graph& graph) const { 330 | igraph::VectorInt result; 331 | 332 | if (m_nodes.size() == 0) 333 | return result; 334 | if (m_nodes.size() == 1) { 335 | long int eid = graph.getEid(m_nodes.front(), m_nodes.front()); 336 | if (eid >= 0) 337 | result.push_back(eid); 338 | return result; 339 | } 340 | 341 | igraph::VectorInt::const_iterator it = m_nodes.begin(), it2 = it+1; 342 | igraph::VectorInt::const_iterator end = m_nodes.end(); 343 | 344 | while (it2 != end) { 345 | result.push_back(graph.getEid(*it, *it2)); 346 | it++; it2++; 347 | } 348 | result.push_back(graph.getEid(*it, m_nodes.front())); 349 | 350 | return result; 351 | } 352 | 353 | std::string Bud::toString() const { 354 | std::ostringstream oss; 355 | 356 | oss << "Bud:"; 357 | for (igraph::VectorInt::const_iterator it = m_nodes.begin(); it != m_nodes.end(); it++) { 358 | oss << ' ' << *it; 359 | } 360 | if (stem() != 0) { 361 | oss << " (assigned to " << stem()->toString() << ")"; 362 | } 363 | 364 | return oss.str(); 365 | } 366 | 367 | } // end of namespace 368 | -------------------------------------------------------------------------------- /src/lib/model/switchboard.cpp: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | namespace netctrl { 18 | 19 | using namespace igraph; 20 | 21 | SwitchboardControllabilityModel::~SwitchboardControllabilityModel() { 22 | clearControlPaths(); 23 | } 24 | 25 | /** 26 | * \brief Finds another control path adjacent to the given control path, given 27 | * a mapping from nodes to control paths. 28 | * 29 | * \param path the path for which we need to find an adjacent control path 30 | * \param controlPathsByNodes a node-to-path mapping to use 31 | * \return another control path that shares at least one node with the given 32 | * control path, or \c NULL if there is no such path. 33 | */ 34 | SwitchboardControlPath* findControlPathAdjacentTo( 35 | SwitchboardControlPath* path, 36 | std::vector& controlPathsByNodes 37 | ) { 38 | const VectorInt& nodes = path->nodes(); 39 | VectorInt::const_iterator it; 40 | long int node; 41 | SwitchboardControlPath* otherPath; 42 | 43 | for (it = nodes.begin(); it != nodes.end(); it++) { 44 | node = static_cast(*it); 45 | otherPath = controlPathsByNodes[node]; 46 | if (otherPath != NULL && otherPath != path) { 47 | return otherPath; 48 | } 49 | } 50 | return NULL; 51 | } 52 | 53 | /** 54 | * \brief Assigns a set of nodes to a control path in a node-to-path mapping. 55 | * 56 | * This is a helper function for \c "SwitchboardControllabilityModel::calculate()". 57 | * 58 | * \param controlPathsByNodes a node-to-path mapping to update 59 | * \param path the path to be assigned to the nodes 60 | * \param pNodes pointer to a vector holding the nodes that are to be assigned to 61 | * the path. When null, it is assumed to be the same as the nodes 62 | * of the path. 63 | */ 64 | void updateControlPathsByNodesMapping( 65 | std::vector& controlPathsByNodes, 66 | SwitchboardControlPath* path, 67 | const VectorInt* pNodes = 0 68 | ) { 69 | const VectorInt& nodes = pNodes ? *pNodes : path->nodes(); 70 | long int node; 71 | 72 | for (VectorInt::const_iterator it = nodes.begin(); it != nodes.end(); it++) { 73 | node = static_cast(*it); 74 | controlPathsByNodes[node] = path; 75 | } 76 | } 77 | 78 | /** 79 | * \brief Tries to merge closed walks into other control paths that share 80 | * at least one node with the closed walk. 81 | * 82 | * \param closedWalksToMerge a queue containing the closed walks that we 83 | * attempt to merge 84 | * \param controlPathsByNodes a mapping from nodes to control paths that 85 | * contain the node 86 | */ 87 | void tryToMergeClosedWalks(std::deque& closedWalksToMerge, 88 | std::vector& controlPathsByNodes) { 89 | bool finished = false; 90 | ClosedWalk* closedWalk; 91 | SwitchboardControlPath* adjacentControlPath; 92 | 93 | // Put a sentinel in closedWalksToMerge so we know when we are about 94 | // to wrap around. 95 | closedWalksToMerge.push_back(NULL); 96 | 97 | while (!finished) { 98 | finished = true; 99 | 100 | // For each walk in closedWalksToMerge... 101 | while (true) { 102 | closedWalk = closedWalksToMerge.front(); 103 | closedWalksToMerge.pop_front(); 104 | 105 | if (closedWalk == NULL) { 106 | // Wrapped around so put the sentinel back and quit here. 107 | closedWalksToMerge.push_back(NULL); 108 | break; 109 | } 110 | 111 | // Test whether the closed walk could be joined with an adjacent 112 | // open or closed walk 113 | adjacentControlPath = findControlPathAdjacentTo(closedWalk, controlPathsByNodes); 114 | 115 | // If we have an adjacent walk, join the closed walk to it. 116 | // Otherwise put the closed walk back into the queue. 117 | if (adjacentControlPath != NULL) { 118 | adjacentControlPath->extendWith(closedWalk); 119 | updateControlPathsByNodesMapping(controlPathsByNodes, 120 | adjacentControlPath, &closedWalk->nodes()); 121 | finished = false; 122 | } else { 123 | closedWalksToMerge.push_back(closedWalk); 124 | } 125 | } 126 | } 127 | 128 | // Pop the sentinel. 129 | closedWalksToMerge.pop_back(); 130 | } 131 | 132 | void SwitchboardControllabilityModel::calculate() { 133 | VectorInt inDegrees, outDegrees; 134 | VectorInt::const_iterator it; 135 | long int i, j, n = m_pGraph->vcount(); 136 | long int balancedCount = 0; 137 | 138 | #define IS_BALANCED(i) ((outDegrees[i] == inDegrees[i]) && outDegrees[i] > 0) 139 | 140 | m_driverNodes.clear(); 141 | m_pGraph->degree(&inDegrees, V(m_pGraph), IGRAPH_IN, true); 142 | m_pGraph->degree(&outDegrees, V(m_pGraph), IGRAPH_OUT, true); 143 | 144 | // Find divergent nodes, count balanced nodes 145 | for (i = 0; i < n; i++) { 146 | if (outDegrees[i] > inDegrees[i]) 147 | m_driverNodes.push_back(i); 148 | else if (IS_BALANCED(i)) { 149 | balancedCount++; 150 | } 151 | } 152 | 153 | if (balancedCount > 0) { 154 | // Find the connected components consisting of balanced nodes 155 | // only. 156 | VectorInt membership; 157 | integer_t cluster_count; 158 | connected_components(*m_pGraph, &membership, 0, &cluster_count, IGRAPH_WEAK); 159 | 160 | VectorBool balancedCluster(cluster_count); 161 | balancedCluster.fill(true); 162 | for (i = 0; i < n; i++) { 163 | if (!IS_BALANCED(i)) { 164 | balancedCluster[(long int)membership[i]] = false; 165 | } 166 | } 167 | 168 | for (i = 0; i < n; i++) { 169 | j = membership[i]; 170 | if (balancedCluster[j]) { 171 | m_driverNodes.push_back(i); 172 | balancedCluster[j] = false; 173 | } 174 | } 175 | } 176 | 177 | #undef IS_BALANCED 178 | 179 | // Clear the list of control paths 180 | clearControlPaths(); 181 | 182 | // Declare some more variables that we will need. 183 | VectorBool edgeUsed(m_pGraph->ecount()); 184 | std::vector controlPathsByNodes(n); 185 | std::unique_ptr path; 186 | std::deque closedWalksToMerge; 187 | 188 | // Start stems from each divergent node until there are no more divergent 189 | // nodes. We are lucky here because m_driverNodes contains all the divergent 190 | // nodes by now -- the only catch is that the last few nodes in m_driverNodes 191 | // are balanced, but we simply skip those for the time being. 192 | for (it = m_driverNodes.begin(); it != m_driverNodes.end(); it++) { 193 | // While the node is divergent... 194 | while (outDegrees[*it] > inDegrees[*it]) { 195 | // Select an arbitrary outgoing edge and follow it until we get stuck. 196 | path = createControlPathFromNode(*it, edgeUsed, outDegrees, inDegrees); 197 | 198 | // For each node in the path, associate the path to the node in 199 | // controlPathsByNodes and then store the path. 200 | updateControlPathsByNodesMapping(controlPathsByNodes, path.get()); 201 | m_controlPaths.push_back(path.release()); 202 | } 203 | } 204 | 205 | // At this point, all the nodes are balanced (w.r.t. their remaining 206 | // degrees), so we can form closed walks from them without watching their 207 | // degrees. 208 | for (i = 0; i < n; i++) { 209 | // While the node still has any outbound edges left... 210 | while (outDegrees[i] > 0) { 211 | // Select an arbitrary outgoing edge and follow it until we get stuck 212 | // and construct a closed walk 213 | path = createControlPathFromNode(i, edgeUsed, outDegrees, inDegrees); 214 | 215 | // Store the closed walk in a deque that holds closed walks that could 216 | // be potentially merged with other open or closed walks 217 | closedWalksToMerge.push_back(static_cast(path.release())); 218 | } 219 | } 220 | 221 | // Try to merge closed walks into adjacent (open) walks 222 | tryToMergeClosedWalks(closedWalksToMerge, controlPathsByNodes); 223 | 224 | // Okay, if we are here, all the closed walks that could have been 225 | // merged into open walks are merged to open walks. All that's left are 226 | // closed walks that could be merged with each other. 227 | std::deque::const_iterator it2; 228 | for (it2 = closedWalksToMerge.begin(); it2 != closedWalksToMerge.end(); it2++) { 229 | updateControlPathsByNodesMapping(controlPathsByNodes, *it2); 230 | } 231 | 232 | // Try to merge closed walks into adjacent (open or closed) walks 233 | tryToMergeClosedWalks(closedWalksToMerge, controlPathsByNodes); 234 | 235 | // Any remaining closed walks must be stored into the result 236 | std::copy(closedWalksToMerge.begin(), closedWalksToMerge.end(), 237 | std::back_inserter(m_controlPaths)); 238 | } 239 | 240 | void SwitchboardControllabilityModel::clearControlPaths() { 241 | for (std::vector::const_iterator it = m_controlPaths.begin(); 242 | it != m_controlPaths.end(); it++) { 243 | delete *it; 244 | } 245 | } 246 | 247 | ControllabilityModel* SwitchboardControllabilityModel::clone() { 248 | SwitchboardControllabilityModel* result = 249 | new SwitchboardControllabilityModel(m_pGraph); 250 | result->setControllabilityMeasure(this->controllabilityMeasure()); 251 | return result; 252 | } 253 | 254 | float SwitchboardControllabilityModel::controllability() const { 255 | size_t numPaths; 256 | 257 | switch (m_controllabilityMeasure) { 258 | case NODE_MEASURE: 259 | return m_driverNodes.size() / static_cast(m_pGraph->vcount()); 260 | 261 | case EDGE_MEASURE: 262 | numPaths = 0; 263 | for (std::vector::const_iterator it = m_controlPaths.begin(); 264 | it != m_controlPaths.end(); ++it) { 265 | if ((*it)->needsInputSignal()) { 266 | numPaths++; 267 | } 268 | } 269 | // TODO: add balanced components 270 | return numPaths / static_cast(m_pGraph->ecount()); 271 | 272 | default: 273 | return 0; 274 | } 275 | } 276 | 277 | SwitchboardControllabilityModel::ControllabilityMeasure 278 | SwitchboardControllabilityModel::controllabilityMeasure() const { 279 | return m_controllabilityMeasure; 280 | } 281 | 282 | std::vector SwitchboardControllabilityModel::controlPaths() const { 283 | return m_controlPaths; 284 | } 285 | 286 | std::unique_ptr 287 | SwitchboardControllabilityModel::createControlPathFromNode(long int start, 288 | VectorBool& edgeUsed, VectorInt& outDegrees, VectorInt& inDegrees) const { 289 | long int v, w; 290 | VectorInt walk, incs; 291 | SwitchboardControlPath* path; 292 | 293 | v = start; 294 | while (v != -1) { 295 | // Find an outbound edge that has not been used yet 296 | w = -1; 297 | m_pGraph->incident(&incs, v, IGRAPH_OUT); 298 | for (VectorInt::const_iterator it = incs.begin(); it != incs.end(); it++) { 299 | if (!edgeUsed[*it]) { 300 | w = *it; 301 | break; 302 | } 303 | } 304 | 305 | // Did we get stuck? If so, break out of the loop. 306 | if (w == -1) { 307 | break; 308 | } 309 | 310 | // Add v to the walk 311 | walk.push_back(v); 312 | 313 | // Mark edge w as used and update v to the node edge w is pointing to. 314 | // Also update the degree vectors 315 | edgeUsed[w] = true; 316 | outDegrees[v]--; 317 | v = IGRAPH_OTHER(m_pGraph->c_graph(), w, v); 318 | inDegrees[v]--; 319 | } 320 | 321 | // If the graph is directed, we can traverse the path backwards (we are 322 | // essentially using each edge in both directions) 323 | if (!m_pGraph->isDirected()) { 324 | walk.push_back(v); 325 | v = walk.size(); 326 | for (w = v - 1; w > 0; ) { 327 | outDegrees[walk[w]]--; 328 | w--; 329 | if (w == 0) { 330 | break; 331 | } 332 | walk.push_back(walk[w]); 333 | inDegrees[walk[w]]--; 334 | } 335 | inDegrees[walk[0]]--; 336 | 337 | v = walk[0]; 338 | } 339 | 340 | // Add v to the walk unless it is equal to the starting point (in which case 341 | // we have a closed walk) 342 | if (v != start) { 343 | walk.push_back(v); 344 | path = new OpenWalk(walk); 345 | } else if (walk.size() == 0) { 346 | // There were no available outbound edges from the start node so we just 347 | // return NULL 348 | path = NULL; 349 | } else { 350 | // This is a closed walk. 351 | path = new ClosedWalk(walk); 352 | } 353 | 354 | return std::unique_ptr(path); 355 | } 356 | 357 | VectorInt SwitchboardControllabilityModel::driverNodes() const { 358 | return m_driverNodes; 359 | } 360 | 361 | VectorInt SwitchboardControllabilityModel::changesInDriverNodesAfterEdgeRemoval() const { 362 | VectorInt degreeDiffs, outDegrees; 363 | VectorInt result(m_pGraph->ecount()); 364 | EdgeSelector es = E(m_pGraph); 365 | EdgeIterator eit(es); 366 | 367 | m_pGraph->degree(&outDegrees, V(m_pGraph), IGRAPH_OUT, true); 368 | m_pGraph->degree(°reeDiffs, V(m_pGraph), IGRAPH_IN, true); 369 | degreeDiffs -= outDegrees; 370 | 371 | while (!eit.end()) { 372 | Edge edge = *eit; 373 | long int i = eit.get(); 374 | long int u = edge.source(), v = edge.destination(); 375 | 376 | if (degreeDiffs[u] == -1) { 377 | /* source vertex will become balanced instead of divergent */ 378 | result[i]--; 379 | } 380 | if (degreeDiffs[v] == 0) { 381 | /* target vertex will become divergent instead of balanced */ 382 | result[i]++; 383 | } 384 | 385 | /* Treating special cases */ 386 | if (degreeDiffs[u] == 0 && degreeDiffs[v] == 0) { 387 | /* u and v may potentially have been part of a balanced component. 388 | * In this case, the component already has a driver node before 389 | * the removal, so we will have to decrease result[i] by 1 */ 390 | if (isInBalancedComponent(u, degreeDiffs)) 391 | result[i]--; 392 | } 393 | if (degreeDiffs[v] == 1) { 394 | /* v is convergent but will become balanced. If all its neighbors 395 | * are balanced (except u), we may suspect that it becomes part 396 | * of a balanced component, which will require one more driver 397 | * node, so we will have to increase result[i] by 1 */ 398 | degreeDiffs[v]--; degreeDiffs[u]++; 399 | if (isInBalancedComponentExcept(v, u, degreeDiffs)) 400 | result[i]++; 401 | degreeDiffs[v]++; degreeDiffs[u]--; 402 | } 403 | if (degreeDiffs[u] == -1) { 404 | /* u is divergent but will become balanced. If all its neighbors 405 | * are balanced (except v), we may suspect that it becomes part 406 | * of a balanced component, which will require one more driver 407 | * node, so we will have to increase result[i] by 1 */ 408 | degreeDiffs[v]--; degreeDiffs[u]++; 409 | if (isInBalancedComponentExcept(u, v, degreeDiffs)) 410 | result[i]++; 411 | degreeDiffs[v]++; degreeDiffs[u]--; 412 | } 413 | 414 | ++eit; 415 | } 416 | 417 | return result; 418 | } 419 | 420 | std::vector SwitchboardControllabilityModel::edgeClasses() const { 421 | VectorInt diffs = changesInDriverNodesAfterEdgeRemoval(); 422 | size_t i, n = diffs.size(); 423 | std::vector result(n); 424 | for (i = 0; i < n; i++) { 425 | if (diffs[i] < 0) 426 | result[i] = EDGE_DISTINGUISHED; 427 | else if (diffs[i] == 0) 428 | result[i] = EDGE_REDUNDANT; 429 | else 430 | result[i] = EDGE_CRITICAL; 431 | } 432 | return result; 433 | } 434 | 435 | bool SwitchboardControllabilityModel::isInBalancedComponent( 436 | long int v, const VectorInt& degreeDiffs) const { 437 | return isInBalancedComponentExcept(v, -1, degreeDiffs); 438 | } 439 | 440 | bool SwitchboardControllabilityModel::isInBalancedComponentExcept( 441 | long int v, long int u, const VectorInt& degreeDiffs) const { 442 | VectorInt neis; 443 | int i, j; 444 | bool result = true; 445 | 446 | /* Is v balanced? If not, we can return early */ 447 | if (degreeDiffs[v] != 0) 448 | return false; 449 | 450 | /* Does v have any neighbors apart from u? If not, v is in a 451 | * _trivial_ balanced component, so we return false */ 452 | neis = m_pGraph->neighbors(v, IGRAPH_ALL); 453 | if (neis.empty() || (neis.size() == 1 && neis[0] == u)) 454 | return false; 455 | 456 | /* Prepare the queue */ 457 | VectorBool visited(m_pGraph->vcount()); 458 | std::deque q; 459 | q.push_back(v); visited[v] = true; 460 | if (u >= 0) 461 | visited[u] = true; 462 | 463 | while (!q.empty()) { 464 | v = q.front(); q.pop_front(); 465 | neis = m_pGraph->neighbors(v, IGRAPH_ALL); 466 | j = neis.size(); 467 | for (i = 0; i < j; i++) { 468 | u = neis[i]; 469 | if (visited[u]) 470 | continue; 471 | if (degreeDiffs[u] != 0) { 472 | result = false; 473 | q.clear(); 474 | break; 475 | } 476 | q.push_back(u); 477 | visited[u] = true; 478 | } 479 | } 480 | 481 | return result; 482 | } 483 | 484 | void SwitchboardControllabilityModel::setControllabilityMeasure( 485 | SwitchboardControllabilityModel::ControllabilityMeasure measure) { 486 | m_controllabilityMeasure = measure; 487 | } 488 | 489 | void SwitchboardControllabilityModel::setGraph(Graph* graph) { 490 | ControllabilityModel::setGraph(graph); 491 | m_driverNodes.clear(); 492 | clearControlPaths(); 493 | } 494 | 495 | 496 | /*************************************************************************/ 497 | 498 | 499 | void SwitchboardControlPath::extendWith(const ClosedWalk* walk) { 500 | const VectorInt& closedWalkNodes = walk->nodes(); 501 | std::set closedWalkNodeSet(closedWalkNodes.begin(), closedWalkNodes.end()); 502 | std::set::const_iterator notFound = closedWalkNodeSet.end(); 503 | VectorInt::const_iterator it, end = m_nodes.end(); 504 | long int pos, i, j, n; 505 | integer_t closedWalkPos; 506 | 507 | for (it = m_nodes.begin(), pos=0; it != end; it++, pos++) { 508 | if (closedWalkNodeSet.find(*it) == notFound) 509 | continue; 510 | 511 | assert(closedWalkNodes.search(0, *it, &closedWalkPos)); 512 | 513 | m_nodes.resize(m_nodes.size() + closedWalkNodes.size()); 514 | 515 | n = pos + closedWalkNodes.size(); 516 | for (i = m_nodes.size()-closedWalkNodes.size()-1, j = m_nodes.size()-1; 517 | i >= pos; i--, j--) { 518 | m_nodes[j] = m_nodes[i]; 519 | } 520 | for (i = pos, j = closedWalkPos; i < n; i++, j++) { 521 | if (j == closedWalkNodes.size()) { 522 | j = 0; 523 | } 524 | m_nodes[i] = closedWalkNodes[j]; 525 | } 526 | break; 527 | } 528 | } 529 | 530 | 531 | VectorInt OpenWalk::edges(const Graph& graph) const { 532 | VectorInt result; 533 | VectorInt::const_iterator it = m_nodes.begin(), it2 = it+1; 534 | VectorInt::const_iterator end = m_nodes.end(); 535 | 536 | while (it2 != end) { 537 | result.push_back(graph.getEid(*it, *it2)); 538 | it++; it2++; 539 | } 540 | 541 | return result; 542 | } 543 | 544 | std::string OpenWalk::toString() const { 545 | std::ostringstream oss; 546 | 547 | oss << "Open walk:"; 548 | for (VectorInt::const_iterator it = m_nodes.begin(); it != m_nodes.end(); it++) { 549 | oss << ' ' << *it; 550 | } 551 | 552 | return oss.str(); 553 | } 554 | 555 | 556 | VectorInt ClosedWalk::edges(const Graph& graph) const { 557 | VectorInt result; 558 | 559 | if (m_nodes.size() == 0) 560 | return result; 561 | if (m_nodes.size() == 1) { 562 | long int eid = graph.getEid(m_nodes.front(), m_nodes.front()); 563 | if (eid >= 0) 564 | result.push_back(eid); 565 | return result; 566 | } 567 | 568 | VectorInt::const_iterator it = m_nodes.begin(), it2 = it+1; 569 | VectorInt::const_iterator end = m_nodes.end(); 570 | 571 | while (it2 != end) { 572 | result.push_back(graph.getEid(*it, *it2)); 573 | it++; it2++; 574 | } 575 | result.push_back(graph.getEid(*it, m_nodes.front())); 576 | 577 | return result; 578 | } 579 | 580 | std::string ClosedWalk::toString() const { 581 | std::ostringstream oss; 582 | 583 | oss << "Closed walk:"; 584 | for (VectorInt::const_iterator it = m_nodes.begin(); it != m_nodes.end(); it++) { 585 | oss << ' ' << *it; 586 | } 587 | 588 | return oss.str(); 589 | } 590 | 591 | } // end of namespaces 592 | 593 | -------------------------------------------------------------------------------- /src/lib/util/directed_matching.cpp: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace netctrl { 9 | 10 | 11 | DirectedMatching::DirectedMatching(const igraph::VectorInt& mapping, 12 | Direction direction) { 13 | long int i, n; 14 | igraph::VectorInt::const_iterator it, end; 15 | 16 | switch (direction) { 17 | case DIRECTION_OUT_IN: 18 | assert(mapping.size() % 2 == 0); 19 | n = mapping.size() / 2; 20 | m_inMapping.resize(n); 21 | m_outMapping.resize(n); 22 | std::copy(mapping.begin(), mapping.begin() + n, m_outMapping.begin()); 23 | std::copy(mapping.begin()+n, mapping.end(), m_inMapping.begin()); 24 | break; 25 | 26 | case DIRECTION_IN_OUT: 27 | assert(mapping.size() % 2 == 0); 28 | n = mapping.size() / 2; 29 | m_inMapping.resize(n); 30 | m_outMapping.resize(n); 31 | std::copy(mapping.begin(), mapping.begin() + n, m_inMapping.begin()); 32 | std::copy(mapping.begin()+n, mapping.end(), m_outMapping.begin()); 33 | break; 34 | 35 | case DIRECTION_IN: 36 | n = mapping.size(); 37 | m_inMapping = mapping; 38 | m_outMapping.resize(n); 39 | std::fill(m_outMapping.begin(), m_outMapping.end(), -1); 40 | 41 | end = m_inMapping.end(); 42 | for (i = 0, it = m_inMapping.begin(); it != end; i++, it++) { 43 | if (*it == -1) 44 | continue; 45 | m_outMapping[*it] = i; 46 | } 47 | break; 48 | 49 | case DIRECTION_OUT: 50 | n = mapping.size(); 51 | m_outMapping = mapping; 52 | m_inMapping.resize(n); 53 | std::fill(m_inMapping.begin(), m_outMapping.end(), -1); 54 | 55 | end = m_outMapping.end(); 56 | for (i = 0, it = m_outMapping.begin(); it != end; i++, it++) { 57 | if (*it == -1) 58 | continue; 59 | m_inMapping[*it] = i; 60 | } 61 | break; 62 | 63 | default: 64 | throw std::runtime_error("invalid direction argument"); 65 | } 66 | } 67 | 68 | 69 | } // end of namespace 70 | -------------------------------------------------------------------------------- /src/ui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(netctrl main.cpp 2 | cmd_arguments.cpp 3 | graph_util.cpp) 4 | target_link_libraries(netctrl netctrl0 igraphpp) 5 | 6 | install(TARGETS netctrl 7 | RUNTIME DESTINATION bin) 8 | -------------------------------------------------------------------------------- /src/ui/SimpleOpt.h: -------------------------------------------------------------------------------- 1 | /*! @file SimpleOpt.h 2 | 3 | @version 3.4 4 | 5 | @brief A cross-platform command line library which can parse almost any 6 | of the standard command line formats in use today. It is designed 7 | explicitly to be portable to any platform and has been tested on Windows 8 | and Linux. See CSimpleOptTempl for the class definition. 9 | 10 | @section features FEATURES 11 | 12 | - MIT Licence allows free use in all software (including GPL 13 | and commercial) 14 | - multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix) 15 | - supports all lengths of option names: 16 | 17 |
- 18 | switch character only (e.g. use stdin for input) 19 |
-o 20 | short (single character) 21 |
-long 22 | long (multiple character, single switch character) 23 |
--longer 24 | long (multiple character, multiple switch characters) 25 |
26 | - supports all types of arguments for options: 27 | 28 |
--option 29 | short/long option flag (no argument) 30 |
--option ARG 31 | short/long option with separate required argument 32 |
--option=ARG 33 | short/long option with combined required argument 34 |
--option[=ARG] 35 | short/long option with combined optional argument 36 |
-oARG 37 | short option with combined required argument 38 |
-o[ARG] 39 | short option with combined optional argument 40 |
41 | - supports options with multiple or variable numbers of arguments: 42 | 43 |
--multi ARG1 ARG2 44 | Multiple arguments 45 |
--multi N ARG-1 ARG-2 ... ARG-N 46 | Variable number of arguments 47 |
48 | - supports case-insensitive option matching on short, long and/or 49 | word arguments. 50 | - supports options which do not use a switch character. i.e. a special 51 | word which is construed as an option. 52 | e.g. "foo.exe open /directory/file.txt" 53 | - supports clumping of multiple short options (no arguments) in a string 54 | e.g. "foo.exe -abcdef file1" <==> "foo.exe -a -b -c -d -e -f file1" 55 | - automatic recognition of a single slash as equivalent to a single 56 | hyphen on Windows, e.g. "/f FILE" is equivalent to "-f FILE". 57 | - file arguments can appear anywhere in the argument list: 58 | "foo.exe file1.txt -a ARG file2.txt --flag file3.txt file4.txt" 59 | files will be returned to the application in the same order they were 60 | supplied on the command line 61 | - short-circuit option matching: "--man" will match "--mandate" 62 | invalid options can be handled while continuing to parse the command 63 | line valid options list can be changed dynamically during command line 64 | processing, i.e. accept different options depending on an option 65 | supplied earlier in the command line. 66 | - implemented with only a single C++ header file 67 | - optionally use no C runtime or OS functions 68 | - char, wchar_t and Windows TCHAR in the same program 69 | - complete working examples included 70 | - compiles cleanly at warning level 4 (Windows/VC.NET 2003), warning 71 | level 3 (Windows/VC6) and -Wall (Linux/gcc) 72 | 73 | @section usage USAGE 74 | 75 | The SimpleOpt class is used by following these steps: 76 | 77 |
    78 |
  1. Include the SimpleOpt.h header file 79 | 80 |
      81 |         \#include "SimpleOpt.h"
      82 |         
    83 | 84 |
  2. Define an array of valid options for your program. 85 | 86 |
      87 | @link CSimpleOptTempl::SOption CSimpleOpt::SOption @endlink g_rgOptions[] = {
      88 |     { OPT_FLAG, _T("-a"),     SO_NONE    }, // "-a"
      89 |     { OPT_FLAG, _T("-b"),     SO_NONE    }, // "-b"
      90 |     { OPT_ARG,  _T("-f"),     SO_REQ_SEP }, // "-f ARG"
      91 |     { OPT_HELP, _T("-?"),     SO_NONE    }, // "-?"
      92 |     { OPT_HELP, _T("--help"), SO_NONE    }, // "--help"
      93 |     SO_END_OF_OPTIONS                       // END
      94 | };
      95 | 
    96 | 97 | Note that all options must start with a hyphen even if the slash will 98 | be accepted. This is because the slash character is automatically 99 | converted into a hyphen to test against the list of options. 100 | For example, the following line matches both "-?" and "/?" 101 | (on Windows). 102 | 103 |
     104 |         { OPT_HELP, _T("-?"),     SO_NONE    }, // "-?"
     105 |         
    106 | 107 |
  3. Instantiate a CSimpleOpt object supplying argc, argv and the option 108 | table 109 | 110 |
     111 | @link CSimpleOptTempl CSimpleOpt @endlink args(argc, argv, g_rgOptions);
     112 | 
    113 | 114 |
  4. Process the arguments by calling Next() until it returns false. 115 | On each call, first check for an error by calling LastError(), then 116 | either handle the error or process the argument. 117 | 118 |
     119 | while (args.Next()) {
     120 |     if (args.LastError() == SO_SUCCESS) {
     121 |         handle option: use OptionId(), OptionText() and OptionArg()
     122 |     }
     123 |     else {
     124 |         handle error: see ESOError enums
     125 |     }
     126 | }
     127 | 
    128 | 129 |
  5. Process all non-option arguments with File(), Files() and FileCount() 130 | 131 |
     132 | ShowFiles(args.FileCount(), args.Files());
     133 | 
    134 | 135 |
136 | 137 | @section notes NOTES 138 | 139 | - In MBCS mode, this library is guaranteed to work correctly only when 140 | all option names use only ASCII characters. 141 | - Note that if case-insensitive matching is being used then the first 142 | matching option in the argument list will be returned. 143 | 144 | @section licence MIT LICENCE 145 | 146 | The licence text below is the boilerplate "MIT Licence" used from: 147 | http://www.opensource.org/licenses/mit-license.php 148 | 149 | Copyright (c) 2006-2007, Brodie Thiesfield 150 | 151 | Permission is hereby granted, free of charge, to any person obtaining a 152 | copy of this software and associated documentation files (the "Software"), 153 | to deal in the Software without restriction, including without limitation 154 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 155 | and/or sell copies of the Software, and to permit persons to whom the 156 | Software is furnished to do so, subject to the following conditions: 157 | 158 | The above copyright notice and this permission notice shall be included 159 | in all copies or substantial portions of the Software. 160 | 161 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 162 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 163 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 164 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 165 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 166 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 167 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 168 | */ 169 | 170 | /*! @mainpage 171 | 172 | 173 |
Library SimpleOpt 174 |
Author Brodie Thiesfield [code at jellycan dot com] 175 |
Source http://code.jellycan.com/simpleopt/ 176 |
177 | 178 | @section SimpleOpt SimpleOpt 179 | 180 | A cross-platform library providing a simple method to parse almost any of 181 | the standard command-line formats in use today. 182 | 183 | See the @link SimpleOpt.h SimpleOpt @endlink documentation for full 184 | details. 185 | 186 | @section SimpleGlob SimpleGlob 187 | 188 | A cross-platform file globbing library providing the ability to 189 | expand wildcards in command-line arguments to a list of all matching 190 | files. 191 | 192 | See the @link SimpleGlob.h SimpleGlob @endlink documentation for full 193 | details. 194 | */ 195 | 196 | #ifndef INCLUDED_SimpleOpt 197 | #define INCLUDED_SimpleOpt 198 | 199 | // Default the max arguments to a fixed value. If you want to be able to 200 | // handle any number of arguments, then predefine this to 0 and it will 201 | // use an internal dynamically allocated buffer instead. 202 | #ifdef SO_MAX_ARGS 203 | # define SO_STATICBUF SO_MAX_ARGS 204 | #else 205 | # include // malloc, free 206 | # include // memmove 207 | # define SO_STATICBUF 50 208 | #endif 209 | 210 | namespace SimpleOpt { 211 | 212 | //! Error values 213 | typedef enum _ESOError 214 | { 215 | //! No error 216 | SO_SUCCESS = 0, 217 | 218 | /*! It looks like an option (it starts with a switch character), but 219 | it isn't registered in the option table. */ 220 | SO_OPT_INVALID = -1, 221 | 222 | /*! Multiple options matched the supplied option text. 223 | Only returned when NOT using SO_O_EXACT. */ 224 | SO_OPT_MULTIPLE = -2, 225 | 226 | /*! Option doesn't take an argument, but a combined argument was 227 | supplied. */ 228 | SO_ARG_INVALID = -3, 229 | 230 | /*! SO_REQ_CMB style-argument was supplied to a SO_REQ_SEP option 231 | Only returned when using SO_O_PEDANTIC. */ 232 | SO_ARG_INVALID_TYPE = -4, 233 | 234 | //! Required argument was not supplied 235 | SO_ARG_MISSING = -5, 236 | 237 | /*! Option argument looks like another option. 238 | Only returned when NOT using SO_O_NOERR. */ 239 | SO_ARG_INVALID_DATA = -6 240 | } ESOError; 241 | 242 | //! Option flags 243 | enum _ESOFlags 244 | { 245 | /*! Disallow partial matching of option names */ 246 | SO_O_EXACT = 0x0001, 247 | 248 | /*! Disallow use of slash as an option marker on Windows. 249 | Un*x only ever recognizes a hyphen. */ 250 | SO_O_NOSLASH = 0x0002, 251 | 252 | /*! Permit arguments on single letter options with no equals sign. 253 | e.g. -oARG or -o[ARG] */ 254 | SO_O_SHORTARG = 0x0004, 255 | 256 | /*! Permit single character options to be clumped into a single 257 | option string. e.g. "-a -b -c" <==> "-abc" */ 258 | SO_O_CLUMP = 0x0008, 259 | 260 | /*! Process the entire argv array for options, including the 261 | argv[0] entry. */ 262 | SO_O_USEALL = 0x0010, 263 | 264 | /*! Do not generate an error for invalid options. errors for missing 265 | arguments will still be generated. invalid options will be 266 | treated as files. invalid options in clumps will be silently 267 | ignored. */ 268 | SO_O_NOERR = 0x0020, 269 | 270 | /*! Validate argument type pedantically. Return an error when a 271 | separated argument "-opt arg" is supplied by the user as a 272 | combined argument "-opt=arg". By default this is not considered 273 | an error. */ 274 | SO_O_PEDANTIC = 0x0040, 275 | 276 | /*! Case-insensitive comparisons for short arguments */ 277 | SO_O_ICASE_SHORT = 0x0100, 278 | 279 | /*! Case-insensitive comparisons for long arguments */ 280 | SO_O_ICASE_LONG = 0x0200, 281 | 282 | /*! Case-insensitive comparisons for word arguments 283 | i.e. arguments without any hyphens at the start. */ 284 | SO_O_ICASE_WORD = 0x0400, 285 | 286 | /*! Case-insensitive comparisons for all arg types */ 287 | SO_O_ICASE = 0x0700 288 | }; 289 | 290 | /*! Types of arguments that options may have. Note that some of the _ESOFlags 291 | are not compatible with all argument types. SO_O_SHORTARG requires that 292 | relevant options use either SO_REQ_CMB or SO_OPT. SO_O_CLUMP requires 293 | that relevant options use only SO_NONE. 294 | */ 295 | typedef enum _ESOArgType { 296 | /*! No argument. Just the option flags. 297 | e.g. -o --opt */ 298 | SO_NONE, 299 | 300 | /*! Required separate argument. 301 | e.g. -o ARG --opt ARG */ 302 | SO_REQ_SEP, 303 | 304 | /*! Required combined argument. 305 | e.g. -oARG -o=ARG --opt=ARG */ 306 | SO_REQ_CMB, 307 | 308 | /*! Optional combined argument. 309 | e.g. -o[ARG] -o[=ARG] --opt[=ARG] */ 310 | SO_OPT, 311 | 312 | /*! Multiple separate arguments. The actual number of arguments is 313 | determined programatically at the time the argument is processed. 314 | e.g. -o N ARG1 ARG2 ... ARGN --opt N ARG1 ARG2 ... ARGN */ 315 | SO_MULTI 316 | } ESOArgType; 317 | 318 | //! this option definition must be the last entry in the table 319 | #define SO_END_OF_OPTIONS { -1, NULL, SO_NONE } 320 | 321 | #ifdef _DEBUG 322 | # ifdef _MSC_VER 323 | # include 324 | # define SO_ASSERT(b) _ASSERTE(b) 325 | # else 326 | # include 327 | # define SO_ASSERT(b) assert(b) 328 | # endif 329 | #else 330 | # define SO_ASSERT(b) //!< assertion used to test input data 331 | #endif 332 | 333 | // --------------------------------------------------------------------------- 334 | // MAIN TEMPLATE CLASS 335 | // --------------------------------------------------------------------------- 336 | 337 | /*! @brief Implementation of the SimpleOpt class */ 338 | template 339 | class CSimpleOptTempl 340 | { 341 | public: 342 | /*! @brief Structure used to define all known options. */ 343 | struct SOption { 344 | /*! ID to return for this flag. Optional but must be >= 0 */ 345 | int nId; 346 | 347 | /*! arg string to search for, e.g. "open", "-", "-f", "--file" 348 | Note that on Windows the slash option marker will be converted 349 | to a hyphen so that "-f" will also match "/f". */ 350 | const SOCHAR * pszArg; 351 | 352 | /*! type of argument accepted by this option */ 353 | ESOArgType nArgType; 354 | }; 355 | 356 | /*! @brief Initialize the class. Init() must be called later. */ 357 | CSimpleOptTempl() 358 | : m_rgShuffleBuf(NULL) 359 | { 360 | Init(0, NULL, NULL, 0); 361 | } 362 | 363 | /*! @brief Initialize the class in preparation for use. */ 364 | CSimpleOptTempl( 365 | int argc, 366 | SOCHAR * argv[], 367 | const SOption * a_rgOptions, 368 | int a_nFlags = 0 369 | ) 370 | : m_rgShuffleBuf(NULL) 371 | { 372 | Init(argc, argv, a_rgOptions, a_nFlags); 373 | } 374 | 375 | #ifndef SO_MAX_ARGS 376 | /*! @brief Deallocate any allocated memory. */ 377 | ~CSimpleOptTempl() { if (m_rgShuffleBuf) free(m_rgShuffleBuf); } 378 | #endif 379 | 380 | /*! @brief Initialize the class in preparation for calling Next. 381 | 382 | The table of options pointed to by a_rgOptions does not need to be 383 | valid at the time that Init() is called. However on every call to 384 | Next() the table pointed to must be a valid options table with the 385 | last valid entry set to SO_END_OF_OPTIONS. 386 | 387 | NOTE: the array pointed to by a_argv will be modified by this 388 | class and must not be used or modified outside of member calls to 389 | this class. 390 | 391 | @param a_argc Argument array size 392 | @param a_argv Argument array 393 | @param a_rgOptions Valid option array 394 | @param a_nFlags Optional flags to modify the processing of 395 | the arguments 396 | 397 | @return true Successful 398 | @return false if SO_MAX_ARGC > 0: Too many arguments 399 | if SO_MAX_ARGC == 0: Memory allocation failure 400 | */ 401 | bool Init( 402 | int a_argc, 403 | SOCHAR * a_argv[], 404 | const SOption * a_rgOptions, 405 | int a_nFlags = 0 406 | ); 407 | 408 | /*! @brief Change the current options table during option parsing. 409 | 410 | @param a_rgOptions Valid option array 411 | */ 412 | inline void SetOptions(const SOption * a_rgOptions) { 413 | m_rgOptions = a_rgOptions; 414 | } 415 | 416 | /*! @brief Change the current flags during option parsing. 417 | 418 | Note that changing the SO_O_USEALL flag here will have no affect. 419 | It must be set using Init() or the constructor. 420 | 421 | @param a_nFlags Flags to modify the processing of the arguments 422 | */ 423 | inline void SetFlags(int a_nFlags) { m_nFlags = a_nFlags; } 424 | 425 | /*! @brief Query if a particular flag is set */ 426 | inline bool HasFlag(int a_nFlag) const { 427 | return (m_nFlags & a_nFlag) == a_nFlag; 428 | } 429 | 430 | /*! @brief Advance to the next option if available. 431 | 432 | When all options have been processed it will return false. When true 433 | has been returned, you must check for an invalid or unrecognized 434 | option using the LastError() method. This will be return an error 435 | value other than SO_SUCCESS on an error. All standard data 436 | (e.g. OptionText(), OptionArg(), OptionId(), etc) will be available 437 | depending on the error. 438 | 439 | After all options have been processed, the remaining files from the 440 | command line can be processed in same order as they were passed to 441 | the program. 442 | 443 | @return true option or error available for processing 444 | @return false all options have been processed 445 | */ 446 | bool Next(); 447 | 448 | /*! Stops processing of the command line and returns all remaining 449 | arguments as files. The next call to Next() will return false. 450 | */ 451 | void Stop(); 452 | 453 | /*! @brief Return the last error that occurred. 454 | 455 | This function must always be called before processing the current 456 | option. This function is available only when Next() has returned true. 457 | */ 458 | inline ESOError LastError() const { return m_nLastError; } 459 | 460 | /*! @brief Return the nId value from the options array for the current 461 | option. 462 | 463 | This function is available only when Next() has returned true. 464 | */ 465 | inline int OptionId() const { return m_nOptionId; } 466 | 467 | /*! @brief Return the pszArg from the options array for the current 468 | option. 469 | 470 | This function is available only when Next() has returned true. 471 | */ 472 | inline const SOCHAR * OptionText() const { return m_pszOptionText; } 473 | 474 | /*! @brief Return the argument for the current option where one exists. 475 | 476 | If there is no argument for the option, this will return NULL. 477 | This function is available only when Next() has returned true. 478 | */ 479 | inline SOCHAR * OptionArg() const { return m_pszOptionArg; } 480 | 481 | /*! @brief Validate and return the desired number of arguments. 482 | 483 | This is only valid when OptionId() has return the ID of an option 484 | that is registered as SO_MULTI. It may be called multiple times 485 | each time returning the desired number of arguments. Previously 486 | returned argument pointers are remain valid. 487 | 488 | If an error occurs during processing, NULL will be returned and 489 | the error will be available via LastError(). 490 | 491 | @param n Number of arguments to return. 492 | */ 493 | SOCHAR ** MultiArg(int n); 494 | 495 | /*! @brief Returned the number of entries in the Files() array. 496 | 497 | After Next() has returned false, this will be the list of files (or 498 | otherwise unprocessed arguments). 499 | */ 500 | inline int FileCount() const { return m_argc - m_nLastArg; } 501 | 502 | /*! @brief Return the specified file argument. 503 | 504 | @param n Index of the file to return. This must be between 0 505 | and FileCount() - 1; 506 | */ 507 | inline SOCHAR * File(int n) const { 508 | SO_ASSERT(n >= 0 && n < FileCount()); 509 | return m_argv[m_nLastArg + n]; 510 | } 511 | 512 | /*! @brief Return the array of files. */ 513 | inline SOCHAR ** Files() const { return &m_argv[m_nLastArg]; } 514 | 515 | private: 516 | CSimpleOptTempl(const CSimpleOptTempl &); // disabled 517 | CSimpleOptTempl & operator=(const CSimpleOptTempl &); // disabled 518 | 519 | SOCHAR PrepareArg(SOCHAR * a_pszString) const; 520 | bool NextClumped(); 521 | void ShuffleArg(int a_nStartIdx, int a_nCount); 522 | int LookupOption(const SOCHAR * a_pszOption) const; 523 | int CalcMatch(const SOCHAR *a_pszSource, const SOCHAR *a_pszTest) const; 524 | 525 | // Find the '=' character within a string. 526 | inline SOCHAR * FindEquals(SOCHAR *s) const { 527 | while (*s && *s != (SOCHAR)'=') ++s; 528 | return *s ? s : NULL; 529 | } 530 | bool IsEqual(SOCHAR a_cLeft, SOCHAR a_cRight, int a_nArgType) const; 531 | 532 | inline void Copy(SOCHAR ** ppDst, SOCHAR ** ppSrc, int nCount) const { 533 | #ifdef SO_MAX_ARGS 534 | // keep our promise of no CLIB usage 535 | while (nCount-- > 0) *ppDst++ = *ppSrc++; 536 | #else 537 | memmove(ppDst, ppSrc, nCount * sizeof(SOCHAR*)); 538 | #endif 539 | } 540 | 541 | private: 542 | const SOption * m_rgOptions; //!< pointer to options table 543 | int m_nFlags; //!< flags 544 | int m_nOptionIdx; //!< current argv option index 545 | int m_nOptionId; //!< id of current option (-1 = invalid) 546 | int m_nNextOption; //!< index of next option 547 | int m_nLastArg; //!< last argument, after this are files 548 | int m_argc; //!< argc to process 549 | SOCHAR ** m_argv; //!< argv 550 | const SOCHAR * m_pszOptionText; //!< curr option text, e.g. "-f" 551 | SOCHAR * m_pszOptionArg; //!< curr option arg, e.g. "c:\file.txt" 552 | SOCHAR * m_pszClump; //!< clumped single character options 553 | SOCHAR m_szShort[3]; //!< temp for clump and combined args 554 | ESOError m_nLastError; //!< error status from the last call 555 | SOCHAR ** m_rgShuffleBuf; //!< shuffle buffer for large argc 556 | }; 557 | 558 | // --------------------------------------------------------------------------- 559 | // IMPLEMENTATION 560 | // --------------------------------------------------------------------------- 561 | 562 | template 563 | bool 564 | CSimpleOptTempl::Init( 565 | int a_argc, 566 | SOCHAR * a_argv[], 567 | const SOption * a_rgOptions, 568 | int a_nFlags 569 | ) 570 | { 571 | m_argc = a_argc; 572 | m_nLastArg = a_argc; 573 | m_argv = a_argv; 574 | m_rgOptions = a_rgOptions; 575 | m_nLastError = SO_SUCCESS; 576 | m_nOptionIdx = 0; 577 | m_nOptionId = -1; 578 | m_pszOptionText = NULL; 579 | m_pszOptionArg = NULL; 580 | m_nNextOption = (a_nFlags & SO_O_USEALL) ? 0 : 1; 581 | m_szShort[0] = (SOCHAR)'-'; 582 | m_szShort[2] = (SOCHAR)'\0'; 583 | m_nFlags = a_nFlags; 584 | m_pszClump = NULL; 585 | 586 | #ifdef SO_MAX_ARGS 587 | if (m_argc > SO_MAX_ARGS) { 588 | m_nLastError = SO_ARG_INVALID_DATA; 589 | m_nLastArg = 0; 590 | return false; 591 | } 592 | #else 593 | if (m_rgShuffleBuf) { 594 | free(m_rgShuffleBuf); 595 | } 596 | if (m_argc > SO_STATICBUF) { 597 | m_rgShuffleBuf = (SOCHAR**) malloc(sizeof(SOCHAR*) * m_argc); 598 | if (!m_rgShuffleBuf) { 599 | return false; 600 | } 601 | } 602 | #endif 603 | 604 | return true; 605 | } 606 | 607 | template 608 | bool 609 | CSimpleOptTempl::Next() 610 | { 611 | #ifdef SO_MAX_ARGS 612 | if (m_argc > SO_MAX_ARGS) { 613 | SO_ASSERT(!"Too many args! Check the return value of Init()!"); 614 | return false; 615 | } 616 | #endif 617 | 618 | // process a clumped option string if appropriate 619 | if (m_pszClump && *m_pszClump) { 620 | // silently discard invalid clumped option 621 | bool bIsValid = NextClumped(); 622 | while (*m_pszClump && !bIsValid && HasFlag(SO_O_NOERR)) { 623 | bIsValid = NextClumped(); 624 | } 625 | 626 | // return this option if valid or we are returning errors 627 | if (bIsValid || !HasFlag(SO_O_NOERR)) { 628 | return true; 629 | } 630 | } 631 | SO_ASSERT(!m_pszClump || !*m_pszClump); 632 | m_pszClump = NULL; 633 | 634 | // init for the next option 635 | m_nOptionIdx = m_nNextOption; 636 | m_nOptionId = -1; 637 | m_pszOptionText = NULL; 638 | m_pszOptionArg = NULL; 639 | m_nLastError = SO_SUCCESS; 640 | 641 | // find the next option 642 | SOCHAR cFirst; 643 | int nTableIdx = -1; 644 | int nOptIdx = m_nOptionIdx; 645 | while (nTableIdx < 0 && nOptIdx < m_nLastArg) { 646 | SOCHAR * pszArg = m_argv[nOptIdx]; 647 | m_pszOptionArg = NULL; 648 | 649 | // find this option in the options table 650 | cFirst = PrepareArg(pszArg); 651 | if (pszArg[0] == (SOCHAR)'-') { 652 | // find any combined argument string and remove equals sign 653 | m_pszOptionArg = FindEquals(pszArg); 654 | if (m_pszOptionArg) { 655 | *m_pszOptionArg++ = (SOCHAR)'\0'; 656 | } 657 | } 658 | nTableIdx = LookupOption(pszArg); 659 | 660 | // if we didn't find this option but if it is a short form 661 | // option then we try the alternative forms 662 | if (nTableIdx < 0 663 | && !m_pszOptionArg 664 | && pszArg[0] == (SOCHAR)'-' 665 | && pszArg[1] 666 | && pszArg[1] != (SOCHAR)'-' 667 | && pszArg[2]) 668 | { 669 | // test for a short-form with argument if appropriate 670 | if (HasFlag(SO_O_SHORTARG)) { 671 | m_szShort[1] = pszArg[1]; 672 | int nIdx = LookupOption(m_szShort); 673 | if (nIdx >= 0 674 | && (m_rgOptions[nIdx].nArgType == SO_REQ_CMB 675 | || m_rgOptions[nIdx].nArgType == SO_OPT)) 676 | { 677 | m_pszOptionArg = &pszArg[2]; 678 | pszArg = m_szShort; 679 | nTableIdx = nIdx; 680 | } 681 | } 682 | 683 | // test for a clumped short-form option string and we didn't 684 | // match on the short-form argument above 685 | if (nTableIdx < 0 && HasFlag(SO_O_CLUMP)) { 686 | m_pszClump = &pszArg[1]; 687 | ++m_nNextOption; 688 | if (nOptIdx > m_nOptionIdx) { 689 | ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx); 690 | } 691 | return Next(); 692 | } 693 | } 694 | 695 | // The option wasn't found. If it starts with a switch character 696 | // and we are not suppressing errors for invalid options then it 697 | // is reported as an error, otherwise it is data. 698 | if (nTableIdx < 0) { 699 | if (!HasFlag(SO_O_NOERR) && pszArg[0] == (SOCHAR)'-') { 700 | m_pszOptionText = pszArg; 701 | break; 702 | } 703 | 704 | pszArg[0] = cFirst; 705 | ++nOptIdx; 706 | if (m_pszOptionArg) { 707 | *(--m_pszOptionArg) = (SOCHAR)'='; 708 | } 709 | } 710 | } 711 | 712 | // end of options 713 | if (nOptIdx >= m_nLastArg) { 714 | if (nOptIdx > m_nOptionIdx) { 715 | ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx); 716 | } 717 | return false; 718 | } 719 | ++m_nNextOption; 720 | 721 | // get the option id 722 | ESOArgType nArgType = SO_NONE; 723 | if (nTableIdx < 0) { 724 | m_nLastError = (ESOError) nTableIdx; // error code 725 | } 726 | else { 727 | m_nOptionId = m_rgOptions[nTableIdx].nId; 728 | m_pszOptionText = m_rgOptions[nTableIdx].pszArg; 729 | 730 | // ensure that the arg type is valid 731 | nArgType = m_rgOptions[nTableIdx].nArgType; 732 | switch (nArgType) { 733 | case SO_NONE: 734 | if (m_pszOptionArg) { 735 | m_nLastError = SO_ARG_INVALID; 736 | } 737 | break; 738 | 739 | case SO_REQ_SEP: 740 | if (m_pszOptionArg) { 741 | // they wanted separate args, but we got a combined one, 742 | // unless we are pedantic, just accept it. 743 | if (HasFlag(SO_O_PEDANTIC)) { 744 | m_nLastError = SO_ARG_INVALID_TYPE; 745 | } 746 | } 747 | // more processing after we shuffle 748 | break; 749 | 750 | case SO_REQ_CMB: 751 | if (!m_pszOptionArg) { 752 | m_nLastError = SO_ARG_MISSING; 753 | } 754 | break; 755 | 756 | case SO_OPT: 757 | // nothing to do 758 | break; 759 | 760 | case SO_MULTI: 761 | // nothing to do. Caller must now check for valid arguments 762 | // using GetMultiArg() 763 | break; 764 | } 765 | } 766 | 767 | // shuffle the files out of the way 768 | if (nOptIdx > m_nOptionIdx) { 769 | ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx); 770 | } 771 | 772 | // we need to return the separate arg if required, just re-use the 773 | // multi-arg code because it all does the same thing 774 | if ( nArgType == SO_REQ_SEP 775 | && !m_pszOptionArg 776 | && m_nLastError == SO_SUCCESS) 777 | { 778 | SOCHAR ** ppArgs = MultiArg(1); 779 | if (ppArgs) { 780 | m_pszOptionArg = *ppArgs; 781 | } 782 | } 783 | 784 | return true; 785 | } 786 | 787 | template 788 | void 789 | CSimpleOptTempl::Stop() 790 | { 791 | if (m_nNextOption < m_nLastArg) { 792 | ShuffleArg(m_nNextOption, m_nLastArg - m_nNextOption); 793 | } 794 | } 795 | 796 | template 797 | SOCHAR 798 | CSimpleOptTempl::PrepareArg( 799 | SOCHAR * a_pszString 800 | ) const 801 | { 802 | #ifdef _WIN32 803 | // On Windows we can accept the forward slash as a single character 804 | // option delimiter, but it cannot replace the '-' option used to 805 | // denote stdin. On Un*x paths may start with slash so it may not 806 | // be used to start an option. 807 | if (!HasFlag(SO_O_NOSLASH) 808 | && a_pszString[0] == (SOCHAR)'/' 809 | && a_pszString[1] 810 | && a_pszString[1] != (SOCHAR)'-') 811 | { 812 | a_pszString[0] = (SOCHAR)'-'; 813 | return (SOCHAR)'/'; 814 | } 815 | #endif 816 | return a_pszString[0]; 817 | } 818 | 819 | template 820 | bool 821 | CSimpleOptTempl::NextClumped() 822 | { 823 | // prepare for the next clumped option 824 | m_szShort[1] = *m_pszClump++; 825 | m_nOptionId = -1; 826 | m_pszOptionText = NULL; 827 | m_pszOptionArg = NULL; 828 | m_nLastError = SO_SUCCESS; 829 | 830 | // lookup this option, ensure that we are using exact matching 831 | int nSavedFlags = m_nFlags; 832 | m_nFlags = SO_O_EXACT; 833 | int nTableIdx = LookupOption(m_szShort); 834 | m_nFlags = nSavedFlags; 835 | 836 | // unknown option 837 | if (nTableIdx < 0) { 838 | m_nLastError = (ESOError) nTableIdx; // error code 839 | return false; 840 | } 841 | 842 | // valid option 843 | m_pszOptionText = m_rgOptions[nTableIdx].pszArg; 844 | ESOArgType nArgType = m_rgOptions[nTableIdx].nArgType; 845 | if (nArgType == SO_NONE) { 846 | m_nOptionId = m_rgOptions[nTableIdx].nId; 847 | return true; 848 | } 849 | 850 | if (nArgType == SO_REQ_CMB && *m_pszClump) { 851 | m_nOptionId = m_rgOptions[nTableIdx].nId; 852 | m_pszOptionArg = m_pszClump; 853 | while (*m_pszClump) ++m_pszClump; // must point to an empty string 854 | return true; 855 | } 856 | 857 | // invalid option as it requires an argument 858 | m_nLastError = SO_ARG_MISSING; 859 | return true; 860 | } 861 | 862 | // Shuffle arguments to the end of the argv array. 863 | // 864 | // For example: 865 | // argv[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" }; 866 | // 867 | // ShuffleArg(1, 1) = { "0", "2", "3", "4", "5", "6", "7", "8", "1" }; 868 | // ShuffleArg(5, 2) = { "0", "1", "2", "3", "4", "7", "8", "5", "6" }; 869 | // ShuffleArg(2, 4) = { "0", "1", "6", "7", "8", "2", "3", "4", "5" }; 870 | template 871 | void 872 | CSimpleOptTempl::ShuffleArg( 873 | int a_nStartIdx, 874 | int a_nCount 875 | ) 876 | { 877 | SOCHAR * staticBuf[SO_STATICBUF]; 878 | SOCHAR ** buf = m_rgShuffleBuf ? m_rgShuffleBuf : staticBuf; 879 | int nTail = m_argc - a_nStartIdx - a_nCount; 880 | 881 | // make a copy of the elements to be moved 882 | Copy(buf, m_argv + a_nStartIdx, a_nCount); 883 | 884 | // move the tail down 885 | Copy(m_argv + a_nStartIdx, m_argv + a_nStartIdx + a_nCount, nTail); 886 | 887 | // append the moved elements to the tail 888 | Copy(m_argv + a_nStartIdx + nTail, buf, a_nCount); 889 | 890 | // update the index of the last unshuffled arg 891 | m_nLastArg -= a_nCount; 892 | } 893 | 894 | // match on the long format strings. partial matches will be 895 | // accepted only if that feature is enabled. 896 | template 897 | int 898 | CSimpleOptTempl::LookupOption( 899 | const SOCHAR * a_pszOption 900 | ) const 901 | { 902 | int nBestMatch = -1; // index of best match so far 903 | int nBestMatchLen = 0; // matching characters of best match 904 | int nLastMatchLen = 0; // matching characters of last best match 905 | 906 | for (int n = 0; m_rgOptions[n].nId >= 0; ++n) { 907 | // the option table must use hyphens as the option character, 908 | // the slash character is converted to a hyphen for testing. 909 | SO_ASSERT(m_rgOptions[n].pszArg[0] != (SOCHAR)'/'); 910 | 911 | int nMatchLen = CalcMatch(m_rgOptions[n].pszArg, a_pszOption); 912 | if (nMatchLen == -1) { 913 | return n; 914 | } 915 | if (nMatchLen > 0 && nMatchLen >= nBestMatchLen) { 916 | nLastMatchLen = nBestMatchLen; 917 | nBestMatchLen = nMatchLen; 918 | nBestMatch = n; 919 | } 920 | } 921 | 922 | // only partial matches or no match gets to here, ensure that we 923 | // don't return a partial match unless it is a clear winner 924 | if (HasFlag(SO_O_EXACT) || nBestMatch == -1) { 925 | return SO_OPT_INVALID; 926 | } 927 | return (nBestMatchLen > nLastMatchLen) ? nBestMatch : SO_OPT_MULTIPLE; 928 | } 929 | 930 | // calculate the number of characters that match (case-sensitive) 931 | // 0 = no match, > 0 == number of characters, -1 == perfect match 932 | template 933 | int 934 | CSimpleOptTempl::CalcMatch( 935 | const SOCHAR * a_pszSource, 936 | const SOCHAR * a_pszTest 937 | ) const 938 | { 939 | if (!a_pszSource || !a_pszTest) { 940 | return 0; 941 | } 942 | 943 | // determine the argument type 944 | int nArgType = SO_O_ICASE_LONG; 945 | if (a_pszSource[0] != '-') { 946 | nArgType = SO_O_ICASE_WORD; 947 | } 948 | else if (a_pszSource[1] != '-' && !a_pszSource[2]) { 949 | nArgType = SO_O_ICASE_SHORT; 950 | } 951 | 952 | // match and skip leading hyphens 953 | while (*a_pszSource == (SOCHAR)'-' && *a_pszSource == *a_pszTest) { 954 | ++a_pszSource; 955 | ++a_pszTest; 956 | } 957 | if (*a_pszSource == (SOCHAR)'-' || *a_pszTest == (SOCHAR)'-') { 958 | return 0; 959 | } 960 | 961 | // find matching number of characters in the strings 962 | int nLen = 0; 963 | while (*a_pszSource && IsEqual(*a_pszSource, *a_pszTest, nArgType)) { 964 | ++a_pszSource; 965 | ++a_pszTest; 966 | ++nLen; 967 | } 968 | 969 | // if we have exhausted the source... 970 | if (!*a_pszSource) { 971 | // and the test strings, then it's a perfect match 972 | if (!*a_pszTest) { 973 | return -1; 974 | } 975 | 976 | // otherwise the match failed as the test is longer than 977 | // the source. i.e. "--mant" will not match the option "--man". 978 | return 0; 979 | } 980 | 981 | // if we haven't exhausted the test string then it is not a match 982 | // i.e. "--mantle" will not best-fit match to "--mandate" at all. 983 | if (*a_pszTest) { 984 | return 0; 985 | } 986 | 987 | // partial match to the current length of the test string 988 | return nLen; 989 | } 990 | 991 | template 992 | bool 993 | CSimpleOptTempl::IsEqual( 994 | SOCHAR a_cLeft, 995 | SOCHAR a_cRight, 996 | int a_nArgType 997 | ) const 998 | { 999 | // if this matches then we are doing case-insensitive matching 1000 | if (m_nFlags & a_nArgType) { 1001 | if (a_cLeft >= 'A' && a_cLeft <= 'Z') a_cLeft += 'a' - 'A'; 1002 | if (a_cRight >= 'A' && a_cRight <= 'Z') a_cRight += 'a' - 'A'; 1003 | } 1004 | return a_cLeft == a_cRight; 1005 | } 1006 | 1007 | // calculate the number of characters that match (case-sensitive) 1008 | // 0 = no match, > 0 == number of characters, -1 == perfect match 1009 | template 1010 | SOCHAR ** 1011 | CSimpleOptTempl::MultiArg( 1012 | int a_nCount 1013 | ) 1014 | { 1015 | // ensure we have enough arguments 1016 | if (m_nNextOption + a_nCount > m_nLastArg) { 1017 | m_nLastError = SO_ARG_MISSING; 1018 | return NULL; 1019 | } 1020 | 1021 | // our argument array 1022 | SOCHAR ** rgpszArg = &m_argv[m_nNextOption]; 1023 | 1024 | // Ensure that each of the following don't start with an switch character. 1025 | // Only make this check if we are returning errors for unknown arguments. 1026 | if (!HasFlag(SO_O_NOERR)) { 1027 | for (int n = 0; n < a_nCount; ++n) { 1028 | SOCHAR ch = PrepareArg(rgpszArg[n]); 1029 | if (rgpszArg[n][0] == (SOCHAR)'-') { 1030 | rgpszArg[n][0] = ch; 1031 | m_nLastError = SO_ARG_INVALID_DATA; 1032 | return NULL; 1033 | } 1034 | rgpszArg[n][0] = ch; 1035 | } 1036 | } 1037 | 1038 | // all good 1039 | m_nNextOption += a_nCount; 1040 | return rgpszArg; 1041 | } 1042 | 1043 | 1044 | // --------------------------------------------------------------------------- 1045 | // TYPE DEFINITIONS 1046 | // --------------------------------------------------------------------------- 1047 | 1048 | /*! @brief ASCII/MBCS version of CSimpleOpt */ 1049 | typedef CSimpleOptTempl CSimpleOptA; 1050 | 1051 | /*! @brief wchar_t version of CSimpleOpt */ 1052 | typedef CSimpleOptTempl CSimpleOptW; 1053 | 1054 | #if defined(_UNICODE) 1055 | /*! @brief TCHAR version dependent on if _UNICODE is defined */ 1056 | # define CSimpleOpt CSimpleOptW 1057 | #else 1058 | /*! @brief TCHAR version dependent on if _UNICODE is defined */ 1059 | # define CSimpleOpt CSimpleOptA 1060 | #endif 1061 | 1062 | } // end of namespaces 1063 | 1064 | #endif // INCLUDED_SimpleOpt 1065 | -------------------------------------------------------------------------------- /src/ui/cmd_arguments.cpp: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "cmd_arguments.h" 9 | 10 | using namespace std; 11 | using namespace SimpleOpt; 12 | 13 | enum { 14 | HELP=30000, VERSION, VERBOSE, QUIET, USE_STDIN, OUT_FILE, MODEL, 15 | MODE, USE_EDGE, INPUT_FORMAT, OUTPUT_FORMAT 16 | }; 17 | 18 | CommandLineArguments::CommandLineArguments( 19 | const std::string programName, const std::string version) : 20 | m_executableName(programName), m_versionNumber(version), 21 | m_options(), 22 | inputFile(), verbosity(1), outputFile(), 23 | modelType(SWITCHBOARD_MODEL), operationMode(MODE_DRIVER_NODES), 24 | useEdgeMeasure(false), 25 | inputFormat(GRAPH_FORMAT_AUTO), outputFormat(GRAPH_FORMAT_GML) 26 | { 27 | 28 | addOption(USE_STDIN, "-", SO_NONE); 29 | 30 | addOption(HELP, "-?", SO_NONE); 31 | addOption(HELP, "-h", SO_NONE, "--help"); 32 | 33 | addOption(VERSION, "-V", SO_NONE, "--version"); 34 | addOption(VERBOSE, "-v", SO_NONE, "--verbose"); 35 | addOption(QUIET, "-q", SO_NONE, "--quiet"); 36 | 37 | addOption(OUT_FILE, "-o", SO_REQ_SEP, "--output"); 38 | addOption(MODEL, "-m", SO_REQ_SEP, "--model"); 39 | addOption(MODE, "-M", SO_REQ_SEP, "--mode"); 40 | 41 | addOption(INPUT_FORMAT, "-f", SO_REQ_SEP, "--input-format"); 42 | addOption(OUTPUT_FORMAT, "-F", SO_REQ_SEP, "--output-format"); 43 | 44 | addOption(USE_EDGE, "-e", SO_NONE, "--edge"); 45 | } 46 | 47 | void CommandLineArguments::addOption(int id, const char* option, 48 | SimpleOpt::ESOArgType type, const char* longOption) { 49 | SimpleOpt::CSimpleOpt::SOption opt; 50 | opt.nId = id; 51 | opt.pszArg = option; 52 | opt.nArgType = type; 53 | m_options.push_back(opt); 54 | 55 | if (longOption) { 56 | opt.pszArg = longOption; 57 | m_options.push_back(opt); 58 | } 59 | } 60 | 61 | int CommandLineArguments::handleFormatOption(const std::string& arg, GraphFormat* pFormat) { 62 | *pFormat = GraphUtil::formatFromString(arg); 63 | return *pFormat == GRAPH_FORMAT_UNKNOWN; 64 | } 65 | 66 | int CommandLineArguments::handleOption(int id, const std::string& arg) { 67 | return 0; 68 | } 69 | 70 | void CommandLineArguments::parse(int argc, char** argv) { 71 | CSimpleOpt::SOption* optionSpec = new CSimpleOpt::SOption[m_options.size()+1]; 72 | std::copy(m_options.begin(), m_options.end(), optionSpec); 73 | 74 | optionSpec[m_options.size()].nId = -1; 75 | optionSpec[m_options.size()].pszArg = 0; 76 | optionSpec[m_options.size()].nArgType = SO_NONE; 77 | 78 | CSimpleOpt args(argc, argv, optionSpec); 79 | string arg; 80 | int ret = -1; 81 | 82 | while (ret == -1 && args.Next()) { 83 | if (args.LastError() != SO_SUCCESS) { 84 | cerr << "Invalid argument: " << args.OptionText() << "\n"; 85 | ret = 1; 86 | break; 87 | } 88 | 89 | switch (args.OptionId()) { 90 | /* Processing - */ 91 | case USE_STDIN: 92 | inputFile = "-"; 93 | break; 94 | 95 | /* Processing --help and --version */ 96 | case HELP: 97 | showHelp(cerr); 98 | ret = 0; 99 | break; 100 | 101 | case VERSION: 102 | cerr << m_executableName << ' ' << m_versionNumber << '\n'; 103 | ret = 0; 104 | break; 105 | 106 | /* Log levels */ 107 | case VERBOSE: 108 | verbosity = 2; 109 | break; 110 | case QUIET: 111 | verbosity = 0; 112 | break; 113 | 114 | /* Processing basic algorithm parameters */ 115 | case OUT_FILE: 116 | outputFile = args.OptionArg(); 117 | break; 118 | 119 | case MODEL: 120 | arg = args.OptionArg() ? args.OptionArg() : ""; 121 | if (arg == "liu") 122 | modelType = LIU_MODEL; 123 | else if (arg == "switchboard") 124 | modelType = SWITCHBOARD_MODEL; 125 | else { 126 | cerr << "Unknown model type: " << arg << '\n'; 127 | ret = 1; 128 | } 129 | break; 130 | 131 | case MODE: 132 | arg = args.OptionArg() ? args.OptionArg() : ""; 133 | if (arg == "driver_nodes") 134 | operationMode = MODE_DRIVER_NODES; 135 | else if (arg == "control_paths") 136 | operationMode = MODE_CONTROL_PATHS; 137 | else if (arg == "graph") 138 | operationMode = MODE_GRAPH; 139 | else if (arg == "statistics") 140 | operationMode = MODE_STATISTICS; 141 | else if (arg == "significance") 142 | operationMode = MODE_SIGNIFICANCE; 143 | else { 144 | cerr << "Unknown operation mode: " << arg << '\n'; 145 | ret = 1; 146 | } 147 | break; 148 | 149 | /* Processing advanced algorithm parameters */ 150 | case USE_EDGE: 151 | useEdgeMeasure = true; 152 | break; 153 | 154 | /* Processing format options parameters */ 155 | case INPUT_FORMAT: 156 | arg = args.OptionArg() ? args.OptionArg() : ""; 157 | ret = handleFormatOption(arg, &inputFormat); 158 | if (ret) { 159 | cerr << "Unknown input format: " << arg << '\n'; 160 | ret = 1; 161 | } else { 162 | ret = -1; 163 | } 164 | break; 165 | 166 | case OUTPUT_FORMAT: 167 | arg = args.OptionArg() ? args.OptionArg() : ""; 168 | ret = handleFormatOption(arg, &outputFormat); 169 | if (ret || (outputFormat != GRAPH_FORMAT_GRAPHML && 170 | outputFormat != GRAPH_FORMAT_GML)) { 171 | cerr << "Unknown output format: " << arg << '\n'; 172 | ret = 1; 173 | } else { 174 | ret = -1; 175 | } 176 | break; 177 | 178 | default: 179 | arg = args.OptionArg() ? args.OptionArg() : ""; 180 | ret = handleOption(args.OptionId(), arg); 181 | if (!ret) 182 | ret = -1; 183 | } 184 | } 185 | 186 | if (ret != -1) { 187 | delete[] optionSpec; 188 | exit(ret); 189 | } 190 | 191 | /* If no input file was given, show help and exit */ 192 | if (args.FileCount() == 0 && inputFile != "-") { 193 | delete[] optionSpec; 194 | showHelp(cerr); 195 | exit(1); 196 | } 197 | 198 | /* If the output file is empty, use a single dash, meaning stdout */ 199 | if (outputFile == "") 200 | outputFile = "-"; 201 | 202 | /* Set up the input file if it is not stdin */ 203 | if (inputFile != "-") 204 | inputFile = args.Files()[0]; 205 | 206 | delete[] optionSpec; 207 | } 208 | 209 | void CommandLineArguments::showHelp(ostream& os) const { 210 | os << "Usage:\n " << m_executableName << " [options] inputfile\n" 211 | "\n"; 212 | os << " -h, --help shows this help message\n" 213 | " -V, --version shows the version number\n" 214 | " -v, --verbose verbose mode (more output)\n" 215 | " -q, --quiet quiet mode (less output, only errors)\n" 216 | "\n" 217 | "Basic algorithm parameters:\n" 218 | " -m, --model selects the controllability model to use.\n" 219 | " Supported models: liu, switchboard.\n" 220 | " Default: switchboard.\n" 221 | " -M, --mode selects the mode in which the application will operate.\n" 222 | " Supported modes: driver_nodes, control_paths, graph,\n" 223 | " statistics, significance. Default: driver_nodes.\n" 224 | " -o, --output specifies the name of the output file where the results\n" 225 | " should be written.\n" 226 | "\n" 227 | "Advanced algorithm parameters:\n" 228 | " -e, --edge use the edge-based controllability measure for the\n" 229 | " switchboard model.\n" 230 | "\n" 231 | "Input/output format:\n" 232 | " -f, --input-format specifies the input format for reading graphs.\n" 233 | " Supported formats: auto, edgelist, gml, graphml, lgl, ncol\n" 234 | " Default: auto, except when the input file comes from\n" 235 | " stdin; in this case, edgelist is used.\n" 236 | " -F, --output-format specifies the output format for writing graphs. Used only\n" 237 | " when mode = graph. Supported formats: gml, graphml.\n" 238 | " Default: gml.\n"; 239 | 240 | } 241 | -------------------------------------------------------------------------------- /src/ui/cmd_arguments.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef _CMD_ARGUMENTS_H 4 | #define _CMD_ARGUMENTS_H 5 | 6 | #include 7 | #include 8 | #include 9 | #include "SimpleOpt.h" 10 | #include "graph_util.h" 11 | 12 | /// Possible model types handled by the application 13 | typedef enum { 14 | LIU_MODEL, SWITCHBOARD_MODEL 15 | } ModelType; 16 | 17 | /// Possible operation modes for the application 18 | typedef enum { 19 | MODE_DRIVER_NODES, MODE_STATISTICS, MODE_SIGNIFICANCE, 20 | MODE_CONTROL_PATHS, MODE_GRAPH 21 | } OperationMode; 22 | 23 | /// Parses the command line arguments of the main app 24 | class CommandLineArguments { 25 | private: 26 | /// String storing the name of the executable used to start the program 27 | std::string m_executableName; 28 | 29 | /// String storing the version number of the application 30 | std::string m_versionNumber; 31 | 32 | /// A vector of command line option specifications 33 | std::vector m_options; 34 | 35 | public: 36 | /********************/ 37 | /* Basic parameters */ 38 | /********************/ 39 | 40 | /// Name of the input file 41 | std::string inputFile; 42 | 43 | /// Verbosity level 44 | int verbosity; 45 | 46 | /// Name of the output file 47 | std::string outputFile; 48 | 49 | /// Model type used by the application 50 | ModelType modelType; 51 | 52 | /// Operation mode of the application (i.e. what we are going to calculate) 53 | OperationMode operationMode; 54 | 55 | /***********************/ 56 | /* Advanced parameters */ 57 | /***********************/ 58 | 59 | /// Flag to denote whether we are using the edge-based measure for SBD 60 | bool useEdgeMeasure; 61 | 62 | /***************************/ 63 | /* Input/output parameters */ 64 | /***************************/ 65 | 66 | /// Input format for reading graphs 67 | GraphFormat inputFormat; 68 | 69 | /// Output format for writing graphs 70 | GraphFormat outputFormat; 71 | 72 | public: 73 | /// Constructor 74 | CommandLineArguments(const std::string programName = "netctrl", 75 | const std::string version = NETCTRL_VERSION_STRING); 76 | 77 | /// Adds an option to the list of command line options 78 | void addOption(int id, const char* option, SimpleOpt::ESOArgType type, 79 | const char* longOption = 0); 80 | 81 | /// Handles the option with the given ID and argument 82 | /** 83 | * \return zero if everything is OK, an exit code otherwise 84 | */ 85 | int handleOption(int id, const std::string& arg); 86 | 87 | /// Parses the command line arguments 88 | void parse(int argc, char** argv); 89 | 90 | /// Shows a help message on the given stream 91 | void showHelp(std::ostream& os) const; 92 | 93 | protected: 94 | /// Handles an option which takes a graph format as a parameter. 95 | int handleFormatOption(const std::string& arg, GraphFormat* pFormat); 96 | 97 | /// Shows the "General options" section from the help message 98 | void showGeneralOptionsHelp(std::ostream& os) const; 99 | }; 100 | 101 | #endif // _CMD_ARGUMENTS_BASE_H 102 | -------------------------------------------------------------------------------- /src/ui/graph_util.cpp: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include "graph_util.h" 7 | 8 | using namespace std; 9 | using namespace igraph; 10 | 11 | GraphFormat GraphUtil::formatFromString(const std::string& str) { 12 | if (str == "edgelist") 13 | return GRAPH_FORMAT_EDGELIST; 14 | else if (str == "ncol") 15 | return GRAPH_FORMAT_NCOL; 16 | else if (str == "lgl") 17 | return GRAPH_FORMAT_LGL; 18 | else if (str == "graphml") 19 | return GRAPH_FORMAT_GRAPHML; 20 | else if (str == "gml") 21 | return GRAPH_FORMAT_GML; 22 | else 23 | return GRAPH_FORMAT_UNKNOWN; 24 | } 25 | 26 | GraphFormat GraphUtil::detectFormat(const string& filename) { 27 | string::size_type idx = filename.rfind('.'); 28 | 29 | if (idx == std::string::npos) 30 | return GRAPH_FORMAT_UNKNOWN; 31 | 32 | string extension = filename.substr(idx + 1); 33 | transform(extension.begin(), extension.end(), extension.begin(), ::tolower); 34 | if (extension == "gml") 35 | return GRAPH_FORMAT_GML; 36 | if (extension == "ncol") 37 | return GRAPH_FORMAT_NCOL; 38 | if (extension == "lgl") 39 | return GRAPH_FORMAT_LGL; 40 | if (extension == "txt") 41 | return GRAPH_FORMAT_EDGELIST; 42 | if (extension == "graphml") 43 | return GRAPH_FORMAT_GRAPHML; 44 | 45 | return GRAPH_FORMAT_UNKNOWN; 46 | } 47 | 48 | Graph GraphUtil::readGraph(const string& filename, GraphFormat format, bool directed) { 49 | if (format == GRAPH_FORMAT_AUTO || format == GRAPH_FORMAT_UNKNOWN) 50 | format = GraphUtil::detectFormat(filename); 51 | 52 | FILE* fptr = fopen(filename.c_str(), "r"); 53 | if (fptr == NULL) { 54 | ostringstream oss; 55 | oss << "File not found: " << filename; 56 | throw runtime_error(oss.str()); 57 | } 58 | 59 | try { 60 | Graph result = readGraph(fptr, format, directed); 61 | fclose(fptr); 62 | return result; 63 | } catch (const UnknownGraphFormatException& ex) { 64 | throw UnknownGraphFormatException(filename); 65 | } 66 | } 67 | 68 | Graph GraphUtil::readGraph(FILE* fptr, GraphFormat format, bool directed) { 69 | Graph result; 70 | 71 | switch (format) { 72 | case GRAPH_FORMAT_EDGELIST: 73 | result = read_edgelist(fptr, 0, directed); 74 | break; 75 | 76 | case GRAPH_FORMAT_NCOL: 77 | result = read_ncol(fptr, true, IGRAPH_ADD_WEIGHTS_IF_PRESENT, directed); 78 | break; 79 | 80 | case GRAPH_FORMAT_LGL: 81 | result = read_lgl(fptr, false, IGRAPH_ADD_WEIGHTS_IF_PRESENT, directed); 82 | break; 83 | 84 | case GRAPH_FORMAT_GRAPHML: 85 | result = read_graphml(fptr); 86 | break; 87 | 88 | case GRAPH_FORMAT_GML: 89 | result = read_gml(fptr); 90 | break; 91 | 92 | default: 93 | throw UnknownGraphFormatException(); 94 | } 95 | 96 | return result; 97 | } 98 | 99 | void GraphUtil::writeGraph(FILE* fptr, const Graph& graph, GraphFormat format) { 100 | switch (format) { 101 | case GRAPH_FORMAT_GRAPHML: 102 | write_graphml(graph, fptr); 103 | break; 104 | 105 | case GRAPH_FORMAT_GML: 106 | write_gml(graph, fptr); 107 | break; 108 | 109 | default: 110 | throw UnknownGraphFormatException(); 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/ui/graph_util.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef _GRAPH_UTIL_H 4 | #define _GRAPH_UTIL_H 5 | 6 | #include 7 | #include 8 | 9 | /// Supported formats 10 | typedef enum { 11 | GRAPH_FORMAT_AUTO, 12 | GRAPH_FORMAT_UNKNOWN, 13 | GRAPH_FORMAT_EDGELIST, 14 | GRAPH_FORMAT_NCOL, 15 | GRAPH_FORMAT_LGL, 16 | GRAPH_FORMAT_GRAPHML, 17 | GRAPH_FORMAT_GML 18 | } GraphFormat; 19 | 20 | /// Exception thrown when the format of a graph is unknown 21 | class UnknownGraphFormatException : public std::runtime_error { 22 | private: 23 | std::string m_filename; 24 | 25 | public: 26 | explicit UnknownGraphFormatException(const std::string& filename = "") : 27 | std::runtime_error("unknown graph format"), m_filename(filename) {} 28 | ~UnknownGraphFormatException() throw() {} 29 | }; 30 | 31 | class GraphUtil { 32 | public: 33 | /// Tries to detect the format of a graph from its filename 34 | static GraphFormat detectFormat(const std::string& filename); 35 | 36 | /// Converts a string into the corresponding GraphFormat constant 37 | static GraphFormat formatFromString(const std::string& str); 38 | 39 | /// Reads a graph without having to know what format it is in 40 | static igraph::Graph readGraph(const std::string& filename, 41 | GraphFormat format = GRAPH_FORMAT_AUTO, 42 | bool directed = true); 43 | 44 | /// Reads a graph from the given stream using the given format 45 | static igraph::Graph readGraph(FILE* fptr, GraphFormat format, 46 | bool directed = true); 47 | 48 | /// Writes a graph to the given stream using the given format 49 | static void writeGraph(FILE* fptr, const igraph::Graph& graph, GraphFormat format); 50 | }; 51 | 52 | #endif // _GRAPH_UTIL_H 53 | -------------------------------------------------------------------------------- /src/ui/logging.h: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef _LOGGING_H 4 | 5 | #include 6 | 7 | #define LOGGING_FUNCTION(funcname, level) \ 8 | void funcname(const char* format, ...) { \ 9 | va_list arglist; \ 10 | if (m_args.verbosity < level) \ 11 | return; \ 12 | va_start(arglist, format); \ 13 | vfprintf(stderr, format, arglist); \ 14 | fprintf(stderr, "\n"); \ 15 | va_end(arglist); \ 16 | } 17 | 18 | #endif // _LOGGING_H 19 | -------------------------------------------------------------------------------- /src/ui/main.cpp: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "cmd_arguments.h" 16 | #include "graph_util.h" 17 | #include "logging.h" 18 | 19 | using namespace igraph; 20 | using namespace netctrl; 21 | 22 | 23 | /// Helper function to split a string around a delimiter character 24 | std::vector &split(const std::string& s, char delim, 25 | std::vector& elems) { 26 | std::stringstream ss(s); 27 | std::string item; 28 | while (std::getline(ss, item, delim)) { 29 | elems.push_back(item); 30 | } 31 | return elems; 32 | } 33 | 34 | /// Helper function to split a string around a delimiter character 35 | std::vector split(const std::string& s, char delim) { 36 | std::vector elems; 37 | return split(s, delim, elems); 38 | } 39 | 40 | class NetworkControllabilityApp { 41 | private: 42 | /// Parsed command line arguments 43 | CommandLineArguments m_args; 44 | 45 | /// The C-style output file object where the results will be written 46 | FILE* m_outputFileObject; 47 | 48 | /// Graph being analyzed by the UI 49 | std::unique_ptr m_pGraph; 50 | 51 | /// Controllability model being calculated on the graph 52 | std::unique_ptr m_pModel; 53 | 54 | /// The C++-style output stream where the results will be written 55 | std::ostream* m_pOutputStream; 56 | 57 | public: 58 | LOGGING_FUNCTION(debug, 2); 59 | LOGGING_FUNCTION(info, 1); 60 | LOGGING_FUNCTION(error, 0); 61 | 62 | /// Constructor 63 | NetworkControllabilityApp() : m_outputFileObject(0), m_pOutputStream(0) {} 64 | 65 | /// Destructor 66 | ~NetworkControllabilityApp() { 67 | if (m_outputFileObject != 0 && m_outputFileObject != stdout) { 68 | fclose(m_outputFileObject); 69 | } 70 | 71 | if (m_pOutputStream != 0 && m_pOutputStream != &std::cout) { 72 | delete m_pOutputStream; 73 | } 74 | } 75 | 76 | /// Returns the C-style output file object where the results should be written 77 | FILE* getOutputFileObject() { 78 | if (m_outputFileObject == 0) { 79 | if (isWritingToStandardOutput()) { 80 | m_outputFileObject = stdout; 81 | } else { 82 | m_outputFileObject = fopen(m_args.outputFile.c_str(), "w"); 83 | if (m_outputFileObject == 0) { 84 | error("cannot open output file for writing: %s", 85 | m_args.outputFile.c_str()); 86 | exit(3); 87 | } 88 | } 89 | } 90 | return m_outputFileObject; 91 | } 92 | 93 | /// Returns the C++-style output stream where the results should be written 94 | std::ostream& getOutputStream() { 95 | if (m_pOutputStream == 0) { 96 | if (isWritingToStandardOutput()) { 97 | m_pOutputStream = &std::cout; 98 | } else { 99 | m_pOutputStream = new std::ofstream(m_args.outputFile.c_str()); 100 | if (m_pOutputStream->fail()) { 101 | error("cannot open output file for writing: %s", 102 | m_args.outputFile.c_str()); 103 | exit(3); 104 | } 105 | } 106 | } 107 | return *m_pOutputStream; 108 | } 109 | 110 | /// Returns whether we are writing to the standard output 111 | bool isWritingToStandardOutput() { 112 | return m_args.outputFile.empty() || m_args.outputFile == "-"; 113 | } 114 | 115 | /// Returns whether we are running in quiet mode 116 | bool isQuiet() { 117 | return m_args.verbosity < 1; 118 | } 119 | 120 | /// Returns whether we are running in verbose mode 121 | bool isVerbose() { 122 | return m_args.verbosity > 1; 123 | } 124 | 125 | /// Loads a graph from the given file 126 | /** 127 | * If the name of the file is "-", the file is assumed to be the 128 | * standard input. 129 | */ 130 | std::unique_ptr loadGraph(const std::string& filename, GraphFormat format) { 131 | std::unique_ptr result; 132 | 133 | if (filename == "-") { 134 | // Loading graph from standard input 135 | if (format == GRAPH_FORMAT_AUTO) 136 | format = GRAPH_FORMAT_EDGELIST; 137 | result.reset(new Graph(GraphUtil::readGraph(stdin, format))); 138 | } else if (filename.find("://") != filename.npos) { 139 | // Generating graph from model 140 | size_t pos = filename.find("://"); 141 | std::string model = filename.substr(0, pos); 142 | std::vector params = split(filename.substr(pos+3), ','); 143 | 144 | if (model == "er") { 145 | // Erdos-Renyi network 146 | if (params.size() < 2) { 147 | error("ER generator requires two or three arguments: number of nodes, " 148 | "average degree and directedness (optional)"); 149 | return result; // points to null 150 | } 151 | 152 | long int n = atoi(params[0].c_str()); 153 | float k = atof(params[1].c_str()); 154 | bool directed = true; 155 | if (params.size() >= 3) { 156 | directed = atoi(params[2].c_str()) != 0; 157 | } 158 | return erdos_renyi_game_gnm(n, directed ? n*k : n*k/2.0, 159 | directed, false); 160 | } else { 161 | error("Unknown graph generator: %s", model.c_str()); 162 | return result; // points to null 163 | } 164 | } else { 165 | // Loading graph from file 166 | result.reset(new Graph(GraphUtil::readGraph(filename, format))); 167 | result->setAttribute("filename", filename); 168 | } 169 | 170 | return result; 171 | } 172 | 173 | /// Runs the user interface 174 | int run(int argc, char** argv) { 175 | int retval; 176 | 177 | m_args.parse(argc, argv); 178 | 179 | info(">> loading graph: %s", m_args.inputFile.c_str()); 180 | m_pGraph = loadGraph(m_args.inputFile, m_args.inputFormat); 181 | if (m_pGraph.get() == NULL) 182 | return 2; 183 | 184 | info(">> graph is %s and has %ld vertices and %ld edges", 185 | m_pGraph->isDirected() ? "directed" : "undirected", 186 | (long)m_pGraph->vcount(), (long)m_pGraph->ecount()); 187 | 188 | switch (m_args.modelType) { 189 | case LIU_MODEL: 190 | m_pModel.reset(new LiuControllabilityModel(m_pGraph.get())); 191 | break; 192 | case SWITCHBOARD_MODEL: 193 | { 194 | SwitchboardControllabilityModel* sbdModel; 195 | sbdModel = new SwitchboardControllabilityModel(m_pGraph.get()); 196 | sbdModel->setControllabilityMeasure( 197 | m_args.useEdgeMeasure ? 198 | SwitchboardControllabilityModel::EDGE_MEASURE : 199 | SwitchboardControllabilityModel::NODE_MEASURE 200 | ); 201 | m_pModel.reset(sbdModel); 202 | } 203 | break; 204 | } 205 | 206 | switch (m_args.operationMode) { 207 | case MODE_CONTROL_PATHS: 208 | retval = runControlPaths(); 209 | break; 210 | 211 | case MODE_DRIVER_NODES: 212 | retval = runDriverNodes(); 213 | break; 214 | 215 | case MODE_GRAPH: 216 | retval = runGraph(); 217 | break; 218 | 219 | case MODE_STATISTICS: 220 | retval = runStatistics(); 221 | break; 222 | 223 | case MODE_SIGNIFICANCE: 224 | retval = runSignificance(); 225 | break; 226 | 227 | default: 228 | retval = 1; 229 | } 230 | 231 | if (!retval && !isWritingToStandardOutput()) { 232 | info(">> results were written to %s", m_args.outputFile.c_str()); 233 | } 234 | 235 | return retval; 236 | } 237 | 238 | /// Runs the control path calculation mode 239 | int runControlPaths() { 240 | info(">> calculating control paths"); 241 | m_pModel->calculate(); 242 | 243 | std::vector paths = m_pModel->controlPaths(); 244 | std::ostream& out = getOutputStream(); 245 | 246 | info(">> found %d control path(s)", paths.size()); 247 | for (std::vector::const_iterator it = paths.begin(); 248 | it != paths.end(); it++) { 249 | out << (*it)->toString() << '\n'; 250 | } 251 | 252 | return 0; 253 | } 254 | 255 | /// Runs the driver node calculation mode 256 | int runDriverNodes() { 257 | info(">> calculating control paths and driver nodes"); 258 | m_pModel->calculate(); 259 | 260 | VectorInt driver_nodes = m_pModel->driverNodes(); 261 | std::ostream& out = getOutputStream(); 262 | 263 | info(">> found %d driver node(s)", driver_nodes.size()); 264 | for (VectorInt::const_iterator it = driver_nodes.begin(); it != driver_nodes.end(); it++) { 265 | any name(m_pGraph->vertex(*it).getAttribute("name", (long int)*it)); 266 | if (name.type() == typeid(std::string)) { 267 | out << name.as() << '\n'; 268 | } else { 269 | out << name.as() << '\n'; 270 | } 271 | } 272 | 273 | return 0; 274 | } 275 | 276 | /// Runs the annotated graph output mode 277 | int runGraph() { 278 | long int i, j, n; 279 | 280 | info(">> calculating control paths and driver nodes"); 281 | m_pModel->calculate(); 282 | 283 | VectorInt driver_nodes = m_pModel->driverNodes(); 284 | std::vector paths = m_pModel->controlPaths(); 285 | info(">> found %d driver node(s) and %d control path(s)", 286 | driver_nodes.size(), paths.size()); 287 | 288 | info(">> classifying edges"); 289 | std::vector edge_classes = m_pModel->edgeClasses(); 290 | 291 | // Mark the driver nodes 292 | for (VectorInt::const_iterator it = driver_nodes.begin(); it != driver_nodes.end(); it++) { 293 | m_pGraph->vertex(*it).setAttribute("is_driver", true); 294 | } 295 | 296 | // Mark the edge types and path indices 297 | j = 0; 298 | for (std::vector::const_iterator it = paths.begin(); 299 | it != paths.end(); it++, j++) { 300 | const igraph::VectorInt& vec = (*it)->edges(*m_pGraph.get()); 301 | n = vec.size(); 302 | for (i = 0; i < n; i++) { 303 | igraph::Edge edge = m_pGraph->edge(vec[i]); 304 | edge.setAttribute("path_type", (*it)->name()); 305 | edge.setAttribute("path_indices", j); 306 | edge.setAttribute("path_order", i); 307 | } 308 | } 309 | 310 | // Mark the edge classes 311 | n = m_pGraph->ecount(); 312 | for (i = 0; i < n; i++) { 313 | igraph::Edge edge = m_pGraph->edge(i); 314 | edge.setAttribute("edge_class", edgeClassToString(edge_classes[i])); 315 | } 316 | 317 | // Print the graph 318 | GraphUtil::writeGraph(getOutputFileObject(), (*m_pGraph.get()), m_args.outputFormat); 319 | 320 | return 0; 321 | } 322 | 323 | /// Runs the signficance calculation mode 324 | int runSignificance() { 325 | size_t observedDriverNodeCount; 326 | size_t numTrials = 100, i; 327 | float numNodes = m_pGraph->vcount(); 328 | float controllability; 329 | Vector counts; 330 | std::ostream& out = getOutputStream(); 331 | 332 | info(">> calculating control paths and driver nodes"); 333 | m_pModel->calculate(); 334 | 335 | observedDriverNodeCount = m_pModel->driverNodes().size(); 336 | controllability = m_pModel->controllability(); 337 | 338 | info(">> found %d driver node(s)", observedDriverNodeCount); 339 | out << "Observed\t" << controllability << '\n'; 340 | 341 | // Testing Erdos-Renyi null model 342 | info(">> testing Erdos-Renyi null model"); 343 | counts.clear(); 344 | for (i = 0; i < numTrials; i++) { 345 | std::unique_ptr graph = igraph::erdos_renyi_game_gnm( 346 | numNodes, m_pGraph->ecount(), 347 | m_pGraph->isDirected(), false); 348 | 349 | std::unique_ptr pModel(m_pModel->clone()); 350 | pModel->setGraph(graph.get()); 351 | pModel->calculate(); 352 | 353 | counts.push_back(pModel->controllability()); 354 | } 355 | counts.sort(); 356 | out << "ER\t" << counts.sum() / counts.size() << '\n'; 357 | 358 | // Testing configuration model 359 | VectorInt inDegrees, outDegrees; 360 | m_pGraph->degree(&outDegrees, V(m_pGraph.get()), IGRAPH_OUT, true); 361 | m_pGraph->degree(&inDegrees, V(m_pGraph.get()), IGRAPH_IN, true); 362 | 363 | info(">> testing configuration model (preserving joint degree distribution)"); 364 | counts.clear(); 365 | for (i = 0; i < numTrials; i++) { 366 | std::unique_ptr graph = 367 | igraph::degree_sequence_game(outDegrees, inDegrees, 368 | IGRAPH_DEGSEQ_CONFIGURATION); 369 | 370 | std::unique_ptr pModel(m_pModel->clone()); 371 | pModel->setGraph(graph.get()); 372 | pModel->calculate(); 373 | 374 | counts.push_back(pModel->controllability()); 375 | } 376 | counts.sort(); 377 | out << "Configuration\t" << counts.sum() / counts.size() << '\n'; 378 | 379 | // Testing configuration model 380 | info(">> testing configuration model (destroying joint degree distribution)"); 381 | counts.clear(); 382 | for (i = 0; i < numTrials; i++) { 383 | inDegrees.shuffle(); 384 | outDegrees.shuffle(); 385 | 386 | std::unique_ptr graph = 387 | igraph::degree_sequence_game(outDegrees, inDegrees, 388 | IGRAPH_DEGSEQ_CONFIGURATION); 389 | 390 | std::unique_ptr pModel(m_pModel->clone()); 391 | pModel->setGraph(graph.get()); 392 | pModel->calculate(); 393 | 394 | counts.push_back(pModel->controllability()); 395 | } 396 | counts.sort(); 397 | out << "Configuration_no_joint\t" << counts.sum() / counts.size() << '\n'; 398 | 399 | return 0; 400 | } 401 | 402 | /// Runs the general statistics calculation mode 403 | int runStatistics() { 404 | float n = m_pGraph->vcount(); 405 | float m = m_pGraph->ecount(); 406 | long int num_driver; 407 | long int num_redundant = 0, num_ordinary = 0, num_critical = 0, num_distinguished = 0; 408 | std::ostream& out = getOutputStream(); 409 | 410 | info(">> calculating control paths and driver nodes"); 411 | m_pModel->calculate(); 412 | num_driver = m_pModel->driverNodes().size(); 413 | 414 | info(">> classifying edges"); 415 | std::vector edge_classes = m_pModel->edgeClasses(); 416 | if (edge_classes.size() == m && !edge_classes.empty()) { 417 | for (long int i = 0; i < m; i++) { 418 | if (edge_classes[i] == EDGE_REDUNDANT) 419 | num_redundant++; 420 | else if (edge_classes[i] == EDGE_ORDINARY) 421 | num_ordinary++; 422 | else if (edge_classes[i] == EDGE_DISTINGUISHED) 423 | num_distinguished++; 424 | else 425 | num_critical++; 426 | } 427 | } 428 | 429 | info(">> order is as follows:"); 430 | info(">> driver nodes; distinguished, redundant, ordinary, critical edges"); 431 | 432 | out << num_driver << ' ' 433 | << num_distinguished << ' ' 434 | << num_redundant << ' ' 435 | << num_ordinary << ' ' 436 | << num_critical << '\n'; 437 | out << num_driver / n << ' ' 438 | << num_distinguished / m << ' ' 439 | << num_redundant / m << ' ' 440 | << num_ordinary / m << ' ' 441 | << num_critical / m << '\n'; 442 | 443 | return 0; 444 | } 445 | 446 | }; 447 | 448 | int main(int argc, char** argv) { 449 | NetworkControllabilityApp app; 450 | 451 | igraph::AttributeHandler::attach(); 452 | return app.run(argc, argv); 453 | } 454 | 455 | -------------------------------------------------------------------------------- /src/ui/walker_sampling.hpp: -------------------------------------------------------------------------------- 1 | /* vim:set ts=4 sw=4 sts=4 et: */ 2 | 3 | #ifndef _WALKER_SAMPLING_HPP 4 | #define _WALKER_SAMPLING_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * Efficient random sampling with replacement using Walker's alias method. 13 | * 14 | * Make sure to initialize the random number generator by calling \c srand 15 | * before using this class! 16 | */ 17 | class WalkerSampling { 18 | 19 | private: 20 | /** 21 | * Vector specifying the index of the "other" element that is contained 22 | * in a given bin. 23 | */ 24 | std::vector m_indexes; 25 | 26 | /** 27 | * Vector specifying the probability of drawing the "own" and not the "other" 28 | * element from a given bin. 29 | */ 30 | std::vector m_probs; 31 | 32 | public: 33 | 34 | /** 35 | * Constructs a new, uninitialized sampler. 36 | * 37 | * You must initialize the sampler by calling \c initialize() before 38 | * using it. 39 | */ 40 | WalkerSampling() : m_indexes(), m_probs() {} 41 | 42 | /** 43 | * Constructs a new sampler whose weights are initialized from the 44 | * given container. 45 | * 46 | * \param begin the beginning of the container 47 | * \param end the end of the container 48 | */ 49 | template 50 | WalkerSampling(InputIterator begin, InputIterator end) : m_indexes(), m_probs() { 51 | initialize(begin, end); 52 | } 53 | 54 | /** 55 | * Initializes the sampler from the weights in the given container. 56 | * 57 | * \param begin the beginning of the container 58 | * \param end the end of the container 59 | */ 60 | template 61 | void initialize(InputIterator begin, InputIterator end); 62 | 63 | /** 64 | * Draws a given number of samples from the sampler and writes them to 65 | * an iterator. 66 | * 67 | * \param n the number of samples to draw 68 | * \param it an output iterator to write the results to. 69 | */ 70 | template 71 | void sample(OutputIterator it, size_t n=1) const; 72 | }; 73 | 74 | 75 | template 76 | void WalkerSampling::initialize(InputIterator begin, InputIterator end) { 77 | InputIterator it; 78 | const double sum = std::accumulate(begin, end, 0.0); 79 | size_t n = end-begin, i; 80 | std::vector shortIndexes; 81 | std::vector longIndexes; 82 | std::vector::iterator it2; 83 | 84 | // Initialize m_probs and m_indexes 85 | m_probs.resize(n); 86 | m_indexes.resize(n); 87 | std::fill(m_indexes.begin(), m_indexes.end(), -1); 88 | 89 | // Normalize the probabilities 90 | for (it = begin, it2 = m_probs.begin(); it != end; ++it, ++it2) { 91 | *it2 = (*it / sum) * n; 92 | } 93 | 94 | // Initialize shortIndexes and longIndexes 95 | for (i = 0, it2 = m_probs.begin(); it2 != m_probs.end(); ++i, ++it2) { 96 | if (*it2 < 1) { 97 | shortIndexes.push_back(i); 98 | } else if (*it2 > 1) { 99 | longIndexes.push_back(i); 100 | } 101 | } 102 | 103 | // Prepare the tables 104 | while (!shortIndexes.empty() && !longIndexes.empty()) { 105 | long int shortIndex = shortIndexes.pop_back(); 106 | long int longIndex = longIndexes.back(); 107 | m_indexes[shortIndex] = longIndex; 108 | m_probs[longIndex] -= (1 - m_probs[shortIndex]); 109 | if (m_probs[longIndex] < 1) { 110 | shortIndexes.push_back(longIndex); 111 | longIndexes.pop_back(); 112 | } 113 | } 114 | } 115 | 116 | template 117 | void WalkerSampling::sample(OutputIterator it, size_t n) const { 118 | double u; 119 | long int j; 120 | size_t m = m_probs.size(); 121 | 122 | while (n > 0) { 123 | u = rand() / static_cast(RAND_MAX); 124 | j = rand() % m; 125 | *it = (u < m_probs[j]) ? j : m_indexes[j]; 126 | n--; ++it; 127 | } 128 | } 129 | 130 | #endif // _WALKER_SAMPLING_HPP 131 | --------------------------------------------------------------------------------