pax_global_header00006660000000000000000000000064152043753120014514gustar00rootroot0000000000000052 comment=8518deb1b0bfad3fc6ac5a49858acc758ee62026 vtraag-libleidenalg-8518deb/000077500000000000000000000000001520437531200160335ustar00rootroot00000000000000vtraag-libleidenalg-8518deb/CHANGELOG000066400000000000000000000052451520437531200172530ustar00rootroot000000000000000.13.0 - Added support for minimum community size. 0.12.0 - Update C core to 1.0 0.11.1 - Corrected CMake export targets 0.11.0 - Corrected include paths - Removed empty Graph::Graph constructor (PR #6) - Explicitly set C++ version (PR #4) 0.10.0 - Separated C++ core into this external library, Python interface remains available at https://github.com/vtraag/leidenalg. 0.9.0 - Update C core to 0.10.1 0.8.10 - Fixed installation from source package (issue #101) 0.8.9 - Fixed bug with renaming of python-igraph to igraph (issue #93) - Removed irrelevant node_sizes argument for RBConfigurationVertexPartition and ModularityVertexPartition - Improved documentation 0.8.8 - Corrected relabeling bug (PR #82) - Improved error handling, avoiding some crashses (issue #81) 0.8.7 - Improved numerical stability 0.8.6 - Removed accidentally left DEBUG statement 0.8.5 - Corrected iterating over nodes (PR #70). - Fixed segfault with move_nodes_constrained (issue #68) - Fixed problem with initial_membership (issue #66) 0.8.4 - Update C core to 0.9.1 - Fixed caching problem (issue #62) - Fixed missing node_sizes for modularity (issue #60) 0.8.3 - Fixed missing parameter in find_partition_multiplex by @TomKellyGenetics (PR #50) 0.8.2 - New option to constrain community size by @orenbenkiki (PR #46) - Great performance improvement by @ragibson (PR #40) - Minor improvements and clarifications 0.8.1 - Fixed performance problem (issue #35) - Improved documentation 0.8.0 - New option to keep some nodes "fixed" by @iosonofabio (PR #8, #9) - Corrected bipartite clustering - Corrected some documentation - Several minor bugfixes 0.6.1 - Minor corrections to documentation - Added doctest to examples in documentation - Removed trailing semicolons throughout code - Corrected some errors in CPMVertexPartition.Bipartite 0.6.0 - Major API changes, now exposing actual classes and optimisation routine. - Improved algorithm, now runs faster and finds better solutions. - Improved error handling, doing more type checking. - Improved documentation throughout, now done using Sphinx and available from readthedocs.org. - Now includes testing module, available through python setup.py test. 0.5.3 - Fixed bug concerning weights (were rounded to integers). - Improved documentation. - Included an HOWTO on extending the current package. - Fixed some minor bugs. 0.5.2 - Ensured that random neighbour selection works in O(1) rather than O(k), with k the average number of neighbours. - Optimized the calculation of weight from/to community. - Included some missing references. 0.5.1 Corrected some mistakes which prevented it from being properly used on PyPi. No serious changes were made. 0.5 Initial release vtraag-libleidenalg-8518deb/CMakeLists.txt000066400000000000000000000036271520437531200206030ustar00rootroot00000000000000cmake_minimum_required (VERSION 3.23) # Add etc/cmake to CMake's search path so we can put our private stuff there list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/etc/cmake) include(version) project(libleidenalg VERSION ${PACKAGE_VERSION_BASE}) include(GNUInstallDirs) include(CMakePackageConfigHelpers) include(packaging) find_package(igraph 1 CONFIG REQUIRED) set_target_properties(igraph::igraph PROPERTIES INTERFACE_LINK_LIBRARIES "" ) # Set C++ standard version set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True) # Set default symbol visibility to hidden set(CMAKE_C_VISIBILITY_PRESET hidden) set(CMAKE_CXX_VISIBILITY_PRESET hidden) # Expose the BUILD_SHARED_LIBS option in the ccmake UI option(BUILD_SHARED_LIBS "Build shared libraries" ON) add_subdirectory(src) add_subdirectory(include) configure_package_config_file( "${PROJECT_SOURCE_DIR}/libleidenalgConfig.cmake.in" "${PROJECT_BINARY_DIR}/libleidenalgConfig.cmake" INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/libleidenalg" PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/libleidenalgConfigVersion.cmake" COMPATIBILITY SameMinorVersion) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/libleidenalgConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/libleidenalgConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/libleidenalg" COMPONENT dev ) export( TARGETS libleidenalg FILE "${PROJECT_BINARY_DIR}/libleidenalgTargets.cmake") export(PACKAGE libleidenalg) install( TARGETS libleidenalg EXPORT libleidenalgTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib FILE_SET headers DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libleidenalg" COMPONENT dev ) install( EXPORT libleidenalgTargets FILE libleidenalgTargets.cmake NAMESPACE libleidenalg:: DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/libleidenalg" ) vtraag-libleidenalg-8518deb/CMakePresets.json000066400000000000000000000051451520437531200212610ustar00rootroot00000000000000{ "version": 3, "cmakeMinimumRequired": { "major": 3, "minor": 23, "patch": 0 }, "configurePresets": [ { "name": "ninja", "displayName": "Ninja Configure Settings", "description": "Sets build and install directories", "binaryDir": "${sourceDir}/builds/${presetName}", "generator": "Ninja" }, { "name": "ninja-toolchain", "displayName": "Ninja Configure Settings with toolchain", "description": "Sets build and install directories", "binaryDir": "${sourceDir}/builds/${presetName}-toolchain", "generator": "Ninja", "toolchainFile": "$env{TOOLCHAINFILE}" }, { "name": "ninja-multi-vcpkg", "displayName": "Ninja Multi-Config Configure Settings", "description": "Configure with vcpkg toolchain", "binaryDir": "${sourceDir}/builds/${presetName}", "generator": "Ninja Multi-Config", "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": { "type": "FILEPATH", "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" } } }, { "name": "msbuild-vcpkg", "displayName": "MSBuild (vcpkg toolchain) Configure Settings", "description": "Configure with VS generators and with vcpkg toolchain", "binaryDir": "${sourceDir}/builds/${presetName}", "generator": "Visual Studio 17 2022", "architecture": { "strategy": "set", "value": "x64" }, "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": { "type": "FILEPATH", "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" } } } ], "buildPresets": [ { "name": "ninja", "configurePreset": "ninja", "displayName": "Build with Ninja", "description": "Build with Ninja" }, { "name": "ninja-multi-vcpkg", "configurePreset": "ninja-multi-vcpkg", "displayName": "Build ninja-multi-vcpkg", "description": "Build ninja-multi-vcpkg Configurations" }, { "name": "ninja-toolchain", "configurePreset": "ninja-toolchain", "displayName": "Build ninja-toolchain", "description": "Build ninja with a toolchain" }, { "name": "msbuild-vcpkg", "configurePreset": "msbuild-vcpkg", "displayName": "Build MSBuild", "description": "Build with MSBuild (VS)" } ], "testPresets": [ { "name": "ninja", "configurePreset": "ninja" }, { "name": "ninja-multi-vcpkg", "configurePreset": "ninja-multi-vcpkg" }, { "name": "default-vs", "configurePreset": "msbuild-vcpkg" } ] }vtraag-libleidenalg-8518deb/CONTRIBUTING.md000066400000000000000000000033261520437531200202700ustar00rootroot00000000000000# Contribute ## Issues Bug reports and other questions are always welcome at the issue tracker of this repository: https://github.com/vtraag/libleidenalg/issues. Please make sure you provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) to help solve the issue. ## Pull Requests If you want to fix a bug, Pull Requests are also always welcome. New features are of course also welcome as Pull Request. Before embarking, please first open an issue with the proposed feature. If you want to implement a new community detection quality function, while using the Leiden algorithm to optimise it, please read the next section carefully. ## Implement new community detection methods All methods derive from `MutableVertexPartition`, which implements almost all necessary details, such as moving actual nodes while maintaining the internal administration. Similarly, it provides all the necessary functionality for initialising a partition. Additionally, there are two abstract classes that derive from this base `ResolutionParameterVertexPartition` and `LinearResolutionParameterVertexPartition` (which in turn derives from the former class). If you want a method with a resolution parameter, you should derive from one of these two classes, otherwise, simply from the base class `MutableVertexPartition`. There are two functions that you need to implement yourself: `diff_move` and `quality`. Note that they should always be consistent, so that we can double check the internal consistency. You should also ensure that the `diff_move` function can be correctly used on any aggregate graph (i.e. moving a node in the aggregate graph indeed corresponds to moving a set of nodes in the individual graph).vtraag-libleidenalg-8518deb/LICENSE000066400000000000000000001045051520437531200170450ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . vtraag-libleidenalg-8518deb/README.md000066400000000000000000000171661520437531200173250ustar00rootroot00000000000000# Introduction This package implements the Leiden algorithm in `C++`. It relies on `igraph` for it to function. Besides the relative flexibility of the implementation, it also scales well, and can be run on graphs of millions of nodes (as long as they can fit in memory). The core class is `Optimiser` which finds the optimal partition using the Leiden algorithm[^1], which is an extension of the Louvain algorithm[^2] for a number of different methods. The methods currently implemented are (1) modularity[^3], (2) Reichardt and Bornholdt's model using the configuration null model and the Erdös-Rényi null model[^4], (3) the Constant Potts model (CPM) [^5], (4) Significance [^6], and finally (5) Surprise [^7]. In addition, it supports multiplex partition optimisation allowing community detection on for example negative links [^8] or multiple time slices [^9]. There is the possibility of only partially optimising a partition, so that some community assignments remain fixed [^10]. It also provides some support for community detection on bipartite graphs. This package contains the `C++` code only. Most people will find it easier to work with the Python interface at https://github.com/vtraag/leidenalg or the Elixir interface at https://github.com/georgeguimaraes/leidenfold. # Installation The build system uses CMake and follows the prototypical CMake build steps: 1. Get the source code. You can download the source code from the latest release at https://github.com/vtraag/libleidenalg/releases. Alternatively, you can clone the repository using `git`. 2. Create a build directory You can create a build directory anywhere. A common location is to create a subdirectory in the source code as ``` mkdir build ``` 3. Configure the build system Assuming you created the build directory as a subdirectory, you can run the following ``` cmake .. ``` Note that the build directory should be your current working directory. 4. Build the library ``` cmake --build . ``` 5. Install the library ``` cmake --build . --target install ``` You can change the installation location of `libleidenalg` using [`CMAKE_INSTALL_PREFIX`](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html) as usual. You can change whether a static or dynamic library should be built using [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html) as usual. This library depends on `igraph`, which you should install before. See https://igraph.org/c/doc/igraph-Installation.html for more details. If you have installed `igraph` in a non-standard location, CMake might not be able to find it automatically. If you use `CMAKE_INSTALL_PATH=` to install `igraph`, you can specify the []`CMAKE_PREFIX_PATH=`](https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html) when configuring `libleidenalg` to find `igraph`. # Usage The `Optimiser` class is responsible for optimising a `MutableVertexPartition` (possibly multiple in the case of a multiplex approach). The `MutableVertexPartition` is just a base class, and should be implemented to provide explicit quality function: - `CPMVertexPartition` - `ModularityVertexPartition` - `RBConfigurationVertexPartition` - `RBERVertexPartition` - `SignificanceVertexPartition` - `SurpriseVertexPartition` Some of these classes depend on intermediate derived classes. The implementation of a quality function in a derived class, essentially comes to down implementing the `diff_move` and `quality`, see also [`CONTRIBUTING.md`](CONTRIBUTING.md) in this repository. An `igraph_t` object from `igraph` is used to construct a separate `Graph` object, which can be used to construct a `MutableVertexPartition`. For example, to find a partition using CPM, you could do the following ```C igraph_t g; igraph_famous(&g, "Zachary"); Graph graph(&g); CPMVertexPartition part(&graph, 0.05 /* resolution */ ); Optimiser o; o.optimise_partition(&part); ``` In the `example` directory, we added a complete example, including the CMake build files (please note that this directory is not included in the release source package, and only available on the GitHub repository). In order to compile it, first make sure you have properly installed `igraph` and `libleidenalg`. In the `example` directory, you can then follow the standard CMake routine to build it ```bash mkdir build && cd build cmake .. cmake --build . ``` and run `./example`. Licence ------- Copyright (C) 2020 V.A. Traag, (C) 2022 Andrew Robbins This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. # References Please cite the references appropriately in case they are used. [^1]: Traag, V.A., Waltman. L., Van Eck, N.-J. (2018). From Louvain to Leiden: guaranteeing well-connected communities. Scientific reports, 9(1), 5233. [10.1038/s41598-019-41695-z](http://dx.doi.org/10.1038/s41598-019-41695-z) [^2]: Blondel, V. D., Guillaume, J.-L., Lambiotte, R., & Lefebvre, E. (2008). Fast unfolding of communities in large networks. Journal of Statistical Mechanics: Theory and Experiment, 10008(10), 6. [10.1088/1742-5468/2008/10/P10008](http://doi.org/10.1088/1742-5468/2008/10/P10008) [^3]: Newman, M. E. J., & Girvan, M. (2004). Finding and evaluating community structure in networks. Physical Review E, 69(2), 026113. [10.1103/PhysRevE.69.026113](http://doi.org/10.1103/PhysRevE.69.026113) [^4]: Reichardt, J., & Bornholdt, S. (2006). Statistical mechanics of community detection. Physical Review E, 74(1), 016110. [10.1103/PhysRevE.74.016110](http://doi.org/10.1103/PhysRevE.74.016110) [^5]: Traag, V. A., Van Dooren, P., & Nesterov, Y. (2011). Narrow scope for resolution-limit-free community detection. Physical Review E, 84(1), 1. [10.1103/PhysRevE.84.016114](http://doi.org/10.1103/PhysRevE.84.16114) [^6]: Traag, V. A., Krings, G., & Van Dooren, P. (2013). Significant scales in community structure. Scientific Reports, 3, 2930. [10.1038/srep02930](http://doi.org/10.1038/srep02930) [^7]: Traag, V. A., Aldecoa, R., & Delvenne, J.-C. (2015). Detecting communities using asymptotical surprise. Physical Review E, 92(2), 1. [10.1103/PhysRevE.92.022816](http://doi.org/10.1103/PhysRevE.92.022816) [^8]: Traag, V. A., & Bruggeman, J. (2009). Community detection in networks with positive and negative links. Physical Review E, 80(3), 036115. [10.1103/PhysRevE.80.036115](http://doi.org/10.1103/PhysRevE.80.036115) [^9]: Mucha, P. J., Richardson, T., Macon, K., Porter, M. A., & Onnela, J.-P. (2010). Community structure in time-dependent, multiscale, and multiplex networks. Science, 328(5980), 876–8. [10.1126/science.1184819](http://doi.org/10.1126/science.1184819) [^10]: Zanini, F., Berghuis, B. A., Jones, R. C., Robilant, B. N. di, Nong, R. Y., Norton, J., Clarke, Michael F., Quake, S. R. (2019). northstar: leveraging cell atlases to identify healthy and neoplastic cells in transcriptomes from human tumors. BioRxiv, 820928. [10.1101/820928](https://doi.org/10.1101/820928) vtraag-libleidenalg-8518deb/etc/000077500000000000000000000000001520437531200166065ustar00rootroot00000000000000vtraag-libleidenalg-8518deb/etc/arith_apple_m1.h000066400000000000000000000003021520437531200216370ustar00rootroot00000000000000#define IEEE_8087 #define Arith_Kind_ASL 1 #define Long int #define Intcast (int)(long) #define Double_Align #define X64_bit_pointers #define NANCHECK #define QNaN0 0x0 #define QNaN1 0x7ff80000 vtraag-libleidenalg-8518deb/etc/cmake/000077500000000000000000000000001520437531200176665ustar00rootroot00000000000000vtraag-libleidenalg-8518deb/etc/cmake/GetGitRevisionDescription.cmake000066400000000000000000000114501520437531200257770ustar00rootroot00000000000000# - Returns a version string from Git # # These functions force a re-configure on each git commit so that you can # trust the values of the variables in your build system. # # get_git_head_revision( [ ...]) # # Returns the refspec and sha hash of the current head revision # # git_describe( [ ...]) # # Returns the results of git describe on the source tree, and adjusting # the output so that it tests false if an error occurs. # # git_get_exact_tag( [ ...]) # # Returns the results of git describe --exact-match on the source tree, # and adjusting the output so that it tests false if there was no exact # matching tag. # # git_local_changes() # # Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. # Uses the return code of "git diff-index --quiet HEAD --". # Does not regard untracked files. # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) if(__get_git_revision_description) return() endif() set(__get_git_revision_description YES) # We must run the following at "include" time, not at function call time, # to find the path to this module rather than the path to a calling list file get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) function(get_git_head_revision _refspecvar _hashvar) set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(GIT_DIR "${GIT_PARENT_DIR}/.git") while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) # We have reached the root directory, we are not in git set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) return() endif() set(GIT_DIR "${GIT_PARENT_DIR}/.git") endwhile() # check if this is a submodule if(NOT IS_DIRECTORY ${GIT_DIR}) file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) endif() set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") if(NOT EXISTS "${GIT_DATA}") file(MAKE_DIRECTORY "${GIT_DATA}") endif() if(NOT EXISTS "${GIT_DIR}/HEAD") return() endif() set(HEAD_FILE "${GIT_DATA}/HEAD") configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) endfunction() function(git_describe _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() get_git_head_revision(refspec hash) if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() if(NOT hash) set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) return() endif() # TODO sanitize #if((${ARGN}" MATCHES "&&") OR # (ARGN MATCHES "||") OR # (ARGN MATCHES "\\;")) # message("Please report the following error to the project!") # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") #endif() execute_process(COMMAND "${GIT_EXECUTABLE}" describe ${hash} ${ARGN} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_get_exact_tag _var) git_describe(out --exact-match ${ARGN}) set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_local_changes _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() get_git_head_revision(refspec hash) if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() if(NOT hash) set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) return() endif() execute_process(COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(res EQUAL 0) set(${_var} "CLEAN" PARENT_SCOPE) else() set(${_var} "DIRTY" PARENT_SCOPE) endif() endfunction() vtraag-libleidenalg-8518deb/etc/cmake/GetGitRevisionDescription.cmake.in000066400000000000000000000024031520437531200264020ustar00rootroot00000000000000# # Internal file for GetGitRevisionDescription.cmake # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) else() configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() vtraag-libleidenalg-8518deb/etc/cmake/cpack_install_script.cmake000066400000000000000000000023611520437531200250650ustar00rootroot00000000000000# Custom CPack install script that allows us to whitelist files to be copied # to the tarball from the root directory, instead of copying the entire root # directory recursively if(CPACK_SOURCE_INSTALLED_DIRECTORIES) # Generate a version file in the build folder if we don't have one in the # source folder if(EXISTS "${SOURCE_DIR}/VERSION") set(VERSION_FILE "${SOURCE_DIR}/VERSION") else() execute_process( COMMAND "${CMAKE_COMMAND}" --build "${CPACK_PACKAGE_DIRECTORY}" --target versionfile RESULT_VARIABLE EXIT_CODE ) message(INFO "Created VERSION file at ${CPACK_PACKAGE_DIRECTORY}/VERSION") if(NOT EXIT_CODE EQUAL 0) message(FATAL_ERROR "Failed to determine the version number of libleidenalg that is being packaged.") endif() set(VERSION_FILE "${CPACK_PACKAGE_DIRECTORY}/VERSION") endif() list(GET CPACK_BUILD_SOURCE_DIRS 0 SOURCE_DIR) file( INSTALL "${SOURCE_DIR}/CMakeLists.txt" "${SOURCE_DIR}/LICENSE" "${SOURCE_DIR}/CHANGELOG" "${SOURCE_DIR}/libleidenalgConfig.cmake.in" "${VERSION_FILE}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) endif() vtraag-libleidenalg-8518deb/etc/cmake/create_version_file.cmake000066400000000000000000000004731520437531200247030ustar00rootroot00000000000000# CMake script that generates the VERSION file in the build folder # # Script variables that need to be set before calling it via "cmake -P": # # * VERSION should be set to the exact version number # * VERSION_FILE_PATH should be set to the name of the version file FILE(WRITE "${VERSION_FILE_PATH}" "${VERSION}") vtraag-libleidenalg-8518deb/etc/cmake/packaging.cmake000066400000000000000000000027021520437531200226150ustar00rootroot00000000000000set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "libleidenalg library") set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/vtraag/libleidenalg") set(CPACK_PACKAGE_VENDOR "Vincent Traag") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") ############################################################################# ## Configuration of the source package ############################################################################# # Set source package name and format set(CPACK_SOURCE_PACKAGE_FILE_NAME "libleidenalg-${CMAKE_PROJECT_VERSION}") set(CPACK_SOURCE_GENERATOR "TGZ") # Declare what to include in the source tarball. Unfortunately we can only # declare full directories here, not individual files. set( CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/etc/cmake;/etc/cmake" "${CMAKE_SOURCE_DIR}/include;/include" "${CMAKE_SOURCE_DIR}/src;/src" ) # CPack is pretty dumb as it can only copy full directories (sans the ignored # files) to the target tarball by default. We need to install also the # correct version file. set(CPACK_INSTALL_SCRIPT "${CMAKE_SOURCE_DIR}/etc/cmake/cpack_install_script.cmake") # Ignore the build and all hidden folders set( CPACK_SOURCE_IGNORE_FILES "\\\\..*/" "${CMAKE_SOURCE_DIR}/build" ) ############################################################################# ## Now we can include CPack ############################################################################# include(CPack) vtraag-libleidenalg-8518deb/etc/cmake/version.cmake000066400000000000000000000046011520437531200223560ustar00rootroot00000000000000include(GetGitRevisionDescription) # At this point, libleidenalg is either the main CMake project or a subproject of # another project. CMAKE_SOURCE_DIR would point to the root of the main # project if we are a subproject so we cannot use that; we need to use # CMAKE_CURRENT_SOURCE_DIR to get the directory containing the CMakeLists.txt # file that version.cmake was included from, which is the top-level # CMakeLists.txt file of libleidenalg itself set(VERSION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/VERSION") set(NEXT_VERSION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/NEXT_VERSION") if(EXISTS "${VERSION_FILE}") file(READ "${VERSION_FILE}" PACKAGE_VERSION) string(STRIP "${PACKAGE_VERSION}" PACKAGE_VERSION) message(STATUS "Version number: ${PACKAGE_VERSION}") else() find_package(Git QUIET) if(Git_FOUND) git_describe(PACKAGE_VERSION) else() set(PACKAGE_VERSION "NOTFOUND") endif() if(PACKAGE_VERSION) if(EXISTS "${NEXT_VERSION_FILE}") file(READ "${NEXT_VERSION_FILE}" PACKAGE_VERSION) string(STRIP "${PACKAGE_VERSION}" PACKAGE_VERSION) get_git_head_revision(GIT_REFSPEC GIT_COMMIT_HASH) string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 GIT_COMMIT_HASH_SHORT) string(APPEND PACKAGE_VERSION "-dev+${GIT_COMMIT_HASH_SHORT}") endif() message(STATUS "Version number from Git: ${PACKAGE_VERSION}") elseif(EXISTS "${NEXT_VERSION_FILE}") file(READ "${NEXT_VERSION_FILE}" PACKAGE_VERSION) string(STRIP "${PACKAGE_VERSION}" PACKAGE_VERSION) string(APPEND PACKAGE_VERSION "-dev") message(STATUS "Version number: ${PACKAGE_VERSION}") else() # Read version from vcpkg.json file(READ ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg.json VCPKG_JSON) string(JSON PACKAGE_VERSION GET ${VCPKG_JSON} "version") message(STATUS "Version number from vcpkg.json: ${PACKAGE_VERSION}") endif() endif() string(REGEX MATCH "^[^-]+" PACKAGE_VERSION_BASE "${PACKAGE_VERSION}") string( REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9+])" "\\1;\\2;\\3" PACKAGE_VERSION_PARTS "${PACKAGE_VERSION_BASE}" ) list(GET PACKAGE_VERSION_PARTS 0 PACKAGE_VERSION_MAJOR) list(GET PACKAGE_VERSION_PARTS 1 PACKAGE_VERSION_MINOR) list(GET PACKAGE_VERSION_PARTS 2 PACKAGE_VERSION_PATCH) if(PACKAGE_VERSION MATCHES "^[^-]+-") string( REGEX REPLACE "^[^-]+-([^+]*)" "\\1" PACKAGE_VERSION_PRERELEASE "${PACKAGE_VERSION}" ) else() set(PACKAGE_VERSION_PRERELEASE "cmake-experimental") endif() vtraag-libleidenalg-8518deb/include/000077500000000000000000000000001520437531200174565ustar00rootroot00000000000000vtraag-libleidenalg-8518deb/include/CMakeLists.txt000066400000000000000000000007401520437531200222170ustar00rootroot00000000000000target_sources(libleidenalg PUBLIC FILE_SET headers TYPE HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR}/include/ FILES Optimiser.h GraphHelper.h MutableVertexPartition.h ModularityVertexPartition.h SignificanceVertexPartition.h SurpriseVertexPartition.h ResolutionParameterVertexPartition.h LinearResolutionParameterVertexPartition.h CPMVertexPartition.h RBConfigurationVertexPartition.h RBERVertexPartition.h ) vtraag-libleidenalg-8518deb/include/CPMVertexPartition.h000066400000000000000000000015371520437531200233440ustar00rootroot00000000000000#ifndef CPMVERTEXPARTITION_H #define CPMVERTEXPARTITION_H #include "LinearResolutionParameterVertexPartition.h" class LIBLEIDENALG_EXPORT CPMVertexPartition : public LinearResolutionParameterVertexPartition { public: CPMVertexPartition(Graph* graph, vector membership, double resolution_parameter); CPMVertexPartition(Graph* graph, vector membership); CPMVertexPartition(Graph* graph, double resolution_parameter); CPMVertexPartition(Graph* graph); virtual ~CPMVertexPartition(); virtual CPMVertexPartition* create(Graph* graph); virtual CPMVertexPartition* create(Graph* graph, vector const& membership); virtual double diff_move(size_t v, size_t new_comm); virtual double quality(double resolution_parameter); protected: private: }; #endif // CPMVERTEXPARTITION_H vtraag-libleidenalg-8518deb/include/GraphHelper.h000066400000000000000000000152021520437531200220300ustar00rootroot00000000000000#ifndef GRAPHHELPER_INCLUDED #define GRAPHHELPER_INCLUDED #include #include "libleidenalg_export.h" #include #include #include #include #include //#ifdef DEBUG #include using std::cerr; using std::endl; //#endif class MutableVertexPartition; using std::vector; using std::pair; using std::set; using std::deque; using std::make_pair; vector range(size_t n); bool orderCSize(const size_t* A, const size_t* B); double KL(double q, double p); double KLL(double q, double p); template T sum(vector vec) { T sum_of_elems = T(); for (T x : vec) sum_of_elems += x; return sum_of_elems; }; class Exception : public std::exception { public: Exception(const char* str) { this->str = str; } virtual const char* what() const throw() { return this->str; } private: const char* str; }; inline size_t get_random_int(size_t from, size_t to, igraph_rng_t* rng) { return igraph_rng_get_integer(rng, from, to); }; void shuffle(vector& v, igraph_rng_t* rng); class LIBLEIDENALG_EXPORT Graph { public: Graph(igraph_t* graph, vector const& edge_weights, vector const& node_sizes, vector const& node_self_weights, int correct_self_loops); Graph(igraph_t* graph, vector const& edge_weights, vector const& node_sizes, vector const& node_self_weights); Graph(igraph_t* graph, vector const& edge_weights, vector const& node_sizes, int correct_self_loops); Graph(igraph_t* graph, vector const& edge_weights, vector const& node_sizes); Graph(igraph_t* graph, int correct_self_loops); Graph(igraph_t* graph); Graph(); ~Graph(); static Graph* GraphFromEdgeWeights(igraph_t* graph, vector const& edge_weights, int correct_self_loops); static Graph* GraphFromEdgeWeights(igraph_t* graph, vector const& edge_weights); static Graph* GraphFromNodeSizes(igraph_t* graph, vector const& node_sizes, int correct_self_loops); static Graph* GraphFromNodeSizes(igraph_t* graph, vector const& node_sizes); int has_self_loops(); double possible_edges(); double possible_edges(double n); Graph* collapse_graph(MutableVertexPartition* partition); vector const& get_neighbour_edges(size_t v, igraph_neimode_t mode); vector const& get_neighbours(size_t v, igraph_neimode_t mode); size_t get_random_neighbour(size_t v, igraph_neimode_t mode, igraph_rng_t* rng); inline size_t get_random_node(igraph_rng_t* rng) { return get_random_int(0, this->vcount() - 1, rng); }; inline const igraph_t* get_igraph() { return this->_graph; }; inline size_t vcount() { return igraph_vcount(this->_graph); }; inline size_t ecount() { return igraph_ecount(this->_graph); }; inline double total_weight() { return this->_total_weight; }; inline double total_size() { return this->_total_size; }; inline int is_directed() { return this->_is_directed; }; inline double density() { return this->_density; }; inline int correct_self_loops() { return this->_correct_self_loops; }; inline int is_weighted() { return this->_is_weighted; }; inline double edge_weight(size_t e) { #ifdef DEBUG if (e > this->_edge_weights.size()) throw Exception("Edges outside of range of edge weights."); #endif return this->_edge_weights[e]; }; inline void edge(size_t eid, size_t &from, size_t &to) { from = IGRAPH_FROM(this->get_igraph(), eid); to = IGRAPH_TO(this->get_igraph(), eid); } inline vector edge(size_t e) { vector edge(2); this->edge(e, edge[0], edge[1]); return edge; } inline double node_size(size_t v) { return this->_node_sizes[v]; }; inline double node_self_weight(size_t v) { return this->_node_self_weights[v]; }; inline size_t degree(size_t v, igraph_neimode_t mode) { if (mode == IGRAPH_IN || !this->is_directed()) return this->_degree_in[v]; else if (mode == IGRAPH_OUT) return this->_degree_out[v]; else if (mode == IGRAPH_ALL) return this->_degree_all[v]; else throw Exception("Incorrect mode specified."); }; inline double strength(size_t v, igraph_neimode_t mode) { if (mode == IGRAPH_IN || !this->is_directed()) return this->_strength_in[v]; else if (mode == IGRAPH_OUT) return this->_strength_out[v]; else throw Exception("Incorrect mode specified."); }; protected: int _remove_graph; const igraph_t* _graph; void init_inclist_adjlist(); /* We use lazy inclist and adjlist so that we can easily get whatever * we need, without needing additional memory. */ igraph_lazy_inclist_t _inclist_in; igraph_lazy_inclist_t _inclist_out; igraph_lazy_inclist_t _inclist_all; igraph_lazy_adjlist_t _adjlist_in; igraph_lazy_adjlist_t _adjlist_out; igraph_lazy_adjlist_t _adjlist_all; // Utility variables to easily access the strength of each node vector _strength_in; vector _strength_out; vector _degree_in; vector _degree_out; vector _degree_all; vector _edge_weights; // Used for the weight of the edges. vector _node_sizes; // Used for the size of the nodes. vector _node_self_weights; // Used for the self weight of the nodes. void cache_neighbours(size_t v, igraph_neimode_t mode); vector _cached_neighs_from; size_t _current_node_cache_neigh_from; vector _cached_neighs_to; size_t _current_node_cache_neigh_to; vector _cached_neighs_all; size_t _current_node_cache_neigh_all; void cache_neighbour_edges(size_t v, igraph_neimode_t mode); vector _cached_neigh_edges_from; size_t _current_node_cache_neigh_edges_from; vector _cached_neigh_edges_to; size_t _current_node_cache_neigh_edges_to; vector _cached_neigh_edges_all; size_t _current_node_cache_neigh_edges_all; double _total_weight; double _total_size; int _is_weighted; bool _is_directed; int _correct_self_loops; double _density; void init_admin(); void set_defaults(); void set_default_edge_weight(); void set_default_node_size(); void set_self_weights(); }; // We need this ugly way to include the MutableVertexPartition // to overcome a circular linkage problem. #include "MutableVertexPartition.h" #endif // GRAPHHELPER_INCLUDED vtraag-libleidenalg-8518deb/include/LinearResolutionParameterVertexPartition.h000066400000000000000000000014001520437531200300510ustar00rootroot00000000000000#ifndef LINEARRESOLUTIONPARAMETERVERTEXPARTITION_H #define LINEARRESOLUTIONPARAMETERVERTEXPARTITION_H #include "ResolutionParameterVertexPartition.h" class LIBLEIDENALG_EXPORT LinearResolutionParameterVertexPartition : public ResolutionParameterVertexPartition { public: LinearResolutionParameterVertexPartition(Graph* graph, vector membership, double resolution_parameter); LinearResolutionParameterVertexPartition(Graph* graph, vector membership); LinearResolutionParameterVertexPartition(Graph* graph, double resolution_parameter); LinearResolutionParameterVertexPartition(Graph* graph); virtual ~LinearResolutionParameterVertexPartition(); private: }; #endif // RESOLUTIONPARAMETERVERTEXPARTITION_H vtraag-libleidenalg-8518deb/include/ModularityVertexPartition.h000066400000000000000000000012631520437531200250520ustar00rootroot00000000000000#ifndef MODULARITYVERTEXPARTITION_H #define MODULARITYVERTEXPARTITION_H #include "MutableVertexPartition.h" class LIBLEIDENALG_EXPORT ModularityVertexPartition : public MutableVertexPartition { public: ModularityVertexPartition(Graph* graph, vector const& membership); ModularityVertexPartition(Graph* graph); virtual ~ModularityVertexPartition(); virtual ModularityVertexPartition* create(Graph* graph); virtual ModularityVertexPartition* create(Graph* graph, vector const& membership); virtual double diff_move(size_t v, size_t new_comm); virtual double quality(); protected: private: }; #endif // MODULARITYVERTEXPARTITION_H vtraag-libleidenalg-8518deb/include/MutableVertexPartition.h000066400000000000000000000160641520437531200243170ustar00rootroot00000000000000#ifndef MUTABLEVERTEXPARTITION_H #define MUTABLEVERTEXPARTITION_H #include #include "GraphHelper.h" #include #include #include #include using std::string; using std::map; using std::make_pair; using std::pair; using std::sort; using std::reverse; using std::priority_queue; /**************************************************************************** Contains a partition of graph. This class contains the basic implementation for optimising a partition. Specifically, it implements all the administration necessary to keep track of the partition from various points of view. Internally, it keeps track of the number of internal edges (or total weight), the size of the communities, the total incoming degree (or weight) for a community, etc... When deriving from this class, one can easily use this administration to provide their own implementation. In order to keep the administration up-to-date, all changes in partition should be done through move_node. This function moves a node from one community to another, and updates all the administration. It is possible to manually update the membership vector, and then call __init_admin() which completely refreshes all the administration. This is only possible by updating the membership vector, not by changing some of the other variables. The basic idea is that diff_move computes the difference in the quality function if we call move_node for the same move. Using this framework, the Leiden method in the optimisation class can call these general functions in order to optimise the quality function. *****************************************************************************/ class LIBLEIDENALG_EXPORT MutableVertexPartition { public: MutableVertexPartition(Graph* graph, vector const& membership); MutableVertexPartition(Graph* graph); virtual MutableVertexPartition* create(Graph* graph); virtual MutableVertexPartition* create(Graph* graph, vector const& membership); virtual ~MutableVertexPartition(); inline size_t membership(size_t v) { return this->_membership[v]; }; inline vector const& membership() const { return this->_membership; }; double csize(size_t comm); size_t cnodes(size_t comm); vector get_community(size_t comm); vector< vector > get_communities(); size_t n_communities(); void move_node(size_t v,size_t new_comm); virtual double diff_move(size_t v, size_t new_comm) { throw Exception("Function not implemented. This should be implemented in a derived class, since the base class does not implement a specific method."); }; virtual double quality() { throw Exception("Function not implemented. This should be implemented in a derived class, since the base class does not implement a specific method."); }; inline Graph* get_graph() { return this->graph; }; void renumber_communities(); void renumber_communities(vector const& fixed_nodes, vector const& fixed_membership); void renumber_communities(vector const& new_membership); void set_membership(vector const& new_membership); void relabel_communities(vector const& new_comm_id); vector static rank_order_communities(vector partitions); size_t get_empty_community(); size_t add_empty_community(); void from_coarse_partition(vector const& coarse_partition_membership); void from_coarse_partition(MutableVertexPartition* partition); void from_coarse_partition(MutableVertexPartition* partition, vector const& coarser_membership); void from_coarse_partition(vector const& coarse_partition_membership, vector const& coarse_node); void from_partition(MutableVertexPartition* partition); inline double total_weight_in_comm(size_t comm) { return comm < _n_communities ? this->_total_weight_in_comm[comm] : 0.0; }; inline double total_weight_from_comm(size_t comm) { return comm < _n_communities ? this->_total_weight_from_comm[comm] : 0.0; }; inline double total_weight_to_comm(size_t comm) { return comm < _n_communities ? this->_total_weight_to_comm[comm] : 0.0; }; inline double total_weight_in_all_comms() { return this->_total_weight_in_all_comms; }; inline size_t total_possible_edges_in_all_comms() { return this->_total_possible_edges_in_all_comms; }; inline double weight_to_comm(size_t v, size_t comm) { if (this->_current_node_cache_community_to != v) { this->cache_neigh_communities(v, IGRAPH_OUT); this->_current_node_cache_community_to = v; } if (comm < this->_cached_weight_to_community.size()) return this->_cached_weight_to_community[comm]; else return 0.0; } inline double weight_from_comm(size_t v, size_t comm) { if (!this->graph->is_directed()) return weight_to_comm(v, comm); if (this->_current_node_cache_community_from != v) { this->cache_neigh_communities(v, IGRAPH_IN); this->_current_node_cache_community_from = v; } if (comm < this->_cached_weight_from_community.size()) return this->_cached_weight_from_community[comm]; else return 0.0; } vector const& get_neigh_comms(size_t v, igraph_neimode_t); vector get_neigh_comms(size_t v, igraph_neimode_t mode, vector const& constrained_membership); // By delegating the responsibility for deleting the graph to the partition, // we no longer have to worry about deleting this graph. int destructor_delete_graph; protected: void init_admin(); vector _membership; // Membership vector, i.e. \sigma_i = c means that node i is in community c Graph* graph; // Community size vector _csize; // Number of nodes in community vector< size_t > _cnodes; double weight_vertex_tofrom_comm(size_t v, size_t comm, igraph_neimode_t mode); void set_default_attrs(); private: // Keep track of the internal weight of each community vector _total_weight_in_comm; // Keep track of the total weight to a community vector _total_weight_to_comm; // Keep track of the total weight from a community vector _total_weight_from_comm; // Keep track of the total internal weight double _total_weight_in_all_comms; size_t _total_possible_edges_in_all_comms; size_t _n_communities; vector _empty_communities; void cache_neigh_communities(size_t v, igraph_neimode_t mode); size_t _current_node_cache_community_from; vector _cached_weight_from_community; vector _cached_neigh_comms_from; size_t _current_node_cache_community_to; vector _cached_weight_to_community; vector _cached_neigh_comms_to; size_t _current_node_cache_community_all; vector _cached_weight_all_community; vector _cached_neigh_comms_all; void clean_mem(); void init_graph_admin(); void update_n_communities(); }; #endif // MUTABLEVERTEXPARTITION_H vtraag-libleidenalg-8518deb/include/Optimiser.h000066400000000000000000000151451520437531200216100ustar00rootroot00000000000000#ifndef OPTIMISER_H #define OPTIMISER_H #include "GraphHelper.h" #include "MutableVertexPartition.h" #include #include #include #include using std::cerr; using std::endl; using std::set; using std::map; /**************************************************************************** Class for doing community detection using the Leiden algorithm. Given a certain partition type is calls diff_move for trying to move a node to another community. It moves the node to the community that *maximises* this diff_move. If no further improvement is possible, the graph is aggregated (collapse_graph) and the method is reiterated on that graph. ****************************************************************************/ class LIBLEIDENALG_EXPORT Optimiser { public: Optimiser(); double optimise_partition(MutableVertexPartition* partition); double optimise_partition(MutableVertexPartition* partition, vector const& is_membership_fixed); template T* find_partition(Graph* graph); template T* find_partition(Graph* graph, double resolution_parameter); // The multiplex functions that simultaneously optimise multiple graphs and partitions (i.e. methods) // Each node will be in the same community in all graphs, and the graphs are expected to have identical nodes // Optionally we can loop over all possible communities instead of only the neighbours. In the case of negative // layer weights this may be necessary. double optimise_partition(vector partitions, vector layer_weights, vector const& is_membership_fixed); double optimise_partition(vector partitions, vector layer_weights, vector const& is_membership_fixed, size_t max_comm_size); double move_nodes(MutableVertexPartition* partition); double move_nodes(MutableVertexPartition* partition, int consider_comms); double move_nodes(MutableVertexPartition* partition, vector const& is_membership_fixed, int consider_comms, bool renumber_fixed_nodes); double move_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, bool renumber_fixed_nodes); double move_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, int consider_comms, int consider_empty_community); double move_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, int consider_comms, int consider_empty_community, bool renumber_fixed_nodes); double merge_nodes(MutableVertexPartition* partition); double merge_nodes(MutableVertexPartition* partition, int consider_comms); double merge_nodes(MutableVertexPartition* partition, vector const& is_membership_fixed, int consider_comms, bool renumber_fixed_nodes); double merge_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, bool renumber_fixed_nodes); double merge_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, int consider_comms, bool renumber_fixed_nodes); double move_nodes_constrained(MutableVertexPartition* partition, MutableVertexPartition* constrained_partition); double move_nodes_constrained(MutableVertexPartition* partition, int consider_comms, MutableVertexPartition* constrained_partition); double move_nodes_constrained(vector partitions, vector layer_weights, MutableVertexPartition* constrained_partition); double move_nodes_constrained(vector partitions, vector layer_weights, int consider_comms, MutableVertexPartition* constrained_partition, bool enforce_community_size_constraint); double merge_nodes_constrained(MutableVertexPartition* partition, MutableVertexPartition* constrained_partition); double merge_nodes_constrained(MutableVertexPartition* partition, int consider_comms, MutableVertexPartition* constrained_partition); double merge_nodes_constrained(vector partitions, vector layer_weights, MutableVertexPartition* constrained_partition); double merge_nodes_constrained(vector partitions, vector layer_weights, int consider_comms, MutableVertexPartition* constrained_partition, bool enforce_community_size_constraint); inline void set_rng_seed(size_t seed) { igraph_rng_seed(&rng, seed); }; virtual ~Optimiser(); int consider_comms; // Indicates how communities will be considered for improvement. Should be one of the parameters below int refine_partition; // Refine partition before aggregating int refine_consider_comms; // Indicates how communities will be considered for improvement within the refinement. Should be one of the parameters below int optimise_routine; // What routine to use for optimisation int refine_routine; // What routine to use for optimisation int consider_empty_community; // Determine whether to consider moving nodes to an empty community size_t min_comm_size; // Constraint the minimum community size. size_t max_comm_size; // Constrain the maximum community size. double community_constraint_enforcement; // Control how strictly the community size constraints should be enforced. static const int ALL_COMMS = 1; // Consider all communities for improvement. static const int ALL_NEIGH_COMMS = 2; // Consider all neighbour communities for improvement. static const int RAND_COMM = 3; // Consider a random community for improvement. static const int RAND_NEIGH_COMM = 4; // Consider a random community among the neighbours for improvement. static const int MOVE_NODES = 10; // Use move node routine static const int MERGE_NODES = 11; // Use merge node routine protected: private: double improvement_community_constraints(size_t old_size, size_t new_size, size_t v_size); igraph_rng_t rng; }; template T* Optimiser::find_partition(Graph* graph) { T* partition = new T(graph); #ifdef DEBUG cerr << "Use default partition (all nodes in own community)" << endl; #endif this->optimise_partition(partition); return partition; } template T* Optimiser::find_partition(Graph* graph, double resolution_parameter) { T* partition = new T(graph, resolution_parameter); #ifdef DEBUG cerr << "Use default partition (all nodes in own community)" << endl; #endif this->optimise_partition(partition); return partition; } #endif // OPTIMISER_H vtraag-libleidenalg-8518deb/include/RBConfigurationVertexPartition.h000066400000000000000000000017611520437531200257570ustar00rootroot00000000000000#ifndef RBCONFIGURATIONVERTEXPARTITION_H #define RBCONFIGURATIONVERTEXPARTITION_H #include "LinearResolutionParameterVertexPartition.h" class LIBLEIDENALG_EXPORT RBConfigurationVertexPartition : public LinearResolutionParameterVertexPartition { public: RBConfigurationVertexPartition(Graph* graph, vector const& membership, double resolution_parameter); RBConfigurationVertexPartition(Graph* graph, vector const& membership); RBConfigurationVertexPartition(Graph* graph, double resolution_parameter); RBConfigurationVertexPartition(Graph* graph); virtual ~RBConfigurationVertexPartition(); virtual RBConfigurationVertexPartition* create(Graph* graph); virtual RBConfigurationVertexPartition* create(Graph* graph, vector const& membership); virtual double diff_move(size_t v, size_t new_comm); virtual double quality(double resolution_parameter); protected: private: }; #endif // RBCONFIGURATIONVERTEXPARTITION_H vtraag-libleidenalg-8518deb/include/RBERVertexPartition.h000066400000000000000000000015711520437531200234550ustar00rootroot00000000000000#ifndef RBERVERTEXPARTITION_H #define RBERVERTEXPARTITION_H #include "LinearResolutionParameterVertexPartition.h" class LIBLEIDENALG_EXPORT RBERVertexPartition : public LinearResolutionParameterVertexPartition { public: RBERVertexPartition(Graph* graph, vector const& membership, double resolution_parameter); RBERVertexPartition(Graph* graph, vector const& membership); RBERVertexPartition(Graph* graph, double resolution_parameter); RBERVertexPartition(Graph* graph); virtual ~RBERVertexPartition(); virtual RBERVertexPartition* create(Graph* graph); virtual RBERVertexPartition* create(Graph* graph, vector const& membership); virtual double diff_move(size_t v, size_t new_comm); virtual double quality(double resolution_parameter); protected: private: }; #endif // RBERVERTEXPARTITION_H vtraag-libleidenalg-8518deb/include/ResolutionParameterVertexPartition.h000066400000000000000000000020411520437531200267200ustar00rootroot00000000000000#ifndef RESOLUTIONPARAMETERVERTEXPARTITION_H #define RESOLUTIONPARAMETERVERTEXPARTITION_H #include "MutableVertexPartition.h" class LIBLEIDENALG_EXPORT ResolutionParameterVertexPartition : public MutableVertexPartition { public: ResolutionParameterVertexPartition(Graph* graph, vector membership, double resolution_parameter); ResolutionParameterVertexPartition(Graph* graph, vector membership); ResolutionParameterVertexPartition(Graph* graph, double resolution_parameter); ResolutionParameterVertexPartition(Graph* graph); virtual ~ResolutionParameterVertexPartition(); double resolution_parameter; virtual double quality() { return this->quality(this->resolution_parameter); }; virtual double quality(double resolution_parameter) { throw Exception("Function not implemented. This should be implemented in a derived class, since the base class does not implement a specific method."); }; private: }; #endif // RESOLUTIONPARAMETERVERTEXPARTITION_H vtraag-libleidenalg-8518deb/include/SignificanceVertexPartition.h000066400000000000000000000012751520437531200253060ustar00rootroot00000000000000#ifndef SIGNIFICANCEVERTEXPARTITION_H #define SIGNIFICANCEVERTEXPARTITION_H #include "MutableVertexPartition.h" class LIBLEIDENALG_EXPORT SignificanceVertexPartition : public MutableVertexPartition { public: SignificanceVertexPartition(Graph* graph, vector const& membership); SignificanceVertexPartition(Graph* graph); virtual ~SignificanceVertexPartition(); virtual SignificanceVertexPartition* create(Graph* graph); virtual SignificanceVertexPartition* create(Graph* graph, vector const& membership); virtual double diff_move(size_t v, size_t new_comm); virtual double quality(); protected: private: }; #endif // SIGNIFICANCEVERTEXPARTITION_H vtraag-libleidenalg-8518deb/include/SurpriseVertexPartition.h000066400000000000000000000014401520437531200245320ustar00rootroot00000000000000#ifndef SURPRISEVERTEXPARTITION_H #define SURPRISEVERTEXPARTITION_H #include "MutableVertexPartition.h" #include using std::cerr; using std::endl; class LIBLEIDENALG_EXPORT SurpriseVertexPartition: public MutableVertexPartition { public: SurpriseVertexPartition(Graph* graph, vector const& membership); SurpriseVertexPartition(Graph* graph, SurpriseVertexPartition* partition); SurpriseVertexPartition(Graph* graph); virtual ~SurpriseVertexPartition(); virtual SurpriseVertexPartition* create(Graph* graph); virtual SurpriseVertexPartition* create(Graph* graph, vector const& membership); virtual double diff_move(size_t v, size_t new_comm); virtual double quality(); protected: private: }; #endif // SURPRISEVERTEXPARTITION_H vtraag-libleidenalg-8518deb/libleidenalgConfig.cmake.in000066400000000000000000000013571520437531200232110ustar00rootroot00000000000000# - Config file for the libleidenalg package # It defines the following variables # LIBLEIDENALG_INCLUDEDIR - include directories for libleidenalg # LIBLEIDENALG_LIBRARIES - libraries to link against set(LIBLEIDENALG_VERSION "@PACKAGE_VERSION_BASE@") @PACKAGE_INIT@ # Compute paths set_and_check(LIBLEIDENALG_INCLUDEDIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") # Our library dependencies (contains definitions for IMPORTED targets) if(NOT TARGET libleidenalg AND NOT LIBLEIDENALG_BINARY_DIR) include("${CMAKE_CURRENT_LIST_DIR}/libleidenalgTargets.cmake") endif() # These are IMPORTED targets created by libleidenalgTargets.cmake set_and_check(LIBLEIDENALG_LIBRARIES "@PACKAGE_CMAKE_INSTALL_LIBDIR@") check_required_components(libleidenalg) vtraag-libleidenalg-8518deb/src/000077500000000000000000000000001520437531200166225ustar00rootroot00000000000000vtraag-libleidenalg-8518deb/src/CMakeLists.txt000066400000000000000000000025411520437531200213640ustar00rootroot00000000000000add_library(libleidenalg GraphHelper.cpp Optimiser.cpp MutableVertexPartition.cpp ModularityVertexPartition.cpp SignificanceVertexPartition.cpp SurpriseVertexPartition.cpp ResolutionParameterVertexPartition.cpp LinearResolutionParameterVertexPartition.cpp CPMVertexPartition.cpp RBConfigurationVertexPartition.cpp RBERVertexPartition.cpp ) set_target_properties(libleidenalg PROPERTIES VERSION ${PROJECT_VERSION}) set_target_properties(libleidenalg PROPERTIES SOVERSION 1) # Generate export header include file include(GenerateExportHeader) generate_export_header(libleidenalg STATIC_DEFINE LEIDENALG_STATIC EXPORT_FILE_NAME ${PROJECT_BINARY_DIR}/include/libleidenalg_export.h ) # Make sure we install the export header target_sources(libleidenalg PUBLIC FILE_SET headers TYPE HEADERS BASE_DIRS ${PROJECT_BINARY_DIR}/include/ FILES ${PROJECT_BINARY_DIR}/include/libleidenalg_export.h ) # Make sure we can find include files target_include_directories( libleidenalg PUBLIC $ $ ) if (NOT BUILD_SHARED_LIBS) target_compile_definitions(libleidenalg PRIVATE LEIDENALG_STATIC) else() target_compile_definitions(libleidenalg PRIVATE libleidenalg_EXPORTS) endif() target_link_libraries (libleidenalg PUBLIC igraph::igraph) vtraag-libleidenalg-8518deb/src/CPMVertexPartition.cpp000066400000000000000000000120341520437531200230350ustar00rootroot00000000000000#include "CPMVertexPartition.h" CPMVertexPartition::CPMVertexPartition(Graph* graph, vector membership, double resolution_parameter) : LinearResolutionParameterVertexPartition(graph, membership, resolution_parameter) { } CPMVertexPartition::CPMVertexPartition(Graph* graph, vector membership) : LinearResolutionParameterVertexPartition(graph, membership) { } CPMVertexPartition::CPMVertexPartition(Graph* graph, double resolution_parameter) : LinearResolutionParameterVertexPartition(graph, resolution_parameter) { } CPMVertexPartition::CPMVertexPartition(Graph* graph) : LinearResolutionParameterVertexPartition(graph) { } CPMVertexPartition::~CPMVertexPartition() { } CPMVertexPartition* CPMVertexPartition::create(Graph* graph) { return new CPMVertexPartition(graph, this->resolution_parameter); } CPMVertexPartition* CPMVertexPartition::create(Graph* graph, vector const& membership) { return new CPMVertexPartition(graph, membership, this->resolution_parameter); } /******************************************************************************** RBER implementation of a vertex partition (which includes a resolution parameter). ********************************************************************************/ double CPMVertexPartition::diff_move(size_t v, size_t new_comm) { #ifdef DEBUG cerr << "double CPMVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; cerr << "Using resolution parameter: " << this->resolution_parameter << "." << endl; #endif size_t old_comm = this->membership(v); double diff = 0.0; if (new_comm != old_comm) { double w_to_old = this->weight_to_comm(v, old_comm); #ifdef DEBUG cerr << "\t" << "w_to_old: " << w_to_old << endl; #endif double w_to_new = this->weight_to_comm(v, new_comm); #ifdef DEBUG cerr << "\t" << "w_to_new: " << w_to_new << endl; #endif double w_from_old = this->weight_from_comm(v, old_comm); #ifdef DEBUG cerr << "\t" << "w_from_old: " << w_from_old << endl; #endif double w_from_new = this->weight_from_comm(v, new_comm); #ifdef DEBUG cerr << "\t" << "w_from_new: " << w_from_new << endl; #endif double nsize = this->graph->node_size(v); #ifdef DEBUG cerr << "\t" << "nsize: " << nsize << endl; #endif double csize_old = this->csize(old_comm); #ifdef DEBUG cerr << "\t" << "csize_old: " << csize_old << endl; #endif double csize_new = this->csize(new_comm); #ifdef DEBUG cerr << "\t" << "csize_new: " << csize_new << endl; #endif double self_weight = this->graph->node_self_weight(v); #ifdef DEBUG cerr << "\t" << "self_weight: " << self_weight << endl; cerr << "\t" << "density: " << this->graph->density() << endl; #endif double possible_edge_difference_old = 0.0; if (this->graph->correct_self_loops()) possible_edge_difference_old = nsize*(2.0*csize_old - nsize); else possible_edge_difference_old = nsize*(2.0*csize_old - nsize - 1.0); #ifdef DEBUG cerr << "\t" << "possible_edge_difference_old: " << possible_edge_difference_old << endl; #endif double diff_old = w_to_old + w_from_old - self_weight - this->resolution_parameter*possible_edge_difference_old; #ifdef DEBUG cerr << "\t" << "diff_old: " << diff_old << endl; #endif double possible_edge_difference_new = 0.0; if (this->graph->correct_self_loops()) possible_edge_difference_new = nsize*(2.0*csize_new + nsize); else possible_edge_difference_new = nsize*(2.0*csize_new + nsize - 1.0); #ifdef DEBUG cerr << "\t" << "possible_edge_difference_new: " << possible_edge_difference_new << endl; #endif double diff_new = w_to_new + w_from_new + self_weight - this->resolution_parameter*possible_edge_difference_new; #ifdef DEBUG cerr << "\t" << "diff_new: " << diff_new << endl; #endif diff = diff_new - diff_old; #ifdef DEBUG cerr << "\t" << "diff: " << diff << endl;; #endif } #ifdef DEBUG cerr << "exit CPMVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; cerr << "return " << diff << endl << endl; #endif return diff; } double CPMVertexPartition::quality(double resolution_parameter) { #ifdef DEBUG cerr << "double CPMVertexPartition::quality()" << endl; #endif double mod = 0.0; for (size_t c = 0; c < this->n_communities(); c++) { double csize = this->csize(c); double w = this->total_weight_in_comm(c); double comm_possible_edges = this->graph->possible_edges(csize); #ifdef DEBUG cerr << "\t" << "Comm: " << c << ", w_c=" << w << ", n_c=" << csize << ", comm_possible_edges=" << comm_possible_edges << ", p=" << this->graph->density() << "." << endl; #endif mod += w - resolution_parameter*comm_possible_edges; } #ifdef DEBUG cerr << "exit double CPMVertexPartition::quality()" << endl; cerr << "return " << mod << endl << endl; #endif return (2.0 - this->graph->is_directed())*mod; } vtraag-libleidenalg-8518deb/src/GraphHelper.cpp000066400000000000000000000564041520437531200215400ustar00rootroot00000000000000#include "GraphHelper.h" #ifdef DEBUG using std::cerr; using std::endl; #endif vector range(size_t n) { vector range_vec(n); for(size_t i = 0; i < n; i++) range_vec[i] = i; return range_vec; } bool orderCSize(const size_t* A, const size_t* B) { if (A[1] == B[1]) { if (A[2] == B[2]) return A[0] < B[0]; else return A[2] > B[2]; } else return A[1] > B[1]; } void shuffle(vector& v, igraph_rng_t* rng) { size_t n = v.size(); if (n > 0) { for (size_t idx = n - 1; idx > 0; idx--) { size_t rand_idx = get_random_int(0, idx, rng); size_t tmp = v[idx]; v[idx] = v[rand_idx]; v[rand_idx] = tmp; } } } /**************************************************************************** The binary Kullback-Leibler divergence. ****************************************************************************/ double KL(double q, double p) { double KL = 0.0; if (q > 0.0 && p > 0.0) KL += q*log(q/p); if (q < 1.0 && p < 1.0) KL += (1.0-q)*log((1.0-q)/(1.0-p)); return KL; } double KLL(double q, double p) { double KL = 0.0; if (q > 0.0 && p > 0.0) KL += q*log(q/p); if (q < 1.0 && p < 1.0) KL += (1.0-q)*log((1.0-q)/(1.0-p)); if (q < p) KL *= -1; return KL; } Graph::Graph(igraph_t* graph, vector const& edge_weights, vector const& node_sizes, vector const& node_self_weights, int correct_self_loops) { this->_graph = graph; this->_remove_graph = false; this->init_inclist_adjlist(); if (edge_weights.size() != this->ecount()) throw Exception("Edge weights vector inconsistent length with the edge count of the graph."); this->_edge_weights = edge_weights; this->_is_weighted = true; if (node_sizes.size() != this->vcount()) throw Exception("Node size vector inconsistent length with the vertex count of the graph."); this->_node_sizes = node_sizes; if (node_self_weights.size() != this->vcount()) throw Exception("Node self weights vector inconsistent length with the vertex count of the graph."); this->_node_self_weights = node_self_weights; this->_correct_self_loops = correct_self_loops; this->init_admin(); } Graph::Graph(igraph_t* graph, vector const& edge_weights, vector const& node_sizes, vector const& node_self_weights) { this->_graph = graph; this->_remove_graph = false; this->init_inclist_adjlist(); if (edge_weights.size() != this->ecount()) throw Exception("Edge weights vector inconsistent length with the edge count of the graph."); this->_edge_weights = edge_weights; this->_is_weighted = true; if (node_sizes.size() != this->vcount()) throw Exception("Node size vector inconsistent length with the vertex count of the graph."); this->_node_sizes = node_sizes; this->_correct_self_loops = this->has_self_loops(); this->_node_self_weights = node_self_weights; this->init_admin(); } Graph::Graph(igraph_t* graph, vector const& edge_weights, vector const& node_sizes, int correct_self_loops) { this->_graph = graph; this->_remove_graph = false; this->init_inclist_adjlist(); if (edge_weights.size() != this->ecount()) throw Exception("Edge weights vector inconsistent length with the edge count of the graph."); this->_edge_weights = edge_weights; this->_is_weighted = true; if (node_sizes.size() != this->vcount()) throw Exception("Node size vector inconsistent length with the vertex count of the graph."); this->_node_sizes = node_sizes; this->_correct_self_loops = correct_self_loops; this->init_admin(); this->set_self_weights(); } Graph::Graph(igraph_t* graph, vector const& edge_weights, vector const& node_sizes) { this->_graph = graph; this->_remove_graph = false; this->init_inclist_adjlist(); if (edge_weights.size() != this->ecount()) throw Exception("Edge weights vector inconsistent length with the edge count of the graph."); this->_edge_weights = edge_weights; this->_is_weighted = true; if (node_sizes.size() != this->vcount()) throw Exception("Node size vector inconsistent length with the vertex count of the graph."); this->_node_sizes = node_sizes; this->_correct_self_loops = this->has_self_loops(); this->init_admin(); this->set_self_weights(); } Graph* Graph::GraphFromEdgeWeights(igraph_t* graph, vector const& edge_weights, int correct_self_loops) { Graph* g = new Graph(graph, correct_self_loops); if (edge_weights.size() != g->ecount()) throw Exception("Edge weights vector inconsistent length with the edge count of the graph."); g->_edge_weights = edge_weights; g->_is_weighted = true; g->set_default_node_size(); g->init_admin(); g->set_self_weights(); return g; } Graph* Graph::GraphFromEdgeWeights(igraph_t* graph, vector const& edge_weights) { Graph* g = new Graph(graph); if (edge_weights.size() != g->ecount()) throw Exception("Edge weights vector inconsistent length with the edge count of the graph."); g->_edge_weights = edge_weights; g->_is_weighted = true; g->set_default_node_size(); g->init_admin(); g->set_self_weights(); return g; } Graph* Graph::GraphFromNodeSizes(igraph_t* graph, vector const& node_sizes, int correct_self_loops) { Graph* g = new Graph(graph, correct_self_loops); if (node_sizes.size() != g->vcount()) throw Exception("Node size vector inconsistent length with the vertex count of the graph."); g->_node_sizes = node_sizes; g->set_default_edge_weight(); g->_is_weighted = false; g->init_admin(); g->set_self_weights(); return g; } Graph* Graph::GraphFromNodeSizes(igraph_t* graph, vector const& node_sizes) { Graph* g = new Graph(graph); g->_graph = graph; g->_remove_graph = false; g->set_defaults(); g->_is_weighted = false; if (node_sizes.size() != g->vcount()) throw Exception("Node size vector inconsistent length with the vertex count of the graph."); g->_node_sizes = node_sizes; g->_correct_self_loops = g->has_self_loops(); g->init_admin(); g->set_self_weights(); return g; } Graph::Graph(igraph_t* graph, int correct_self_loops) { this->_graph = graph; this->_remove_graph = false; this->init_inclist_adjlist(); this->_correct_self_loops = correct_self_loops; this->set_defaults(); this->_is_weighted = false; this->init_admin(); this->set_self_weights(); } Graph::Graph(igraph_t* graph) { this->_graph = graph; this->_remove_graph = false; this->init_inclist_adjlist(); this->set_defaults(); this->_is_weighted = false; this->_correct_self_loops = this->has_self_loops(); this->init_admin(); this->set_self_weights(); } Graph::~Graph() { if (this->_remove_graph) { igraph_destroy((igraph_t*)this->_graph); delete this->_graph; } igraph_lazy_inclist_destroy(&this->_inclist_in); igraph_lazy_inclist_destroy(&this->_inclist_out); igraph_lazy_inclist_destroy(&this->_inclist_all); igraph_lazy_adjlist_destroy(&this->_adjlist_in); igraph_lazy_adjlist_destroy(&this->_adjlist_out); igraph_lazy_adjlist_destroy(&this->_adjlist_all); } void Graph::init_inclist_adjlist() { igraph_lazy_inclist_init(this->_graph, &this->_inclist_in, IGRAPH_IN, IGRAPH_LOOPS_TWICE); igraph_lazy_inclist_init(this->_graph, &this->_inclist_out, IGRAPH_OUT, IGRAPH_LOOPS_TWICE); igraph_lazy_inclist_init(this->_graph, &this->_inclist_all, IGRAPH_ALL, IGRAPH_LOOPS_TWICE); igraph_lazy_adjlist_init(this->_graph, &this->_adjlist_in, IGRAPH_IN, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); igraph_lazy_adjlist_init(this->_graph, &this->_adjlist_out, IGRAPH_OUT, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); igraph_lazy_adjlist_init(this->_graph, &this->_adjlist_all, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE); } int Graph::has_self_loops() { igraph_bool_t has_self_loops; igraph_has_loop(this->_graph, &has_self_loops); return has_self_loops; } double Graph::possible_edges() { return this->possible_edges(this->vcount()); } double Graph::possible_edges(double n) { double possible_edges = n*(n-1); if (!this->is_directed()) possible_edges /= 2; if (this->correct_self_loops()) possible_edges += n; return possible_edges; } void Graph::set_defaults() { this->set_default_edge_weight(); this->set_default_node_size(); } void Graph::set_default_edge_weight() { size_t m = this->ecount(); // Set default edge weight of 1.0 this->_edge_weights.clear(); this->_edge_weights.resize(m); fill(this->_edge_weights.begin(), this->_edge_weights.end(), 1.0); this->_is_weighted = false; } void Graph::set_default_node_size() { size_t n = this->vcount(); // Set default node size of 1 this->_node_sizes.clear(); this->_node_sizes.resize(n); fill(this->_node_sizes.begin(), this->_node_sizes.end(), 1); } void Graph::set_self_weights() { size_t n = this->vcount(); // Set default self_weights of the total weight of any possible self-loops this->_node_self_weights.clear(); this->_node_self_weights.resize(n); for (size_t v = 0; v < n; v++) { #ifdef DEBUG cerr << "\t" << "Size node " << v << ": " << this->node_size(v) << endl; #endif double self_weight = 0.0; // There should be only one self loop igraph_integer_t eid; // Get edge id for self loop igraph_get_eid(this->_graph, &eid, v, v, this->is_directed(), false); if (eid >= 0) self_weight = this->edge_weight(eid); this->_node_self_weights[v] = self_weight; #ifdef DEBUG cerr << "\t" << "Self weight node " << v << ": " << self_weight << endl; #endif } } void Graph::init_admin() { size_t m = this->ecount(); size_t n = this->vcount(); this->_is_directed = igraph_is_directed(this->_graph); this->_strength_in.clear(); this->_strength_in.resize(n, 0.0); this->_degree_in.clear(); this->_degree_in.resize(n, 0.0); if (this->_is_directed) { this->_strength_out.clear(); this->_strength_out.resize(n, 0.0); this->_degree_out.clear(); this->_degree_out.resize(n, 0); this->_degree_all.clear(); this->_degree_all.resize(n, 0); } // Determine total weight in the graph. this->_total_weight = 0.0; for (size_t e = 0; e < m; e++) { double w = this->edge_weight(e); this->_total_weight += w; size_t from, to; this->edge(e, from, to); if (this->is_directed()) { this->_strength_in[to] += w; this->_strength_out[from] += w; this->_degree_in[to]++; this->_degree_out[from]++; this->_degree_all[to]++; this->_degree_all[from]++; } else { // we only compute strength_in and degree_in for undirected graphs this->_strength_in[to] += w; this->_strength_in[from] += w; // recall that igraph ignores the mode for undirected graphs this->_degree_in[to]++; this->_degree_in[from]++; } } // Make sure to multiply by 2 for undirected graphs //if (!this->is_directed()) // this->_total_weight *= 2.0; this->_total_size = 0; for (size_t v = 0; v < n; v++) this->_total_size += this->node_size(v); // Calculate density; double w = this->total_weight(); double n_size = this->total_size(); // For now we default to not correcting self loops. // this->_correct_self_loops = false; (remove this as this is set in the constructor) double normalise = 0.0; if (this->_correct_self_loops) normalise = n_size*n_size; else normalise = n_size*(n_size - 1); if (this->is_directed()) this->_density = w/normalise; else this->_density = 2*w/normalise; this->_current_node_cache_neigh_edges_from = n + 1; this->_current_node_cache_neigh_edges_to = n + 1; this->_current_node_cache_neigh_edges_all = n + 1; this->_current_node_cache_neigh_from = n + 1; this->_current_node_cache_neigh_to = n + 1; this->_current_node_cache_neigh_all = n + 1; } void Graph::cache_neighbour_edges(size_t v, igraph_neimode_t mode) { #ifdef DEBUG cerr << "void Graph::cache_neighbour_edges(" << v << ", " << mode << ");" << endl; #endif size_t degree = this->degree(v, mode); #ifdef DEBUG cerr << "Degree: " << degree << endl; #endif igraph_vector_int_t *incident_edges; vector* _cached_neigh_edges = NULL; switch (mode) { case IGRAPH_IN: this->_current_node_cache_neigh_edges_from = v; _cached_neigh_edges = &(this->_cached_neigh_edges_from); incident_edges = igraph_lazy_inclist_get(&this->_inclist_in, v); break; case IGRAPH_OUT: this->_current_node_cache_neigh_edges_to = v; _cached_neigh_edges = &(this->_cached_neigh_edges_to); incident_edges = igraph_lazy_inclist_get(&this->_inclist_out, v); break; case IGRAPH_ALL: this->_current_node_cache_neigh_edges_all = v; _cached_neigh_edges = &(this->_cached_neigh_edges_all); incident_edges = igraph_lazy_inclist_get(&this->_inclist_all, v); break; } _cached_neigh_edges->assign(igraph_vector_int_get_ptr(incident_edges, 0), igraph_vector_int_get_ptr(incident_edges, degree)); #ifdef DEBUG cerr << "Number of edges: " << _cached_neigh_edges->size() << endl; #endif #ifdef DEBUG cerr << "exit void Graph::cache_neighbour_edges(" << v << ", " << mode << ");" << endl; #endif } vector const& Graph::get_neighbour_edges(size_t v, igraph_neimode_t mode) { if (!this->is_directed()) mode = IGRAPH_ALL; // igraph ignores mode for undirected graphs switch (mode) { case IGRAPH_IN: if (this->_current_node_cache_neigh_edges_from != v) { cache_neighbour_edges(v, mode); this->_current_node_cache_neigh_edges_from = v; } return this->_cached_neigh_edges_from; case IGRAPH_OUT: if (this->_current_node_cache_neigh_edges_to != v) { cache_neighbour_edges(v, mode); this->_current_node_cache_neigh_edges_to = v; } return this->_cached_neigh_edges_to; case IGRAPH_ALL: if (this->_current_node_cache_neigh_edges_all != v) { cache_neighbour_edges(v, mode); this->_current_node_cache_neigh_edges_all = v; } return this->_cached_neigh_edges_all; } throw Exception("Incorrect model for getting neighbour edges."); } void Graph::cache_neighbours(size_t v, igraph_neimode_t mode) { #ifdef DEBUG cerr << "void Graph::cache_neighbours(" << v << ", " << mode << ");" << endl; #endif size_t degree = this->degree(v, mode); #ifdef DEBUG cerr << "Degree: " << degree << endl; #endif igraph_vector_int_t *neighbours; vector* _cached_neighs = NULL; switch (mode) { case IGRAPH_IN: this->_current_node_cache_neigh_from = v; _cached_neighs = &(this->_cached_neighs_from); neighbours = igraph_lazy_adjlist_get(&this->_adjlist_in, v); break; case IGRAPH_OUT: this->_current_node_cache_neigh_to = v; _cached_neighs = &(this->_cached_neighs_to); neighbours = igraph_lazy_adjlist_get(&this->_adjlist_out, v); break; case IGRAPH_ALL: this->_current_node_cache_neigh_all = v; _cached_neighs = &(this->_cached_neighs_all); neighbours = igraph_lazy_adjlist_get(&this->_adjlist_all, v); break; } _cached_neighs->assign(igraph_vector_int_get_ptr(neighbours, 0), igraph_vector_int_get_ptr(neighbours, degree)); #ifdef DEBUG cerr << "Number of edges: " << _cached_neighs->size() << endl; #endif #ifdef DEBUG cerr << "exit void Graph::cache_neighbours(" << v << ", " << mode << ");" << endl; #endif } vector< size_t > const& Graph::get_neighbours(size_t v, igraph_neimode_t mode) { if (!this->is_directed()) mode = IGRAPH_ALL; // igraph ignores mode for undirected graphs switch (mode) { case IGRAPH_IN: if (this->_current_node_cache_neigh_from != v) { cache_neighbours(v, mode); this -> _current_node_cache_neigh_from = v; } #ifdef DEBUG cerr << "Returning " << this->_cached_neighs_from.size() << " incoming neighbours" << endl; #endif return this->_cached_neighs_from; case IGRAPH_OUT: if (this->_current_node_cache_neigh_to != v) { cache_neighbours(v, mode); this -> _current_node_cache_neigh_to = v; } #ifdef DEBUG cerr << "Returning " << this->_cached_neighs_to.size() << " incoming neighbours" << endl; #endif return this->_cached_neighs_to; case IGRAPH_ALL: if (this->_current_node_cache_neigh_all != v) { cache_neighbours(v, mode); this->_current_node_cache_neigh_all = v; } #ifdef DEBUG cerr << "Returning " << this->_cached_neighs_all.size() << " incoming neighbours" << endl; #endif return this->_cached_neighs_all; } throw Exception("Invalid mode for getting neighbours."); } /******************************************************************************** * This should return a random neighbour in O(1) ********************************************************************************/ size_t Graph::get_random_neighbour(size_t v, igraph_neimode_t mode, igraph_rng_t* rng) { size_t node=v; size_t rand_neigh = -1; if (this->degree(v, mode) <= 0) throw Exception("Cannot select a random neighbour for an isolated node."); if (this->is_directed() && mode != IGRAPH_ALL) { if (mode == IGRAPH_OUT) { // Get indices of where neighbours are size_t cum_degree_this_node = (size_t) VECTOR(this->_graph->os)[node]; size_t cum_degree_next_node = (size_t) VECTOR(this->_graph->os)[node+1]; // Get a random index from them size_t rand_neigh_idx = get_random_int(cum_degree_this_node, cum_degree_next_node - 1, rng); // Return the neighbour at that index #ifdef DEBUG cerr << "Degree: " << this->degree(node, mode) << " diff in cumulative: " << cum_degree_next_node - cum_degree_this_node << endl; #endif rand_neigh = VECTOR(this->_graph->to)[ (size_t)VECTOR(this->_graph->oi)[rand_neigh_idx] ]; } else if (mode == IGRAPH_IN) { // Get indices of where neighbours are size_t cum_degree_this_node = (size_t) VECTOR(this->_graph->is)[node]; size_t cum_degree_next_node = (size_t) VECTOR(this->_graph->is)[node+1]; // Get a random index from them size_t rand_neigh_idx = get_random_int(cum_degree_this_node, cum_degree_next_node - 1, rng); #ifdef DEBUG cerr << "Degree: " << this->degree(node, mode) << " diff in cumulative: " << cum_degree_next_node - cum_degree_this_node << endl; #endif // Return the neighbour at that index rand_neigh = VECTOR(this->_graph->from)[ (size_t)VECTOR(this->_graph->ii)[rand_neigh_idx] ]; } } else { // both in- and out- neighbors in a directed graph. size_t cum_outdegree_this_node = (size_t)VECTOR(this->_graph->os)[node]; size_t cum_indegree_this_node = (size_t)VECTOR(this->_graph->is)[node]; size_t cum_outdegree_next_node = (size_t)VECTOR(this->_graph->os)[node+1]; size_t cum_indegree_next_node = (size_t)VECTOR(this->_graph->is)[node+1]; size_t total_outdegree = cum_outdegree_next_node - cum_outdegree_this_node; size_t total_indegree = cum_indegree_next_node - cum_indegree_this_node; size_t rand_idx = get_random_int(0, total_outdegree + total_indegree - 1, rng); #ifdef DEBUG cerr << "Degree: " << this->degree(node, mode) << " diff in cumulative: " << total_outdegree + total_indegree << endl; #endif // From among in or out neighbours? if (rand_idx < total_outdegree) { // From among outgoing neighbours size_t rand_neigh_idx = cum_outdegree_this_node + rand_idx; rand_neigh = VECTOR(this->_graph->to)[ (size_t)VECTOR(this->_graph->oi)[rand_neigh_idx] ]; } else { // From among incoming neighbours size_t rand_neigh_idx = cum_indegree_this_node + rand_idx - total_outdegree; rand_neigh = VECTOR(this->_graph->from)[ (size_t)VECTOR(this->_graph->ii)[rand_neigh_idx] ]; } } return rand_neigh; } /**************************************************************************** Creates a graph with communities as node and links as weights between communities. The weight of the edges in the new graph is simply the sum of the weight of the edges between the communities. The self weight of a node (i.e. the weight of its self loop) is the internal weight of a community. The size of a node in the new graph is simply the size of the community in the old graph. *****************************************************************************/ Graph* Graph::collapse_graph(MutableVertexPartition* partition) { #ifdef DEBUG cerr << "Graph* Graph::collapse_graph(vector membership)" << endl; #endif #ifdef DEBUG cerr << "Current graph has " << this->vcount() << " nodes and " << this->ecount() << " edges." << endl; cerr << "Collapsing to graph with " << partition->n_communities() << " nodes." << endl; #endif size_t n_collapsed = partition->n_communities(); vector > community_memberships = partition->get_communities(); vector collapsed_weights; double total_collapsed_weight = 0.0; vector edge_weight_to_community(n_collapsed, 0.0); vector neighbour_comm_added(n_collapsed, false); // collapsed edges for new graph igraph_vector_int_t edges; igraph_vector_int_init(&edges, 0); for (size_t v_comm = 0; v_comm < n_collapsed; v_comm++) { vector neighbour_communities; for (size_t v : community_memberships[v_comm]) { for (size_t e : this->get_neighbour_edges(v, IGRAPH_OUT)) { size_t from, to; this->edge(e, from, to); if ((size_t) from != v) { // need to skip because IGRAPH_OUT is ignored for undirected graphs continue; } size_t u_comm = partition->membership(to); double w = this->edge_weight(e); // Self loops appear twice here if the graph is undirected, so divide by 2.0 in that case. if (from == to && !this->is_directed()) w /= 2.0; if (!neighbour_comm_added[u_comm]) { neighbour_comm_added[u_comm] = true; neighbour_communities.push_back(u_comm); } edge_weight_to_community[u_comm] += w; } } for (size_t u_comm : neighbour_communities) { igraph_vector_int_push_back(&edges, v_comm); igraph_vector_int_push_back(&edges, u_comm); collapsed_weights.push_back(edge_weight_to_community[u_comm]); total_collapsed_weight += edge_weight_to_community[u_comm]; // reset edge_weight_to_community to all 0.0 and neighbour_comm_added to all false edge_weight_to_community[u_comm] = 0.0; neighbour_comm_added[u_comm] = false; } } // Create graph based on edges igraph_t* graph = new igraph_t(); igraph_create(graph, &edges, n_collapsed, this->is_directed()); igraph_vector_int_destroy(&edges); if ((size_t) igraph_vcount(graph) != partition->n_communities()) throw Exception("Something went wrong with collapsing the graph."); // Calculate new node sizes vector csizes(n_collapsed, 0); for (size_t c = 0; c < partition->n_communities(); c++) csizes[c] = partition->csize(c); Graph* G = new Graph(graph, collapsed_weights, csizes, this->_correct_self_loops); G->_remove_graph = true; #ifdef DEBUG cerr << "exit Graph::collapse_graph(vector membership)" << endl << endl; #endif return G; } vtraag-libleidenalg-8518deb/src/LinearResolutionParameterVertexPartition.cpp000066400000000000000000000017131520437531200275570ustar00rootroot00000000000000#include "LinearResolutionParameterVertexPartition.h" LinearResolutionParameterVertexPartition::LinearResolutionParameterVertexPartition(Graph* graph, vector membership, double resolution_parameter) : ResolutionParameterVertexPartition(graph, membership, resolution_parameter) { } LinearResolutionParameterVertexPartition::LinearResolutionParameterVertexPartition(Graph* graph, vector membership) : ResolutionParameterVertexPartition(graph, membership) { } LinearResolutionParameterVertexPartition::LinearResolutionParameterVertexPartition(Graph* graph, double resolution_parameter) : ResolutionParameterVertexPartition(graph, resolution_parameter) { } LinearResolutionParameterVertexPartition::LinearResolutionParameterVertexPartition(Graph* graph) : ResolutionParameterVertexPartition(graph) { } LinearResolutionParameterVertexPartition::~LinearResolutionParameterVertexPartition() { } vtraag-libleidenalg-8518deb/src/ModularityVertexPartition.cpp000066400000000000000000000124211520437531200245470ustar00rootroot00000000000000#include "ModularityVertexPartition.h" #ifdef DEBUG #include using std::cerr; using std::endl; #endif ModularityVertexPartition::ModularityVertexPartition(Graph* graph, vector const& membership) : MutableVertexPartition(graph, membership) { } ModularityVertexPartition::ModularityVertexPartition(Graph* graph) : MutableVertexPartition(graph) { } ModularityVertexPartition::~ModularityVertexPartition() { } ModularityVertexPartition* ModularityVertexPartition::create(Graph* graph) { return new ModularityVertexPartition(graph); } ModularityVertexPartition* ModularityVertexPartition::create(Graph* graph, vector const& membership) { return new ModularityVertexPartition(graph, membership); } /***************************************************************************** Returns the difference in modularity if we move a node to a new community *****************************************************************************/ double ModularityVertexPartition::diff_move(size_t v, size_t new_comm) { #ifdef DEBUG cerr << "double ModularityVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; #endif size_t old_comm = this->_membership[v]; double diff = 0.0; double total_weight = this->graph->total_weight()*(2.0 - this->graph->is_directed()); if (total_weight == 0.0) return 0.0; if (new_comm != old_comm) { #ifdef DEBUG cerr << "\t" << "old_comm: " << old_comm << endl; #endif double w_to_old = this->weight_to_comm(v, old_comm); #ifdef DEBUG cerr << "\t" << "w_to_old: " << w_to_old << endl; #endif double w_from_old = this->weight_from_comm(v, old_comm); #ifdef DEBUG cerr << "\t" << "w_from_old: " << w_from_old << endl; #endif double w_to_new = this->weight_to_comm(v, new_comm); #ifdef DEBUG cerr << "\t" << "w_to_new: " << w_to_new << endl; #endif double w_from_new = this->weight_from_comm(v, new_comm); #ifdef DEBUG cerr << "\t" << "w_from_new: " << w_from_new << endl; #endif double k_out = this->graph->strength(v, IGRAPH_OUT); #ifdef DEBUG cerr << "\t" << "k_out: " << k_out << endl; #endif double k_in = this->graph->strength(v, IGRAPH_IN); #ifdef DEBUG cerr << "\t" << "k_in: " << k_in << endl; #endif double self_weight = this->graph->node_self_weight(v); #ifdef DEBUG cerr << "\t" << "self_weight: " << self_weight << endl; #endif double K_out_old = this->total_weight_from_comm(old_comm); #ifdef DEBUG cerr << "\t" << "K_out_old: " << K_out_old << endl; #endif double K_in_old = this->total_weight_to_comm(old_comm); #ifdef DEBUG cerr << "\t" << "K_in_old: " << K_in_old << endl; #endif double K_out_new = this->total_weight_from_comm(new_comm) + k_out; #ifdef DEBUG cerr << "\t" << "K_out_new: " << K_out_new << endl; #endif double K_in_new = this->total_weight_to_comm(new_comm) + k_in; #ifdef DEBUG cerr << "\t" << "K_in_new: " << K_in_new << endl; cerr << "\t" << "total_weight: " << total_weight << endl; #endif double diff_old = (w_to_old - k_out*K_in_old/total_weight) + \ (w_from_old - k_in*K_out_old/total_weight); #ifdef DEBUG cerr << "\t" << "diff_old: " << diff_old << endl; #endif double diff_new = (w_to_new + self_weight - k_out*K_in_new/total_weight) + \ (w_from_new + self_weight - k_in*K_out_new/total_weight); #ifdef DEBUG cerr << "\t" << "diff_new: " << diff_new << endl; #endif diff = diff_new - diff_old; #ifdef DEBUG cerr << "\t" << "diff: " << diff << endl; #endif } #ifdef DEBUG cerr << "exit double ModularityVertexPartition::diff_move((" << v << ", " << new_comm << ")" << endl; cerr << "return " << diff << endl << endl; #endif double m; if (this->graph->is_directed()) m = this->graph->total_weight(); else m = 2*this->graph->total_weight(); return diff/m; } /***************************************************************************** Give the modularity of the partition. We here use the unscaled version of modularity, in other words, we don"t normalise by the number of edges. ******************************************************************************/ double ModularityVertexPartition::quality() { #ifdef DEBUG cerr << "double ModularityVertexPartition::quality()" << endl; #endif double mod = 0.0; double m; if (this->graph->is_directed()) m = this->graph->total_weight(); else m = 2*this->graph->total_weight(); if (m == 0) return 0.0; for (size_t c = 0; c < this->n_communities(); c++) { double w = this->total_weight_in_comm(c); double w_out = this->total_weight_from_comm(c); double w_in = this->total_weight_to_comm(c); #ifdef DEBUG double csize = this->csize(c); cerr << "\t" << "Comm: " << c << ", size=" << csize << ", w=" << w << ", w_out=" << w_out << ", w_in=" << w_in << "." << endl; #endif mod += w - w_out*w_in/((this->graph->is_directed() ? 1.0 : 4.0)*this->graph->total_weight()); } double q = (2.0 - this->graph->is_directed())*mod; #ifdef DEBUG cerr << "exit double ModularityVertexPartition::quality()" << endl; cerr << "return " << q/m << endl << endl; #endif return q/m; } vtraag-libleidenalg-8518deb/src/MutableVertexPartition.cpp000066400000000000000000001020031520437531200240030ustar00rootroot00000000000000#include "MutableVertexPartition.h" #ifdef DEBUG using std::cerr; using std::endl; #endif /**************************************************************************** Create a new vertex partition. Parameters: graph -- The igraph.Graph on which this partition is defined. membership=None -- The membership vector of this partition, i.e. an community number for each node. So membership[i] = c implies that node i is in community c. If None, it is initialised with each node in its own community. weight_attr=None -- What edge attribute should be used as a weight for the edges? If None, the weight defaults to 1. size_attr=None -- What node attribute should be used for keeping track of the size of the node? In some methods (e.g. CPM or Significance), we need to keep track of the total size of the community. So when we aggregate/collapse the graph, we should know how many nodes were in a community. If None, the size of a node defaults to 1. self_weight_attr=None -- What node attribute should be used for the self weight? If None, the self_weight is recalculated each time.""" *****************************************************************************/ MutableVertexPartition::MutableVertexPartition(Graph* graph, vector const& membership) { this->destructor_delete_graph = false; this->graph = graph; if (membership.size() != graph->vcount()) { throw Exception("Membership vector has incorrect size."); } this->_membership = membership; this->init_admin(); } MutableVertexPartition::MutableVertexPartition(Graph* graph) { this->destructor_delete_graph = false; this->graph = graph; this->_membership = range(graph->vcount()); this->init_admin(); } MutableVertexPartition* MutableVertexPartition::create(Graph* graph) { return new MutableVertexPartition(graph); } MutableVertexPartition* MutableVertexPartition::create(Graph* graph, vector const& membership) { return new MutableVertexPartition(graph, membership); } MutableVertexPartition::~MutableVertexPartition() { this->clean_mem(); if (this->destructor_delete_graph) delete this->graph; } void MutableVertexPartition::clean_mem() { } double MutableVertexPartition::csize(size_t comm) { if (comm < this->_csize.size()) return this->_csize[comm]; else return 0; } size_t MutableVertexPartition::cnodes(size_t comm) { if (comm < this->_cnodes.size()) return this->_cnodes[comm]; else return 0; } vector MutableVertexPartition::get_community(size_t comm) { vector community; community.reserve(this->_cnodes[comm]); for (size_t i = 0; i < this->graph->vcount(); i++) if (this->_membership[i] == comm) community.push_back(i); return community; } vector< vector > MutableVertexPartition::get_communities() { vector< vector > communities(this->_n_communities); for (size_t c = 0; c < this->_n_communities; c++) { size_t cn = this->_cnodes[c]; communities[c].reserve(cn); } for (size_t i = 0; i < this->graph->vcount(); i++) communities[this->_membership[i]].push_back(i); return communities; } size_t MutableVertexPartition::n_communities() { return this->_n_communities; } /**************************************************************************** Initialise all the administration based on the membership vector. *****************************************************************************/ void MutableVertexPartition::init_admin() { #ifdef DEBUG cerr << "void MutableVertexPartition::init_admin()" << endl; #endif size_t n = this->graph->vcount(); // First determine number of communities (assuming they are consecutively numbered this->update_n_communities(); // Reset administration this->_total_weight_in_comm.clear(); this->_total_weight_in_comm.resize(this->_n_communities); this->_total_weight_from_comm.clear(); this->_total_weight_from_comm.resize(this->_n_communities); this->_total_weight_to_comm.clear(); this->_total_weight_to_comm.resize(this->_n_communities); this->_csize.clear(); this->_csize.resize(this->_n_communities); this->_cnodes.clear(); this->_cnodes.resize(this->_n_communities); this->_current_node_cache_community_from = n + 1; this->_cached_weight_from_community.resize(this->_n_communities, 0); this->_current_node_cache_community_to = n + 1; this->_cached_weight_to_community.resize(this->_n_communities, 0); this->_current_node_cache_community_all = n + 1; this->_cached_weight_all_community.resize(this->_n_communities, 0); this->_cached_neigh_comms_all.resize(n); if (this->get_graph()->is_directed()) { this->_cached_neigh_comms_from.resize(n); this->_cached_neigh_comms_to.resize(n); } this->_empty_communities.clear(); this->_total_weight_in_all_comms = 0.0; for (size_t v = 0; v < n; v++) { size_t v_comm = this->_membership[v]; // Update the community size this->_csize[v_comm] += this->graph->node_size(v); // Update the community size this->_cnodes[v_comm] += 1; } size_t m = graph->ecount(); for (size_t e = 0; e < m; e++) { size_t v, u; this->graph->edge(e, v, u); size_t v_comm = this->_membership[v]; size_t u_comm = this->_membership[u]; // Get the weight of the edge double w = this->graph->edge_weight(e); // Add weight to the outgoing weight of community of v this->_total_weight_from_comm[v_comm] += w; #ifdef DEBUG cerr << "\t" << "Add (" << v << ", " << u << ") weight " << w << " to from_comm " << v_comm << "." << endl; #endif // Add weight to the incoming weight of community of u this->_total_weight_to_comm[u_comm] += w; #ifdef DEBUG cerr << "\t" << "Add (" << v << ", " << u << ") weight " << w << " to to_comm " << u_comm << "." << endl; #endif if (!this->graph->is_directed()) { #ifdef DEBUG cerr << "\t" << "Add (" << u << ", " << v << ") weight " << w << " to from_comm " << u_comm << "." << endl; #endif this->_total_weight_from_comm[u_comm] += w; #ifdef DEBUG cerr << "\t" << "Add (" << u << ", " << v << ") weight " << w << " to to_comm " << v_comm << "." << endl; #endif this->_total_weight_to_comm[v_comm] += w; } // If it is an edge within a community if (v_comm == u_comm) { this->_total_weight_in_comm[v_comm] += w; this->_total_weight_in_all_comms += w; #ifdef DEBUG cerr << "\t" << "Add (" << v << ", " << u << ") weight " << w << " to in_comm " << v_comm << "." << endl; #endif } } this->_total_possible_edges_in_all_comms = 0; for (size_t c = 0; c < this->_n_communities; c++) { double n_c = this->csize(c); double possible_edges = this->graph->possible_edges(n_c); #ifdef DEBUG cerr << "\t" << "c=" << c << ", n_c=" << n_c << ", possible_edges=" << possible_edges << endl; #endif this->_total_possible_edges_in_all_comms += possible_edges; // It is possible that some community have a zero size (if the order // is for example not consecutive. We add those communities to the empty // communities vector for consistency. if (this->_cnodes[c] == 0) this->_empty_communities.push_back(c); } #ifdef DEBUG cerr << "exit MutableVertexPartition::init_admin()" << endl << endl; #endif } void MutableVertexPartition::update_n_communities() { this->_n_communities = 0; for (size_t i = 0; i < this->graph->vcount(); i++) if (this->_membership[i] >= this->_n_communities) this->_n_communities = this->_membership[i] + 1; } /**************************************************************************** Renumber the communities so that they are numbered 0,...,q-1 where q is the number of communities. This also removes any empty communities, as they will not be given a new number. *****************************************************************************/ void MutableVertexPartition::renumber_communities() { vector partitions(1); partitions[0] = this; vector new_comm_id = MutableVertexPartition::rank_order_communities(partitions); this->relabel_communities(new_comm_id); } /**************************************************************************** Renumber the communities according to the new labels in new_comm_id. This adjusts the internal bookkeeping as required, avoiding the more costly setup required in init_admin(). In particular, this avoids recomputation of weights in/from/to each community by simply assigning the previously computed values to the new, relabeled communities. For instance, a new_comm_id of <1, 2, 0> will change the labels such that community 0 becomes 1, community 1 becomes 2, and community 2 becomes 0. *****************************************************************************/ void MutableVertexPartition::relabel_communities(vector const& new_comm_id) { if (this->_n_communities != new_comm_id.size()) { throw Exception("Problem swapping community labels. Mismatch between n_communities and new_comm_id vector."); } size_t n = this->graph->vcount(); for (size_t i = 0; i < n; i++) this->_membership[i] = new_comm_id[this->_membership[i]]; this->update_n_communities(); size_t nbcomms = this->n_communities(); vector new_total_weight_in_comm(nbcomms, 0.0); vector new_total_weight_from_comm(nbcomms, 0.0); vector new_total_weight_to_comm(nbcomms, 0.0); vector new_csize(nbcomms, 0); vector new_cnodes(nbcomms, 0); // Relabel community admin for (size_t c = 0; c < new_comm_id.size(); c++) { size_t new_c = new_comm_id[c]; if (this->_cnodes[c] > 0) { new_total_weight_in_comm[new_c] = this->_total_weight_in_comm[c]; new_total_weight_from_comm[new_c] = this->_total_weight_from_comm[c]; new_total_weight_to_comm[new_c] = this->_total_weight_to_comm[c]; new_csize[new_c] = this->_csize[c]; new_cnodes[new_c] = this->_cnodes[c]; } } this->_total_weight_in_comm = new_total_weight_in_comm; this->_total_weight_from_comm = new_total_weight_from_comm; this->_total_weight_to_comm = new_total_weight_to_comm; this->_csize = new_csize; this->_cnodes = new_cnodes; this->_empty_communities.clear(); for (size_t c = 0; c < nbcomms; c++) { if (this->_cnodes[c] == 0) { this->_empty_communities.push_back(c); } } // invalidate cached weight vectors for (size_t c : this->_cached_neigh_comms_from) this->_cached_weight_from_community[c] = 0; this->_cached_neigh_comms_from.clear(); this->_cached_weight_from_community.resize(nbcomms, 0); this->_current_node_cache_community_from = n + 1; for (size_t c : this->_cached_neigh_comms_to) this->_cached_weight_to_community[c] = 0; this->_cached_neigh_comms_to.clear(); this->_cached_weight_to_community.resize(nbcomms, 0); this->_current_node_cache_community_to = n + 1; for (size_t c : this->_cached_neigh_comms_all) this->_cached_weight_all_community[c] = 0; this->_cached_neigh_comms_all.clear(); this->_cached_weight_all_community.resize(nbcomms, 0); this->_current_node_cache_community_all = n + 1; #ifdef DEBUG if (this->_csize.size() < this->_n_communities || this->_cnodes.size() < this->_n_communities || this->_total_weight_in_comm.size() < this->_n_communities || this->_total_weight_to_comm.size() < this->_n_communities || this->_total_weight_from_comm.size() < this->_n_communities || this->_cached_weight_from_community.size() < this->_n_communities || this->_cached_weight_to_community.size() < this->_n_communities || this->_cached_weight_all_community.size() < this->_n_communities) { cerr << "ERROR: MutableVertexPartition bookkeeping is too small after rearrange_community_labels." << endl; } this->init_admin(); for (size_t c = 0; c < this->_n_communities; c++) { if (fabs(new_total_weight_in_comm[c] - this->_total_weight_in_comm[c]) > 1e-6 || fabs(new_total_weight_from_comm[c] - this->_total_weight_from_comm[c]) > 1e-6 || fabs(new_total_weight_to_comm[c] - this->_total_weight_to_comm[c]) > 1e-6 || new_csize[c] != this->_csize[c] || new_cnodes[c] != this->_cnodes[c]) { cerr << "ERROR: MutableVertexPartition bookkeeping is incorrect after rearrange_community_labels." << endl; cerr << "Community c has " << endl << "total_weight_in_comm=" << new_total_weight_in_comm[c] << " (should be " << this->_total_weight_in_comm[c] << ")" << endl << "total_weight_from_comm=" << new_total_weight_from_comm[c] << " (should be " << this->_total_weight_from_comm[c] << ")" << endl << "total_weight_to_comm=" << new_total_weight_to_comm[c] << " (should be " << this->_total_weight_to_comm[c] << ")" << endl << "csize=" << new_csize[c] << " (should be " << this->_csize[c] << ")" << endl << "cnodes=" << new_cnodes[c] << " (should be " << this->_cnodes[c] << ")" << endl; } } #endif } vector MutableVertexPartition::rank_order_communities(vector partitions) { size_t nb_layers = partitions.size(); size_t nb_comms = partitions[0]->n_communities(); #ifdef DEBUG size_t n = partitions[0]->graph->vcount(); for (size_t layer = 0; layer < nb_layers; layer++) { for (size_t v = 0; v < n; v++) { if (partitions[0]->membership(v) != partitions[layer]->membership(v)) cerr << "Membership of all partitions are not equal"; } } #endif // First sort the communities by size // Csizes // first - community // second - csize // third - number of nodes (may be aggregate nodes), to account for communities with zero weight. vector csizes; for (size_t i = 0; i < nb_comms; i++) { double csize = 0; for (size_t layer = 0; layer < nb_layers; layer++) csize += partitions[layer]->csize(i); size_t* row = new size_t[3]; row[0] = i; row[1] = csize; row[2] = partitions[0]->cnodes(i); csizes.push_back(row); } sort(csizes.begin(), csizes.end(), orderCSize); // Then use the sort order to assign new communities, // such that the largest community gets the lowest index. vector new_comm_id(nb_comms, 0); for (size_t i = 0; i < nb_comms; i++) { size_t comm = csizes[i][0]; new_comm_id[comm] = i; delete[] csizes[i]; } return new_comm_id; } /**************************************************************************** Renumber the communities using the original fixed membership vector. Notice that this doesn't ensure any property of the community numbers. *****************************************************************************/ void MutableVertexPartition::renumber_communities(vector const& fixed_nodes, vector const& fixed_membership) { #ifdef DEBUG cerr << "void MutableVertexPartition::renumber_communities(" << &fixed_nodes << ", " << &fixed_membership << ")" << endl; #endif // Skip whole thing if there are no fixed nodes for efficiency if (fixed_nodes.size() == 0) return; // The number of communities does not depend on whether some are fixed size_t nb_comms = n_communities(); // Fill the community map with the original communities vector new_comm_id(nb_comms); vector comm_assigned_bool(nb_comms); priority_queue, std::greater > new_comm_assigned; for (size_t v : fixed_nodes) { if (!comm_assigned_bool[_membership[v]]) { size_t fixed_comm_v = fixed_membership[v]; #ifdef DEBUG cerr << "Setting map for fixed community " << fixed_comm_v << endl; #endif new_comm_id[_membership[v]] = fixed_comm_v; comm_assigned_bool[_membership[v]] = true; new_comm_assigned.push(fixed_comm_v); } } // Index of the most recently added community size_t cc = 0; for (size_t c = 0; c != nb_comms; c++) { if(!comm_assigned_bool[c]) { // Look for the first free integer while (!new_comm_assigned.empty() && cc == new_comm_assigned.top()) { new_comm_assigned.pop(); cc++; } // Assign the community #ifdef DEBUG cerr << "Setting map for free community " << cc << endl; #endif new_comm_id[c] = cc++; } } this->relabel_communities(new_comm_id); } void MutableVertexPartition::renumber_communities(vector const& membership) { cerr << "This function is deprecated, use MutableVertexPartition::set_membership(vector const& membership)" << endl; this->set_membership(membership); } size_t MutableVertexPartition::get_empty_community() { if (this->_empty_communities.empty()) { // If there was no empty community yet, // we will create a new one. add_empty_community(); } return this->_empty_communities.back(); } void MutableVertexPartition::set_membership(vector const& membership) { #ifdef DEBUG cerr << "void MutableVertexPartition::set_membership(" << &membership << ")" << endl; #endif this->_membership = membership; this->clean_mem(); this->init_admin(); #ifdef DEBUG cerr << "exit MutableVertexPartition::set_membership(" << &membership << ")" << endl; #endif } size_t MutableVertexPartition::add_empty_community() { this->_n_communities = this->_n_communities + 1; if (this->_n_communities > this->graph->vcount()) throw Exception("There cannot be more communities than nodes, so there must already be an empty community."); size_t new_comm = this->_n_communities - 1; this->_csize.resize(this->_n_communities); this->_csize[new_comm] = 0; this->_cnodes.resize(this->_n_communities); this->_cnodes[new_comm] = 0; this->_total_weight_in_comm.resize(this->_n_communities); this->_total_weight_in_comm[new_comm] = 0; this->_total_weight_from_comm.resize(this->_n_communities); this->_total_weight_from_comm[new_comm] = 0; this->_total_weight_to_comm.resize(this->_n_communities); this->_total_weight_to_comm[new_comm] = 0; this->_cached_weight_all_community.resize(this->_n_communities); this->_cached_weight_from_community.resize(this->_n_communities); this->_cached_weight_to_community.resize(this->_n_communities); this->_empty_communities.push_back(new_comm); #ifdef DEBUG cerr << "Added empty community " << new_comm << endl; #endif return new_comm; } /**************************************************************************** Move a node to a new community and update the administration. Parameters: v -- Node to move. new_comm -- To which community should it move. *****************************************************************************/ void MutableVertexPartition::move_node(size_t v,size_t new_comm) { #ifdef DEBUG cerr << "void MutableVertexPartition::move_node(" << v << ", " << new_comm << ")" << endl; if (new_comm >= this->n_communities()) cerr << "ERROR: New community (" << new_comm << ") larger than total number of communities (" << this->n_communities() << ")." << endl; #endif // Move node and update internal administration if (new_comm >= this->_n_communities) { if (new_comm < this->graph->vcount()) { while (new_comm >= this->_n_communities) this->add_empty_community(); } else { throw Exception("Cannot add new communities beyond the number of nodes."); } } // Keep track of all possible edges in all communities; double node_size = this->graph->node_size(v); size_t old_comm = this->_membership[v]; #ifdef DEBUG cerr << "Node size: " << node_size << ", old comm: " << old_comm << ", new comm: " << new_comm << endl; #endif // Incidentally, this is independent of whether we take into account self-loops or not // (i.e. whether we count as n_c^2 or as n_c(n_c - 1). Be careful to do this before the // adaptation of the community sizes, otherwise the calculations are incorrect. if (new_comm != old_comm) { double delta_possible_edges_in_comms = 2.0*node_size*(ptrdiff_t)(this->_csize[new_comm] - this->_csize[old_comm] + node_size)/(2.0 - this->graph->is_directed()); _total_possible_edges_in_all_comms += delta_possible_edges_in_comms; #ifdef DEBUG cerr << "Change in possible edges in all comms: " << delta_possible_edges_in_comms << endl; #endif } // Remove from old community #ifdef DEBUG cerr << "Removing from old community " << old_comm << ", community size: " << this->_csize[old_comm] << endl; #endif this->_cnodes[old_comm] -= 1; this->_csize[old_comm] -= node_size; #ifdef DEBUG cerr << "Removed from old community." << endl; #endif // We have to use the size of the set of nodes rather than the csize // to account for nodes that have a zero size (i.e. community may not be empty, but // may have zero size). if (this->_cnodes[old_comm] == 0) { #ifdef DEBUG cerr << "Adding community " << old_comm << " to empty communities." << endl; #endif this->_empty_communities.push_back(old_comm); #ifdef DEBUG cerr << "Added community " << old_comm << " to empty communities." << endl; #endif } if (this->_cnodes[new_comm] == 0) { #ifdef DEBUG cerr << "Removing from empty communities (number of empty communities is " << this->_empty_communities.size() << ")." << endl; #endif vector::reverse_iterator it_comm = this->_empty_communities.rbegin(); while (it_comm != this->_empty_communities.rend() && *it_comm != new_comm) { #ifdef DEBUG cerr << "Empty community " << *it_comm << " != new community " << new_comm << endl; #endif it_comm++; } #ifdef DEBUG cerr << "Erasing empty community " << *it_comm << endl; if (it_comm == this->_empty_communities.rend()) cerr << "ERROR: empty community does not exist." << endl; #endif if (it_comm != this->_empty_communities.rend()) this->_empty_communities.erase( (++it_comm).base() ); } #ifdef DEBUG cerr << "Adding to new community " << new_comm << ", community size: " << this->_csize[new_comm] << endl; #endif // Add to new community this->_cnodes[new_comm] += 1; this->_csize[new_comm] += this->graph->node_size(v); // Switch outgoing links #ifdef DEBUG cerr << "Added to new community." << endl; #endif // Use set for incident edges, because self loop appears twice igraph_neimode_t modes[2] = {IGRAPH_OUT, IGRAPH_IN}; for (size_t mode_i = 0; mode_i < 2; mode_i++) { igraph_neimode_t mode = modes[mode_i]; // Loop over all incident edges vector const& neighbours = this->graph->get_neighbours(v, mode); vector const& neighbour_edges = this->graph->get_neighbour_edges(v, mode); size_t degree = neighbours.size(); #ifdef DEBUG if (mode == IGRAPH_OUT) cerr << "\t" << "Looping over outgoing links." << endl; else if (mode == IGRAPH_IN) cerr << "\t" << "Looping over incoming links." << endl; else cerr << "\t" << "Looping over unknown mode." << endl; #endif for (size_t idx = 0; idx < degree; idx++) { size_t u = neighbours[idx]; size_t e = neighbour_edges[idx]; size_t u_comm = this->_membership[u]; // Get the weight of the edge double w = this->graph->edge_weight(e); if (mode == IGRAPH_OUT) { // Remove the weight from the outgoing weights of the old community this->_total_weight_from_comm[old_comm] -= w; // Add the weight to the outgoing weights of the new community this->_total_weight_from_comm[new_comm] += w; #ifdef DEBUG cerr << "\t" << "Moving link (" << v << "-" << u << ") " << "outgoing weight " << w << " from " << old_comm << " to " << new_comm << "." << endl; #endif } else if (mode == IGRAPH_IN) { // Remove the weight from the outgoing weights of the old community this->_total_weight_to_comm[old_comm] -= w; // Add the weight to the outgoing weights of the new community this->_total_weight_to_comm[new_comm] += w; #ifdef DEBUG cerr << "\t" << "Moving link (" << v << "-" << u << ") " << "incoming weight " << w << " from " << old_comm << " to " << new_comm << "." << endl; #endif } else throw Exception("Incorrect mode for updating the admin."); // Get internal weight (if it is an internal edge) double int_weight = w/(this->graph->is_directed() ? 1.0 : 2.0)/( u == v ? 2.0 : 1.0); // If it is an internal edge in the old community if (old_comm == u_comm) { // Remove the internal weight this->_total_weight_in_comm[old_comm] -= int_weight; this->_total_weight_in_all_comms -= int_weight; #ifdef DEBUG cerr << "\t" << "From link (" << v << "-" << u << ") " << "remove internal weight " << int_weight << " from " << old_comm << "." << endl; #endif } // If it is an internal edge in the new community // i.e. if u is in the new community, or if it is a self loop if ((new_comm == u_comm) || (u == v)) { // Add the internal weight this->_total_weight_in_comm[new_comm] += int_weight; this->_total_weight_in_all_comms += int_weight; #ifdef DEBUG cerr << "\t" << "From link (" << v << "-" << u << ") " << "add internal weight " << int_weight << " to " << new_comm << "." << endl; #endif } } } #ifdef DEBUG // Check this->_total_weight_in_all_comms double check_total_weight_in_all_comms = 0.0; for (size_t c = 0; c < this->n_communities(); c++) check_total_weight_in_all_comms += this->total_weight_in_comm(c); cerr << "Internal _total_weight_in_all_comms=" << this->_total_weight_in_all_comms << ", calculated check_total_weight_in_all_comms=" << check_total_weight_in_all_comms << endl; #endif // Update the membership vector this->_membership[v] = new_comm; #ifdef DEBUG cerr << "exit MutableVertexPartition::move_node(" << v << ", " << new_comm << ")" << endl << endl; #endif } /**************************************************************************** Read new communities from coarser partition assuming that the community represents a node in the coarser partition (with the same index as the community number). ****************************************************************************/ void MutableVertexPartition::from_coarse_partition(vector const& coarse_partition_membership) { this->from_coarse_partition(coarse_partition_membership, this->_membership); } void MutableVertexPartition::from_coarse_partition(MutableVertexPartition* coarse_partition) { this->from_coarse_partition(coarse_partition, this->_membership); } void MutableVertexPartition::from_coarse_partition(MutableVertexPartition* coarse_partition, vector const& coarse_node) { this->from_coarse_partition(coarse_partition->membership(), coarse_node); } /**************************************************************************** Set the current community of all nodes to the community specified in the partition assuming that the coarser partition is created using the membership as specified by coarser_membership. In other words node i becomes node coarse_node[i] in the coarser partition and thus has community coarse_partition_membership[coarse_node[i]]. ****************************************************************************/ void MutableVertexPartition::from_coarse_partition(vector const& coarse_partition_membership, vector const& coarse_node) { // Read the coarser partition for (size_t v = 0; v < this->graph->vcount(); v++) { // In the coarser partition, the node should have the community id // as represented by the coarser_membership vector size_t v_level2 = coarse_node[v]; // In the coarser partition, this node is represented by v_level2 size_t v_comm_level2 = coarse_partition_membership[v_level2]; // Set local membership to community found for node at second level this->_membership[v] = v_comm_level2; } this->clean_mem(); this->init_admin(); } /**************************************************************************** Read new partition from another partition. ****************************************************************************/ void MutableVertexPartition::from_partition(MutableVertexPartition* partition) { // Assign the membership of every node in the supplied partition // to the one in this partition for (size_t v = 0; v < this->graph->vcount(); v++) this->_membership[v] = partition->membership(v); this->clean_mem(); this->init_admin(); } void MutableVertexPartition::cache_neigh_communities(size_t v, igraph_neimode_t mode) { // TODO: We can probably calculate at once the IN, OUT and ALL // rather than this being called multiple times. // Weight between vertex and community #ifdef DEBUG cerr << "double MutableVertexPartition::cache_neigh_communities(" << v << ", " << mode << ")." << endl; #endif vector* _cached_weight_tofrom_community = NULL; vector* _cached_neighs_comms = NULL; switch (mode) { case IGRAPH_IN: _cached_weight_tofrom_community = &(this->_cached_weight_from_community); _cached_neighs_comms = &(this->_cached_neigh_comms_from); break; case IGRAPH_OUT: _cached_weight_tofrom_community = &(this->_cached_weight_to_community); _cached_neighs_comms = &(this->_cached_neigh_comms_to); break; case IGRAPH_ALL: _cached_weight_tofrom_community = &(this->_cached_weight_all_community); _cached_neighs_comms = &(this->_cached_neigh_comms_all); break; } // Reset cached communities for (size_t c : *_cached_neighs_comms) (*_cached_weight_tofrom_community)[c] = 0; // Loop over all incident edges vector const& neighbours = this->graph->get_neighbours(v, mode); vector const& neighbour_edges = this->graph->get_neighbour_edges(v, mode); size_t degree = neighbours.size(); // Reset cached neighbours _cached_neighs_comms->clear(); for (size_t idx = 0; idx < degree; idx++) { size_t u = neighbours[idx]; size_t e = neighbour_edges[idx]; // If it is an edge to the requested community #ifdef DEBUG size_t v_comm = this->_membership[v]; #endif size_t comm = this->_membership[u]; // Get the weight of the edge double w = this->graph->edge_weight(e); // Self loops appear twice here if the graph is undirected, so divide by 2.0 in that case. if (u == v && !this->graph->is_directed()) w /= 2.0; #ifdef DEBUG cerr << "\t" << "Edge (" << v << "-" << u << "), Comm (" << v_comm << "-" << comm << ") weight: " << w << "." << endl; #endif (*_cached_weight_tofrom_community)[comm] += w; // REMARK: Notice in the rare case of negative weights, being exactly equal // for a certain community, that this community may then potentially be added multiple // times to the _cached_neighs. However, I don' believe this causes any further issue, // so that's why I leave this here as is. if ((*_cached_weight_tofrom_community)[comm] != 0) _cached_neighs_comms->push_back(comm); } #ifdef DEBUG cerr << "exit Graph::cache_neigh_communities(" << v << ", " << mode << ")." << endl; #endif } vector const& MutableVertexPartition::get_neigh_comms(size_t v, igraph_neimode_t mode) { if (!this->get_graph()->is_directed()) mode = IGRAPH_ALL; // igraph ignores mode for undirected graphs switch (mode) { case IGRAPH_IN: if (this->_current_node_cache_community_from != v) { cache_neigh_communities(v, mode); this->_current_node_cache_community_from = v; } return this->_cached_neigh_comms_from; case IGRAPH_OUT: if (this->_current_node_cache_community_to != v) { cache_neigh_communities(v, mode); this->_current_node_cache_community_to = v; } return this->_cached_neigh_comms_to; case IGRAPH_ALL: if (this->_current_node_cache_community_all != v) { cache_neigh_communities(v, mode); this->_current_node_cache_community_all = v; } return this->_cached_neigh_comms_all; } throw Exception("Problem obtaining neighbour communities, invalid mode."); } vector MutableVertexPartition::get_neigh_comms(size_t v, igraph_neimode_t mode, vector const& constrained_membership) { vector neigh_comms; vector comm_added(this->n_communities(), false); for (size_t u : this->graph->get_neighbours(v, mode)) { if (constrained_membership[v] == constrained_membership[u]) { size_t comm = this->membership(u); if (!comm_added[comm]) { neigh_comms.push_back(comm); comm_added[comm]; } } } return neigh_comms; } vtraag-libleidenalg-8518deb/src/Optimiser.cpp000066400000000000000000001621661520437531200213150ustar00rootroot00000000000000#include "Optimiser.h" /**************************************************************************** Create a new Optimiser object Parameters: consider_comms -- Consider communities in a specific manner: ALL_COMMS -- Consider all communities for improvement. ALL_NEIGH_COMMS -- Consider all neighbour communities for improvement. RAND_COMM -- Consider a random commmunity for improvement. RAND_NEIGH_COMM -- Consider a random community among the neighbours for improvement. ****************************************************************************/ Optimiser::Optimiser() { this->consider_comms = Optimiser::ALL_NEIGH_COMMS; this->optimise_routine = Optimiser::MOVE_NODES; this->refine_consider_comms = Optimiser::RAND_NEIGH_COMM; this->refine_routine = Optimiser::MERGE_NODES; this->refine_partition = true; this->consider_empty_community = true; this->min_comm_size = 0; this->max_comm_size = 0; this->community_constraint_enforcement = 0; igraph_rng_init(&rng, &igraph_rngtype_mt19937); igraph_rng_seed(&rng, time(NULL)); } Optimiser::~Optimiser() { igraph_rng_destroy(&rng); } /** * Calculate the improvement with respect to community size constraints. * * @param min_size The minimum community size * @param max_size The maximum community size * @param old_size The current size of the old community (i.e. before moving the node) * @param new_size The current size of the new community (i.e. before moving the node) * @param v_size The size of the node which is moved from the old to the new community * @param beta Parameter controlling the enforcement of the constraints * @return Improvement */ double Optimiser::improvement_community_constraints(size_t old_size, size_t new_size, size_t v_size) { double improvement = 0.0; if (community_constraint_enforcement <= 0) return 0.0; if (max_comm_size > 0) { // What is the difference for the old community wrt the maximum community size? if (old_size > max_comm_size) improvement += sqrt(old_size - max_comm_size); if (old_size - v_size > max_comm_size) improvement -= sqrt((old_size - v_size) - max_comm_size); // What is the difference for the new community wrt the maximum community size? if (new_size > max_comm_size) improvement += sqrt(new_size - max_comm_size); if (new_size + v_size > max_comm_size) improvement -= sqrt((new_size + v_size) - max_comm_size); } if (min_comm_size > 0) { // What is the difference for the old community wrt the minimum community size? if (old_size < min_comm_size) improvement += sqrt(min_comm_size - old_size); if (old_size > v_size && old_size - v_size < min_comm_size) // Only consider if community is not empty improvement -= sqrt(min_comm_size - (old_size - v_size)); // What is the difference for the new community wrt the minimum community size? if (new_size > 0 && new_size < min_comm_size) // Only consider if community is not empty improvement += sqrt(min_comm_size - new_size); if (new_size + v_size < min_comm_size) improvement -= sqrt(min_comm_size - (new_size + v_size)); } return community_constraint_enforcement*improvement; } /***************************************************************************** optimise the provided partition. *****************************************************************************/ double Optimiser::optimise_partition(MutableVertexPartition* partition) { size_t n = partition->get_graph()->vcount(); vector is_membership_fixed(n, false); return this->optimise_partition(partition, is_membership_fixed); } double Optimiser::optimise_partition(MutableVertexPartition* partition, vector const& is_membership_fixed) { vector partitions(1); partitions[0] = partition; vector layer_weights(1, 1.0); return this->optimise_partition(partitions, layer_weights, is_membership_fixed); } /***************************************************************************** optimise the providede partitions simultaneously. We here use the sum of the difference of the moves as the overall quality function, each partition weighted by the layer weight. *****************************************************************************/ /***************************************************************************** optimise the provided partition. *****************************************************************************/ double Optimiser::optimise_partition(vector partitions, vector layer_weights, vector const& is_membership_fixed) { #ifdef DEBUG cerr << "void Optimiser::optimise_partition(vector partitions, vector layer_weights, vector const& is_membership_fixed)" << endl; #endif double q = 0.0; // Number of multiplex layers size_t nb_layers = partitions.size(); if (nb_layers == 0) throw Exception("No partitions provided."); // Get graphs for all layers vector graphs(nb_layers); for (size_t layer = 0; layer < nb_layers; layer++) graphs[layer] = partitions[layer]->get_graph(); // Number of nodes in the graphs. Should be the same across // all graphs, so we only take the first one. size_t n = graphs[0]->vcount(); // Make sure that all graphs contain the exact same number of nodes. // We assume the index of each vertex in the graph points to the // same node (but then in a different layer). for (Graph* graph : graphs) if (graph->vcount() != n) throw Exception("Number of nodes are not equal for all graphs."); // Make sure that the minimum community size is less than or equals the // maximum community size if (max_comm_size > 0 && min_comm_size > 0 && max_comm_size < min_comm_size) throw Exception("Maximum community size is smaller than the minimum community size."); // Get the fixed membership for fixed nodes vector fixed_nodes; vector fixed_membership(n); for (size_t v = 0; v < n; v++) { if (is_membership_fixed[v]) { fixed_nodes.push_back(v); fixed_membership[v] = partitions[0]->membership(v); } } // Initialize the vector of the collapsed graphs for all layers vector collapsed_graphs(nb_layers); vector collapsed_partitions(nb_layers); // Declare the collapsed_graph variable which will contain the graph // collapsed by its communities. We will use this variables at each // further iteration, so we don't keep a collapsed graph at each pass. for (size_t layer = 0; layer < nb_layers; layer++) { collapsed_graphs[layer] = graphs[layer]; collapsed_partitions[layer] = partitions[layer]; } // Declare which nodes in the collapsed graph are fixed, which to start is // simply equal to is_membership_fixed vector is_collapsed_membership_fixed(is_membership_fixed); // This reflects the aggregate node, which to start with is simply equal to the graph. vector aggregate_node_per_individual_node = range(n); bool aggregate_further = true; // As long as there remains improvement iterate double improv = 0.0; do { // Optimise partition for collapsed graph #ifdef DEBUG q = 0.0; for (size_t layer = 0; layer < nb_layers; layer++) q += partitions[layer]->quality()*layer_weights[layer]; cerr << "Quality before moving " << q << endl; #endif if (this->optimise_routine == Optimiser::MOVE_NODES) improv += this->move_nodes(collapsed_partitions, layer_weights, is_collapsed_membership_fixed, this->consider_comms, this->consider_empty_community, false); else if (this->optimise_routine == Optimiser::MERGE_NODES) improv += this->merge_nodes(collapsed_partitions, layer_weights, is_collapsed_membership_fixed, this->consider_comms, false); #ifdef DEBUG cerr << "Found " << collapsed_partitions[0]->n_communities() << " communities, improved " << improv << endl; q = 0.0; for (size_t layer = 0; layer < nb_layers; layer++) q += partitions[layer]->quality()*layer_weights[layer]; cerr << "Quality after moving " << q << endl; #endif // DEBUG // Make sure improvement on coarser scale is reflected on the // scale of the graph as a whole. for (size_t layer = 0; layer < nb_layers; layer++) { if (collapsed_partitions[layer] != partitions[layer]) { if (this->refine_partition) partitions[layer]->from_coarse_partition(collapsed_partitions[layer], aggregate_node_per_individual_node); else partitions[layer]->from_coarse_partition(collapsed_partitions[layer]); } } #ifdef DEBUG q = 0.0; for (size_t layer = 0; layer < nb_layers; layer++) q += partitions[layer]->quality()*layer_weights[layer]; cerr << "Quality on finer partition " << q << endl; #endif // DEBUG #ifdef DEBUG cerr << "Number of communities: " << partitions[0]->n_communities() << endl; #endif // Collapse graph (i.e. community graph) // If we do refine the partition, we separate communities in slightly more // fine-grained parts for which we collapse the graph. vector sub_collapsed_partitions(nb_layers); vector new_collapsed_graphs(nb_layers); vector new_collapsed_partitions(nb_layers); if (this->refine_partition) { // First create a new partition, which should be a sub partition // of the collapsed partition, i.e. such that all clusters of // the partition are strictly partitioned in the subpartition. #ifdef DEBUG cerr << "\tBefore SLM " << collapsed_partitions[0]->n_communities() << " communities." << endl; #endif for (size_t layer = 0; layer < nb_layers; layer++) { sub_collapsed_partitions[layer] = collapsed_partitions[layer]->create(collapsed_graphs[layer]); } // Then move around nodes but restrict movement to within original communities. #ifdef DEBUG cerr << "\tStarting refinement with " << sub_collapsed_partitions[0]->n_communities() << " communities." << endl; #endif if (this->refine_routine == Optimiser::MOVE_NODES) this->move_nodes_constrained(sub_collapsed_partitions, layer_weights, refine_consider_comms, collapsed_partitions[0], false); else if (this->refine_routine == Optimiser::MERGE_NODES) this->merge_nodes_constrained(sub_collapsed_partitions, layer_weights, refine_consider_comms, collapsed_partitions[0], false); #ifdef DEBUG cerr << "\tAfter applying refinement found " << sub_collapsed_partitions[0]->n_communities() << " communities." << endl; #endif // Determine new aggregate node per individual node for (size_t v = 0; v < n; v++) { size_t aggregate_node = aggregate_node_per_individual_node[v]; aggregate_node_per_individual_node[v] = sub_collapsed_partitions[0]->membership(aggregate_node); } // Collapse graph based on sub collapsed partition for (size_t layer = 0; layer < nb_layers; layer++) { new_collapsed_graphs[layer] = collapsed_graphs[layer]->collapse_graph(sub_collapsed_partitions[layer]); } // Determine the membership for the collapsed graph vector new_collapsed_membership(new_collapsed_graphs[0]->vcount()); // Every node within the collapsed graph should be assigned // to the community of the original partition before the refinement. // We thus check for each node what the community is in the refined partition // and set the membership equal to the original partition (i.e. // even though the aggregation may be slightly different, the // membership of the aggregated nodes is as indicated by the original partition.) #ifdef DEBUG //cerr << "Refinement\tOrig" << endl; #endif // DEBUG for (size_t v = 0; v < collapsed_graphs[0]->vcount(); v++) { size_t new_aggregate_node = sub_collapsed_partitions[0]->membership(v); new_collapsed_membership[new_aggregate_node] = collapsed_partitions[0]->membership(v); #ifdef DEBUG //cerr << sub_collapsed_partition->membership(v) << "\t" << sub_collapsed_partition->membership(v) << endl; #endif // DEBUG } // Determine which collapsed nodes are fixed is_collapsed_membership_fixed.clear(); is_collapsed_membership_fixed.resize(new_collapsed_graphs[0]->vcount(), false); for (size_t v = 0; v < n; v++) if (is_membership_fixed[v]) is_collapsed_membership_fixed[aggregate_node_per_individual_node[v]] = true; // Create new collapsed partition for (size_t layer = 0; layer < nb_layers; layer++) { delete sub_collapsed_partitions[layer]; new_collapsed_partitions[layer] = collapsed_partitions[layer]->create(new_collapsed_graphs[layer], new_collapsed_membership); } } else { for (size_t layer = 0; layer < nb_layers; layer++) { new_collapsed_graphs[layer] = collapsed_graphs[layer]->collapse_graph(collapsed_partitions[layer]); // Create collapsed partition (i.e. default partition of each node in its own community). new_collapsed_partitions[layer] = collapsed_partitions[layer]->create(new_collapsed_graphs[layer]); #ifdef DEBUG cerr << "Layer " << layer << endl; cerr << "Old collapsed graph " << collapsed_graphs[layer] << ", vcount is " << collapsed_graphs[layer]->vcount() << endl; cerr << "New collapsed graph " << new_collapsed_graphs[layer] << ", vcount is " << new_collapsed_graphs[layer]->vcount() << endl; #endif } } // Determine whether to aggregate further // If all is fixed, no need to aggregate aggregate_further = false; for (const bool& membership_fixed : is_collapsed_membership_fixed) { if(!membership_fixed) { aggregate_further = true; break; } } // else, check whether anything has stirred since last time aggregate_further &= (new_collapsed_graphs[0]->vcount() < collapsed_graphs[0]->vcount()) && (collapsed_graphs[0]->vcount() > collapsed_partitions[0]->n_communities()); #ifdef DEBUG cerr << "Aggregate further " << aggregate_further << endl; #endif // Delete the previous collapsed partition and graph for (size_t layer = 0; layer < nb_layers; layer++) { if (collapsed_partitions[layer] != partitions[layer]) delete collapsed_partitions[layer]; if (collapsed_graphs[layer] != graphs[layer]) delete collapsed_graphs[layer]; } // and set them to the new one. collapsed_partitions = new_collapsed_partitions; collapsed_graphs = new_collapsed_graphs; #ifdef DEBUG for (size_t layer = 0; layer < nb_layers; layer++) { cerr << "Calculate partition " << layer << " quality." << endl; q = partitions[layer]->quality()*layer_weights[layer]; cerr << "Calculate collapsed partition quality." << endl; double q_collapsed = 0.0; q_collapsed += collapsed_partitions[layer]->quality()*layer_weights[layer]; if (fabs(q - q_collapsed) > 1e-6) { cerr << "ERROR: Quality of original partition and collapsed partition are not equal." << endl; } cerr << "partition->quality()=" << q << ", collapsed_partition->quality()=" << q_collapsed << endl; cerr << "graph->total_weight()=" << graphs[layer]->total_weight() << ", collapsed_graph->total_weight()=" << collapsed_graphs[layer]->total_weight() << endl; cerr << "graph->vcount()=" << graphs[layer]->vcount() << ", collapsed_graph->vcount()=" << collapsed_graphs[layer]->vcount() << endl; cerr << "graph->ecount()=" << graphs[layer]->ecount() << ", collapsed_graph->ecount()=" << collapsed_graphs[layer]->ecount() << endl; cerr << "graph->is_directed()=" << graphs[layer]->is_directed() << ", collapsed_graph->is_directed()=" << collapsed_graphs[layer]->is_directed() << endl; cerr << "graph->correct_self_loops()=" << graphs[layer]->correct_self_loops() << ", collapsed_graph->correct_self_loops()=" << collapsed_graphs[layer]->correct_self_loops() << endl << endl; } #endif // DEBUG } while (aggregate_further); // Clean up memory after use. for (size_t layer = 0; layer < nb_layers; layer++) { if (collapsed_partitions[layer] != partitions[layer]) delete collapsed_partitions[layer]; if (collapsed_graphs[layer] != graphs[layer]) delete collapsed_graphs[layer]; } // Make sure the resulting communities are called 0,...,r-1 // where r is the number of communities. The exception is fixed // nodes which should keep the numbers of the original communities q = 0.0; partitions[0]->renumber_communities(); partitions[0]->renumber_communities(fixed_nodes, fixed_membership); vector const& membership = partitions[0]->membership(); // We only renumber the communities for the first graph, // since the communities for the other graphs should just be equal // to the membership of the first graph. for (size_t layer = 1; layer < nb_layers; layer++) { partitions[layer]->set_membership(membership); q += partitions[layer]->quality()*layer_weights[layer]; } return improv; } /***************************************************************************** Move nodes to other communities depending on how other communities are considered, see consider_comms parameter of the class. Parameters: partition -- The partition to optimise. ******************************************************************************/ double Optimiser::move_nodes(MutableVertexPartition* partition) { return this->move_nodes(partition, this->consider_comms); } double Optimiser::move_nodes(MutableVertexPartition* partition, int consider_comms) { vector is_membership_fixed(partition->get_graph()->vcount()); return this->move_nodes(partition, is_membership_fixed, consider_comms, false); } double Optimiser::move_nodes(MutableVertexPartition* partition, vector const& is_membership_fixed, int consider_comms, bool renumber_fixed_nodes) { vector partitions(1); partitions[0] = partition; vector layer_weights(1, 1.0); return this->move_nodes(partitions, layer_weights, is_membership_fixed, consider_comms, this->consider_empty_community, renumber_fixed_nodes); } double Optimiser::merge_nodes(MutableVertexPartition* partition) { return this->merge_nodes(partition, this->consider_comms); } double Optimiser::merge_nodes(MutableVertexPartition* partition, int consider_comms) { vector is_membership_fixed(partition->get_graph()->vcount()); return this->merge_nodes(partition, is_membership_fixed, consider_comms, false); } double Optimiser::merge_nodes(MutableVertexPartition* partition, vector const& is_membership_fixed, int consider_comms, bool renumber_fixed_nodes) { vector partitions(1); partitions[0] = partition; vector layer_weights(1, 1.0); return this->merge_nodes(partitions, layer_weights, is_membership_fixed, consider_comms, renumber_fixed_nodes); } double Optimiser::move_nodes_constrained(MutableVertexPartition* partition, MutableVertexPartition* constrained_partition) { return this->move_nodes_constrained(partition, this->refine_consider_comms, constrained_partition); } double Optimiser::move_nodes_constrained(MutableVertexPartition* partition, int consider_comms, MutableVertexPartition* constrained_partition) { vector partitions(1); partitions[0] = partition; vector layer_weights(1, 1.0); return this->move_nodes_constrained(partitions, layer_weights, consider_comms, constrained_partition, true); } double Optimiser::merge_nodes_constrained(MutableVertexPartition* partition, MutableVertexPartition* constrained_partition) { return this->merge_nodes_constrained(partition, this->refine_consider_comms, constrained_partition); } double Optimiser::merge_nodes_constrained(MutableVertexPartition* partition, int consider_comms, MutableVertexPartition* constrained_partition) { vector partitions(1); partitions[0] = partition; vector layer_weights(1, 1.0); return this->merge_nodes_constrained(partitions, layer_weights, consider_comms, constrained_partition, true); } /***************************************************************************** Move nodes to neighbouring communities such that each move improves the given quality function maximally (i.e. greedily) for multiple layers, i.e. for multiplex networks. Each node will be in the same community in each layer, but the method may be different, or the weighting may be different for different layers. Notably, this can be used in the case of negative links, where you would like to weigh the negative links with a negative weight. Parameters: partitions -- The partitions to optimise. layer_weights -- The weights used for the different layers. ******************************************************************************/ double Optimiser::move_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, bool renumber_fixed_nodes) { return this->move_nodes(partitions, layer_weights, is_membership_fixed, this->consider_comms, this->consider_empty_community, renumber_fixed_nodes); } double Optimiser::move_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, int consider_comms, int consider_empty_community) { return this->move_nodes(partitions, layer_weights, is_membership_fixed, consider_comms, consider_empty_community, true); } double Optimiser::move_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, int consider_comms, int consider_empty_community, bool renumber_fixed_nodes) { #ifdef DEBUG cerr << "double Optimiser::move_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, int consider_comms, int consider_empty_community, bool renumber_fixed_nodes)" << endl; #endif // Number of multiplex layers size_t nb_layers = partitions.size(); if (nb_layers == 0) return -1.0; // Get graphs vector graphs(nb_layers); for (size_t layer = 0; layer < nb_layers; layer++) graphs[layer] = partitions[layer]->get_graph(); // Number of nodes in the graph size_t n = graphs[0]->vcount(); // Get the fixed membership for fixed nodes vector fixed_nodes; vector fixed_membership(n); if (renumber_fixed_nodes) { for (size_t v = 0; v < n; v++) { if (is_membership_fixed[v]) { fixed_nodes.push_back(v); fixed_membership[v] = partitions[0]->membership(v); } } } // Total improvement while moving nodes double total_improv = 0.0; for (Graph* graph : graphs) if (graph->vcount() != n) throw Exception("Number of nodes are not equal for all graphs."); // Number of moved nodes during one loop size_t nb_moves = 0; // Make sure that the minimum community size is less than or equals the // maximum community size if (max_comm_size > 0 && min_comm_size > 0 && max_comm_size < min_comm_size) throw Exception("Maximum community size is smaller than the minimum community size."); // Fixed nodes are also stable nodes vector is_node_stable(is_membership_fixed); // Establish vertex order // We normally initialize the normal vertex order // of considering node 0,1,... // But if we use a random order, we shuffle this order. // Also, we skip fixed nodes from the queue for efficiency reasons vector nodes; for (size_t v = 0; v != is_membership_fixed.size(); v++) { if (!is_membership_fixed[v]) nodes.push_back(v); } shuffle(nodes, &rng); deque vertex_order(nodes.begin(), nodes.end()); // Initialize the degree vector // If we want to debug the function, we will calculate some additional values. // In particular, the following consistencies could be checked: // (1) - The difference in the quality function after a move should match // the reported difference when calling diff_move. // (2) - The quality function should be exactly the same value after // aggregating/collapsing the graph. vector comm_added(partitions[0]->n_communities(), false); vector comms; // As long as the queue is not empty while(!vertex_order.empty()) { size_t v = vertex_order.front(); vertex_order.pop_front(); // What is the current community of the node (this should be the same for all layers) size_t v_comm = partitions[0]->membership(v); if (consider_comms == ALL_COMMS) { for(size_t comm = 0; comm < partitions[0]->n_communities(); comm++) { for (size_t layer = 0; layer < nb_layers; layer++) { if (partitions[layer]->cnodes(comm) > 0 && !comm_added[comm]) { comms.push_back(comm); comm_added[comm] = true; break; // Break from for loop in layer } } } } else if (consider_comms == ALL_NEIGH_COMMS) { /****************************ALL NEIGH COMMS*****************************/ for (size_t layer = 0; layer < nb_layers; layer++) { for (size_t comm : partitions[layer]->get_neigh_comms(v, IGRAPH_ALL)) { if (!comm_added[comm]) { comms.push_back(comm); comm_added[comm] = true; } } } } else if (consider_comms == RAND_COMM) { /****************************RAND COMM***********************************/ size_t rand_comm = partitions[0]->membership(graphs[0]->get_random_node(&rng)); // No need to check if random_comm is already added, we only add one comm comms.push_back(rand_comm); comm_added[rand_comm] = true; } else if (consider_comms == RAND_NEIGH_COMM) { /****************************RAND NEIGH COMM*****************************/ size_t rand_layer = get_random_int(0, nb_layers - 1, &rng); if (graphs[rand_layer]->degree(v, IGRAPH_ALL) > 0) { size_t rand_comm = partitions[0]->membership(graphs[rand_layer]->get_random_neighbour(v, IGRAPH_ALL, &rng)); // No need to check if random_comm is already added, we only add one comm comms.push_back(rand_comm); comm_added[rand_comm] = true; } } // Check if we should move to an empty community if (consider_empty_community) { if ( partitions[0]->cnodes(v_comm) > 1 ) // We should not move a node when it is already in its own empty community (this may otherwise create more empty communities than nodes) { size_t n_comms = partitions[0]->n_communities(); size_t comm = partitions[0]->get_empty_community(); #ifdef DEBUG cerr << "Checking empty community (" << comm << ") for partition " << partitions[0] << endl; #endif comms.push_back(comm); if (partitions[0]->n_communities() > n_comms) { // If the empty community has just been added, we need to make sure // that is has also been added to the other layers for (size_t layer = 1; layer < nb_layers; layer++) partitions[layer]->add_empty_community(); comm_added.push_back(true); } } } #ifdef DEBUG cerr << "Consider " << comms.size() << " communities for moving." << endl; #endif size_t max_comm = v_comm; double max_improv = (0 < max_comm_size && max_comm_size < partitions[0]->csize(v_comm)) ? -INFINITY : 10*DBL_EPSILON; double v_size = graphs[0]->node_size(v); double old_size = partitions[0]->csize(v_comm); for (size_t comm : comms) { // reset comm_added to all false comm_added[comm] = false; double possible_improv = 0.0; if (comm != v_comm) possible_improv += this->improvement_community_constraints(old_size, partitions[0]->csize(comm), v_size); // Consider the improvement of moving to a community for all layers for (size_t layer = 0; layer < nb_layers; layer++) { // Make sure to multiply it by the weight per layer possible_improv += layer_weights[layer]*partitions[layer]->diff_move(v, comm); } if (possible_improv > max_improv) { max_comm = comm; max_improv = possible_improv; } } // Clear comms comms.clear(); is_node_stable[v] = true; // If we actually plan to move the node if (max_comm != v_comm) { // Keep track of improvement total_improv += max_improv; #ifdef DEBUG // If we are debugging, calculate quality function double q_improv = 0; #endif for (size_t layer = 0; layer < nb_layers; layer++) { MutableVertexPartition* partition = partitions[layer]; #ifdef DEBUG // If we are debugging, calculate quality function double q1 = partition->quality(); #endif // Actually move the node partition->move_node(v, max_comm); #ifdef DEBUG // If we are debugging, calculate quality function // and report difference double q2 = partition->quality(); double q_delta = layer_weights[layer]*(q2 - q1); q_improv += q_delta; cerr << "Move node " << v << " from " << v_comm << " to " << max_comm << " for layer " << layer << " (diff_move=" << max_improv << ", q2 - q1=" << q_delta << ")" << endl; #endif } #ifdef DEBUG if (fabs(q_improv - max_improv) > 1e-6) { cerr << "ERROR: Inconsistency while moving nodes, improvement as measured by quality function did not equal the improvement measured by the diff_move function." << endl << " (diff_move=" << max_improv << ", q2 - q1=" << q_improv << ")" << endl; } #endif // Mark neighbours as unstable (if not in new community and not fixed) for (Graph* graph : graphs) { for (size_t u : graph->get_neighbours(v, IGRAPH_ALL)) { // If the neighbour was stable and is not in the new community, we // should mark it as unstable, and add it to the queue, skipping // fixed nodes if (is_node_stable[u] && partitions[0]->membership(u) != max_comm && !is_membership_fixed[u]) { vertex_order.push_back(u); is_node_stable[u] = false; } } } // Keep track of number of moves nb_moves += 1; } } partitions[0]->renumber_communities(); if (renumber_fixed_nodes) partitions[0]->renumber_communities(fixed_nodes, fixed_membership); vector const& membership = partitions[0]->membership(); for (size_t layer = 1; layer < nb_layers; layer++) { partitions[layer]->set_membership(membership); #ifdef DEBUG cerr << "Renumbered communities for layer " << layer << " for " << partitions[layer]->n_communities() << " communities." << endl; #endif //DEBUG } return total_improv; } double Optimiser::merge_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, bool renumber_fixed_nodes) { return this->merge_nodes(partitions, layer_weights, is_membership_fixed, this->consider_comms, renumber_fixed_nodes); } double Optimiser::merge_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, int consider_comms, bool renumber_fixed_nodes) { #ifdef DEBUG cerr << "double Optimiser::merge_nodes(vector partitions, vector layer_weights, vector const& is_membership_fixed, int consider_comms, bool renumber_fixed_nodes, size_t max_comm)" << std::endl; #endif // Number of multiplex layers size_t nb_layers = partitions.size(); if (nb_layers == 0) return -1.0; // Get graphs vector graphs(nb_layers); for (size_t layer = 0; layer < nb_layers; layer++) graphs[layer] = partitions[layer]->get_graph(); // Number of nodes in the graph size_t n = graphs[0]->vcount(); // Make sure that the minimum community size is less than or equals the // maximum community size if (max_comm_size > 0 && min_comm_size > 0 && max_comm_size < min_comm_size) throw Exception("Maximum community size is smaller than the minimum community size."); // Get the fixed membership for fixed nodes vector fixed_nodes; vector fixed_membership(n); if (renumber_fixed_nodes) { for (size_t v = 0; v < n; v++) { if (is_membership_fixed[v]) { fixed_nodes.push_back(v); fixed_membership[v] = partitions[0]->membership(v); } } } // Total improvement while merging nodes double total_improv = 0.0; for (Graph* graph : graphs) if (graph->vcount() != n) throw Exception("Number of nodes are not equal for all graphs."); // Establish vertex order, skipping fixed nodes // We normally initialize the normal vertex order // of considering node 0,1,... vector vertex_order; for (size_t v = 0; v != n; v++) if (!is_membership_fixed[v]) vertex_order.push_back(v); // But if we use a random order, we shuffle this order. shuffle(vertex_order, &rng); vector comm_added(partitions[0]->n_communities(), false); vector comms; // Iterate over all nodes for (size_t v : vertex_order) { // What is the current community of the node (this should be the same for all layers) size_t v_comm = partitions[0]->membership(v); // Clear comms for (size_t comm : comms) comm_added[comm] = false; comms.clear(); #ifdef DEBUG cerr << "Consider moving node " << v << " from " << v_comm << "." << endl; #endif if (partitions[0]->cnodes(v_comm) == 1) { if (consider_comms == ALL_COMMS) { for(size_t comm = 0; comm < partitions[0]->n_communities(); comm++) { for (size_t layer = 0; layer < nb_layers; layer++) { if (partitions[layer]->cnodes(comm) > 0 && !comm_added[comm]) { comms.push_back(comm); comm_added[comm] = true; break; // Break from for loop in layer } } } } else if (consider_comms == ALL_NEIGH_COMMS) { /****************************ALL NEIGH COMMS*****************************/ for (size_t layer = 0; layer < nb_layers; layer++) { for (size_t comm : partitions[layer]->get_neigh_comms(v, IGRAPH_ALL)) { if (!comm_added[comm]) { comms.push_back(comm); comm_added[comm] = true; } } } } else if (consider_comms == RAND_COMM) { /****************************RAND COMM***********************************/ size_t rand_comm = partitions[0]->membership(graphs[0]->get_random_node(&rng)); // No need to check if random_comm is already added, we only add one comm comms.push_back(rand_comm); comm_added[rand_comm] = true; } else if (consider_comms == RAND_NEIGH_COMM) { /****************************RAND NEIGH COMM*****************************/ size_t rand_layer = get_random_int(0, nb_layers - 1, &rng); size_t k = graphs[rand_layer]->degree(v, IGRAPH_ALL); if (k > 0) { // Make sure there is also a probability not to move the node if (get_random_int(0, k, &rng) > 0) { size_t rand_comm = partitions[0]->membership(graphs[rand_layer]->get_random_neighbour(v, IGRAPH_ALL, &rng)); // No need to check if random_comm is already added, we only add one comm comms.push_back(rand_comm); comm_added[rand_comm] = true; } } } #ifdef DEBUG cerr << "Consider " << comms.size() << " communities for moving node " << v << "." << endl; #endif size_t max_comm = v_comm; double max_improv = (0 < max_comm_size && max_comm_size < partitions[0]->csize(v_comm)) ? -INFINITY : 0; double v_size = graphs[0]->node_size(v); double old_size = partitions[0]->csize(v_comm); for (size_t comm : comms) { double possible_improv = 0.0; if (comm != v_comm) possible_improv += this->improvement_community_constraints(old_size, partitions[0]->csize(comm), v_size); // Consider the improvement of moving to a community for all layers for (size_t layer = 0; layer < nb_layers; layer++) { // Make sure to multiply it by the weight per layer possible_improv += layer_weights[layer]*partitions[layer]->diff_move(v, comm); } #ifdef DEBUG cerr << "Improvement of " << possible_improv << " when move to " << comm << "." << endl; #endif if (possible_improv >= max_improv) { max_comm = comm; max_improv = possible_improv; } } // If we actually plan to move the node if (max_comm != v_comm) { // Keep track of improvement total_improv += max_improv; #ifdef DEBUG // If we are debugging, calculate quality function double q_improv = 0; #endif for (size_t layer = 0; layer < nb_layers; layer++) { MutableVertexPartition* partition = partitions[layer]; #ifdef DEBUG // If we are debugging, calculate quality function double q1 = partition->quality(); #endif // Actually move the node partition->move_node(v, max_comm); #ifdef DEBUG // If we are debugging, calculate quality function // and report difference double q2 = partition->quality(); double q_delta = layer_weights[layer]*(q2 - q1); q_improv += q_delta; cerr << "Move node " << v << " from " << v_comm << " to " << max_comm << " for layer " << layer << " (diff_move=" << max_improv << ", q2 - q1=" << q_delta << ")" << endl; #endif } #ifdef DEBUG if (fabs(q_improv - max_improv) > 1e-6) { cerr << "ERROR: Inconsistency while moving nodes, improvement as measured by quality function did not equal the improvement measured by the diff_move function." << endl << " (diff_move=" << max_improv << ", q2 - q1=" << q_improv << ")" << endl; } #endif } } } partitions[0]->renumber_communities(); if (renumber_fixed_nodes) partitions[0]->renumber_communities(fixed_nodes, fixed_membership); vector const& membership = partitions[0]->membership(); for (size_t layer = 1; layer < nb_layers; layer++) { partitions[layer]->set_membership(membership); #ifdef DEBUG cerr << "Renumbered communities for layer " << layer << " for " << partitions[layer]->n_communities() << " communities." << endl; #endif //DEBUG } return total_improv; } double Optimiser::move_nodes_constrained(vector partitions, vector layer_weights, MutableVertexPartition* constrained_partition) { return this->move_nodes_constrained(partitions, layer_weights, this->refine_consider_comms, constrained_partition, true); } double Optimiser::move_nodes_constrained(vector partitions, vector layer_weights, int consider_comms, MutableVertexPartition* constrained_partition, bool enforce_community_size_constraint) { #ifdef DEBUG cerr << "double Optimiser::move_nodes_constrained(vector partitions, vector layer_weights, int consider_comms, MutableVertexPartition* constrained_partition)" << std::endl; #endif // Number of multiplex layers size_t nb_layers = partitions.size(); if (nb_layers == 0) return -1.0; // Get graphs vector graphs(nb_layers); for (size_t layer = 0; layer < nb_layers; layer++) graphs[layer] = partitions[layer]->get_graph(); // Number of nodes in the graph size_t n = graphs[0]->vcount(); // Total improvement while moving nodes double total_improv = 0.0; for (size_t layer = 0; layer < nb_layers; layer++) if (graphs[layer]->vcount() != n) throw Exception("Number of nodes are not equal for all graphs."); // Number of moved nodes during one loop size_t nb_moves = 0; // Make sure that the minimum community size is less than or equals the // maximum community size if (max_comm_size > 0 && min_comm_size > 0 && max_comm_size < min_comm_size) throw Exception("Maximum community size is smaller than the minimum community size."); // Establish vertex order // We normally initialize the normal vertex order // of considering node 0,1,... vector is_node_stable(n, false); // But if we use a random order, we shuffle this order. vector nodes = range(n); shuffle(nodes, &rng); deque vertex_order(nodes.begin(), nodes.end()); vector< vector > constrained_comms = constrained_partition->get_communities(); // Initialize the degree vector // If we want to debug the function, we will calculate some additional values. // In particular, the following consistencies could be checked: // (1) - The difference in the quality function after a move should match // the reported difference when calling diff_move. // (2) - The quality function should be exactly the same value after // aggregating/collapsing the graph. vector comm_added(partitions[0]->n_communities(), false); vector comms; // As long as the queue is not empty while(!vertex_order.empty()) { size_t v = vertex_order.front(); vertex_order.pop_front(); // Clear comms for (size_t comm : comms) comm_added[comm] = false; comms.clear(); // What is the current community of the node (this should be the same for all layers) size_t v_comm = partitions[0]->membership(v); if (consider_comms == ALL_COMMS) { // Add all communities to the set comms that are within the constrained community. size_t v_constrained_comm = constrained_partition->membership(v); for (size_t u : constrained_comms[v_constrained_comm]) { size_t u_comm = partitions[0]->membership(u); if (!comm_added[u_comm]) { comms.push_back(u_comm); comm_added[u_comm] = true; } } } else if (consider_comms == ALL_NEIGH_COMMS) { /****************************ALL NEIGH COMMS*****************************/ for (size_t layer = 0; layer < nb_layers; layer++) { for (size_t comm : partitions[layer]->get_neigh_comms(v, IGRAPH_ALL, constrained_partition->membership())) { if (!comm_added[comm]) { comms.push_back(comm); comm_added[comm] = true; } } } } else if (consider_comms == RAND_COMM) { /****************************RAND COMM***********************************/ size_t v_constrained_comm = constrained_partition->membership(v); size_t random_idx = get_random_int(0, constrained_comms[v_constrained_comm].size() - 1, &rng); size_t rand_comm = constrained_comms[v_constrained_comm][random_idx]; // No need to check if random_comm is already added, we only add one comm comms.push_back(rand_comm); comm_added[rand_comm] = true; } else if (consider_comms == RAND_NEIGH_COMM) { /****************************RAND NEIGH COMM*****************************/ // Draw a random community among the neighbours, proportional to the // frequency of the communities among the neighbours. Notice this is no // longer vector all_neigh_comms_incl_dupes; for (size_t layer = 0; layer < nb_layers; layer++) { vector neigh_comm_layer = partitions[layer]->get_neigh_comms(v, IGRAPH_ALL, constrained_partition->membership()); all_neigh_comms_incl_dupes.insert(all_neigh_comms_incl_dupes.end(), neigh_comm_layer.begin(), neigh_comm_layer.end()); } if (all_neigh_comms_incl_dupes.size() > 0) { size_t random_idx = get_random_int(0, all_neigh_comms_incl_dupes.size() - 1, &rng); size_t rand_comm = all_neigh_comms_incl_dupes[random_idx]; // No need to check if random_comm is already added, we only add one comm comms.push_back(rand_comm); comm_added[rand_comm] = true; } } #ifdef DEBUG cerr << "Consider " << comms.size() << " communities for moving." << endl; #endif size_t max_comm = v_comm; double max_improv = (0 < max_comm_size && max_comm_size < partitions[0]->csize(v_comm)) ? -INFINITY : 10*DBL_EPSILON; double v_size = graphs[0]->node_size(v); double old_size = partitions[0]->csize(v_comm); for (size_t comm : comms) { double possible_improv = 0.0; if (enforce_community_size_constraint) if (comm != v_comm) possible_improv += this->improvement_community_constraints(old_size, partitions[0]->csize(comm), v_size); // Consider the improvement of moving to a community for all layers for (size_t layer = 0; layer < nb_layers; layer++) { // Make sure to multiply it by the weight per layer possible_improv += layer_weights[layer]*partitions[layer]->diff_move(v, comm); } // Check if improvement is best if (possible_improv > max_improv) { max_comm = comm; max_improv = possible_improv; } } is_node_stable[v] = true; // If we actually plan to move the nove if (max_comm != v_comm) { // Keep track of improvement total_improv += max_improv; #ifdef DEBUG // If we are debugging, calculate quality function double q_improv = 0; #endif for (size_t layer = 0; layer < nb_layers; layer++) { MutableVertexPartition* partition = partitions[layer]; #ifdef DEBUG // If we are debugging, calculate quality function double q1 = partition->quality(); #endif // Actually move the node partition->move_node(v, max_comm); #ifdef DEBUG // If we are debugging, calculate quality function // and report difference double q2 = partition->quality(); double q_delta = layer_weights[layer]*(q2 - q1); q_improv += q_delta; cerr << "Move node " << v << " from " << v_comm << " to " << max_comm << " for layer " << layer << " (diff_move=" << max_improv << ", q2 - q1=" << q_delta << ")" << endl; #endif } #ifdef DEBUG if (fabs(q_improv - max_improv) > 1e-6) { cerr << "ERROR: Inconsistency while moving nodes, improvement as measured by quality function did not equal the improvement measured by the diff_move function." << endl << " (diff_move=" << max_improv << ", q2 - q1=" << q_improv << ")" << endl; } #endif // Mark neighbours as unstable (if not in new community and not fixed) for (Graph* graph : graphs) { for (size_t u : graph->get_neighbours(v, IGRAPH_ALL)) { // If the neighbour was stable and is not in the new community, we // should mark it as unstable, and add it to the queue, skipping // fixed nodes if (is_node_stable[u] && partitions[0]->membership(u) != max_comm && constrained_partition->membership(u) == constrained_partition->membership(v)) { vertex_order.push_back(u); is_node_stable[u] = false; } } } // Keep track of number of moves nb_moves += 1; } #ifdef DEBUG cerr << "Moved " << nb_moves << " nodes." << endl; #endif } partitions[0]->renumber_communities(); vector const& membership = partitions[0]->membership(); for (size_t layer = 1; layer < nb_layers; layer++) { partitions[layer]->set_membership(membership); #ifdef DEBUG cerr << "Renumbered communities for layer " << layer << " for " << partitions[layer]->n_communities() << " communities." << endl; #endif //DEBUG } return total_improv; } double Optimiser::merge_nodes_constrained(vector partitions, vector layer_weights, MutableVertexPartition* constrained_partition) { return this->merge_nodes_constrained(partitions, layer_weights, this->refine_consider_comms, constrained_partition, true); } double Optimiser::merge_nodes_constrained(vector partitions, vector layer_weights, int consider_comms, MutableVertexPartition* constrained_partition, bool enforce_community_size_constraint) { #ifdef DEBUG cerr << "double Optimiser::merge_nodes_constrained(vector partitions, vector layer_weights, int consider_comms, MutableVertexPartition* constrained_partition)" << std::endl; #endif // Number of multiplex layers size_t nb_layers = partitions.size(); if (nb_layers == 0) return -1.0; // Get graphs vector graphs(nb_layers); for (size_t layer = 0; layer < nb_layers; layer++) graphs[layer] = partitions[layer]->get_graph(); // Number of nodes in the graph size_t n = graphs[0]->vcount(); // Make sure that the minimum community size is less than or equals the // maximum community size if (max_comm_size > 0 && min_comm_size > 0 && max_comm_size < min_comm_size) throw Exception("Maximum community size is smaller than the minimum community size."); // Total improvement while merging nodes double total_improv = 0.0; for (size_t layer = 0; layer < nb_layers; layer++) if (graphs[layer]->vcount() != n) throw Exception("Number of nodes are not equal for all graphs."); // Establish vertex order // We normally initialize the normal vertex order // of considering node 0,1,... vector vertex_order = range(n); // But if we use a random order, we shuffle this order. shuffle(vertex_order, &rng); vector< vector > constrained_comms = constrained_partition->get_communities(); vector comm_added(partitions[0]->n_communities(), false); vector comms; // For each node for (size_t v : vertex_order) { // What is the current community of the node (this should be the same for all layers) size_t v_comm = partitions[0]->membership(v); if (partitions[0]->cnodes(v_comm) == 1) { // Clear comms for (size_t comm : comms) comm_added[comm] = false; comms.clear(); if (consider_comms == ALL_COMMS) { // Add all communities to the set comms that are within the constrained community. size_t v_constrained_comm = constrained_partition->membership(v); for (size_t u : constrained_comms[v_constrained_comm]) { size_t u_comm = partitions[0]->membership(u); if (!comm_added[u_comm]) { comms.push_back(u_comm); comm_added[u_comm] = true; } } } else if (consider_comms == ALL_NEIGH_COMMS) { /****************************ALL NEIGH COMMS*****************************/ for (size_t layer = 0; layer < nb_layers; layer++) { for (size_t u : partitions[layer]->get_graph()->get_neighbours(v, IGRAPH_ALL)) { if (constrained_partition->membership(v) == constrained_partition->membership(u)) { size_t comm = partitions[layer]->membership(u); if (!comm_added[comm]) { comms.push_back(comm); comm_added[comm] = true; } } } } } else if (consider_comms == RAND_COMM) { /****************************RAND COMM***********************************/ size_t v_constrained_comm = constrained_partition->membership(v); size_t random_idx = get_random_int(0, constrained_comms[v_constrained_comm].size() - 1, &rng); size_t rand_comm = constrained_comms[v_constrained_comm][random_idx]; // No need to check if random_comm is already added, we only add one comm comms.push_back(rand_comm); comm_added[rand_comm] = true; } else if (consider_comms == RAND_NEIGH_COMM) { /****************************RAND NEIGH COMM*****************************/ // Draw a random community among the neighbours, proportional to the // frequency of the communities among the neighbours. Notice this is no // longer vector all_neigh_comms_incl_dupes; for (size_t layer = 0; layer < nb_layers; layer++) { vector neigh_comm_layer = partitions[layer]->get_neigh_comms(v, IGRAPH_ALL, constrained_partition->membership()); all_neigh_comms_incl_dupes.insert(all_neigh_comms_incl_dupes.end(), neigh_comm_layer.begin(), neigh_comm_layer.end()); } size_t k = all_neigh_comms_incl_dupes.size(); if (k > 0) { // Make sure there is also a probability not to move the node if (get_random_int(0, k, &rng) > 0) { size_t random_idx = get_random_int(0, k - 1, &rng); size_t rand_comm = all_neigh_comms_incl_dupes[random_idx]; // No need to check if random_comm is already added, we only add one comm comms.push_back(rand_comm); comm_added[rand_comm] = true; } } } #ifdef DEBUG cerr << "Consider " << comms.size() << " communities for moving." << endl; #endif size_t max_comm = v_comm; double max_improv = (0 < max_comm_size && max_comm_size < partitions[0]->csize(v_comm)) ? -INFINITY : 0; double v_size = graphs[0]->node_size(v); double old_size = partitions[0]->csize(v_comm); for (size_t comm : comms) { // reset comm_added to all false comm_added[comm] = false; double possible_improv = 0.0; if (enforce_community_size_constraint) if (comm != v_comm) possible_improv += this->improvement_community_constraints(old_size, partitions[0]->csize(comm), v_size); // Consider the improvement of moving to a community for all layers for (size_t layer = 0; layer < nb_layers; layer++) { // Make sure to multiply it by the weight per layer possible_improv += layer_weights[layer]*partitions[layer]->diff_move(v, comm); } if (possible_improv >= max_improv) { max_comm = comm; max_improv = possible_improv; } } // If we actually plan to move the node if (max_comm != v_comm) { // Keep track of improvement total_improv += max_improv; #ifdef DEBUG // If we are debugging, calculate quality function double q_improv = 0; #endif for (size_t layer = 0; layer < nb_layers; layer++) { MutableVertexPartition* partition = partitions[layer]; #ifdef DEBUG // If we are debugging, calculate quality function double q1 = partition->quality(); #endif // Actually move the node partition->move_node(v, max_comm); #ifdef DEBUG // If we are debugging, calculate quality function // and report difference double q2 = partition->quality(); double q_delta = layer_weights[layer]*(q2 - q1); q_improv += q_delta; cerr << "Move node " << v << " from " << v_comm << " to " << max_comm << " for layer " << layer << " (diff_move=" << max_improv << ", q2 - q1=" << q_delta << ")" << endl; #endif } #ifdef DEBUG if (fabs(q_improv - max_improv) > 1e-6) { cerr << "ERROR: Inconsistency while moving nodes, improvement as measured by quality function did not equal the improvement measured by the diff_move function." << endl << " (diff_move=" << max_improv << ", q2 - q1=" << q_improv << ")" << endl; } #endif } } } partitions[0]->renumber_communities(); vector const& membership = partitions[0]->membership(); for (size_t layer = 1; layer < nb_layers; layer++) { partitions[layer]->set_membership(membership); #ifdef DEBUG cerr << "Renumbered communities for layer " << layer << " for " << partitions[layer]->n_communities() << " communities." << endl; #endif //DEBUG } return total_improv; } vtraag-libleidenalg-8518deb/src/RBConfigurationVertexPartition.cpp000066400000000000000000000135101520437531200254510ustar00rootroot00000000000000#include "RBConfigurationVertexPartition.h" RBConfigurationVertexPartition::RBConfigurationVertexPartition(Graph* graph, vector const& membership, double resolution_parameter) : LinearResolutionParameterVertexPartition(graph, membership, resolution_parameter) { } RBConfigurationVertexPartition::RBConfigurationVertexPartition(Graph* graph, vector const& membership) : LinearResolutionParameterVertexPartition(graph, membership) { } RBConfigurationVertexPartition::RBConfigurationVertexPartition(Graph* graph, double resolution_parameter) : LinearResolutionParameterVertexPartition(graph, resolution_parameter) { } RBConfigurationVertexPartition::RBConfigurationVertexPartition(Graph* graph) : LinearResolutionParameterVertexPartition(graph) { } RBConfigurationVertexPartition::~RBConfigurationVertexPartition() { } RBConfigurationVertexPartition* RBConfigurationVertexPartition::create(Graph* graph) { return new RBConfigurationVertexPartition(graph, this->resolution_parameter); } RBConfigurationVertexPartition* RBConfigurationVertexPartition::create(Graph* graph, vector const& membership) { return new RBConfigurationVertexPartition(graph, membership, this->resolution_parameter); } /***************************************************************************** Returns the difference in modularity if we move a node to a new community *****************************************************************************/ double RBConfigurationVertexPartition::diff_move(size_t v, size_t new_comm) { #ifdef DEBUG cerr << "double RBConfigurationVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; #endif size_t old_comm = this->_membership[v]; double diff = 0.0; double total_weight = this->graph->total_weight()*(2.0 - this->graph->is_directed()); if (total_weight == 0.0) return 0.0; if (new_comm != old_comm) { #ifdef DEBUG cerr << "\t" << "old_comm: " << old_comm << endl; #endif double w_to_old = this->weight_to_comm(v, old_comm); #ifdef DEBUG cerr << "\t" << "w_to_old: " << w_to_old << endl; #endif double w_from_old = this->weight_from_comm(v, old_comm); #ifdef DEBUG cerr << "\t" << "w_from_old: " << w_from_old << endl; #endif double w_to_new = this->weight_to_comm(v, new_comm); #ifdef DEBUG cerr << "\t" << "w_to_new: " << w_to_new << endl; #endif double w_from_new = this->weight_from_comm(v, new_comm); #ifdef DEBUG cerr << "\t" << "w_from_new: " << w_from_new << endl; #endif double k_out = this->graph->strength(v, IGRAPH_OUT); #ifdef DEBUG cerr << "\t" << "k_out: " << k_out << endl; #endif double k_in = this->graph->strength(v, IGRAPH_IN); #ifdef DEBUG cerr << "\t" << "k_in: " << k_in << endl; #endif double self_weight = this->graph->node_self_weight(v); #ifdef DEBUG cerr << "\t" << "self_weight: " << self_weight << endl; #endif double K_out_old = this->total_weight_from_comm(old_comm); #ifdef DEBUG cerr << "\t" << "K_out_old: " << K_out_old << endl; #endif double K_in_old = this->total_weight_to_comm(old_comm); #ifdef DEBUG cerr << "\t" << "K_in_old: " << K_in_old << endl; #endif double K_out_new = this->total_weight_from_comm(new_comm) + k_out; #ifdef DEBUG cerr << "\t" << "K_out_new: " << K_out_new << endl; #endif double K_in_new = this->total_weight_to_comm(new_comm) + k_in; #ifdef DEBUG cerr << "\t" << "K_in_new: " << K_in_new << endl; cerr << "\t" << "total_weight: " << total_weight << endl; #endif double diff_old = (w_to_old - this->resolution_parameter*k_out*K_in_old/total_weight) + \ (w_from_old - this->resolution_parameter*k_in*K_out_old/total_weight); #ifdef DEBUG cerr << "\t" << "diff_old: " << diff_old << endl; #endif double diff_new = (w_to_new + self_weight - this->resolution_parameter*k_out*K_in_new/total_weight) + \ (w_from_new + self_weight - this->resolution_parameter*k_in*K_out_new/total_weight); #ifdef DEBUG cerr << "\t" << "diff_new: " << diff_new << endl; #endif diff = diff_new - diff_old; #ifdef DEBUG cerr << "\t" << "diff: " << diff << endl; #endif } #ifdef DEBUG cerr << "exit RBConfigurationVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; cerr << "return " << diff << endl << endl; #endif return diff; } /***************************************************************************** Give the modularity of the partition. We here use the unscaled version of modularity, in other words, we don"t normalise by the number of edges. ******************************************************************************/ double RBConfigurationVertexPartition::quality(double resolution_parameter) { #ifdef DEBUG cerr << "double ModularityVertexPartition::quality()" << endl; #endif double mod = 0.0; double m; if (this->graph->is_directed()) m = this->graph->total_weight(); else m = 2*this->graph->total_weight(); if (m == 0) return 0.0; for (size_t c = 0; c < this->n_communities(); c++) { double w = this->total_weight_in_comm(c); double w_out = this->total_weight_from_comm(c); double w_in = this->total_weight_to_comm(c); #ifdef DEBUG double csize = this->csize(c); cerr << "\t" << "Comm: " << c << ", size=" << csize << ", w=" << w << ", w_out=" << w_out << ", w_in=" << w_in << "." << endl; #endif mod += w - resolution_parameter*w_out*w_in/((this->graph->is_directed() ? 1.0 : 4.0)*this->graph->total_weight()); } double q = (2.0 - this->graph->is_directed())*mod; #ifdef DEBUG cerr << "exit double RBConfigurationVertexPartition::quality()" << endl; cerr << "return " << q << endl << endl; #endif return q; } vtraag-libleidenalg-8518deb/src/RBERVertexPartition.cpp000066400000000000000000000121301520437531200231450ustar00rootroot00000000000000#include "RBERVertexPartition.h" RBERVertexPartition::RBERVertexPartition(Graph* graph, vector const& membership, double resolution_parameter) : LinearResolutionParameterVertexPartition(graph, membership, resolution_parameter) { } RBERVertexPartition::RBERVertexPartition(Graph* graph, vector const& membership) : LinearResolutionParameterVertexPartition(graph, membership) { } RBERVertexPartition::RBERVertexPartition(Graph* graph, double resolution_parameter) : LinearResolutionParameterVertexPartition(graph, resolution_parameter) { } RBERVertexPartition::RBERVertexPartition(Graph* graph) : LinearResolutionParameterVertexPartition(graph) { } RBERVertexPartition* RBERVertexPartition::create(Graph* graph) { return new RBERVertexPartition(graph, this->resolution_parameter); } RBERVertexPartition* RBERVertexPartition::create(Graph* graph, vector const& membership) { return new RBERVertexPartition(graph, membership, this->resolution_parameter); } RBERVertexPartition::~RBERVertexPartition() { } /******************************************************************************** RBER implementation of a vertex partition (which includes a resolution parameter). ********************************************************************************/ double RBERVertexPartition::diff_move(size_t v, size_t new_comm) { #ifdef DEBUG cerr << "double RBERVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; #endif size_t old_comm = this->membership(v); double diff = 0.0; if (new_comm != old_comm) { double w_to_old = this->weight_to_comm(v, old_comm); #ifdef DEBUG cerr << "\t" << "w_to_old: " << w_to_old << endl; #endif double w_to_new = this->weight_to_comm(v, new_comm); #ifdef DEBUG cerr << "\t" << "w_to_new: " << w_to_new << endl; #endif double w_from_old = this->weight_from_comm(v, old_comm); #ifdef DEBUG cerr << "\t" << "w_from_old: " << w_from_old << endl; #endif double w_from_new = this->weight_from_comm(v, new_comm); #ifdef DEBUG cerr << "\t" << "w_from_new: " << w_from_new << endl; #endif double nsize = this->graph->node_size(v); #ifdef DEBUG cerr << "\t" << "nsize: " << nsize << endl; #endif double csize_old = this->csize(old_comm); #ifdef DEBUG cerr << "\t" << "csize_old: " << csize_old << endl; #endif double csize_new = this->csize(new_comm); #ifdef DEBUG cerr << "\t" << "csize_new: " << csize_new << endl; #endif double self_weight = this->graph->node_self_weight(v); #ifdef DEBUG cerr << "\t" << "self_weight: " << self_weight << endl; cerr << "\t" << "density: " << this->graph->density() << endl; #endif double possible_edge_difference_old = 0.0; if (this->graph->correct_self_loops()) possible_edge_difference_old = nsize*(ptrdiff_t)(2.0*csize_old - nsize); else possible_edge_difference_old = nsize*(ptrdiff_t)(2.0*csize_old - nsize - 1.0); #ifdef DEBUG cerr << "\t" << "possible_edge_difference_old: " << possible_edge_difference_old << endl; #endif double diff_old = w_to_old + w_from_old - self_weight - this->resolution_parameter*this->graph->density()*possible_edge_difference_old; #ifdef DEBUG cerr << "\t" << "diff_old: " << diff_old << endl; #endif double possible_edge_difference_new = 0.0; if (this->graph->correct_self_loops()) possible_edge_difference_new = nsize*(ptrdiff_t)(2.0*csize_new + nsize); else possible_edge_difference_new = nsize*(ptrdiff_t)(2.0*csize_new + nsize - 1.0); #ifdef DEBUG cerr << "\t" << "possible_edge_difference_new: " << possible_edge_difference_new << endl; #endif double diff_new = w_to_new + w_from_new + self_weight - this->resolution_parameter*this->graph->density()*possible_edge_difference_new; #ifdef DEBUG cerr << "\t" << "diff_new: " << diff_new << endl; #endif diff = diff_new - diff_old; #ifdef DEBUG cerr << "\t" << "diff: " << diff << endl;; #endif } #ifdef DEBUG cerr << "exit RBERVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; cerr << "return " << diff << endl << endl; #endif return diff; } double RBERVertexPartition::quality(double resolution_parameter) { #ifdef DEBUG cerr << "double RBERVertexPartition::quality()" << endl; #endif double mod = 0.0; for (size_t c = 0; c < this->n_communities(); c++) { double csize = this->csize(c); double w = this->total_weight_in_comm(c); double comm_possible_edges = this->graph->possible_edges(csize); #ifdef DEBUG cerr << "\t" << "Comm: " << c << ", w_c=" << w << ", n_c=" << csize << ", comm_possible_edges=" << comm_possible_edges << ", p=" << this->graph->density() << "." << endl; #endif mod += w - resolution_parameter*this->graph->density()*comm_possible_edges; } #ifdef DEBUG cerr << "exit double RBERVertexPartition::quality()" << endl; cerr << "return " << mod << endl << endl; #endif return (2.0 - this->graph->is_directed())*mod; } vtraag-libleidenalg-8518deb/src/ResolutionParameterVertexPartition.cpp000066400000000000000000000017311520437531200264240ustar00rootroot00000000000000#include "ResolutionParameterVertexPartition.h" ResolutionParameterVertexPartition::ResolutionParameterVertexPartition(Graph* graph, vector membership, double resolution_parameter) : MutableVertexPartition(graph, membership) { this->resolution_parameter = resolution_parameter; } ResolutionParameterVertexPartition::ResolutionParameterVertexPartition(Graph* graph, vector membership) : MutableVertexPartition(graph, membership) { this->resolution_parameter = 1.0; } ResolutionParameterVertexPartition::ResolutionParameterVertexPartition(Graph* graph, double resolution_parameter) : MutableVertexPartition(graph) { this->resolution_parameter = resolution_parameter; } ResolutionParameterVertexPartition::ResolutionParameterVertexPartition(Graph* graph) : MutableVertexPartition(graph) { this->resolution_parameter = 1.0; } ResolutionParameterVertexPartition::~ResolutionParameterVertexPartition() { } vtraag-libleidenalg-8518deb/src/SignificanceVertexPartition.cpp000066400000000000000000000132501520437531200250010ustar00rootroot00000000000000#include "SignificanceVertexPartition.h" #ifdef DEBUG #include using std::cerr; using std::endl; #endif SignificanceVertexPartition::SignificanceVertexPartition(Graph* graph, vector const& membership) : MutableVertexPartition(graph, membership) { } SignificanceVertexPartition::SignificanceVertexPartition(Graph* graph) : MutableVertexPartition(graph) { } SignificanceVertexPartition* SignificanceVertexPartition::create(Graph* graph) { return new SignificanceVertexPartition(graph); } SignificanceVertexPartition* SignificanceVertexPartition::create(Graph* graph, vector const& membership) { return new SignificanceVertexPartition(graph, membership); } SignificanceVertexPartition::~SignificanceVertexPartition() { } double SignificanceVertexPartition::diff_move(size_t v, size_t new_comm) { #ifdef DEBUG cerr << "virtual double SignificanceVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; #endif size_t old_comm = this->membership(v); double nsize = this->graph->node_size(v); double diff = 0.0; if (new_comm != old_comm) { double normalise = (2.0 - this->graph->is_directed()); double p = this->graph->density(); #ifdef DEBUG double n = this->graph->total_size(); cerr << "\t" << "Community: " << old_comm << " => " << new_comm << "." << endl; cerr << "\t" << "n: " << n << ", m: " << this->graph->total_weight() << ", p: " << p << "." << endl; #endif //Old comm double n_old = this->csize(old_comm); double N_old = this->graph->possible_edges(n_old); double m_old = this->total_weight_in_comm(old_comm); double q_old = 0.0; if (N_old > 0) q_old = m_old/N_old; #ifdef DEBUG cerr << "\t" << "n_old: " << n_old << ", N_old: " << N_old << ", m_old: " << m_old << ", q_old: " << q_old << ", KL: " << KL(q_old, p) << "." << endl; #endif // Old comm after move double n_oldx = n_old - nsize; // It should not be possible that this becomes negative, so no need for ptrdiff_t here. double N_oldx = this->graph->possible_edges(n_oldx); double sw = this->graph->node_self_weight(v); // Be careful to exclude the self weight here, because this is include in the weight_to_comm function. double wtc = this->weight_to_comm(v, old_comm) - sw; double wfc = this->weight_from_comm(v, old_comm) - sw; #ifdef DEBUG cerr << "\t" << "wtc: " << wtc << ", wfc: " << wfc << ", sw: " << sw << "." << endl; #endif double m_oldx = m_old - wtc/normalise - wfc/normalise - sw; double q_oldx = 0.0; if (N_oldx > 0) q_oldx = m_oldx/N_oldx; #ifdef DEBUG cerr << "\t" << "n_oldx: " << n_oldx << ", N_oldx: " << N_oldx << ", m_oldx: " << m_oldx << ", q_oldx: " << q_oldx << ", KL: " << KL(q_oldx, p) << "." << endl; #endif // New comm double n_new = this->csize(new_comm); double N_new = this->graph->possible_edges(n_new); double m_new = this->total_weight_in_comm(new_comm); double q_new = 0.0; if (N_new > 0) q_new = m_new/N_new; #ifdef DEBUG cerr << "\t" << "n_new: " << n_new << ", N_new: " << N_new << ", m_new: " << m_new << ", q_new: " << q_new << ", KL: " << KL(q_new, p) << "." << endl; #endif // New comm after move double n_newx = n_new + nsize; double N_newx = this->graph->possible_edges(n_newx); wtc = this->weight_to_comm(v, new_comm); wfc = this->weight_from_comm(v, new_comm); sw = this->graph->node_self_weight(v); #ifdef DEBUG cerr << "\t" << "wtc: " << wtc << ", wfc: " << wfc << ", sw: " << sw << "." << endl; #endif double m_newx = m_new + wtc/normalise + wfc/normalise + sw; double q_newx = 0.0; if (N_newx > 0) q_newx = m_newx/N_newx; #ifdef DEBUG cerr << "\t" << "n_newx: " << n_newx << ", N_newx: " << N_newx << ", m_newx: " << m_newx << ", q_newx: " << q_newx << ", KL: " << KL(q_newx, p) << "." << endl; #endif // Calculate actual diff if (N_oldx != N_new || q_oldx != q_new) diff += (double)N_oldx*KLL(q_oldx, p) - (double)N_new*KLL(q_new, p); if (N_newx != N_old || q_newx != q_old) diff += (double)N_newx*KLL(q_newx, p) - (double)N_old*KLL(q_old, p); #ifdef DEBUG cerr << "\t" << "diff: " << diff << "." << endl; #endif } #ifdef DEBUG cerr << "exit double SignificanceVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; cerr << "return " << diff << endl << endl; #endif return diff; } /******************************************************************************** Calculate the significance of the partition. *********************************************************************************/ double SignificanceVertexPartition::quality() { #ifdef DEBUG cerr << "double SignificanceVertexPartition::quality()"; double n = this->graph->total_size(); #endif double S = 0.0; double p = this->graph->density(); #ifdef DEBUG cerr << "\t" << "n=" << n << ", m=" << this->graph->total_weight() << ", p=" << p << "." << endl; #endif for (size_t c = 0; c < this->n_communities(); c++) { double n_c = this->csize(c); double m_c = this->total_weight_in_comm(c); double p_c = 0.0; size_t N_c = this->graph->possible_edges(n_c); if (N_c > 0) p_c = m_c/N_c; #ifdef DEBUG cerr << "\t" << "c=" << c << ", n_c=" << n_c << ", m_c=" << m_c << ", N_c=" << N_c << ", p_c=" << p_c << ", p=" << p << ", KLL=" << KL(p_c, p) << "." << endl; #endif S += N_c*KLL(p_c, p); } #ifdef DEBUG cerr << "exit SignificanceVertexPartition::quality()" << endl; cerr << "return " << S << endl << endl; #endif return S; } vtraag-libleidenalg-8518deb/src/SurpriseVertexPartition.cpp000066400000000000000000000105761520437531200242430ustar00rootroot00000000000000#include "SurpriseVertexPartition.h" SurpriseVertexPartition::SurpriseVertexPartition(Graph* graph, vector const& membership) : MutableVertexPartition(graph, membership) { } SurpriseVertexPartition::SurpriseVertexPartition(Graph* graph) : MutableVertexPartition(graph) { } SurpriseVertexPartition* SurpriseVertexPartition::create(Graph* graph) { return new SurpriseVertexPartition(graph); } SurpriseVertexPartition* SurpriseVertexPartition::create(Graph* graph, vector const& membership) { return new SurpriseVertexPartition(graph, membership); } SurpriseVertexPartition::~SurpriseVertexPartition() { } double SurpriseVertexPartition::diff_move(size_t v, size_t new_comm) { #ifdef DEBUG cerr << "virtual double SurpriseVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; #endif size_t old_comm = this->membership(v); size_t nsize = this->graph->node_size(v); #ifdef DEBUG cerr << "\t" << "nsize: " << nsize << endl; #endif double diff = 0.0; double m = this->graph->total_weight(); if (m == 0) return 0.0; if (new_comm != old_comm) { double normalise = (2.0 - this->graph->is_directed()); size_t n = this->graph->total_size(); size_t n2 = this->graph->possible_edges(n); #ifdef DEBUG cerr << "\t" << "Community: " << old_comm << " => " << new_comm << "." << endl; cerr << "\t" << "m: " << m << ", n2: " << n2 << "." << endl; #endif // Before move double mc = this->total_weight_in_all_comms(); size_t nc2 = this->total_possible_edges_in_all_comms(); #ifdef DEBUG cerr << "\t" << "mc: " << mc << ", nc2: " << nc2 << "." << endl; #endif // To old comm size_t n_old = this->csize(old_comm); double sw = this->graph->node_self_weight(v); double wtc = this->weight_to_comm(v, old_comm) - sw; double wfc = this->weight_from_comm(v, old_comm) - sw; #ifdef DEBUG cerr << "\t" << "wtc: " << wtc << ", wfc: " << wfc << ", sw: " << sw << "." << endl; #endif double m_old = wtc/normalise + wfc/normalise + sw; #ifdef DEBUG cerr << "\t" << "m_old: " << m_old << ", n_old: " << n_old << "." << endl; #endif // To new comm size_t n_new = this->csize(new_comm); wtc = this->weight_to_comm(v, new_comm); wfc = this->weight_from_comm(v, new_comm); sw = this->graph->node_self_weight(v); #ifdef DEBUG cerr << "\t" << "wtc: " << wtc << ", wfc: " << wfc << ", sw: " << sw << "." << endl; #endif double m_new = wtc/normalise + wfc/normalise + sw; #ifdef DEBUG cerr << "\t" << "m_new: " << m_new << ", n_new: " << n_new << "." << endl; #endif double q = mc/m; double s = (double)nc2/(double)n2; double q_new = (mc - m_old + m_new)/m; #ifdef DEBUG cerr << "\t" << "mc - m_old + m_new=" << (mc - m_old + m_new) << endl; #endif double delta_nc2 = 2.0*nsize*(ptrdiff_t)(n_new - n_old + nsize)/normalise; double s_new = (double)(nc2 + delta_nc2)/(double)n2; #ifdef DEBUG cerr << "\t" << "delta_nc2=" << delta_nc2 << endl; #endif #ifdef DEBUG cerr << "\t" << "q:\t" << q << ", s:\t" << s << "." << endl; cerr << "\t" << "q_new:\t" << q_new << ", s_new:\t" << s_new << "." << endl; #endif diff = m*(KLL(q_new, s_new) - KLL(q, s)); #ifdef DEBUG cerr << "\t" << "diff: " << diff << "." << endl; #endif } #ifdef DEBUG cerr << "exit double SurpriseVertexPartition::diff_move(" << v << ", " << new_comm << ")" << endl; cerr << "return " << diff << endl << endl; #endif return diff; } double SurpriseVertexPartition::quality() { #ifdef DEBUG cerr << "double SurpriseVertexPartition::quality()" << endl; #endif double mc = this->total_weight_in_all_comms(); size_t nc2 = this->total_possible_edges_in_all_comms(); double m = this->graph->total_weight(); size_t n = this->graph->total_size(); if (m == 0) return 0.0; size_t n2 = this->graph->possible_edges(n); #ifdef DEBUG cerr << "\t" << "mc=" << mc << ", m=" << m << ", nc2=" << nc2 << ", n2=" << n2 << "." << endl; #endif double q = mc/m; double s = (double)nc2/(double)n2; #ifdef DEBUG cerr << "\t" << "q:\t" << q << ", s:\t" << s << "." << endl; #endif double S = m*KLL(q,s); #ifdef DEBUG cerr << "exit SurpriseVertexPartition::quality()" << endl; cerr << "return " << S << endl << endl; #endif return S; } vtraag-libleidenalg-8518deb/vcpkg.json000066400000000000000000000011321520437531200200350ustar00rootroot00000000000000{ "name": "libleidenalg", "version": "0.11.1-35-g8518deb", "maintainers": "V.A. Traag ", "description": "Leiden is a general algorithm for methods of community detection in large networks.", "homepage": "https://github.com/vtraag/libleidenalg", "license": "GPL-3.0-or-later", "builtin-baseline": "253704407ae68efa37bf8f5b59b3e06dd40d3d3f", "dependencies": [ { "name": "igraph", "version>=": "1.0.0" }, { "name": "vcpkg-cmake", "host": true }, { "name": "vcpkg-cmake-config", "host": true } ] }