Add graph references
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
cff-version: 2.8.0
|
||||
message: "If you use this software, please cite it as below."
|
||||
authors:
|
||||
- family-names: "Edler"
|
||||
given-names: "Daniel"
|
||||
orcid: "https://orcid.org/0000-0001-5420-0591"
|
||||
- family-names: "Holmgren"
|
||||
given-names: "Anton"
|
||||
orcid: "https://orcid.org/0000-0001-5859-4073"
|
||||
- family-names: "Rosvall"
|
||||
given-names: "Martin"
|
||||
orcid: "https://orcid.org/0000-0002-7181-9940"
|
||||
title: "The MapEquation software package"
|
||||
version: 2.8.0
|
||||
date-released: 2024-06-20
|
||||
url: "https://mapequation.org"
|
||||
@@ -0,0 +1,48 @@
|
||||
# Declare the files needed to compile our vendored Infomap copy
|
||||
add_library(infomap_vendored
|
||||
OBJECT
|
||||
EXCLUDE_FROM_ALL
|
||||
src/core/MetaMapEquation.cpp
|
||||
src/core/MemMapEquation.cpp
|
||||
src/core/InfoEdge.cpp
|
||||
src/core/BiasedMapEquation.cpp
|
||||
src/core/StateNetwork.cpp
|
||||
src/core/iterators/InfomapIterator.cpp
|
||||
src/core/InfomapBase.cpp
|
||||
src/core/InfoNode.cpp
|
||||
src/io/Network.cpp
|
||||
src/io/ClusterMap.cpp
|
||||
src/io/ProgramInterface.cpp
|
||||
src/io/Output.cpp
|
||||
src/io/Config.cpp
|
||||
src/utils/Log.cpp
|
||||
src/utils/FileURI.cpp
|
||||
src/utils/FlowCalculator.cpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
infomap_vendored
|
||||
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/
|
||||
|
||||
PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${PROJECT_BINARY_DIR}/include
|
||||
${PROJECT_BINARY_DIR}/src # config.h
|
||||
)
|
||||
|
||||
if(BUILD_SHARED_LIBS)
|
||||
set_property(TARGET infomap_vendored PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
# Since these are included as object files, they should call the
|
||||
# function as is (without visibility specification)
|
||||
target_compile_definitions(infomap_vendored PRIVATE IGRAPH_STATIC)
|
||||
|
||||
# Infomap uses unsigned indices in OpenMP loops, which requires
|
||||
# at least OpenMP 3.0. As of 2025, MSVC only supports OpenMP 2.0,
|
||||
# thus this check is necessary.
|
||||
if(IGRAPH_OPENMP_SUPPORT AND OpenMP_CXX_VERSION VERSION_GREATER_EQUAL 3)
|
||||
target_link_libraries(infomap_vendored PRIVATE OpenMP::OpenMP_CXX)
|
||||
endif()
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
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
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
.. image:: https://github.com/mapequation/infomap/actions/workflows/build.yml/badge.svg
|
||||
|
||||
Infomap
|
||||
=======
|
||||
|
||||
Infomap is a network clustering algorithm based on the `Map equation`_.
|
||||
|
||||
For detailed documentation, see `mapequation.org/infomap`_.
|
||||
|
||||
For a list of recent changes, see `CHANGELOG.md`_ in the source directory.
|
||||
|
||||
.. _Map equation: https://www.mapequation.org/publications.html#Rosvall-Axelsson-Bergstrom-2009-Map-equation
|
||||
.. _`mapequation.org/infomap`: https://www.mapequation.org/infomap
|
||||
.. _`CHANGELOG.md`: https://github.com/mapequation/infomap/blob/master/CHANGELOG.md
|
||||
|
||||
Getting started
|
||||
---------------
|
||||
|
||||
Infomap can be installed either from `PyPI`_ using ``pip`` or by
|
||||
compiling from source.
|
||||
|
||||
An experimental Javascript version for browsers is available on `NPM`_.
|
||||
|
||||
.. _PyPI: https://pypi.org/project/infomap/
|
||||
|
||||
Using pip
|
||||
---------
|
||||
|
||||
A pre-compiled version is available for macOS users.
|
||||
|
||||
Installing on other operating systems requires a
|
||||
working ``gcc`` or ``clang`` compiler.
|
||||
|
||||
To install, run::
|
||||
|
||||
pip install infomap
|
||||
|
||||
|
||||
To upgrade, run::
|
||||
|
||||
pip install --upgrade infomap
|
||||
|
||||
|
||||
When the Python package is installed, an executable called
|
||||
``infomap`` (with lowercase i) is available from any directory.
|
||||
|
||||
To get started, read `Infomap Python API`_.
|
||||
|
||||
.. _`Infomap Python API`: https://mapequation.github.io/infomap/python/
|
||||
|
||||
Using Docker
|
||||
------------
|
||||
|
||||
There are currently two Docker images available on `Docker Hub`_.
|
||||
|
||||
- ``mapequation/infomap``
|
||||
- ``mapequation/infomap:notebook`` based on ``jupyter/scipy-notebook``
|
||||
|
||||
The image ``mapequation/infomap`` can be started with
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -it --rm \
|
||||
-v `pwd`:/data \
|
||||
mapequation/infomap
|
||||
[infomap arguments]
|
||||
|
||||
You can also use the supplied `docker-compose.yml`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose run --rm infomap
|
||||
|
||||
The image ``mapequation/infomap:notebook`` can be started with
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run \
|
||||
-v `pwd`:/home/jovyan/work \
|
||||
-p 8888:8888 \
|
||||
mapequation/infomap:notebook \
|
||||
start.sh jupyter lab
|
||||
|
||||
Or similarly, using docker-compose:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up notebook
|
||||
|
||||
.. _`Docker Hub`: https://hub.docker.com/r/mapequation/infomap
|
||||
.. _`docker-compose.yml`: https://github.com/mapequation/infomap/blob/master/docker-compose.yml
|
||||
|
||||
Compiling from source
|
||||
---------------------
|
||||
|
||||
Installing Infomap from source requires a working ``gcc`` or ``clang`` compiler.
|
||||
|
||||
To download and compile the newest version from `Github`_, clone the repository
|
||||
by running
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
git clone git@github.com:mapequation/infomap.git
|
||||
cd infomap
|
||||
make
|
||||
|
||||
This creates the binary ``Infomap``, run it using::
|
||||
|
||||
./Infomap [options] network_data destination
|
||||
|
||||
For a list of options, run::
|
||||
|
||||
./Infomap --help
|
||||
|
||||
Read `the documentation`_ to learn more about the different options.
|
||||
|
||||
.. _Github: https://www.github.com/mapequation/infomap
|
||||
.. _the documentation: https://www.mapequation.org/infomap
|
||||
|
||||
Npm package
|
||||
-----------
|
||||
|
||||
An experimental Javascript web worker is available on `NPM`_.
|
||||
|
||||
To install it, run
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
npm install @mapequation/infomap
|
||||
|
||||
.. _NPM: https://www.npmjs.com/package/@mapequation/infomap
|
||||
|
||||
Feedback
|
||||
--------
|
||||
|
||||
If you have any questions, suggestions or issues regarding the software,
|
||||
please add them to `GitHub issues`_.
|
||||
|
||||
.. _Github issues: http://www.github.com/mapequation/infomap/issues
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
For contact information, see `mapequation.org/about.html`_.
|
||||
|
||||
.. _`mapequation.org/about.html`: https://www.mapequation.org/about.html
|
||||
|
||||
Terms of use
|
||||
------------
|
||||
|
||||
Infomap is released under a dual licence.
|
||||
|
||||
To give everyone maximum freedom to make use of Infomap
|
||||
and derivative works, we make the code open source under
|
||||
the GNU General Public License version 3 or any
|
||||
later version (see `LICENSE_GPLv3.txt`_).
|
||||
|
||||
For a non-copyleft license, please contact us.
|
||||
|
||||
.. _LICENSE_GPLv3.txt: https://github.com/mapequation/infomap/blob/master/LICENSE_GPLv3.txt
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOMAP_H_
|
||||
#define INFOMAP_H_
|
||||
|
||||
#include "core/InfomapBase.h"
|
||||
#include "io/Config.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <map>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
// Wrapper class for the Python API
|
||||
struct InfomapWrapper : public InfomapBase {
|
||||
public:
|
||||
InfomapWrapper() : InfomapBase() { }
|
||||
InfomapWrapper(const std::string& flags) : InfomapBase(flags) { }
|
||||
InfomapWrapper(const Config& conf) : InfomapBase(conf) { }
|
||||
virtual ~InfomapWrapper() = default;
|
||||
|
||||
// ===================================================
|
||||
// Wrapper methods
|
||||
// ===================================================
|
||||
|
||||
void readInputData(std::string filename = "", bool accumulate = true) { m_network.readInputData(std::move(filename), accumulate); }
|
||||
|
||||
void addNode(unsigned int id) { m_network.addNode(id); }
|
||||
void addNode(unsigned int id, std::string name) { m_network.addNode(id, std::move(name)); }
|
||||
void addNode(unsigned int id, double weight) { m_network.addNode(id, weight); }
|
||||
void addNode(unsigned int id, std::string name, double weight) { m_network.addNode(id, std::move(name), weight); }
|
||||
|
||||
void addName(unsigned int id, const std::string& name) { m_network.addName(id, name); }
|
||||
std::string getName(unsigned int id) const
|
||||
{
|
||||
auto& names = m_network.names();
|
||||
auto it = names.find(id);
|
||||
return it != names.end() ? it->second : "";
|
||||
}
|
||||
|
||||
const std::map<unsigned int, std::string>& getNames() const { return m_network.names(); }
|
||||
|
||||
void addPhysicalNode(unsigned int id, const std::string& name = "") { m_network.addPhysicalNode(id, name); }
|
||||
void addStateNode(unsigned int id, unsigned int physId) { m_network.addStateNode(id, physId); }
|
||||
|
||||
void addLink(unsigned int sourceId, unsigned int targetId, double weight = 1.0) { m_network.addLink(sourceId, targetId, weight); }
|
||||
void addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight) { m_network.addLink(sourceId, targetId, weight); }
|
||||
void addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight = 1.0) { m_network.addMultilayerLink(layer1, n1, layer2, n2, weight); }
|
||||
void addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight) { m_network.addMultilayerIntraLink(layer, n1, n2, weight); }
|
||||
void addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight) { m_network.addMultilayerInterLink(layer1, n, layer2, interWeight); }
|
||||
|
||||
void setBipartiteStartId(unsigned int startId) { m_network.setBipartiteStartId(startId); }
|
||||
|
||||
std::map<std::pair<unsigned int, unsigned int>, double> getLinks(bool flow) const
|
||||
{
|
||||
std::map<std::pair<unsigned int, unsigned int>, double> links;
|
||||
|
||||
for (const auto& node : m_network.nodeLinkMap()) {
|
||||
const auto sourceId = node.first.id;
|
||||
|
||||
for (const auto& link : node.second) {
|
||||
const auto targetId = link.first.id;
|
||||
links[{ sourceId, targetId }] = flow ? link.second.flow : link.second.weight;
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
std::map<unsigned int, unsigned int> getModules(int level = 1, bool states = false)
|
||||
{
|
||||
if (haveMemory() && !states) {
|
||||
throw std::runtime_error("Cannot get modules on higher-order network without states.");
|
||||
}
|
||||
std::map<unsigned int, unsigned int> modules;
|
||||
for (auto it = iterTree(level); !it.isEnd(); ++it) {
|
||||
auto& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
modules[states ? node.stateId : node.physicalId] = it.moduleId();
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
using InfomapBase::codelength;
|
||||
using InfomapBase::getEntropyRate;
|
||||
using InfomapBase::getMultilevelModules;
|
||||
using InfomapBase::iterLeafNodes;
|
||||
using InfomapBase::iterTree;
|
||||
using InfomapBase::run;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // INFOMAP_H_
|
||||
@@ -0,0 +1,240 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "BiasedMapEquation.h"
|
||||
#include "FlowData.h"
|
||||
#include "InfoNode.h"
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstdlib>
|
||||
#include "StateNetwork.h"
|
||||
|
||||
namespace infomap {
|
||||
|
||||
double BiasedMapEquation::s_totalDegree = 1;
|
||||
unsigned int BiasedMapEquation::s_numNodes = 0;
|
||||
|
||||
void BiasedMapEquation::setNetworkProperties(const StateNetwork& network)
|
||||
{
|
||||
s_totalDegree = network.sumWeightedDegree();
|
||||
// Negative entropy bias is based on discrete counts, if average weight is below 1, use unweighted total degree
|
||||
if (s_totalDegree < network.sumDegree()) {
|
||||
s_totalDegree = network.sumDegree();
|
||||
}
|
||||
s_numNodes = network.numNodes();
|
||||
}
|
||||
|
||||
double BiasedMapEquation::getIndexCodelength() const
|
||||
{
|
||||
return indexCodelength + indexEntropyBiasCorrection;
|
||||
}
|
||||
|
||||
double BiasedMapEquation::getModuleCodelength() const
|
||||
{
|
||||
return moduleCodelength + biasedCost + moduleEntropyBiasCorrection;
|
||||
}
|
||||
|
||||
double BiasedMapEquation::getCodelength() const
|
||||
{
|
||||
return codelength + biasedCost + getEntropyBiasCorrection();
|
||||
}
|
||||
|
||||
double BiasedMapEquation::getEntropyBiasCorrection() const
|
||||
{
|
||||
return indexEntropyBiasCorrection + moduleEntropyBiasCorrection;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
std::ostream& BiasedMapEquation::print(std::ostream& out) const
|
||||
{
|
||||
out << indexCodelength << " + " << moduleCodelength;
|
||||
if (preferredNumModules != 0) {
|
||||
out << " + " << biasedCost;
|
||||
}
|
||||
if (useEntropyBiasCorrection) {
|
||||
out << " + " << getEntropyBiasCorrection();
|
||||
}
|
||||
out << " = " << io::toPrecision(getCodelength());
|
||||
return out;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const BiasedMapEquation& mapEq)
|
||||
{
|
||||
return mapEq.print(out);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
void BiasedMapEquation::init(const Config& config)
|
||||
{
|
||||
Log(3) << "BiasedMapEquation::init()...\n";
|
||||
preferredNumModules = config.preferredNumberOfModules;
|
||||
useEntropyBiasCorrection = config.entropyBiasCorrection;
|
||||
entropyBiasCorrectionMultiplier = config.entropyBiasCorrectionMultiplier;
|
||||
}
|
||||
|
||||
void BiasedMapEquation::initNetwork(InfoNode& root)
|
||||
{
|
||||
Log(3) << "BiasedMapEquation::initNetwork()...\n";
|
||||
Base::initNetwork(root);
|
||||
}
|
||||
|
||||
void BiasedMapEquation::initPartition(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
calculateCodelength(nodes);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
double BiasedMapEquation::calcNumModuleCost(unsigned int numModules) const
|
||||
{
|
||||
if (preferredNumModules == 0) return 0;
|
||||
int deltaNumModules = numModules - preferredNumModules;
|
||||
return 1 * std::abs(deltaNumModules);
|
||||
}
|
||||
|
||||
double BiasedMapEquation::calcIndexEntropyBiasCorrection(unsigned int numModules) const
|
||||
{
|
||||
return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * (numModules - 1) / (2 * s_totalDegree) : 0;
|
||||
}
|
||||
|
||||
double BiasedMapEquation::calcModuleEntropyBiasCorrection() const
|
||||
{
|
||||
return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * s_numNodes / (2 * s_totalDegree) : 0;
|
||||
}
|
||||
|
||||
double BiasedMapEquation::calcEntropyBiasCorrection(unsigned int numModules) const
|
||||
{
|
||||
return useEntropyBiasCorrection ? entropyBiasCorrectionMultiplier * (numModules - 1 + s_numNodes) / (2 * s_totalDegree) : 0;
|
||||
}
|
||||
|
||||
void BiasedMapEquation::calculateCodelength(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
calculateCodelengthTerms(nodes);
|
||||
|
||||
calculateCodelengthFromCodelengthTerms();
|
||||
|
||||
currentNumModules = nodes.size();
|
||||
|
||||
biasedCost = calcNumModuleCost(currentNumModules);
|
||||
|
||||
indexEntropyBiasCorrection = calcIndexEntropyBiasCorrection(currentNumModules);
|
||||
moduleEntropyBiasCorrection = calcModuleEntropyBiasCorrection();
|
||||
}
|
||||
|
||||
double BiasedMapEquation::calcCodelength(const InfoNode& parent) const
|
||||
{
|
||||
return parent.isLeafModule()
|
||||
? calcCodelengthOnModuleOfLeafNodes(parent)
|
||||
: calcCodelengthOnModuleOfModules(parent);
|
||||
}
|
||||
|
||||
double BiasedMapEquation::calcCodelengthOnModuleOfModules(const InfoNode& parent) const
|
||||
{
|
||||
double L = Base::calcCodelengthOnModuleOfModules(parent);
|
||||
if (!useEntropyBiasCorrection)
|
||||
return L;
|
||||
|
||||
return L + entropyBiasCorrectionMultiplier * parent.childDegree() / (2 * s_totalDegree);
|
||||
}
|
||||
|
||||
double BiasedMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const
|
||||
{
|
||||
double L = Base::calcCodelength(parent);
|
||||
if (!useEntropyBiasCorrection)
|
||||
return L;
|
||||
|
||||
return L + entropyBiasCorrectionMultiplier * parent.childDegree() / (2 * s_totalDegree);
|
||||
}
|
||||
|
||||
int BiasedMapEquation::getDeltaNumModulesIfMoving(unsigned int oldModule,
|
||||
unsigned int newModule,
|
||||
std::vector<unsigned int>& moduleMembers)
|
||||
{
|
||||
bool removeOld = moduleMembers[oldModule] == 1;
|
||||
bool createNew = moduleMembers[newModule] == 0;
|
||||
int deltaNumModules = removeOld && !createNew ? -1 : (!removeOld && createNew ? 1 : 0);
|
||||
return deltaNumModules;
|
||||
}
|
||||
|
||||
double BiasedMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlow& oldModuleDelta,
|
||||
DeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers)
|
||||
{
|
||||
double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
|
||||
|
||||
if (preferredNumModules == 0)
|
||||
return deltaL;
|
||||
|
||||
int deltaNumModules = getDeltaNumModulesIfMoving(oldModuleDelta.module, newModuleDelta.module, moduleMembers);
|
||||
|
||||
double deltaBiasedCost = calcNumModuleCost(currentNumModules + deltaNumModules) - biasedCost;
|
||||
|
||||
double deltaEntropyBiasCorrection = calcEntropyBiasCorrection(currentNumModules + deltaNumModules) - getEntropyBiasCorrection();
|
||||
|
||||
return deltaL + deltaBiasedCost + deltaEntropyBiasCorrection;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
void BiasedMapEquation::updateCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlow& oldModuleDelta,
|
||||
DeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers)
|
||||
{
|
||||
Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
|
||||
|
||||
if (preferredNumModules == 0)
|
||||
return;
|
||||
|
||||
int deltaNumModules = getDeltaNumModulesIfMoving(oldModuleDelta.module, newModuleDelta.module, moduleMembers);
|
||||
|
||||
currentNumModules += deltaNumModules;
|
||||
biasedCost = calcNumModuleCost(currentNumModules);
|
||||
indexEntropyBiasCorrection = calcIndexEntropyBiasCorrection(currentNumModules);
|
||||
moduleEntropyBiasCorrection = calcModuleEntropyBiasCorrection();
|
||||
}
|
||||
|
||||
void BiasedMapEquation::consolidateModules(std::vector<InfoNode*>& modules)
|
||||
{
|
||||
unsigned int numModules = 0;
|
||||
for (auto& module : modules) {
|
||||
if (module == nullptr)
|
||||
continue;
|
||||
++numModules;
|
||||
}
|
||||
currentNumModules = numModules;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug
|
||||
// ===================================================
|
||||
|
||||
void BiasedMapEquation::printDebug() const
|
||||
{
|
||||
std::cout << "BiasedMapEquation\n";
|
||||
Base::printDebug();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,180 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef BIASED_MAPEQUATION_H_
|
||||
#define BIASED_MAPEQUATION_H_
|
||||
|
||||
#include "MapEquation.h"
|
||||
#include "FlowData.h"
|
||||
#include "../utils/Log.h"
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InfoNode;
|
||||
class StateNetwork;
|
||||
|
||||
class BiasedMapEquation : private MapEquation<> {
|
||||
using Base = MapEquation<>;
|
||||
|
||||
public:
|
||||
using FlowDataType = FlowData;
|
||||
using DeltaFlowDataType = DeltaFlow;
|
||||
|
||||
// ===================================================
|
||||
// Getters
|
||||
// ===================================================
|
||||
|
||||
double getIndexCodelength() const override;
|
||||
|
||||
double getModuleCodelength() const override;
|
||||
|
||||
double getCodelength() const override;
|
||||
|
||||
double getEntropyBiasCorrection() const;
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
std::ostream& print(std::ostream& out) const override;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream&, const BiasedMapEquation&);
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
void init(const Config& config) override;
|
||||
|
||||
void initTree(InfoNode& /*root*/) override { }
|
||||
|
||||
void initNetwork(InfoNode& root) override;
|
||||
|
||||
using Base::initSuperNetwork;
|
||||
|
||||
using Base::initSubNetwork;
|
||||
|
||||
void initPartition(std::vector<InfoNode*>& nodes) override;
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
double calcCodelength(const InfoNode& parent) const override;
|
||||
|
||||
using Base::addMemoryContributions;
|
||||
|
||||
double getDeltaCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlow& oldModuleDelta,
|
||||
DeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers) override;
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
void updateCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlow& oldModuleDelta,
|
||||
DeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers) override;
|
||||
|
||||
void consolidateModules(std::vector<InfoNode*>& modules) override;
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug
|
||||
// ===================================================
|
||||
|
||||
void printDebug() const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
// ===================================================
|
||||
// Private member functions
|
||||
// ===================================================
|
||||
double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override;
|
||||
double calcCodelengthOnModuleOfModules(const InfoNode& parent) const override;
|
||||
|
||||
static int getDeltaNumModulesIfMoving(unsigned int oldModule, unsigned int newModule, std::vector<unsigned int>& moduleMembers);
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
void calculateCodelength(std::vector<InfoNode*>& nodes) override;
|
||||
|
||||
using Base::calculateCodelengthTerms;
|
||||
|
||||
using Base::calculateCodelengthFromCodelengthTerms;
|
||||
|
||||
double calcNumModuleCost(unsigned int numModules) const;
|
||||
|
||||
double calcIndexEntropyBiasCorrection(unsigned int numModules) const;
|
||||
double calcModuleEntropyBiasCorrection() const;
|
||||
double calcEntropyBiasCorrection(unsigned int numModules) const;
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
public:
|
||||
// ===================================================
|
||||
// Public member variables
|
||||
// ===================================================
|
||||
|
||||
using Base::codelength;
|
||||
using Base::indexCodelength;
|
||||
using Base::moduleCodelength;
|
||||
|
||||
private:
|
||||
// ===================================================
|
||||
// Private member variables
|
||||
// ===================================================
|
||||
|
||||
using Base::enter_log_enter;
|
||||
using Base::enterFlow;
|
||||
using Base::enterFlow_log_enterFlow;
|
||||
using Base::exit_log_exit;
|
||||
using Base::flow_log_flow; // node.(flow + exitFlow)
|
||||
using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same
|
||||
|
||||
// For hierarchical
|
||||
using Base::exitNetworkFlow;
|
||||
using Base::exitNetworkFlow_log_exitNetworkFlow;
|
||||
|
||||
// For biased
|
||||
unsigned int preferredNumModules = 0;
|
||||
unsigned int currentNumModules = 0;
|
||||
double biasedCost = 0.0;
|
||||
|
||||
// For entropy bias correction
|
||||
bool useEntropyBiasCorrection = false;
|
||||
double entropyBiasCorrectionMultiplier = 1;
|
||||
double indexEntropyBiasCorrection = 0;
|
||||
double moduleEntropyBiasCorrection = 0;
|
||||
static double s_totalDegree;
|
||||
static unsigned int s_numNodes;
|
||||
|
||||
public:
|
||||
static void setNetworkProperties(const StateNetwork& network);
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // BIASED_MAPEQUATION_H_
|
||||
@@ -0,0 +1,165 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef FLOWDATA_H_
|
||||
#define FLOWDATA_H_
|
||||
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
struct FlowData {
|
||||
double flow = 0.0;
|
||||
double enterFlow = 0.0;
|
||||
double exitFlow = 0.0;
|
||||
double teleportFlow = 0.0;
|
||||
double teleportSourceFlow = 0.0;
|
||||
double teleportWeight = 0.0;
|
||||
double danglingFlow = 0.0;
|
||||
|
||||
FlowData() = default;
|
||||
FlowData(double flow) : flow(flow) { }
|
||||
|
||||
FlowData& operator+=(const FlowData& other)
|
||||
{
|
||||
flow += other.flow;
|
||||
enterFlow += other.enterFlow;
|
||||
exitFlow += other.exitFlow;
|
||||
teleportFlow += other.teleportFlow;
|
||||
teleportSourceFlow += other.teleportSourceFlow;
|
||||
teleportWeight += other.teleportWeight;
|
||||
danglingFlow += other.danglingFlow;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FlowData& operator-=(const FlowData& other)
|
||||
{
|
||||
flow -= other.flow;
|
||||
enterFlow -= other.enterFlow;
|
||||
exitFlow -= other.exitFlow;
|
||||
teleportFlow -= other.teleportFlow;
|
||||
teleportSourceFlow -= other.teleportSourceFlow;
|
||||
teleportWeight -= other.teleportWeight;
|
||||
danglingFlow -= other.danglingFlow;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const FlowData& data)
|
||||
{
|
||||
return out << "flow: " << data.flow << ", enter: " << data.enterFlow << ", exit: " << data.exitFlow
|
||||
<< ", teleWeight: " << data.teleportWeight << ", danglingFlow: " << data.danglingFlow
|
||||
<< ", teleFlow: " << data.teleportFlow;
|
||||
}
|
||||
};
|
||||
|
||||
struct DeltaFlow {
|
||||
unsigned int module = 0;
|
||||
double deltaExit = 0.0;
|
||||
double deltaEnter = 0.0;
|
||||
unsigned int count = 0;
|
||||
|
||||
explicit DeltaFlow(unsigned int module, double deltaExit, double deltaEnter)
|
||||
: module(module),
|
||||
deltaExit(deltaExit),
|
||||
deltaEnter(deltaEnter) { }
|
||||
|
||||
DeltaFlow() = default;
|
||||
DeltaFlow(const DeltaFlow&) = default;
|
||||
DeltaFlow(DeltaFlow&&) = default;
|
||||
DeltaFlow& operator=(const DeltaFlow&) = default;
|
||||
DeltaFlow& operator=(DeltaFlow&&) = default;
|
||||
virtual ~DeltaFlow() = default;
|
||||
|
||||
DeltaFlow& operator+=(const DeltaFlow& other)
|
||||
{
|
||||
module = other.module;
|
||||
deltaExit += other.deltaExit;
|
||||
deltaEnter += other.deltaEnter;
|
||||
++count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual void reset()
|
||||
{
|
||||
module = 0;
|
||||
deltaExit = 0.0;
|
||||
deltaEnter = 0.0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
friend void swap(DeltaFlow& first, DeltaFlow& second) noexcept
|
||||
{
|
||||
std::swap(first.module, second.module);
|
||||
std::swap(first.deltaExit, second.deltaExit);
|
||||
std::swap(first.deltaEnter, second.deltaEnter);
|
||||
std::swap(first.count, second.count);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const DeltaFlow& data)
|
||||
{
|
||||
return out << "module: " << data.module << ", deltaEnter: " << data.deltaEnter << ", deltaExit: " << data.deltaExit << ", count: " << data.count;
|
||||
}
|
||||
};
|
||||
|
||||
struct MemDeltaFlow : DeltaFlow {
|
||||
double sumDeltaPlogpPhysFlow = 0.0;
|
||||
double sumPlogpPhysFlow = 0.0;
|
||||
|
||||
MemDeltaFlow() = default;
|
||||
|
||||
explicit MemDeltaFlow(unsigned int module, double deltaExit, double deltaEnter, double sumDeltaPlogpPhysFlow = 0.0, double sumPlogpPhysFlow = 0.0)
|
||||
: DeltaFlow(module, deltaExit, deltaEnter),
|
||||
sumDeltaPlogpPhysFlow(sumDeltaPlogpPhysFlow),
|
||||
sumPlogpPhysFlow(sumPlogpPhysFlow) { }
|
||||
|
||||
MemDeltaFlow& operator+=(const MemDeltaFlow& other)
|
||||
{
|
||||
DeltaFlow::operator+=(other);
|
||||
sumDeltaPlogpPhysFlow += other.sumDeltaPlogpPhysFlow;
|
||||
sumPlogpPhysFlow += other.sumPlogpPhysFlow;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset() override
|
||||
{
|
||||
DeltaFlow::reset();
|
||||
sumDeltaPlogpPhysFlow = 0.0;
|
||||
sumPlogpPhysFlow = 0.0;
|
||||
}
|
||||
|
||||
friend void swap(MemDeltaFlow& first, MemDeltaFlow& second) noexcept
|
||||
{
|
||||
swap(static_cast<DeltaFlow&>(first), static_cast<DeltaFlow&>(second));
|
||||
std::swap(first.sumDeltaPlogpPhysFlow, second.sumDeltaPlogpPhysFlow);
|
||||
std::swap(first.sumPlogpPhysFlow, second.sumPlogpPhysFlow);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const MemDeltaFlow& data)
|
||||
{
|
||||
return out << "module: " << data.module << ", deltaEnter: " << data.deltaEnter << ", deltaExit: " << data.deltaExit << ", count: " << data.count << ", sumDeltaPlogpPhysFlow: " << data.sumDeltaPlogpPhysFlow << ", sumPlogpPhysFlow: " << data.sumPlogpPhysFlow;
|
||||
}
|
||||
};
|
||||
|
||||
struct PhysData {
|
||||
unsigned int physNodeIndex;
|
||||
double sumFlowFromM2Node; // The amount of flow from the memory node in this physical node
|
||||
|
||||
explicit PhysData(unsigned int physNodeIndex, double sumFlowFromM2Node = 0.0)
|
||||
: physNodeIndex(physNodeIndex), sumFlowFromM2Node(sumFlowFromM2Node) { }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const PhysData& data)
|
||||
{
|
||||
return out << "physNodeIndex: " << data.physNodeIndex << ", sumFlowFromM2Node: " << data.sumFlowFromM2Node;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // FLOWDATA_H_
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "InfoEdge.h"
|
||||
#include "InfoNode.h"
|
||||
|
||||
namespace infomap {
|
||||
|
||||
InfoNode& infomap::InfoEdge::other(InfoNode& node) const
|
||||
{
|
||||
return (node == *source) ? *target : *source;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const InfoEdge& edge)
|
||||
{
|
||||
return out << "(" << *edge.source << ") -> (" << *edge.target << "), flow: "
|
||||
<< edge.data.flow;
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,47 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOEDGE_H_
|
||||
#define INFOEDGE_H_
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
struct EdgeData {
|
||||
public:
|
||||
EdgeData() = default;
|
||||
|
||||
EdgeData(double weight, double flow) : weight(weight), flow(flow) { }
|
||||
|
||||
double weight;
|
||||
double flow;
|
||||
};
|
||||
|
||||
class InfoNode;
|
||||
|
||||
class InfoEdge {
|
||||
public:
|
||||
InfoEdge(InfoNode& source, InfoNode& target, double weight, double flow)
|
||||
: data(weight, flow),
|
||||
source(&source),
|
||||
target(&target) { }
|
||||
|
||||
InfoNode& other(InfoNode& node) const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const InfoEdge& edge);
|
||||
|
||||
EdgeData data;
|
||||
InfoNode* source;
|
||||
InfoNode* target;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // INFOEDGE_H_
|
||||
@@ -0,0 +1,405 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "InfoNode.h"
|
||||
#include "InfomapBase.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
InfoNode::~InfoNode() noexcept
|
||||
{
|
||||
if (m_infomap != nullptr) {
|
||||
delete m_infomap;
|
||||
}
|
||||
|
||||
deleteChildren();
|
||||
|
||||
if (next != nullptr)
|
||||
next->previous = previous;
|
||||
if (previous != nullptr)
|
||||
previous->next = next;
|
||||
if (parent != nullptr) {
|
||||
if (parent->firstChild == this)
|
||||
parent->firstChild = next;
|
||||
if (parent->lastChild == this)
|
||||
parent->lastChild = previous;
|
||||
}
|
||||
|
||||
// Delete outgoing edges.
|
||||
// TODO: Renders ingoing edges invalid. Assume or assert that all nodes on the same level are deleted?
|
||||
for (edge_iterator outEdgeIt(begin_outEdge());
|
||||
outEdgeIt != end_outEdge();
|
||||
++outEdgeIt) {
|
||||
delete *outEdgeIt;
|
||||
}
|
||||
}
|
||||
|
||||
InfomapBase& InfoNode::setInfomap(InfomapBase* infomap)
|
||||
{
|
||||
disposeInfomap();
|
||||
m_infomap = infomap;
|
||||
if (infomap == nullptr)
|
||||
throw std::logic_error("InfoNode::setInfomap(...) called with null infomap");
|
||||
return *m_infomap;
|
||||
}
|
||||
|
||||
InfomapBase& InfoNode::getInfomap()
|
||||
{
|
||||
if (m_infomap == nullptr)
|
||||
throw std::logic_error("InfoNode::getInfomap() called but infomap is null");
|
||||
return *m_infomap;
|
||||
}
|
||||
|
||||
const InfomapBase& InfoNode::getInfomap() const
|
||||
{
|
||||
if (m_infomap == nullptr)
|
||||
throw std::logic_error("InfoNode::getInfomap() called but infomap is null");
|
||||
return *m_infomap;
|
||||
}
|
||||
|
||||
InfoNode* InfoNode::getInfomapRoot() noexcept
|
||||
{
|
||||
return m_infomap != nullptr ? &m_infomap->root() : nullptr;
|
||||
}
|
||||
|
||||
InfoNode const* InfoNode::getInfomapRoot() const noexcept
|
||||
{
|
||||
return m_infomap != nullptr ? &m_infomap->root() : nullptr;
|
||||
}
|
||||
|
||||
bool InfoNode::disposeInfomap() noexcept
|
||||
{
|
||||
if (m_infomap != nullptr) {
|
||||
delete m_infomap;
|
||||
m_infomap = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int InfoNode::depth() const noexcept
|
||||
{
|
||||
unsigned int depth = 0;
|
||||
InfoNode* n = parent;
|
||||
while (n != nullptr) {
|
||||
++depth;
|
||||
n = n->parent;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
unsigned int InfoNode::firstDepthBelow() const noexcept
|
||||
{
|
||||
unsigned int depthBelow = 0;
|
||||
InfoNode* child = firstChild;
|
||||
while (child != nullptr) {
|
||||
++depthBelow;
|
||||
child = child->firstChild;
|
||||
}
|
||||
return depthBelow;
|
||||
}
|
||||
|
||||
unsigned int InfoNode::childIndex() const noexcept
|
||||
{
|
||||
unsigned int childIndex = 0;
|
||||
const InfoNode* n(this);
|
||||
while (n->previous) {
|
||||
n = n->previous;
|
||||
++childIndex;
|
||||
}
|
||||
return childIndex;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> InfoNode::calculatePath() const noexcept
|
||||
{
|
||||
const InfoNode* current = this;
|
||||
std::vector<unsigned int> path;
|
||||
while (current->parent != nullptr) {
|
||||
path.push_back(current->childIndex() + 1);
|
||||
current = current->parent;
|
||||
if (current->owner != nullptr) {
|
||||
current = current->owner;
|
||||
}
|
||||
}
|
||||
std::reverse(path.begin(), path.end());
|
||||
return path;
|
||||
}
|
||||
|
||||
unsigned int InfoNode::infomapChildDegree() const noexcept
|
||||
{
|
||||
return m_infomap == nullptr ? childDegree() : m_infomap->root().childDegree();
|
||||
}
|
||||
|
||||
void InfoNode::addChild(InfoNode* child) noexcept
|
||||
{
|
||||
if (firstChild == nullptr) {
|
||||
child->previous = nullptr;
|
||||
firstChild = child;
|
||||
} else {
|
||||
child->previous = lastChild;
|
||||
lastChild->next = child;
|
||||
}
|
||||
lastChild = child;
|
||||
child->next = nullptr;
|
||||
child->parent = this;
|
||||
++m_childDegree;
|
||||
}
|
||||
|
||||
void InfoNode::releaseChildren() noexcept
|
||||
{
|
||||
firstChild = nullptr;
|
||||
lastChild = nullptr;
|
||||
m_childDegree = 0;
|
||||
}
|
||||
|
||||
InfoNode& InfoNode::replaceChildrenWithOneNode()
|
||||
{
|
||||
if (childDegree() == 1)
|
||||
return *firstChild;
|
||||
if (firstChild == nullptr)
|
||||
throw std::logic_error("replaceChildrenWithOneNode called on a node without any children.");
|
||||
if (firstChild->firstChild == nullptr)
|
||||
throw std::logic_error("replaceChildrenWithOneNode called on a node without any grandchildren.");
|
||||
auto* middleNode = new InfoNode();
|
||||
InfoNode::child_iterator nodeIt = begin_child();
|
||||
unsigned int numOriginalChildrenLeft = m_childDegree;
|
||||
auto d0 = m_childDegree;
|
||||
do {
|
||||
InfoNode* n = nodeIt.current();
|
||||
++nodeIt;
|
||||
middleNode->addChild(n);
|
||||
} while (--numOriginalChildrenLeft != 0);
|
||||
|
||||
releaseChildren();
|
||||
addChild(middleNode);
|
||||
auto d1 = middleNode->replaceChildrenWithGrandChildren();
|
||||
if (d1 != d0)
|
||||
throw std::logic_error("replaceChildrenWithOneNode replaced different number of children as having before");
|
||||
return *middleNode;
|
||||
}
|
||||
|
||||
unsigned int InfoNode::replaceChildrenWithGrandChildren() noexcept
|
||||
{
|
||||
if (firstChild == nullptr)
|
||||
return 0;
|
||||
InfoNode::child_iterator nodeIt = begin_child();
|
||||
unsigned int numOriginalChildrenLeft = m_childDegree;
|
||||
unsigned int numChildrenReplaced = 0;
|
||||
do {
|
||||
InfoNode* n = nodeIt.current();
|
||||
++nodeIt;
|
||||
numChildrenReplaced += n->replaceWithChildren();
|
||||
} while (--numOriginalChildrenLeft != 0);
|
||||
return numChildrenReplaced;
|
||||
}
|
||||
|
||||
unsigned int InfoNode::replaceWithChildren() noexcept
|
||||
{
|
||||
if (isLeaf() || isRoot())
|
||||
return 0;
|
||||
|
||||
// Re-parent children
|
||||
unsigned int deltaChildDegree = 0;
|
||||
InfoNode* child = firstChild;
|
||||
do {
|
||||
child->parent = parent;
|
||||
child = child->next;
|
||||
++deltaChildDegree;
|
||||
} while (child != nullptr);
|
||||
parent->m_childDegree += deltaChildDegree - 1; // -1 as this node is deleted
|
||||
|
||||
firstChild->previous = previous;
|
||||
lastChild->next = next;
|
||||
|
||||
if (parent->firstChild == this) {
|
||||
parent->firstChild = firstChild;
|
||||
} else {
|
||||
previous->next = firstChild;
|
||||
}
|
||||
|
||||
if (parent->lastChild == this) {
|
||||
parent->lastChild = lastChild;
|
||||
} else {
|
||||
next->previous = lastChild;
|
||||
}
|
||||
|
||||
// Release connected nodes before delete, otherwise children are deleted and neighbours are reconnected.
|
||||
firstChild = nullptr;
|
||||
lastChild = nullptr;
|
||||
next = nullptr;
|
||||
previous = nullptr;
|
||||
parent = nullptr;
|
||||
delete this;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void InfoNode::replaceChildrenWithGrandChildrenDebug() noexcept
|
||||
{
|
||||
if (firstChild == nullptr)
|
||||
return;
|
||||
InfoNode::child_iterator nodeIt = begin_child();
|
||||
unsigned int numOriginalChildrenLeft = m_childDegree;
|
||||
do {
|
||||
InfoNode* n = nodeIt.current();
|
||||
++nodeIt;
|
||||
n->replaceWithChildrenDebug();
|
||||
} while (--numOriginalChildrenLeft != 0);
|
||||
}
|
||||
|
||||
void InfoNode::replaceWithChildrenDebug() noexcept
|
||||
{
|
||||
if (isLeaf() || isRoot())
|
||||
return;
|
||||
|
||||
// Re-parent children
|
||||
unsigned int deltaChildDegree = 0;
|
||||
InfoNode* child = firstChild;
|
||||
do {
|
||||
child->parent = parent;
|
||||
child = child->next;
|
||||
++deltaChildDegree;
|
||||
} while (child != nullptr);
|
||||
parent->m_childDegree += deltaChildDegree - 1; // -1 as this node is deleted
|
||||
|
||||
if (parent->firstChild == this) {
|
||||
parent->firstChild = firstChild;
|
||||
} else {
|
||||
previous->next = firstChild;
|
||||
firstChild->previous = previous;
|
||||
}
|
||||
|
||||
if (parent->lastChild == this) {
|
||||
parent->lastChild = lastChild;
|
||||
} else {
|
||||
next->previous = lastChild;
|
||||
lastChild->next = next;
|
||||
}
|
||||
|
||||
// Release connected nodes before delete, otherwise children are deleted and neighbours are reconnected.
|
||||
firstChild = nullptr;
|
||||
lastChild = nullptr;
|
||||
next = nullptr;
|
||||
previous = nullptr;
|
||||
parent = nullptr;
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
void InfoNode::remove(bool removeChildren) noexcept
|
||||
{
|
||||
firstChild = removeChildren ? nullptr : firstChild;
|
||||
delete this;
|
||||
}
|
||||
|
||||
void InfoNode::deleteChildren() noexcept
|
||||
{
|
||||
if (firstChild == nullptr)
|
||||
return;
|
||||
|
||||
child_iterator nodeIt = begin_child();
|
||||
do {
|
||||
InfoNode* n = nodeIt.current();
|
||||
++nodeIt;
|
||||
delete n;
|
||||
} while (nodeIt.current() != nullptr);
|
||||
|
||||
firstChild = nullptr;
|
||||
lastChild = nullptr;
|
||||
m_childDegree = 0;
|
||||
}
|
||||
|
||||
void InfoNode::calcChildDegree() noexcept
|
||||
{
|
||||
m_childrenChanged = false;
|
||||
if (firstChild == nullptr)
|
||||
m_childDegree = 0;
|
||||
else if (firstChild == lastChild)
|
||||
m_childDegree = 1;
|
||||
else {
|
||||
m_childDegree = 0;
|
||||
for (auto& child : children()) {
|
||||
(void)child;
|
||||
++m_childDegree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InfoNode::setChildDegree(unsigned int value) noexcept
|
||||
{
|
||||
m_childDegree = value;
|
||||
m_childrenChanged = false;
|
||||
}
|
||||
|
||||
void InfoNode::initClean() noexcept
|
||||
{
|
||||
releaseChildren();
|
||||
previous = next = parent = nullptr;
|
||||
|
||||
physicalNodes.clear();
|
||||
}
|
||||
|
||||
void InfoNode::sortChildrenOnFlow(bool recurse) noexcept
|
||||
{
|
||||
if (childDegree() == 0)
|
||||
return;
|
||||
std::vector<InfoNode*> nodes(childDegree());
|
||||
double lastFlow = 1.0;
|
||||
bool isSorted = true;
|
||||
unsigned int i = 0;
|
||||
for (InfoNode& child : children()) {
|
||||
if (child.data.flow > lastFlow) {
|
||||
isSorted = false;
|
||||
}
|
||||
nodes[i] = &child;
|
||||
lastFlow = child.data.flow;
|
||||
++i;
|
||||
}
|
||||
if (!isSorted) {
|
||||
std::sort(nodes.begin(), nodes.end(), [](const InfoNode* a, const InfoNode* b) {
|
||||
return b->data.flow < a->data.flow;
|
||||
});
|
||||
releaseChildren();
|
||||
for (auto node : nodes) {
|
||||
addChild(node);
|
||||
}
|
||||
}
|
||||
if (recurse) {
|
||||
for (InfoNode& child : children()) {
|
||||
auto newRoot = child.getInfomapRoot();
|
||||
InfoNode& node = newRoot ? *newRoot : child;
|
||||
node.sortChildrenOnFlow(recurse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int InfoNode::collapseChildren() noexcept
|
||||
{
|
||||
std::swap(collapsedFirstChild, firstChild);
|
||||
std::swap(collapsedLastChild, lastChild);
|
||||
unsigned int numCollapsedChildren = childDegree();
|
||||
releaseChildren();
|
||||
return numCollapsedChildren;
|
||||
}
|
||||
|
||||
unsigned int InfoNode::expandChildren()
|
||||
{
|
||||
bool haveCollapsedChildren = collapsedFirstChild != nullptr;
|
||||
if (haveCollapsedChildren) {
|
||||
if (firstChild != nullptr || lastChild != nullptr)
|
||||
throw std::logic_error("Expand collapsed children called on a node that already has children.");
|
||||
std::swap(collapsedFirstChild, firstChild);
|
||||
std::swap(collapsedLastChild, lastChild);
|
||||
calcChildDegree();
|
||||
return childDegree();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,374 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFONODE_H_
|
||||
#define INFONODE_H_
|
||||
|
||||
#include "FlowData.h"
|
||||
#include "InfoEdge.h"
|
||||
#include "iterators/infomapIterators.h"
|
||||
#include "iterators/IterWrapper.h"
|
||||
#include "../utils/MetaCollection.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InfomapBase;
|
||||
|
||||
class InfoNode {
|
||||
public:
|
||||
using child_iterator = ChildIterator<InfoNode*>;
|
||||
using const_child_iterator = ChildIterator<InfoNode const*>;
|
||||
using infomap_child_iterator = InfomapChildIterator<InfoNode*>;
|
||||
using const_infomap_child_iterator = InfomapChildIterator<InfoNode const*>;
|
||||
|
||||
using tree_iterator = TreeIterator<InfoNode*>;
|
||||
using const_tree_iterator = TreeIterator<InfoNode const*>;
|
||||
|
||||
using leaf_node_iterator = LeafNodeIterator<InfoNode*>;
|
||||
using const_leaf_node_iterator = LeafNodeIterator<InfoNode const*>;
|
||||
using leaf_module_iterator = LeafModuleIterator<InfoNode*>;
|
||||
using const_leaf_module_iterator = LeafModuleIterator<InfoNode const*>;
|
||||
|
||||
using post_depth_first_iterator = DepthFirstIterator<InfoNode*, false>;
|
||||
using const_post_depth_first_iterator = DepthFirstIterator<InfoNode const*, false>;
|
||||
|
||||
using edge_iterator = std::vector<InfoEdge*>::iterator;
|
||||
using const_edge_iterator = std::vector<InfoEdge*>::const_iterator;
|
||||
|
||||
using edge_iterator_wrapper = IterWrapper<edge_iterator>;
|
||||
using const_edge_iterator_wrapper = IterWrapper<const_edge_iterator>;
|
||||
|
||||
using infomap_iterator_wrapper = IterWrapper<tree_iterator>;
|
||||
using const_infomap_iterator_wrapper = IterWrapper<const_tree_iterator>;
|
||||
|
||||
using child_iterator_wrapper = IterWrapper<child_iterator>;
|
||||
using const_child_iterator_wrapper = IterWrapper<const_child_iterator>;
|
||||
|
||||
using infomap_child_iterator_wrapper = IterWrapper<infomap_child_iterator>;
|
||||
using const_infomap_child_iterator_wrapper = IterWrapper<const_infomap_child_iterator>;
|
||||
|
||||
public:
|
||||
FlowData data;
|
||||
unsigned int index = 0; // Temporary index used in finding best module
|
||||
unsigned int stateId = 0; // Unique state node id for the leaf nodes
|
||||
unsigned int physicalId = 0; // Physical id equals stateId for first order networks, otherwise can be non-unique
|
||||
unsigned int layerId = 0; // Layer id for multilayer networks
|
||||
std::vector<int> metaData; // Categorical value for each meta data dimension
|
||||
|
||||
InfoNode* owner = nullptr; // Infomap owner (if this is an Infomap root)
|
||||
InfoNode* parent = nullptr;
|
||||
InfoNode* previous = nullptr; // sibling
|
||||
InfoNode* next = nullptr; // sibling
|
||||
InfoNode* firstChild = nullptr;
|
||||
InfoNode* lastChild = nullptr;
|
||||
InfoNode* collapsedFirstChild = nullptr;
|
||||
InfoNode* collapsedLastChild = nullptr;
|
||||
double codelength = 0.0; // TODO: Better design for hierarchical stuff!?
|
||||
bool dirty = false;
|
||||
|
||||
std::vector<PhysData> physicalNodes;
|
||||
MetaCollection metaCollection; // For modules
|
||||
std::vector<unsigned int> stateNodes; // For physically aggregated nodes
|
||||
|
||||
private:
|
||||
unsigned int m_childDegree = 0;
|
||||
bool m_childrenChanged = false;
|
||||
unsigned int m_numLeafMembers = 0;
|
||||
|
||||
std::vector<InfoEdge*> m_outEdges;
|
||||
std::vector<InfoEdge*> m_inEdges;
|
||||
|
||||
InfomapBase* m_infomap = nullptr;
|
||||
|
||||
public:
|
||||
InfoNode(const FlowData& flowData)
|
||||
: data(flowData) {};
|
||||
|
||||
// For first order nodes, physicalId equals stateId
|
||||
InfoNode(const FlowData& flowData, unsigned int stateId)
|
||||
: data(flowData), stateId(stateId), physicalId(stateId) {};
|
||||
|
||||
InfoNode(const FlowData& flowData, unsigned int stateId, unsigned int physicalId)
|
||||
: data(flowData), stateId(stateId), physicalId(physicalId) {};
|
||||
|
||||
InfoNode(const FlowData& flowData, unsigned int stateId, unsigned int physicalId, unsigned int layerId)
|
||||
: data(flowData), stateId(stateId), physicalId(physicalId), layerId(layerId) {};
|
||||
|
||||
InfoNode() = default;
|
||||
|
||||
InfoNode(const InfoNode& other)
|
||||
: data(other.data),
|
||||
index(other.index),
|
||||
stateId(other.stateId),
|
||||
physicalId(other.physicalId),
|
||||
layerId(other.layerId),
|
||||
metaData(other.metaData),
|
||||
parent(other.parent),
|
||||
previous(other.previous),
|
||||
next(other.next),
|
||||
firstChild(other.firstChild),
|
||||
lastChild(other.lastChild),
|
||||
collapsedFirstChild(other.collapsedFirstChild),
|
||||
collapsedLastChild(other.collapsedLastChild),
|
||||
codelength(other.codelength),
|
||||
dirty(other.dirty),
|
||||
metaCollection(other.metaCollection),
|
||||
m_childDegree(other.m_childDegree),
|
||||
m_childrenChanged(other.m_childrenChanged),
|
||||
m_numLeafMembers(other.m_numLeafMembers) { }
|
||||
|
||||
~InfoNode() noexcept;
|
||||
|
||||
InfoNode& operator=(const InfoNode& other)
|
||||
{
|
||||
data = other.data;
|
||||
index = other.index;
|
||||
stateId = other.stateId;
|
||||
physicalId = other.physicalId;
|
||||
layerId = other.layerId;
|
||||
metaData = other.metaData;
|
||||
parent = other.parent;
|
||||
previous = other.previous;
|
||||
next = other.next;
|
||||
firstChild = other.firstChild;
|
||||
lastChild = other.lastChild;
|
||||
collapsedFirstChild = other.collapsedFirstChild;
|
||||
collapsedLastChild = other.collapsedLastChild;
|
||||
codelength = other.codelength;
|
||||
dirty = other.dirty;
|
||||
metaCollection = other.metaCollection;
|
||||
m_childDegree = other.m_childDegree;
|
||||
m_childrenChanged = other.m_childrenChanged;
|
||||
m_numLeafMembers = other.m_numLeafMembers;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// ---------------------------- Getters ----------------------------
|
||||
|
||||
unsigned int getMetaData(unsigned int dimension = 0) noexcept
|
||||
{
|
||||
if (dimension >= metaData.size()) {
|
||||
return 0;
|
||||
}
|
||||
auto meta = metaData[dimension];
|
||||
return meta < 0 ? 0 : static_cast<unsigned int>(meta);
|
||||
}
|
||||
|
||||
// ---------------------------- Infomap ----------------------------
|
||||
InfomapBase& getInfomap();
|
||||
|
||||
const InfomapBase& getInfomap() const;
|
||||
|
||||
InfomapBase& setInfomap(InfomapBase*);
|
||||
|
||||
InfoNode* getInfomapRoot() noexcept;
|
||||
|
||||
InfoNode const* getInfomapRoot() const noexcept;
|
||||
|
||||
/**
|
||||
* Dispose the Infomap instance if it exists
|
||||
* @return true if an existing Infomap instance was deleted
|
||||
*/
|
||||
bool disposeInfomap() noexcept;
|
||||
|
||||
/**
|
||||
* Number of physical nodes in memory nodes
|
||||
*/
|
||||
unsigned int numPhysicalNodes() const noexcept { return physicalNodes.size(); }
|
||||
|
||||
// ---------------------------- Tree iterators ----------------------------
|
||||
|
||||
// Default iteration on children
|
||||
child_iterator begin() noexcept { return { this }; }
|
||||
|
||||
child_iterator end() noexcept { return { nullptr }; }
|
||||
|
||||
const_child_iterator begin() const noexcept { return { this }; }
|
||||
|
||||
const_child_iterator end() const noexcept { return { nullptr }; }
|
||||
|
||||
child_iterator begin_child() noexcept { return { this }; }
|
||||
|
||||
child_iterator end_child() noexcept { return { nullptr }; }
|
||||
|
||||
const_child_iterator begin_child() const noexcept { return { this }; }
|
||||
|
||||
const_child_iterator end_child() const noexcept { return { nullptr }; }
|
||||
|
||||
child_iterator_wrapper children() noexcept { return { { this }, { nullptr } }; }
|
||||
|
||||
const_child_iterator_wrapper children() const noexcept { return { { this }, { nullptr } }; }
|
||||
|
||||
infomap_child_iterator_wrapper infomap_children() noexcept { return { { this }, { nullptr } }; }
|
||||
|
||||
const_infomap_child_iterator_wrapper infomap_children() const noexcept { return { { this }, { nullptr } }; }
|
||||
|
||||
post_depth_first_iterator begin_post_depth_first() noexcept { return { this }; }
|
||||
|
||||
leaf_node_iterator begin_leaf_nodes() noexcept { return { this }; }
|
||||
|
||||
leaf_module_iterator begin_leaf_modules() noexcept { return { this }; }
|
||||
|
||||
tree_iterator begin_tree(unsigned int maxClusterLevel = std::numeric_limits<unsigned int>::max()) noexcept { return { this, static_cast<int>(maxClusterLevel) }; }
|
||||
|
||||
tree_iterator end_tree() noexcept { return { nullptr }; }
|
||||
|
||||
const_tree_iterator begin_tree(unsigned int maxClusterLevel = std::numeric_limits<unsigned int>::max()) const noexcept { return { this, static_cast<int>(maxClusterLevel) }; }
|
||||
|
||||
const_tree_iterator end_tree() const noexcept { return { nullptr }; }
|
||||
|
||||
infomap_iterator_wrapper infomapTree(unsigned int maxClusterLevel = std::numeric_limits<unsigned int>::max()) noexcept { return { { this, static_cast<int>(maxClusterLevel) }, { nullptr } }; }
|
||||
|
||||
const_infomap_iterator_wrapper infomapTree(unsigned int maxClusterLevel = std::numeric_limits<unsigned int>::max()) const noexcept { return { { this, static_cast<int>(maxClusterLevel) }, { nullptr } }; }
|
||||
|
||||
// ---------------------------- Graph iterators ----------------------------
|
||||
|
||||
edge_iterator begin_outEdge() noexcept { return m_outEdges.begin(); }
|
||||
|
||||
edge_iterator end_outEdge() noexcept { return m_outEdges.end(); }
|
||||
|
||||
edge_iterator begin_inEdge() noexcept { return m_inEdges.begin(); }
|
||||
|
||||
edge_iterator end_inEdge() noexcept { return m_inEdges.end(); }
|
||||
|
||||
edge_iterator_wrapper outEdges() noexcept { return { m_outEdges }; }
|
||||
|
||||
edge_iterator_wrapper inEdges() noexcept { return { m_inEdges }; }
|
||||
|
||||
// ---------------------------- Capacity ----------------------------
|
||||
|
||||
unsigned int childDegree() const noexcept { return m_childDegree; }
|
||||
|
||||
bool isLeaf() const noexcept { return firstChild == nullptr; }
|
||||
|
||||
// TODO: Safe to assume all children are leaves if first child is leaf?
|
||||
bool isLeafModule() const noexcept { return m_infomap == nullptr && firstChild != nullptr && firstChild->firstChild == nullptr; }
|
||||
|
||||
bool isRoot() const noexcept { return parent == nullptr; }
|
||||
|
||||
unsigned int depth() const noexcept;
|
||||
|
||||
unsigned int firstDepthBelow() const noexcept;
|
||||
|
||||
unsigned int numLeafMembers() const noexcept { return m_numLeafMembers; }
|
||||
|
||||
bool isDangling() const noexcept { return m_outEdges.empty(); }
|
||||
|
||||
unsigned int outDegree() const noexcept { return m_outEdges.size(); }
|
||||
|
||||
unsigned int inDegree() const noexcept { return m_inEdges.size(); }
|
||||
|
||||
unsigned int degree() const noexcept { return outDegree() + inDegree(); }
|
||||
|
||||
// ---------------------------- Order ----------------------------
|
||||
bool isFirst() const noexcept { return !parent || parent->firstChild == this; }
|
||||
|
||||
bool isLast() const noexcept { return !parent || parent->lastChild == this; }
|
||||
|
||||
unsigned int childIndex() const noexcept;
|
||||
|
||||
// Generate 1-based tree path
|
||||
std::vector<unsigned int> calculatePath() const noexcept;
|
||||
|
||||
unsigned int infomapChildDegree() const noexcept;
|
||||
|
||||
unsigned int id() const noexcept { return stateId; }
|
||||
|
||||
// ---------------------------- Operators ----------------------------
|
||||
|
||||
bool operator==(const InfoNode& rhs) const noexcept { return this == &rhs; }
|
||||
|
||||
bool operator!=(const InfoNode& rhs) const noexcept { return this != &rhs; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const InfoNode& node) noexcept
|
||||
{
|
||||
if (node.isLeaf())
|
||||
out << "[" << node.physicalId << "]";
|
||||
else
|
||||
out << "[module]";
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---------------------------- Mutators ----------------------------
|
||||
|
||||
/**
|
||||
* Clear a cloned node to initial state
|
||||
*/
|
||||
void initClean() noexcept;
|
||||
|
||||
void sortChildrenOnFlow(bool recurse = true) noexcept;
|
||||
|
||||
/**
|
||||
* Release the children and store the child pointers for later expansion
|
||||
* @return the number of children collapsed
|
||||
*/
|
||||
unsigned int collapseChildren() noexcept;
|
||||
|
||||
/**
|
||||
* Expand collapsed children
|
||||
* @return the number of collapsed children expanded
|
||||
*/
|
||||
unsigned int expandChildren();
|
||||
|
||||
// ------ OLD -----
|
||||
|
||||
// After change, set the child degree if known instead of lazily computing it by traversing the linked list
|
||||
void setChildDegree(unsigned int value) noexcept;
|
||||
|
||||
void setNumLeafNodes(unsigned int value) noexcept { m_numLeafMembers = value; }
|
||||
|
||||
void addChild(InfoNode* child) noexcept;
|
||||
|
||||
void releaseChildren() noexcept;
|
||||
|
||||
/**
|
||||
* If not already having a single child, replace children
|
||||
* with a single new node, assuming grandchildren.
|
||||
* @return the single child
|
||||
*/
|
||||
InfoNode& replaceChildrenWithOneNode();
|
||||
|
||||
/**
|
||||
* @return 1 if the node is removed, otherwise 0
|
||||
*/
|
||||
unsigned int replaceWithChildren() noexcept;
|
||||
|
||||
void replaceWithChildrenDebug() noexcept;
|
||||
|
||||
/**
|
||||
* @return The number of children removed
|
||||
*/
|
||||
unsigned int replaceChildrenWithGrandChildren() noexcept;
|
||||
|
||||
void replaceChildrenWithGrandChildrenDebug() noexcept;
|
||||
|
||||
void remove(bool removeChildren) noexcept;
|
||||
|
||||
void deleteChildren() noexcept;
|
||||
|
||||
void addOutEdge(InfoNode& target, double weight, double flow = 0.0) noexcept
|
||||
{
|
||||
auto* edge = new InfoEdge(*this, target, weight, flow);
|
||||
m_outEdges.push_back(edge);
|
||||
target.m_inEdges.push_back(edge);
|
||||
}
|
||||
|
||||
private:
|
||||
void calcChildDegree() noexcept;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // INFONODE_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,586 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOMAP_BASE_H_
|
||||
#define INFOMAP_BASE_H_
|
||||
|
||||
#include "InfomapConfig.h"
|
||||
#include "InfoEdge.h"
|
||||
#include "InfoNode.h"
|
||||
#include "InfomapOptimizerBase.h"
|
||||
#include "iterators/InfomapIterator.h"
|
||||
#include "../io/ClusterMap.h"
|
||||
#include "../io/Network.h"
|
||||
#include "../io/Output.h"
|
||||
#include "../utils/Log.h"
|
||||
#include "../utils/Date.h"
|
||||
#include "../utils/Stopwatch.h"
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
namespace detail {
|
||||
class PartitionQueue;
|
||||
struct PerLevelStat;
|
||||
} // namespace detail
|
||||
|
||||
class InfomapBase : public InfomapConfig<InfomapBase> {
|
||||
template <typename Objective>
|
||||
friend class InfomapOptimizer;
|
||||
|
||||
void initOptimizer(bool forceNoMemory = false);
|
||||
|
||||
public:
|
||||
using PartitionQueue = detail::PartitionQueue;
|
||||
|
||||
InfomapBase() : InfomapConfig<InfomapBase>() { initOptimizer(); }
|
||||
|
||||
explicit InfomapBase(const Config& conf) : InfomapConfig<InfomapBase>(conf), m_network(conf) { initOptimizer(); }
|
||||
|
||||
explicit InfomapBase(const std::string& flags, bool isCli = false) : InfomapConfig<InfomapBase>(flags, isCli)
|
||||
{
|
||||
initOptimizer();
|
||||
m_network.setConfig(*this);
|
||||
m_initialParameters = m_currentParameters = flags;
|
||||
}
|
||||
|
||||
virtual ~InfomapBase() = default;
|
||||
|
||||
// ===================================================
|
||||
// Iterators
|
||||
// ===================================================
|
||||
|
||||
InfomapIterator iterTree(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
|
||||
|
||||
InfomapIteratorPhysical iterTreePhysical(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
|
||||
|
||||
InfomapModuleIterator iterModules(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
|
||||
|
||||
InfomapLeafModuleIterator iterLeafModules(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
|
||||
|
||||
InfomapLeafIterator iterLeafNodes(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
|
||||
|
||||
InfomapLeafIteratorPhysical iterLeafNodesPhysical(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
|
||||
|
||||
InfomapIterator begin(int maxClusterLevel = 1) { return { &root(), maxClusterLevel }; }
|
||||
|
||||
InfomapIterator end() const { return InfomapIterator(nullptr); }
|
||||
|
||||
// ===================================================
|
||||
// Getters
|
||||
// ===================================================
|
||||
|
||||
Network& network() { return m_network; }
|
||||
const Network& network() const { return m_network; }
|
||||
|
||||
InfoNode& root() { return m_root; }
|
||||
const InfoNode& root() const { return m_root; }
|
||||
|
||||
unsigned int numLeafNodes() const { return m_leafNodes.size(); }
|
||||
|
||||
const std::vector<InfoNode*>& leafNodes() const { return m_leafNodes; }
|
||||
|
||||
unsigned int numTopModules() const { return m_root.childDegree(); }
|
||||
|
||||
unsigned int numActiveModules() const { return m_optimizer->numActiveModules(); }
|
||||
|
||||
unsigned int numNonTrivialTopModules() const { return m_numNonTrivialTopModules; }
|
||||
|
||||
bool haveModules() const { return !m_root.isLeaf() && !m_root.firstChild->isLeaf(); }
|
||||
|
||||
bool haveNonTrivialModules() const { return numNonTrivialTopModules() > 0; }
|
||||
|
||||
/**
|
||||
* Number of node levels below the root in current Infomap instance, 1 if no modules
|
||||
*/
|
||||
unsigned int numLevels() const;
|
||||
|
||||
/**
|
||||
* Get maximum depth of any child in the tree, following possible sub Infomap instances
|
||||
*/
|
||||
unsigned int maxTreeDepth() const;
|
||||
|
||||
double getCodelength() const { return m_optimizer->getCodelength(); }
|
||||
|
||||
double getMetaCodelength(bool unweighted = false) const { return m_optimizer->getMetaCodelength(unweighted); }
|
||||
|
||||
double codelength() const { return m_hierarchicalCodelength; }
|
||||
|
||||
const std::vector<double>& codelengths() const { return m_codelengths; }
|
||||
|
||||
double getIndexCodelength() const { return m_optimizer->getIndexCodelength(); }
|
||||
|
||||
double getModuleCodelength() const { return m_hierarchicalCodelength - m_optimizer->getIndexCodelength(); }
|
||||
|
||||
double getHierarchicalCodelength() const { return m_hierarchicalCodelength; }
|
||||
|
||||
double getOneLevelCodelength() const { return m_oneLevelCodelength; }
|
||||
|
||||
double getRelativeCodelengthSavings() const
|
||||
{
|
||||
auto oneLevelCodelength = getOneLevelCodelength();
|
||||
return oneLevelCodelength < 1e-16 ? 0 : 1.0 - codelength() / oneLevelCodelength;
|
||||
}
|
||||
|
||||
double getEntropyRate() { return m_entropyRate; }
|
||||
double getMaxEntropy() { return m_maxEntropy; }
|
||||
double getMaxFlow() { return m_maxFlow; }
|
||||
|
||||
const Date& getStartDate() const { return m_startDate; }
|
||||
const Stopwatch& getElapsedTime() const { return m_elapsedTime; }
|
||||
|
||||
std::vector<InfoNode*>& activeNetwork() const { return *m_activeNetwork; }
|
||||
|
||||
std::map<unsigned int, std::vector<unsigned int>> getMultilevelModules(bool states = false);
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
std::ostream& toString(std::ostream& out) const { return m_optimizer->toString(out); }
|
||||
|
||||
// ===================================================
|
||||
// Run
|
||||
// ===================================================
|
||||
|
||||
using InitialPartition = std::map<unsigned int, unsigned int>;
|
||||
|
||||
const InitialPartition& getInitialPartition() const { return m_initialPartition; }
|
||||
|
||||
InfomapBase& setInitialPartition(const InitialPartition& moduleIds)
|
||||
{
|
||||
m_initialPartition = moduleIds;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void run(const std::string& parameters = "");
|
||||
|
||||
void run(Network& network);
|
||||
|
||||
private:
|
||||
bool isFullNetwork() const { return m_isMain && m_aggregationLevel == 0; }
|
||||
bool isFirstLoop() const { return m_tuneIterationIndex == 0 && isFullNetwork(); }
|
||||
|
||||
InfomapBase* getNewInfomapInstance() const { return new InfomapBase(getConfig()); }
|
||||
InfomapBase* getNewInfomapInstanceWithoutMemory() const
|
||||
{
|
||||
auto im = new InfomapBase();
|
||||
im->initOptimizer(true);
|
||||
return im;
|
||||
}
|
||||
|
||||
InfomapBase& getSubInfomap(InfoNode& node) const
|
||||
{
|
||||
return node.setInfomap(getNewInfomapInstance())
|
||||
.setIsMain(false)
|
||||
.setSubLevel(m_subLevel + 1)
|
||||
.setNonMainConfig(*this);
|
||||
}
|
||||
|
||||
InfomapBase& getSuperInfomap(InfoNode& node) const
|
||||
{
|
||||
return node.setInfomap(getNewInfomapInstanceWithoutMemory())
|
||||
.setIsMain(false)
|
||||
.setSubLevel(m_subLevel + SUPER_LEVEL_ADDITION)
|
||||
.setNonMainConfig(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only the main infomap reads an external cluster file if exist
|
||||
*/
|
||||
InfomapBase& setIsMain(bool isMain)
|
||||
{
|
||||
m_isMain = isMain;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfomapBase& setSubLevel(unsigned int level)
|
||||
{
|
||||
m_subLevel = level;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isTopLevel() const { return (m_subLevel & (SUPER_LEVEL_ADDITION - 1)) == 0; }
|
||||
|
||||
bool isSuperLevelOnTopLevel() const { return m_subLevel == SUPER_LEVEL_ADDITION; }
|
||||
|
||||
bool isMainInfomap() const { return m_isMain; }
|
||||
|
||||
bool haveHardPartition() const { return !m_originalLeafNodes.empty(); }
|
||||
|
||||
// ===================================================
|
||||
// Run: *
|
||||
// ===================================================
|
||||
|
||||
InfomapBase& initNetwork(Network& network);
|
||||
InfomapBase& initNetwork(InfoNode& parent, bool asSuperNetwork = false);
|
||||
|
||||
void generateSubNetwork(Network& network);
|
||||
void generateSubNetwork(InfoNode& parent);
|
||||
|
||||
/**
|
||||
* Init categorical meta data on all nodes from a file with the following format:
|
||||
* # nodeId metaData
|
||||
* 1 1
|
||||
* 2 1
|
||||
* 3 2
|
||||
* 4 2
|
||||
* 5 3
|
||||
*
|
||||
*/
|
||||
InfomapBase& initMetaData(const std::string& metaDataFile);
|
||||
|
||||
/**
|
||||
* Provide an initial partition of the network.
|
||||
*
|
||||
* @param clusterDataFile A .clu file containing cluster data.
|
||||
* @param hard If true, the provided clusters will not be splitted. This reduces the
|
||||
* effective network size during the optimization phase but the hard partitions are
|
||||
* after that replaced by the original nodes.
|
||||
*/
|
||||
InfomapBase& initPartition(const std::string& clusterDataFile, bool hard = false, const Network* network = nullptr);
|
||||
|
||||
/**
|
||||
* Provide an initial partition of the network.
|
||||
*
|
||||
* @param clusterIds map from nodeId to clusterId, doesn't have to be complete
|
||||
* @param hard If true, the provided clusters will not be splitted. This reduces the
|
||||
* effective network size during the optimization phase but the hard partitions are
|
||||
* after that replaced by the original nodes.
|
||||
*/
|
||||
InfomapBase& initPartition(const std::map<unsigned int, unsigned int>& clusterIds, bool hard = false);
|
||||
|
||||
/**
|
||||
* Provide an initial partition of the network.
|
||||
*
|
||||
* @param clusters Each sub-vector contain node IDs for all nodes that should be merged.
|
||||
* @param hard If true, the provided clusters will not be splitted. This reduces the
|
||||
* effective network size during the optimization phase but the hard partitions are
|
||||
* after that replaced by the original nodes.
|
||||
*/
|
||||
InfomapBase& initPartition(std::vector<std::vector<unsigned int>>& clusters, bool hard = false);
|
||||
|
||||
/**
|
||||
* Provide an initial partition of the network.
|
||||
*
|
||||
* @param modules Module indices for each node
|
||||
*/
|
||||
InfomapBase& initPartition(std::vector<unsigned int>& modules, bool hard = false);
|
||||
|
||||
/**
|
||||
* Provide an initial hierarchical partition of the network
|
||||
*
|
||||
* @param tree A tree path for each node
|
||||
*/
|
||||
InfomapBase& initTree(const NodePaths& tree);
|
||||
|
||||
void init();
|
||||
|
||||
void runPartition()
|
||||
{
|
||||
if (twoLevel)
|
||||
partition();
|
||||
else
|
||||
hierarchicalPartition();
|
||||
}
|
||||
|
||||
void restoreHardPartition();
|
||||
|
||||
void writeResult(int trial = -1);
|
||||
|
||||
// ===================================================
|
||||
// runPartition: *
|
||||
// ===================================================
|
||||
|
||||
void hierarchicalPartition();
|
||||
|
||||
void partition();
|
||||
|
||||
// ===================================================
|
||||
// runPartition: init: *
|
||||
// ===================================================
|
||||
|
||||
/**
|
||||
* Done in network?
|
||||
*/
|
||||
void initEnterExitFlow();
|
||||
|
||||
void aggregateFlowValuesFromLeafToRoot();
|
||||
|
||||
// Init terms that is constant for the whole network
|
||||
void initTree() { return m_optimizer->initTree(); }
|
||||
|
||||
void initNetwork() { return m_optimizer->initNetwork(); }
|
||||
|
||||
void initSuperNetwork() { return m_optimizer->initSuperNetwork(); }
|
||||
|
||||
double calcCodelength(const InfoNode& parent) const { return m_optimizer->calcCodelength(parent); }
|
||||
|
||||
/**
|
||||
* Calculate and store codelength on all modules in the tree
|
||||
* @param includeRoot Also calculate the codelength on the root node
|
||||
* @return the hierarchical codelength
|
||||
*/
|
||||
double calcCodelengthOnTree(InfoNode& root, bool includeRoot = true) const;
|
||||
|
||||
// ===================================================
|
||||
// Run: Partition: *
|
||||
// ===================================================
|
||||
|
||||
void setActiveNetworkFromLeafs() { m_activeNetwork = &m_leafNodes; }
|
||||
|
||||
void setActiveNetworkFromChildrenOfRoot();
|
||||
|
||||
void initPartition() { return m_optimizer->initPartition(); }
|
||||
|
||||
void findTopModulesRepeatedly(unsigned int maxLevels);
|
||||
|
||||
unsigned int fineTune();
|
||||
|
||||
unsigned int coarseTune();
|
||||
|
||||
/**
|
||||
* Return the number of effective core loops, i.e. not the last if not at coreLoopLimit
|
||||
*/
|
||||
unsigned int optimizeActiveNetwork() { return m_optimizer->optimizeActiveNetwork(); }
|
||||
|
||||
void moveActiveNodesToPredefinedModules(std::vector<unsigned int>& modules)
|
||||
{
|
||||
return m_optimizer->moveActiveNodesToPredefinedModules(modules);
|
||||
}
|
||||
|
||||
void consolidateModules(bool replaceExistingModules = true)
|
||||
{
|
||||
return m_optimizer->consolidateModules(replaceExistingModules);
|
||||
}
|
||||
|
||||
unsigned int calculateNumNonTrivialTopModules() const;
|
||||
|
||||
unsigned int calculateMaxDepth() const;
|
||||
|
||||
// ===================================================
|
||||
// Partition: findTopModulesRepeatedly: *
|
||||
// ===================================================
|
||||
|
||||
/**
|
||||
* Return true if restored to consolidated optimization state
|
||||
*/
|
||||
bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false)
|
||||
{
|
||||
return m_optimizer->restoreConsolidatedOptimizationPointIfNoImprovement(forceRestore);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Run: Hierarchical Partition: *
|
||||
// ===================================================
|
||||
|
||||
/**
|
||||
* Find super modules applying the whole two-level algorithm on the
|
||||
* top modules iteratively
|
||||
* @param levelLimit The maximum number of super module levels allowed
|
||||
* @return number of levels created
|
||||
*/
|
||||
unsigned int findHierarchicalSuperModules(unsigned int superLevelLimit = std::numeric_limits<unsigned int>::max());
|
||||
|
||||
/**
|
||||
* Find super modules fast by merge and consolidate top modules iteratively
|
||||
* @param levelLimit The maximum number of super module levels allowed
|
||||
* @return number of levels created
|
||||
*/
|
||||
unsigned int findHierarchicalSuperModulesFast(unsigned int superLevelLimit = std::numeric_limits<unsigned int>::max());
|
||||
|
||||
void transformNodeFlowToEnterFlow(InfoNode& parent);
|
||||
|
||||
void resetFlowOnModules();
|
||||
|
||||
unsigned int removeModules();
|
||||
|
||||
unsigned int removeSubModules(bool recalculateCodelengthOnTree);
|
||||
|
||||
unsigned int recursivePartition();
|
||||
|
||||
void queueTopModules(PartitionQueue& partitionQueue);
|
||||
|
||||
void queueLeafModules(PartitionQueue& partitionQueue);
|
||||
|
||||
bool processPartitionQueue(PartitionQueue& queue, PartitionQueue& nextLevel) const;
|
||||
|
||||
public:
|
||||
// ===================================================
|
||||
// Output: *
|
||||
// ===================================================
|
||||
|
||||
/**
|
||||
* Write tree to a .tree file.
|
||||
* @param filename the filename for the output file. If empty, use default
|
||||
* based on output directory and input file name
|
||||
* @param states if memory network, print the state-level network without merging physical nodes within modules
|
||||
* @return the filename written to
|
||||
*/
|
||||
std::string writeTree(const std::string& filename = "", bool states = false) { return infomap::writeTree(*this, m_network, filename, states); }
|
||||
|
||||
/**
|
||||
* Write flow tree to a .ftree file.
|
||||
* This is the same as a .tree file but appended with links aggregated
|
||||
* within modules on all levels in the tree
|
||||
* @param filename the filename for the output file. If empty, use default
|
||||
* based on output directory and input file name
|
||||
* @param states if memory network, print the state-level network without merging physical nodes within modules
|
||||
* @return the filename written to
|
||||
*/
|
||||
std::string writeFlowTree(const std::string& filename = "", bool states = false) { return infomap::writeFlowTree(*this, m_network, filename, states); }
|
||||
|
||||
/**
|
||||
* Write Newick tree to a .tre file.
|
||||
* @param filename the filename for the output file. If empty, use default
|
||||
* based on output directory and input file name
|
||||
* @param states if memory network, print the state-level network without merging physical nodes within modules
|
||||
* @return the filename written to
|
||||
*/
|
||||
std::string writeNewickTree(const std::string& filename = "", bool states = false) { return infomap::writeNewickTree(*this, filename, states); }
|
||||
|
||||
std::string writeJsonTree(const std::string& filename = "", bool states = false, bool writeLinks = false) { return infomap::writeJsonTree(*this, m_network, filename, states, writeLinks); }
|
||||
|
||||
std::string writeCsvTree(const std::string& filename = "", bool states = false) { return infomap::writeCsvTree(*this, m_network, filename, states); }
|
||||
|
||||
/**
|
||||
* Write tree to a .clu file.
|
||||
* @param filename the filename for the output file. If empty, use default
|
||||
* based on output directory and input file name
|
||||
* @param states if memory network, print the state-level network without merging physical nodes within modules
|
||||
* @param moduleIndexLevel the depth from the root on which to advance module index.
|
||||
* Value 1 (default) will give the module index on the coarsest level, 2 the level below and so on.
|
||||
* Value -1 will give the module index for the lowest level, i.e. the finest modular structure.
|
||||
* @return the filename written to
|
||||
*/
|
||||
std::string writeClu(const std::string& filename = "", bool states = false, int moduleIndexLevel = 1) { return infomap::writeClu(*this, m_network, filename, states, moduleIndexLevel); }
|
||||
|
||||
private:
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug: *
|
||||
// ===================================================
|
||||
|
||||
void printDebug() const { return m_optimizer->printDebug(); }
|
||||
#endif
|
||||
|
||||
// ===================================================
|
||||
// Members
|
||||
// ===================================================
|
||||
|
||||
protected:
|
||||
InfoNode m_root;
|
||||
std::vector<InfoNode*> m_leafNodes;
|
||||
std::vector<InfoNode*> m_moduleNodes;
|
||||
std::vector<InfoNode*>* m_activeNetwork = nullptr;
|
||||
|
||||
std::vector<InfoNode*> m_originalLeafNodes;
|
||||
|
||||
Network m_network;
|
||||
InitialPartition m_initialPartition = {}; // nodeId -> moduleId
|
||||
|
||||
const unsigned int SUPER_LEVEL_ADDITION = 1 << 20;
|
||||
bool m_isMain = true;
|
||||
unsigned int m_subLevel = 0;
|
||||
|
||||
bool m_calculateEnterExitFlow = false;
|
||||
|
||||
double m_oneLevelCodelength = 0.0;
|
||||
unsigned int m_numNonTrivialTopModules = 0;
|
||||
unsigned int m_tuneIterationIndex = 0;
|
||||
bool m_isCoarseTune = false;
|
||||
unsigned int m_aggregationLevel = 0;
|
||||
|
||||
double m_hierarchicalCodelength = 0.0;
|
||||
std::vector<double> m_codelengths;
|
||||
double m_entropyRate = 0.0;
|
||||
double m_maxEntropy = 0.0;
|
||||
double m_maxFlow = 0.0;
|
||||
|
||||
double m_sumDanglingFlow = 0.0;
|
||||
|
||||
Date m_startDate;
|
||||
Date m_endDate;
|
||||
Stopwatch m_elapsedTime = Stopwatch(false);
|
||||
std::string m_initialParameters;
|
||||
std::string m_currentParameters;
|
||||
|
||||
std::unique_ptr<InfomapOptimizerBase> m_optimizer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Print per level statistics
|
||||
*/
|
||||
unsigned int printPerLevelCodelength(const InfoNode& parent, std::ostream& out);
|
||||
|
||||
void aggregatePerLevelCodelength(const InfoNode& parent, std::vector<detail::PerLevelStat>& perLevelStat, unsigned int level = 0);
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct PerLevelStat {
|
||||
double codelength() const { return indexLength + leafLength; }
|
||||
|
||||
unsigned int numNodes() const { return numModules + numLeafNodes; }
|
||||
|
||||
unsigned int numModules = 0;
|
||||
unsigned int numLeafNodes = 0;
|
||||
double indexLength = 0.0;
|
||||
double leafLength = 0.0;
|
||||
};
|
||||
|
||||
class PartitionQueue {
|
||||
using PendingModule = InfoNode*;
|
||||
|
||||
std::deque<PendingModule> m_queue;
|
||||
|
||||
public:
|
||||
unsigned int level = 1;
|
||||
unsigned int numNonTrivialModules = 0;
|
||||
double flow = 0.0;
|
||||
double nonTrivialFlow = 0.0;
|
||||
bool skip = false;
|
||||
double indexCodelength = 0.0; // Consolidated
|
||||
double leafCodelength = 0.0; // Consolidated
|
||||
double moduleCodelength = 0.0; // Left to improve on next level
|
||||
|
||||
using size_t = std::deque<PendingModule>::size_type;
|
||||
|
||||
void swap(PartitionQueue& other) noexcept
|
||||
{
|
||||
std::swap(level, other.level);
|
||||
std::swap(numNonTrivialModules, other.numNonTrivialModules);
|
||||
std::swap(flow, other.flow);
|
||||
std::swap(nonTrivialFlow, other.nonTrivialFlow);
|
||||
std::swap(skip, other.skip);
|
||||
std::swap(indexCodelength, other.indexCodelength);
|
||||
std::swap(leafCodelength, other.leafCodelength);
|
||||
std::swap(moduleCodelength, other.moduleCodelength);
|
||||
m_queue.swap(other.m_queue);
|
||||
}
|
||||
|
||||
size_t size() const { return m_queue.size(); }
|
||||
|
||||
void resize(size_t size) { m_queue.resize(size); }
|
||||
|
||||
PendingModule& operator[](size_t i) { return m_queue[i]; }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // INFOMAP_BASE_H_
|
||||
@@ -0,0 +1,137 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOMAP_CONFIG_H_
|
||||
#define INFOMAP_CONFIG_H_
|
||||
|
||||
#include "../io/Config.h"
|
||||
#include "../utils/Random.h"
|
||||
#include "../utils/Log.h"
|
||||
#include <string>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
template <typename Infomap>
|
||||
class InfomapConfig : public Config {
|
||||
public:
|
||||
InfomapConfig() = default;
|
||||
|
||||
InfomapConfig(const std::string& flags, bool isCli = false) : InfomapConfig(Config(flags, isCli)) { }
|
||||
|
||||
InfomapConfig(const Config& conf) : Config(conf), m_rand(/* conf.seedToRandomNumberGenerator */)
|
||||
{
|
||||
Log::precision(conf.verboseNumberPrecision);
|
||||
}
|
||||
|
||||
virtual ~InfomapConfig() = default;
|
||||
|
||||
InfomapConfig(const InfomapConfig&) = default;
|
||||
InfomapConfig& operator=(const InfomapConfig&) = default;
|
||||
InfomapConfig(InfomapConfig&&) noexcept = default;
|
||||
InfomapConfig& operator=(InfomapConfig&&) noexcept = default;
|
||||
|
||||
private:
|
||||
Infomap& get()
|
||||
{
|
||||
return static_cast<Infomap&>(*this);
|
||||
}
|
||||
|
||||
protected:
|
||||
Random m_rand;
|
||||
|
||||
public:
|
||||
Config& getConfig()
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const Config& getConfig() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
Infomap& setConfig(const Config& conf)
|
||||
{
|
||||
*this = conf;
|
||||
/* m_rand.seed(conf.seedToRandomNumberGenerator); */
|
||||
Log::precision(conf.verboseNumberPrecision);
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setNonMainConfig(const Config& conf)
|
||||
{
|
||||
cloneAsNonMain(conf);
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setNumTrials(unsigned int N)
|
||||
{
|
||||
numTrials = N;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setVerbosity(unsigned int level)
|
||||
{
|
||||
verbosity = level;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setTwoLevel(bool value)
|
||||
{
|
||||
twoLevel = value;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setTuneIterationLimit(unsigned int value)
|
||||
{
|
||||
tuneIterationLimit = value;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setFastHierarchicalSolution(unsigned int level)
|
||||
{
|
||||
fastHierarchicalSolution = level;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setOnlySuperModules(bool value)
|
||||
{
|
||||
onlySuperModules = value;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setNoCoarseTune(bool value)
|
||||
{
|
||||
noCoarseTune = value;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setNoInfomap(bool value = true)
|
||||
{
|
||||
noInfomap = value;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setMarkovTime(double codeRate)
|
||||
{
|
||||
markovTime = codeRate;
|
||||
return get();
|
||||
}
|
||||
|
||||
Infomap& setDirected(bool value)
|
||||
{
|
||||
directed = value;
|
||||
flowModel = directed ? FlowModel::directed : FlowModel::undirected;
|
||||
return get();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // INFOMAP_CONFIG_H_
|
||||
@@ -0,0 +1,766 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOMAP_OPTIMIZER_H_
|
||||
#define INFOMAP_OPTIMIZER_H_
|
||||
|
||||
#include "InfomapOptimizerBase.h"
|
||||
#include "InfomapBase.h"
|
||||
#include "../utils/VectorMap.h"
|
||||
#include "../utils/infomath.h"
|
||||
#include "InfoNode.h"
|
||||
#include "FlowData.h"
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
template <typename Objective>
|
||||
class InfomapOptimizer : public InfomapOptimizerBase {
|
||||
using FlowDataType = FlowData;
|
||||
using DeltaFlowDataType = typename Objective::DeltaFlowDataType;
|
||||
|
||||
public:
|
||||
void init(InfomapBase* infomap) override
|
||||
{
|
||||
m_infomap = infomap;
|
||||
m_objective.init(infomap->getConfig());
|
||||
this->setInterruptionHandler(infomap->getConfig().interruptionHandler);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
std::ostream& toString(std::ostream& out) const override { return out << m_objective; }
|
||||
|
||||
// ===================================================
|
||||
// Getters
|
||||
// ===================================================
|
||||
|
||||
double getCodelength() const override { return m_objective.getCodelength(); }
|
||||
|
||||
double getIndexCodelength() const override { return m_objective.getIndexCodelength(); }
|
||||
|
||||
double getModuleCodelength() const override { return m_objective.getModuleCodelength(); }
|
||||
|
||||
double getMetaCodelength(bool unweighted = false) const override;
|
||||
|
||||
protected:
|
||||
unsigned int numActiveModules() const override { return m_infomap->activeNetwork().size() - m_emptyModules.size(); }
|
||||
|
||||
// ===================================================
|
||||
// Run: Init: *
|
||||
// ===================================================
|
||||
|
||||
// Init terms that is constant for the whole network
|
||||
void initTree() override;
|
||||
|
||||
void initNetwork() override;
|
||||
|
||||
void initSuperNetwork() override;
|
||||
|
||||
double calcCodelength(const InfoNode& parent) const override { return m_objective.calcCodelength(parent); }
|
||||
|
||||
// ===================================================
|
||||
// Run: Partition: *
|
||||
// ===================================================
|
||||
|
||||
void initPartition() override;
|
||||
|
||||
void moveActiveNodesToPredefinedModules(std::vector<unsigned int>& modules) override;
|
||||
|
||||
bool moveNodeToPredefinedModule(InfoNode& current, unsigned int module);
|
||||
|
||||
unsigned int optimizeActiveNetwork() override;
|
||||
|
||||
unsigned int tryMoveEachNodeIntoBestModule() override;
|
||||
|
||||
unsigned int tryMoveEachNodeIntoBestModuleInParallel() override;
|
||||
|
||||
void consolidateModules(bool replaceExistingModules = true) override;
|
||||
|
||||
bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false) override;
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug: *
|
||||
// ===================================================
|
||||
|
||||
void printDebug() override { m_objective.printDebug(); }
|
||||
#endif
|
||||
|
||||
// ===================================================
|
||||
// Protected members
|
||||
// ===================================================
|
||||
|
||||
InfomapBase* m_infomap = nullptr;
|
||||
Objective m_objective;
|
||||
Objective m_consolidatedObjective;
|
||||
std::vector<FlowDataType> m_moduleFlowData;
|
||||
std::vector<unsigned int> m_moduleMembers;
|
||||
std::vector<unsigned int> m_emptyModules;
|
||||
};
|
||||
|
||||
// ===================================================
|
||||
// Getters
|
||||
// ===================================================
|
||||
|
||||
template <>
|
||||
inline double InfomapOptimizer<MetaMapEquation>::getMetaCodelength(bool unweighted) const
|
||||
{
|
||||
return m_objective.getMetaCodelength(unweighted);
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
inline double InfomapOptimizer<Objective>::getMetaCodelength(bool /*unweighted*/) const
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Run: Init: *
|
||||
// ===================================================
|
||||
|
||||
template <typename Objective>
|
||||
inline void InfomapOptimizer<Objective>::initTree()
|
||||
{
|
||||
Log(4) << "InfomapOptimizer::initTree()...\n";
|
||||
m_objective.initTree(m_infomap->root());
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
inline void InfomapOptimizer<Objective>::initNetwork()
|
||||
{
|
||||
Log(4) << "InfomapOptimizer::initNetwork()...\n";
|
||||
m_objective.initNetwork(m_infomap->root());
|
||||
|
||||
if (!m_infomap->isMainInfomap())
|
||||
m_objective.initSubNetwork(m_infomap->root()); // TODO: Already called in initNetwork?
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
inline void InfomapOptimizer<Objective>::initSuperNetwork()
|
||||
{
|
||||
Log(4) << "InfomapOptimizer::initSuperNetwork()...\n";
|
||||
m_objective.initSuperNetwork(m_infomap->root());
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Run: Partition: *
|
||||
// ===================================================
|
||||
|
||||
template <typename Objective>
|
||||
void InfomapOptimizer<Objective>::initPartition()
|
||||
{
|
||||
auto& network = m_infomap->activeNetwork();
|
||||
Log(4) << "InfomapOptimizer::initPartition() with " << network.size() << " nodes...\n";
|
||||
|
||||
// Init one module for each node
|
||||
auto numNodes = network.size();
|
||||
m_moduleFlowData.resize(numNodes);
|
||||
m_moduleMembers.assign(numNodes, 1);
|
||||
m_emptyModules.clear();
|
||||
m_emptyModules.reserve(numNodes);
|
||||
|
||||
unsigned int i = 0;
|
||||
for (auto& nodePtr : network) {
|
||||
InfoNode& node = *nodePtr;
|
||||
node.index = i; // Unique module index for each node
|
||||
m_moduleFlowData[i] = node.data;
|
||||
node.dirty = true;
|
||||
++i;
|
||||
}
|
||||
|
||||
m_objective.initPartition(network);
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
void InfomapOptimizer<Objective>::moveActiveNodesToPredefinedModules(std::vector<unsigned int>& modules)
|
||||
{
|
||||
auto& network = m_infomap->activeNetwork();
|
||||
auto numNodes = network.size();
|
||||
if (modules.size() != numNodes)
|
||||
throw std::length_error("Size of predefined modules differ from size of active network.");
|
||||
|
||||
for (unsigned int i = 0; i < numNodes; ++i) {
|
||||
moveNodeToPredefinedModule(*network[i], modules[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
bool InfomapOptimizer<Objective>::moveNodeToPredefinedModule(InfoNode& current, unsigned int newModule)
|
||||
{
|
||||
unsigned int oldM = current.index;
|
||||
unsigned int newM = newModule;
|
||||
|
||||
if (newM == oldM) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeltaFlowDataType oldModuleDelta(oldM, 0.0, 0.0);
|
||||
DeltaFlowDataType newModuleDelta(newM, 0.0, 0.0);
|
||||
|
||||
// For all outlinks
|
||||
for (auto& e : current.outEdges()) {
|
||||
auto& edge = *e;
|
||||
unsigned int otherModule = edge.target->index;
|
||||
if (otherModule == oldM) {
|
||||
oldModuleDelta.deltaExit += edge.data.flow;
|
||||
} else if (otherModule == newM) {
|
||||
newModuleDelta.deltaExit += edge.data.flow;
|
||||
}
|
||||
}
|
||||
// For all inlinks
|
||||
for (auto& e : current.inEdges()) {
|
||||
auto& edge = *e;
|
||||
unsigned int otherModule = edge.source->index;
|
||||
if (otherModule == oldM) {
|
||||
oldModuleDelta.deltaEnter += edge.data.flow;
|
||||
} else if (otherModule == newM) {
|
||||
newModuleDelta.deltaEnter += edge.data.flow;
|
||||
}
|
||||
}
|
||||
|
||||
// For recorded teleportation
|
||||
if (m_infomap->recordedTeleportation) {
|
||||
auto& oldModuleFlowData = m_moduleFlowData[oldM];
|
||||
double deltaEnterOld = (oldModuleFlowData.teleportFlow - current.data.teleportFlow) * current.data.teleportWeight;
|
||||
double deltaExitOld = current.data.teleportFlow * (oldModuleFlowData.teleportWeight - current.data.teleportWeight);
|
||||
oldModuleDelta.deltaEnter += deltaEnterOld;
|
||||
oldModuleDelta.deltaExit += deltaExitOld;
|
||||
|
||||
auto& newModuleFlowData = m_moduleFlowData[newM];
|
||||
double deltaEnterNew = current.data.teleportFlow * newModuleFlowData.teleportWeight;
|
||||
double deltaExitNew = newModuleFlowData.teleportFlow * current.data.teleportWeight;
|
||||
newModuleDelta.deltaEnter += deltaEnterNew;
|
||||
newModuleDelta.deltaExit += deltaExitNew;
|
||||
}
|
||||
// Update empty module vector
|
||||
if (m_moduleMembers[newM] == 0) {
|
||||
m_emptyModules.pop_back();
|
||||
}
|
||||
if (m_moduleMembers[current.index] == 1) {
|
||||
m_emptyModules.push_back(oldM);
|
||||
}
|
||||
|
||||
m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, m_moduleFlowData, m_moduleMembers);
|
||||
|
||||
m_moduleMembers[oldM] -= 1;
|
||||
m_moduleMembers[newM] += 1;
|
||||
|
||||
current.index = newM;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
inline unsigned int InfomapOptimizer<Objective>::optimizeActiveNetwork()
|
||||
{
|
||||
unsigned int coreLoopCount = 0;
|
||||
unsigned int numEffectiveLoops = 0;
|
||||
double oldCodelength = m_objective.getCodelength();
|
||||
unsigned int loopLimit = m_infomap->coreLoopLimit;
|
||||
unsigned int minRandLoop = 2;
|
||||
if (loopLimit >= minRandLoop && m_infomap->randomizeCoreLoopLimit)
|
||||
loopLimit = m_infomap->m_rand.randInt(minRandLoop, loopLimit);
|
||||
if (m_infomap->m_aggregationLevel > 0 || m_infomap->m_isCoarseTune) {
|
||||
loopLimit = 20;
|
||||
}
|
||||
|
||||
do {
|
||||
++coreLoopCount;
|
||||
unsigned int numNodesMoved = m_infomap->innerParallelization
|
||||
? tryMoveEachNodeIntoBestModuleInParallel()
|
||||
: tryMoveEachNodeIntoBestModule();
|
||||
// Break if not enough improvement
|
||||
if (numNodesMoved == 0 || m_objective.getCodelength() >= oldCodelength - m_infomap->minimumCodelengthImprovement)
|
||||
break;
|
||||
++numEffectiveLoops;
|
||||
oldCodelength = m_objective.getCodelength();
|
||||
} while (coreLoopCount != loopLimit);
|
||||
|
||||
return numEffectiveLoops;
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
unsigned int InfomapOptimizer<Objective>::tryMoveEachNodeIntoBestModule()
|
||||
{
|
||||
// Get random enumeration of nodes
|
||||
auto& network = m_infomap->activeNetwork();
|
||||
std::vector<unsigned int> nodeEnumeration(network.size());
|
||||
m_infomap->m_rand.getRandomizedIndexVector(nodeEnumeration);
|
||||
|
||||
auto numNodes = nodeEnumeration.size();
|
||||
unsigned int numMoved = 0;
|
||||
|
||||
// Create map with module links
|
||||
VectorMap<DeltaFlowDataType> deltaFlow(numNodes);
|
||||
|
||||
for (unsigned int i = 0; i < numNodes; ++i) {
|
||||
InfoNode& current = *network[nodeEnumeration[i]];
|
||||
|
||||
this->checkInterruption();
|
||||
|
||||
if (!current.dirty)
|
||||
continue;
|
||||
|
||||
// If other nodes have moved here, don't move away on first loop
|
||||
if (m_moduleMembers[current.index] > 1 && m_infomap->isFirstLoop() && m_infomap->tuneIterationLimit != 1)
|
||||
continue;
|
||||
|
||||
// If no links connecting this node with other nodes, it won't move into others,
|
||||
// and others won't move into this. TODO: Always best leave it alone?
|
||||
// For memory networks, don't skip try move to same physical node!
|
||||
|
||||
deltaFlow.startRound();
|
||||
|
||||
// For all outlinks
|
||||
for (auto& e : current.outEdges()) {
|
||||
auto& edge = *e;
|
||||
InfoNode* neighbour = edge.target;
|
||||
deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, edge.data.flow, 0.0));
|
||||
}
|
||||
// For all inlinks
|
||||
for (auto& e : current.inEdges()) {
|
||||
auto& edge = *e;
|
||||
InfoNode* neighbour = edge.source;
|
||||
deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, 0.0, edge.data.flow));
|
||||
}
|
||||
|
||||
// For not moving
|
||||
deltaFlow.add(current.index, DeltaFlowDataType(current.index, 0.0, 0.0));
|
||||
DeltaFlowDataType& oldModuleDelta = deltaFlow[current.index];
|
||||
oldModuleDelta.module = current.index; // Make sure index is correct if created new
|
||||
|
||||
// Option to move to empty module (if node not already alone)
|
||||
if (m_moduleMembers[current.index] > 1 && !m_emptyModules.empty()) {
|
||||
deltaFlow.add(m_emptyModules.back(), DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0));
|
||||
}
|
||||
|
||||
// For memory networks
|
||||
m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow);
|
||||
|
||||
auto& moduleDeltaEnterExit = deltaFlow.values();
|
||||
unsigned int numModuleLinks = deltaFlow.size();
|
||||
|
||||
// For recorded teleportation
|
||||
if (m_infomap->recordedTeleportation) {
|
||||
for (unsigned int j = 0; j < numModuleLinks; ++j) {
|
||||
auto& deltaEnterExit = moduleDeltaEnterExit[j];
|
||||
auto moduleIndex = deltaEnterExit.module;
|
||||
if (moduleIndex == current.index) {
|
||||
auto& oldModuleFlowData = m_moduleFlowData[moduleIndex];
|
||||
double deltaEnterOld = (oldModuleFlowData.teleportFlow - current.data.teleportFlow) * current.data.teleportWeight;
|
||||
double deltaExitOld = current.data.teleportFlow * (oldModuleFlowData.teleportWeight - current.data.teleportWeight);
|
||||
deltaFlow.add(moduleIndex, DeltaFlowDataType(moduleIndex, deltaExitOld, deltaEnterOld));
|
||||
} else {
|
||||
auto& newModuleFlowData = m_moduleFlowData[moduleIndex];
|
||||
double deltaEnterNew = newModuleFlowData.teleportFlow * current.data.teleportWeight;
|
||||
double deltaExitNew = current.data.teleportFlow * newModuleFlowData.teleportWeight;
|
||||
deltaFlow.add(moduleIndex, DeltaFlowDataType(moduleIndex, deltaExitNew, deltaEnterNew));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Randomize link order for optimized search
|
||||
std::vector<unsigned int> moduleEnumeration(numModuleLinks);
|
||||
m_infomap->m_rand.getRandomizedIndexVector(moduleEnumeration);
|
||||
|
||||
DeltaFlowDataType bestDeltaModule(oldModuleDelta);
|
||||
double bestDeltaCodelength = 0.0;
|
||||
DeltaFlowDataType strongestConnectedModule(oldModuleDelta);
|
||||
double deltaCodelengthOnStrongestConnectedModule = 0.0;
|
||||
|
||||
// Find the move that minimizes the description length
|
||||
for (unsigned int k = 0; k < numModuleLinks; ++k) {
|
||||
auto j = moduleEnumeration[k];
|
||||
unsigned int otherModule = moduleDeltaEnterExit[j].module;
|
||||
if (otherModule != current.index) {
|
||||
double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current,
|
||||
oldModuleDelta,
|
||||
moduleDeltaEnterExit[j],
|
||||
m_moduleFlowData,
|
||||
m_moduleMembers);
|
||||
|
||||
if (deltaCodelength < bestDeltaCodelength - m_infomap->minimumSingleNodeCodelengthImprovement) {
|
||||
bestDeltaModule = moduleDeltaEnterExit[j];
|
||||
bestDeltaCodelength = deltaCodelength;
|
||||
}
|
||||
|
||||
// Save strongest connected module to prefer if codelength improvement equal
|
||||
if (moduleDeltaEnterExit[j].deltaExit > strongestConnectedModule.deltaExit) {
|
||||
strongestConnectedModule = moduleDeltaEnterExit[j];
|
||||
deltaCodelengthOnStrongestConnectedModule = deltaCodelength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer strongest connected module if equal delta codelength
|
||||
if (strongestConnectedModule.module != bestDeltaModule.module && deltaCodelengthOnStrongestConnectedModule <= bestDeltaCodelength + m_infomap->minimumSingleNodeCodelengthImprovement) {
|
||||
bestDeltaModule = strongestConnectedModule;
|
||||
}
|
||||
|
||||
// Make best possible move
|
||||
if (bestDeltaModule.module != current.index) {
|
||||
unsigned int bestModuleIndex = bestDeltaModule.module;
|
||||
// Update empty module vector
|
||||
if (m_moduleMembers[bestModuleIndex] == 0) {
|
||||
m_emptyModules.pop_back();
|
||||
}
|
||||
if (m_moduleMembers[current.index] == 1) {
|
||||
m_emptyModules.push_back(current.index);
|
||||
}
|
||||
|
||||
m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, bestDeltaModule, m_moduleFlowData, m_moduleMembers);
|
||||
|
||||
m_moduleMembers[current.index] -= 1;
|
||||
m_moduleMembers[bestModuleIndex] += 1;
|
||||
|
||||
unsigned int oldModuleIndex = current.index;
|
||||
current.index = bestModuleIndex;
|
||||
|
||||
++numMoved;
|
||||
|
||||
InfoNode* nodeInOldModule = ¤t;
|
||||
unsigned int numLinkedNodesInOldModule = 0;
|
||||
// Mark neighbours as dirty
|
||||
for (auto& e : current.outEdges()) {
|
||||
e->target->dirty = true;
|
||||
if (e->target->index == oldModuleIndex) {
|
||||
nodeInOldModule = e->target;
|
||||
++numLinkedNodesInOldModule;
|
||||
}
|
||||
}
|
||||
for (auto& e : current.inEdges()) {
|
||||
e->source->dirty = true;
|
||||
if (e->source->index == oldModuleIndex) {
|
||||
nodeInOldModule = e->source;
|
||||
++numLinkedNodesInOldModule;
|
||||
}
|
||||
}
|
||||
|
||||
// Move single connected nodes to same module
|
||||
if (numLinkedNodesInOldModule == 1 && m_moduleMembers[oldModuleIndex] == 1) {
|
||||
moveNodeToPredefinedModule(*nodeInOldModule, bestModuleIndex);
|
||||
++numMoved;
|
||||
// Mark neighbours as dirty
|
||||
if (nodeInOldModule->degree() > 1) {
|
||||
for (auto& e : nodeInOldModule->outEdges())
|
||||
e->target->dirty = true;
|
||||
for (auto& e : nodeInOldModule->inEdges())
|
||||
e->source->dirty = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
return numMoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimize the codelength by trying to move each node into best module, in parallel.
|
||||
*
|
||||
* For each node:
|
||||
* 1. Calculate the change in codelength for a move to each of its neighbouring modules or to an empty module
|
||||
* 2. Move to the one that reduces the codelength the most, if any.
|
||||
*
|
||||
* @return The number of nodes moved.
|
||||
*/
|
||||
template <typename Objective>
|
||||
unsigned int InfomapOptimizer<Objective>::tryMoveEachNodeIntoBestModuleInParallel()
|
||||
{
|
||||
// Get random enumeration of nodes
|
||||
auto& network = m_infomap->activeNetwork();
|
||||
std::vector<unsigned int> nodeEnumeration(network.size());
|
||||
m_infomap->m_rand.getRandomizedIndexVector(nodeEnumeration);
|
||||
|
||||
auto numNodes = nodeEnumeration.size();
|
||||
unsigned int numMoved = 0;
|
||||
unsigned int numInvalidMoves = 0;
|
||||
|
||||
#pragma omp parallel for schedule(dynamic) // Use dynamic scheduling as some threads could end early
|
||||
for (unsigned int i = 0; i < numNodes; ++i) {
|
||||
// Pick nodes in random order
|
||||
InfoNode& current = *network[nodeEnumeration[i]];
|
||||
|
||||
if (!current.dirty)
|
||||
continue;
|
||||
|
||||
// If other nodes have moved here, don't move away on first loop
|
||||
if (m_moduleMembers[current.index] > 1 && m_infomap->isFirstLoop() && m_infomap->tuneIterationLimit != 1)
|
||||
continue;
|
||||
|
||||
// If no links connecting this node with other nodes, it won't move into others,
|
||||
// and others won't move into this. TODO: Always best leave it alone?
|
||||
// For memory networks, don't skip try move to same physical node!
|
||||
|
||||
// Create map with module links
|
||||
VectorMap<DeltaFlowDataType> deltaFlow(numNodes);
|
||||
|
||||
// For all outlinks
|
||||
for (auto& e : current.outEdges()) {
|
||||
auto& edge = *e;
|
||||
InfoNode* neighbour = edge.target;
|
||||
deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, edge.data.flow, 0.0));
|
||||
}
|
||||
// For all inlinks
|
||||
for (auto& e : current.inEdges()) {
|
||||
auto& edge = *e;
|
||||
InfoNode* neighbour = edge.source;
|
||||
deltaFlow.add(neighbour->index, DeltaFlowDataType(neighbour->index, 0.0, edge.data.flow));
|
||||
}
|
||||
|
||||
// For not moving
|
||||
deltaFlow.add(current.index, DeltaFlowDataType(current.index, 0.0, 0.0));
|
||||
DeltaFlowDataType& oldModuleDelta = deltaFlow[current.index];
|
||||
oldModuleDelta.module = current.index; // Make sure index is correct if created new
|
||||
|
||||
// Option to move to empty module (if node not already alone)
|
||||
if (m_moduleMembers[current.index] > 1 && !m_emptyModules.empty()) {
|
||||
// deltaFlow[m_emptyModules.back()] += DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0);
|
||||
deltaFlow.add(m_emptyModules.back(), DeltaFlowDataType(m_emptyModules.back(), 0.0, 0.0));
|
||||
}
|
||||
|
||||
// For memory networks
|
||||
m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow);
|
||||
|
||||
auto& moduleDeltaEnterExit = deltaFlow.values();
|
||||
unsigned int numModuleLinks = deltaFlow.size();
|
||||
|
||||
// Randomize link order for optimized search
|
||||
if (numModuleLinks > 2) {
|
||||
for (unsigned int j = 0; j < numModuleLinks - 2; ++j) {
|
||||
unsigned int randPos = m_infomap->m_rand.randInt(j + 1, numModuleLinks - 1);
|
||||
swap(moduleDeltaEnterExit[j], moduleDeltaEnterExit[randPos]);
|
||||
}
|
||||
}
|
||||
|
||||
DeltaFlowDataType bestDeltaModule(oldModuleDelta);
|
||||
double bestDeltaCodelength = 0.0;
|
||||
DeltaFlowDataType strongestConnectedModule(oldModuleDelta);
|
||||
double deltaCodelengthOnStrongestConnectedModule = 0.0;
|
||||
|
||||
// Find the move that minimizes the description length
|
||||
for (unsigned int j = 0; j < deltaFlow.size(); ++j) {
|
||||
unsigned int otherModule = moduleDeltaEnterExit[j].module;
|
||||
if (otherModule != current.index) {
|
||||
double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current,
|
||||
oldModuleDelta,
|
||||
moduleDeltaEnterExit[j],
|
||||
m_moduleFlowData,
|
||||
m_moduleMembers);
|
||||
|
||||
if (deltaCodelength < bestDeltaCodelength - m_infomap->minimumSingleNodeCodelengthImprovement) {
|
||||
bestDeltaModule = moduleDeltaEnterExit[j];
|
||||
bestDeltaCodelength = deltaCodelength;
|
||||
}
|
||||
|
||||
// Save strongest connected module to prefer if codelength improvement equal
|
||||
if (moduleDeltaEnterExit[j].deltaExit > strongestConnectedModule.deltaExit) {
|
||||
strongestConnectedModule = moduleDeltaEnterExit[j];
|
||||
deltaCodelengthOnStrongestConnectedModule = deltaCodelength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer strongest connected module if equal delta codelength
|
||||
if (strongestConnectedModule.module != bestDeltaModule.module && deltaCodelengthOnStrongestConnectedModule <= bestDeltaCodelength + m_infomap->minimumSingleNodeCodelengthImprovement) {
|
||||
bestDeltaModule = strongestConnectedModule;
|
||||
}
|
||||
|
||||
// Make best possible move
|
||||
if (bestDeltaModule.module == current.index) {
|
||||
current.dirty = false;
|
||||
continue;
|
||||
} else {
|
||||
#pragma omp critical(moveUpdate)
|
||||
{
|
||||
unsigned int bestModuleIndex = bestDeltaModule.module;
|
||||
unsigned int oldModuleIndex = current.index;
|
||||
|
||||
bool validMove = bestModuleIndex == m_emptyModules.back()
|
||||
// Check validity of move to empty target
|
||||
? m_moduleMembers[oldModuleIndex] > 1 && !m_emptyModules.empty()
|
||||
// Not valid if the best module is empty now but not when decided
|
||||
: m_moduleMembers[bestModuleIndex] > 0;
|
||||
|
||||
if (validMove) {
|
||||
// Recalculate delta codelength for proposed move to see if still an improvement
|
||||
oldModuleDelta = DeltaFlowDataType(oldModuleIndex, 0.0, 0.0);
|
||||
DeltaFlowDataType newModuleDelta(bestModuleIndex, 0.0, 0.0);
|
||||
|
||||
// For all outlinks
|
||||
for (auto& e : current.outEdges()) {
|
||||
auto& edge = *e;
|
||||
unsigned int otherModule = edge.target->index;
|
||||
if (otherModule == oldModuleIndex)
|
||||
oldModuleDelta.deltaExit += edge.data.flow;
|
||||
else if (otherModule == bestModuleIndex)
|
||||
newModuleDelta.deltaExit += edge.data.flow;
|
||||
}
|
||||
// For all inlinks
|
||||
for (auto& e : current.inEdges()) {
|
||||
auto& edge = *e;
|
||||
unsigned int otherModule = edge.source->index;
|
||||
if (otherModule == oldModuleIndex)
|
||||
oldModuleDelta.deltaEnter += edge.data.flow;
|
||||
else if (otherModule == bestModuleIndex)
|
||||
newModuleDelta.deltaEnter += edge.data.flow;
|
||||
}
|
||||
|
||||
// For memory networks
|
||||
m_objective.addMemoryContributions(current, oldModuleDelta, deltaFlow);
|
||||
|
||||
double deltaCodelength = m_objective.getDeltaCodelengthOnMovingNode(current,
|
||||
oldModuleDelta,
|
||||
newModuleDelta,
|
||||
m_moduleFlowData,
|
||||
m_moduleMembers);
|
||||
|
||||
if (deltaCodelength < 0.0 - m_infomap->minimumSingleNodeCodelengthImprovement) {
|
||||
// Update empty module vector
|
||||
if (m_moduleMembers[bestModuleIndex] == 0) {
|
||||
m_emptyModules.pop_back();
|
||||
}
|
||||
if (m_moduleMembers[oldModuleIndex] == 1) {
|
||||
m_emptyModules.push_back(oldModuleIndex);
|
||||
}
|
||||
|
||||
m_objective.updateCodelengthOnMovingNode(current, oldModuleDelta, bestDeltaModule, m_moduleFlowData, m_moduleMembers);
|
||||
|
||||
m_moduleMembers[oldModuleIndex] -= 1;
|
||||
m_moduleMembers[bestModuleIndex] += 1;
|
||||
|
||||
current.index = bestModuleIndex;
|
||||
|
||||
++numMoved;
|
||||
|
||||
// Mark neighbours as dirty
|
||||
for (auto& e : current.outEdges())
|
||||
e->target->dirty = true;
|
||||
for (auto& e : current.inEdges())
|
||||
e->source->dirty = true;
|
||||
} else {
|
||||
++numInvalidMoves;
|
||||
}
|
||||
} else {
|
||||
++numInvalidMoves;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return numMoved + numInvalidMoves;
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
inline void InfomapOptimizer<Objective>::consolidateModules(bool replaceExistingModules)
|
||||
{
|
||||
auto& network = m_infomap->activeNetwork();
|
||||
auto numNodes = network.size();
|
||||
std::vector<InfoNode*> modules(numNodes, nullptr);
|
||||
|
||||
InfoNode& firstActiveNode = *network[0];
|
||||
auto level = firstActiveNode.depth();
|
||||
auto leafLevel = m_infomap->numLevels();
|
||||
|
||||
if (leafLevel == 1)
|
||||
replaceExistingModules = false;
|
||||
|
||||
// Release children pointers on current parent(s) to put new modules between
|
||||
for (auto& n : network) {
|
||||
n->parent->releaseChildren(); // Safe to call multiple times
|
||||
}
|
||||
|
||||
// Create the new module nodes and re-parent the active network from its common parent to the new module level
|
||||
for (unsigned int i = 0; i < numNodes; ++i) {
|
||||
InfoNode* node = network[i];
|
||||
unsigned int moduleIndex = node->index;
|
||||
if (modules[moduleIndex] == nullptr) {
|
||||
modules[moduleIndex] = new InfoNode(m_moduleFlowData[moduleIndex]);
|
||||
modules[moduleIndex]->index = moduleIndex;
|
||||
node->parent->addChild(modules[moduleIndex]);
|
||||
}
|
||||
modules[moduleIndex]->addChild(node);
|
||||
}
|
||||
|
||||
using NodePair = std::pair<unsigned int, unsigned int>;
|
||||
using EdgeMap = std::map<NodePair, double>;
|
||||
EdgeMap moduleLinks;
|
||||
|
||||
for (auto& node : network) {
|
||||
unsigned int module1 = node->index;
|
||||
for (auto& e : node->outEdges()) {
|
||||
InfoEdge& edge = *e;
|
||||
unsigned int module2 = edge.target->index;
|
||||
if (module1 != module2) {
|
||||
// Use new variables to not swap module1
|
||||
unsigned int m1 = module1, m2 = module2;
|
||||
// If undirected, the order may be swapped to aggregate the edge on an opposite one
|
||||
if (m_infomap->isUndirectedClustering() && m1 > m2)
|
||||
std::swap(m1, m2);
|
||||
auto ret = moduleLinks.insert(std::make_pair(NodePair(m1, m2), edge.data.flow));
|
||||
if (!ret.second) {
|
||||
ret.first->second += edge.data.flow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the aggregated edge flow structure to the new modules
|
||||
for (auto& e : moduleLinks) {
|
||||
const auto& nodePair = e.first;
|
||||
modules[nodePair.first]->addOutEdge(*modules[nodePair.second], 0.0, e.second);
|
||||
}
|
||||
|
||||
if (replaceExistingModules) {
|
||||
if (level == 1) {
|
||||
Log(4) << "Consolidated super modules, removing old modules...\n";
|
||||
for (auto& node : network)
|
||||
node->replaceWithChildren();
|
||||
} else if (level == 2) {
|
||||
Log(4) << "Consolidated sub-modules, removing modules...\n";
|
||||
unsigned int moduleIndex = 0;
|
||||
for (InfoNode& module : m_infomap->root()) {
|
||||
// Store current modular structure on the sub-modules
|
||||
for (auto& subModule : module)
|
||||
subModule.index = moduleIndex;
|
||||
++moduleIndex;
|
||||
}
|
||||
m_infomap->root().replaceChildrenWithGrandChildren();
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the number of non-trivial modules
|
||||
m_infomap->m_numNonTrivialTopModules = 0;
|
||||
for (auto& module : m_infomap->root()) {
|
||||
if (module.childDegree() != 1)
|
||||
++m_infomap->m_numNonTrivialTopModules;
|
||||
}
|
||||
|
||||
m_objective.consolidateModules(modules);
|
||||
m_consolidatedObjective = m_objective;
|
||||
}
|
||||
|
||||
template <typename Objective>
|
||||
inline bool InfomapOptimizer<Objective>::restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore)
|
||||
{
|
||||
if (forceRestore || m_objective.getCodelength() >= m_consolidatedObjective.getCodelength() - m_infomap->minimumSingleNodeCodelengthImprovement) {
|
||||
m_objective = m_consolidatedObjective;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} /* namespace infomap */
|
||||
|
||||
#endif // INFOMAP_OPTIMIZER_H_
|
||||
@@ -0,0 +1,113 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOMAP_OPTIMIZER_BASE_H_
|
||||
#define INFOMAP_OPTIMIZER_BASE_H_
|
||||
|
||||
#include "InfomapBase.h"
|
||||
#include "InfoNode.h"
|
||||
#include "FlowData.h"
|
||||
#include <vector>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InfomapOptimizerBase {
|
||||
friend class InfomapBase;
|
||||
using FlowDataType = FlowData;
|
||||
|
||||
public:
|
||||
InfomapOptimizerBase() = default;
|
||||
|
||||
virtual ~InfomapOptimizerBase() = default;
|
||||
|
||||
virtual void init(InfomapBase* infomap) = 0;
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
virtual std::ostream& toString(std::ostream& out) const = 0;
|
||||
|
||||
// ===================================================
|
||||
// Getters
|
||||
// ===================================================
|
||||
|
||||
virtual double getCodelength() const = 0;
|
||||
|
||||
virtual double getIndexCodelength() const = 0;
|
||||
|
||||
virtual double getModuleCodelength() const = 0;
|
||||
|
||||
virtual double getMetaCodelength(bool /*unweighted*/ = false) const { return 0.0; }
|
||||
|
||||
void setInterruptionHandler(interruptionHandlerFn *interruptionHandler) {
|
||||
this->interruptionHandler = interruptionHandler;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual unsigned int numActiveModules() const = 0;
|
||||
|
||||
void checkInterruption() {
|
||||
if (interruptionHandler) {
|
||||
if (interruptionHandler()) {
|
||||
throw infomap::InterruptException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Run: Init: *
|
||||
// ===================================================
|
||||
|
||||
// Init terms that is constant for the whole network
|
||||
virtual void initTree() = 0;
|
||||
|
||||
virtual void initNetwork() = 0;
|
||||
|
||||
virtual void initSuperNetwork() = 0;
|
||||
|
||||
virtual double calcCodelength(const InfoNode& parent) const = 0;
|
||||
|
||||
// ===================================================
|
||||
// Run: Partition: *
|
||||
// ===================================================
|
||||
|
||||
virtual void initPartition() = 0;
|
||||
|
||||
virtual void moveActiveNodesToPredefinedModules(std::vector<unsigned int>& modules) = 0;
|
||||
|
||||
virtual unsigned int optimizeActiveNetwork() = 0;
|
||||
|
||||
virtual unsigned int tryMoveEachNodeIntoBestModule() = 0;
|
||||
|
||||
// virtual unsigned int tryMoveEachNodeIntoBestModuleLocal() = 0;
|
||||
|
||||
virtual unsigned int tryMoveEachNodeIntoBestModuleInParallel() = 0;
|
||||
|
||||
virtual void consolidateModules(bool replaceExistingModules = true) = 0;
|
||||
|
||||
virtual bool restoreConsolidatedOptimizationPointIfNoImprovement(bool forceRestore = false) = 0;
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug: *
|
||||
// ===================================================
|
||||
|
||||
virtual void printDebug() = 0;
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
interruptionHandlerFn *interruptionHandler = NULL;
|
||||
|
||||
};
|
||||
|
||||
} /* namespace infomap */
|
||||
|
||||
#endif // INFOMAP_OPTIMIZER_BASE_H_
|
||||
@@ -0,0 +1,339 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef MAPEQUATION_H_
|
||||
#define MAPEQUATION_H_
|
||||
|
||||
#include "../utils/infomath.h"
|
||||
#include "../utils/convert.h"
|
||||
#include "../io/Config.h"
|
||||
#include "../utils/Log.h"
|
||||
#include "../utils/VectorMap.h"
|
||||
#include "InfoNode.h"
|
||||
#include "FlowData.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InfoNode;
|
||||
|
||||
template <typename FlowDataType = FlowData, typename DeltaFlowDataType = DeltaFlow>
|
||||
class MapEquation {
|
||||
using ME = MapEquation<FlowDataType, DeltaFlowDataType>;
|
||||
|
||||
public:
|
||||
MapEquation() = default;
|
||||
|
||||
MapEquation(const MapEquation& other) = default;
|
||||
|
||||
MapEquation& operator=(const MapEquation& other) = default;
|
||||
|
||||
MapEquation(MapEquation&& other) noexcept = default;
|
||||
|
||||
MapEquation& operator=(MapEquation&& other) noexcept = default;
|
||||
|
||||
virtual ~MapEquation() = default;
|
||||
|
||||
// ===================================================
|
||||
// Getters
|
||||
// ===================================================
|
||||
|
||||
virtual double getIndexCodelength() const { return indexCodelength; }
|
||||
|
||||
virtual double getModuleCodelength() const { return moduleCodelength; }
|
||||
|
||||
virtual double getCodelength() const { return codelength; }
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
virtual std::ostream& print(std::ostream& out) const
|
||||
{
|
||||
return out << indexCodelength << " + " << moduleCodelength << " = " << io::toPrecision(codelength);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
virtual void init(const Config&)
|
||||
{
|
||||
Log(3) << "MapEquation::init()...\n";
|
||||
}
|
||||
|
||||
virtual void initTree(InfoNode& /*root*/) = 0;
|
||||
|
||||
virtual void initNetwork(InfoNode& root)
|
||||
{
|
||||
Log(3) << "MapEquation::initNetwork()...\n";
|
||||
|
||||
nodeFlow_log_nodeFlow = 0.0;
|
||||
for (InfoNode& node : root) {
|
||||
nodeFlow_log_nodeFlow += infomath::plogp(node.data.flow);
|
||||
}
|
||||
ME::initSubNetwork(root);
|
||||
}
|
||||
|
||||
virtual void initSuperNetwork(InfoNode& root)
|
||||
{
|
||||
Log(3) << "MapEquation::initSuperNetwork()...\n";
|
||||
|
||||
nodeFlow_log_nodeFlow = 0.0;
|
||||
for (InfoNode& node : root) {
|
||||
nodeFlow_log_nodeFlow += infomath::plogp(node.data.enterFlow);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void initSubNetwork(InfoNode& root)
|
||||
{
|
||||
exitNetworkFlow = root.data.exitFlow;
|
||||
exitNetworkFlow_log_exitNetworkFlow = infomath::plogp(exitNetworkFlow);
|
||||
}
|
||||
|
||||
virtual void initPartition(std::vector<InfoNode*>& nodes) { ME::calculateCodelength(nodes); }
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
virtual double calcCodelength(const InfoNode& parent) const
|
||||
{
|
||||
return parent.isLeafModule() ? ME::calcCodelengthOnModuleOfLeafNodes(parent) : ME::calcCodelengthOnModuleOfModules(parent);
|
||||
}
|
||||
|
||||
virtual void addMemoryContributions(InfoNode& /*current*/, DeltaFlowDataType& /*oldModuleDelta*/, DeltaFlowDataType& /*newModuleDelta*/) { }
|
||||
|
||||
virtual void addMemoryContributions(InfoNode& /*current*/, DeltaFlowDataType& /*oldModuleDelta*/, VectorMap<DeltaFlowDataType>& /*moduleDeltaFlow*/) { }
|
||||
|
||||
virtual double getDeltaCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlowDataType& oldModuleDelta,
|
||||
DeltaFlowDataType& newModuleDelta,
|
||||
std::vector<FlowDataType>& moduleFlowData,
|
||||
std::vector<unsigned int>& /*moduleMembers*/);
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
virtual void updateCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlowDataType& oldModuleDelta,
|
||||
DeltaFlowDataType& newModuleDelta,
|
||||
std::vector<FlowDataType>& moduleFlowData,
|
||||
std::vector<unsigned int>& /*moduleMembers*/);
|
||||
|
||||
virtual void consolidateModules(std::vector<InfoNode*>& /*modules*/) = 0;
|
||||
|
||||
// ===================================================
|
||||
// Debug
|
||||
// ===================================================
|
||||
|
||||
#if 0
|
||||
virtual void printDebug() const
|
||||
{
|
||||
std::cout << "(enterFlow_log_enterFlow: " << enterFlow_log_enterFlow << ", "
|
||||
<< "enter_log_enter: " << enter_log_enter << ", "
|
||||
<< "exitNetworkFlow_log_exitNetworkFlow: " << exitNetworkFlow_log_exitNetworkFlow << ") ";
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// ===================================================
|
||||
// Protected member functions
|
||||
// ===================================================
|
||||
|
||||
virtual double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const;
|
||||
|
||||
virtual double calcCodelengthOnModuleOfModules(const InfoNode& parent) const;
|
||||
|
||||
virtual void calculateCodelength(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
ME::calculateCodelengthTerms(nodes);
|
||||
ME::calculateCodelengthFromCodelengthTerms();
|
||||
}
|
||||
|
||||
virtual void calculateCodelengthTerms(std::vector<InfoNode*>& nodes);
|
||||
|
||||
virtual void calculateCodelengthFromCodelengthTerms()
|
||||
{
|
||||
indexCodelength = enterFlow_log_enterFlow - enter_log_enter - exitNetworkFlow_log_exitNetworkFlow;
|
||||
moduleCodelength = -exit_log_exit + flow_log_flow - nodeFlow_log_nodeFlow;
|
||||
codelength = indexCodelength + moduleCodelength;
|
||||
}
|
||||
|
||||
public:
|
||||
// ===================================================
|
||||
// Public member variables
|
||||
// ===================================================
|
||||
|
||||
double codelength = 0.0;
|
||||
double indexCodelength = 0.0;
|
||||
double moduleCodelength = 0.0;
|
||||
|
||||
protected:
|
||||
// ===================================================
|
||||
// Protected member variables
|
||||
// ===================================================
|
||||
|
||||
double nodeFlow_log_nodeFlow = 0.0; // constant while the leaf network is the same
|
||||
double flow_log_flow = 0.0; // node.(flow + exitFlow)
|
||||
double exit_log_exit = 0.0;
|
||||
double enter_log_enter = 0.0;
|
||||
double enterFlow = 0.0;
|
||||
double enterFlow_log_enterFlow = 0.0;
|
||||
|
||||
// For hierarchical
|
||||
double exitNetworkFlow = 0.0;
|
||||
double exitNetworkFlow_log_exitNetworkFlow = 0.0;
|
||||
};
|
||||
|
||||
template <typename FlowDataType, typename DeltaFlowDataType>
|
||||
double MapEquation<FlowDataType, DeltaFlowDataType>::getDeltaCodelengthOnMovingNode(InfoNode& current, DeltaFlowDataType& oldModuleDelta, DeltaFlowDataType& newModuleDelta, std::vector<FlowDataType>& moduleFlowData, std::vector<unsigned int>&)
|
||||
{
|
||||
using infomath::plogp;
|
||||
unsigned int oldModule = oldModuleDelta.module;
|
||||
unsigned int newModule = newModuleDelta.module;
|
||||
double deltaEnterExitOldModule = oldModuleDelta.deltaEnter + oldModuleDelta.deltaExit;
|
||||
double deltaEnterExitNewModule = newModuleDelta.deltaEnter + newModuleDelta.deltaExit;
|
||||
|
||||
double delta_enter = plogp(enterFlow + deltaEnterExitOldModule - deltaEnterExitNewModule) - enterFlow_log_enterFlow;
|
||||
|
||||
double delta_enter_log_enter = -plogp(moduleFlowData[oldModule].enterFlow)
|
||||
- plogp(moduleFlowData[newModule].enterFlow)
|
||||
+ plogp(moduleFlowData[oldModule].enterFlow - current.data.enterFlow + deltaEnterExitOldModule)
|
||||
+ plogp(moduleFlowData[newModule].enterFlow + current.data.enterFlow - deltaEnterExitNewModule);
|
||||
|
||||
double delta_exit_log_exit = -plogp(moduleFlowData[oldModule].exitFlow)
|
||||
- plogp(moduleFlowData[newModule].exitFlow)
|
||||
+ plogp(moduleFlowData[oldModule].exitFlow - current.data.exitFlow + deltaEnterExitOldModule)
|
||||
+ plogp(moduleFlowData[newModule].exitFlow + current.data.exitFlow - deltaEnterExitNewModule);
|
||||
|
||||
double delta_flow_log_flow = -plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow)
|
||||
- plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow)
|
||||
+ plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow
|
||||
- current.data.exitFlow - current.data.flow + deltaEnterExitOldModule)
|
||||
+ plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow
|
||||
+ current.data.exitFlow + current.data.flow - deltaEnterExitNewModule);
|
||||
|
||||
double deltaL = delta_enter - delta_enter_log_enter - delta_exit_log_exit + delta_flow_log_flow;
|
||||
return deltaL;
|
||||
}
|
||||
|
||||
template <typename FlowDataType, typename DeltaFlowDataType>
|
||||
void MapEquation<FlowDataType, DeltaFlowDataType>::updateCodelengthOnMovingNode(InfoNode& current, DeltaFlowDataType& oldModuleDelta, DeltaFlowDataType& newModuleDelta, std::vector<FlowDataType>& moduleFlowData, std::vector<unsigned int>&)
|
||||
{
|
||||
using infomath::plogp;
|
||||
unsigned int oldModule = oldModuleDelta.module;
|
||||
unsigned int newModule = newModuleDelta.module;
|
||||
double deltaEnterExitOldModule = oldModuleDelta.deltaEnter + oldModuleDelta.deltaExit;
|
||||
double deltaEnterExitNewModule = newModuleDelta.deltaEnter + newModuleDelta.deltaExit;
|
||||
|
||||
enterFlow -= moduleFlowData[oldModule].enterFlow + moduleFlowData[newModule].enterFlow;
|
||||
enter_log_enter -= plogp(moduleFlowData[oldModule].enterFlow) + plogp(moduleFlowData[newModule].enterFlow);
|
||||
exit_log_exit -= plogp(moduleFlowData[oldModule].exitFlow) + plogp(moduleFlowData[newModule].exitFlow);
|
||||
flow_log_flow -= plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow) + plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow);
|
||||
|
||||
moduleFlowData[oldModule] -= current.data;
|
||||
moduleFlowData[newModule] += current.data;
|
||||
|
||||
moduleFlowData[oldModule].enterFlow += deltaEnterExitOldModule;
|
||||
moduleFlowData[oldModule].exitFlow += deltaEnterExitOldModule;
|
||||
moduleFlowData[newModule].enterFlow -= deltaEnterExitNewModule;
|
||||
moduleFlowData[newModule].exitFlow -= deltaEnterExitNewModule;
|
||||
|
||||
enterFlow += moduleFlowData[oldModule].enterFlow + moduleFlowData[newModule].enterFlow;
|
||||
enter_log_enter += plogp(moduleFlowData[oldModule].enterFlow) + plogp(moduleFlowData[newModule].enterFlow);
|
||||
exit_log_exit += plogp(moduleFlowData[oldModule].exitFlow) + plogp(moduleFlowData[newModule].exitFlow);
|
||||
flow_log_flow += plogp(moduleFlowData[oldModule].exitFlow + moduleFlowData[oldModule].flow) + plogp(moduleFlowData[newModule].exitFlow + moduleFlowData[newModule].flow);
|
||||
|
||||
enterFlow_log_enterFlow = plogp(enterFlow);
|
||||
|
||||
indexCodelength = enterFlow_log_enterFlow - enter_log_enter - exitNetworkFlow_log_exitNetworkFlow;
|
||||
moduleCodelength = -exit_log_exit + flow_log_flow - nodeFlow_log_nodeFlow;
|
||||
codelength = indexCodelength + moduleCodelength;
|
||||
}
|
||||
|
||||
template <typename FlowDataType, typename DeltaFlowDataType>
|
||||
double MapEquation<FlowDataType, DeltaFlowDataType>::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const
|
||||
{
|
||||
double parentFlow = parent.data.flow;
|
||||
double parentExit = parent.data.exitFlow;
|
||||
double totalParentFlow = parentFlow + parentExit;
|
||||
if (totalParentFlow < 1e-16)
|
||||
return 0.0;
|
||||
|
||||
double indexLength = 0.0;
|
||||
for (const auto& node : parent) {
|
||||
indexLength -= infomath::plogp(node.data.flow / totalParentFlow);
|
||||
}
|
||||
indexLength -= infomath::plogp(parentExit / totalParentFlow);
|
||||
|
||||
indexLength *= totalParentFlow;
|
||||
|
||||
return indexLength;
|
||||
}
|
||||
|
||||
template <typename FlowDataType, typename DeltaFlowDataType>
|
||||
double MapEquation<FlowDataType, DeltaFlowDataType>::calcCodelengthOnModuleOfModules(const InfoNode& parent) const
|
||||
{
|
||||
double parentFlow = parent.data.flow;
|
||||
double parentExit = parent.data.exitFlow;
|
||||
if (parentFlow < 1e-16)
|
||||
return 0.0;
|
||||
|
||||
// H(x) = -xlog(x), T = q + SUM(p), q = exitFlow, p = enterFlow
|
||||
// Normal format
|
||||
// L = q * -log(q/T) + SUM(p * -log(p/T))
|
||||
// Compact format
|
||||
// L = T * ( H(q/T) + SUM( H(p/T) ) )
|
||||
// Expanded format
|
||||
// L = q * -log(q) - q * -log(T) + SUM( p * -log(p) - p * -log(T) )
|
||||
// = T * log(T) - q*log(q) - SUM( p*log(p) )
|
||||
// = -H(T) + H(q) + SUM(H(p))
|
||||
// As T is not known, use expanded format to avoid two loops
|
||||
double sumEnter = 0.0;
|
||||
double sumEnterLogEnter = 0.0;
|
||||
for (const auto& node : parent) {
|
||||
sumEnter += node.data.enterFlow; // rate of enter to finer level
|
||||
sumEnterLogEnter += infomath::plogp(node.data.enterFlow);
|
||||
}
|
||||
// The possibilities from this module: Either exit to coarser level or enter one of its children
|
||||
double totalCodewordUse = parentExit + sumEnter;
|
||||
|
||||
return infomath::plogp(totalCodewordUse) - sumEnterLogEnter - infomath::plogp(parentExit);
|
||||
}
|
||||
|
||||
template <typename FlowDataType, typename DeltaFlowDataType>
|
||||
void MapEquation<FlowDataType, DeltaFlowDataType>::calculateCodelengthTerms(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
enter_log_enter = 0.0;
|
||||
flow_log_flow = 0.0;
|
||||
exit_log_exit = 0.0;
|
||||
enterFlow = 0.0;
|
||||
|
||||
// For each module
|
||||
for (InfoNode* n : nodes) {
|
||||
InfoNode& node = *n;
|
||||
// own node/module codebook
|
||||
flow_log_flow += infomath::plogp(node.data.flow + node.data.exitFlow);
|
||||
|
||||
// use of index codebook
|
||||
enter_log_enter += infomath::plogp(node.data.enterFlow);
|
||||
exit_log_exit += infomath::plogp(node.data.exitFlow);
|
||||
enterFlow += node.data.enterFlow;
|
||||
}
|
||||
enterFlow += exitNetworkFlow;
|
||||
enterFlow_log_enterFlow = infomath::plogp(enterFlow);
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // MAPEQUATION_H_
|
||||
@@ -0,0 +1,451 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "MemMapEquation.h"
|
||||
#include "FlowData.h"
|
||||
#include "InfoNode.h"
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
std::ostream& MemMapEquation::print(std::ostream& out) const
|
||||
{
|
||||
return out << indexCodelength << " + " << moduleCodelength << " = " << io::toPrecision(codelength);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const MemMapEquation& mapEq)
|
||||
{
|
||||
return mapEq.print(out);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
void MemMapEquation::init(const Config& /*config*/)
|
||||
{
|
||||
Log(3) << "MemMapEquation::init()...\n";
|
||||
}
|
||||
|
||||
void MemMapEquation::initNetwork(InfoNode& root)
|
||||
{
|
||||
initPhysicalNodes(root);
|
||||
}
|
||||
|
||||
void MemMapEquation::initSuperNetwork(InfoNode& /*root*/)
|
||||
{
|
||||
// TODO: How use enterFlow instead of flow
|
||||
}
|
||||
|
||||
void MemMapEquation::initSubNetwork(InfoNode& /*root*/)
|
||||
{
|
||||
// Base::initSubNetwork(root);
|
||||
}
|
||||
|
||||
void MemMapEquation::initPartition(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
initPartitionOfPhysicalNodes(nodes);
|
||||
|
||||
calculateCodelength(nodes);
|
||||
}
|
||||
|
||||
void MemMapEquation::initPhysicalNodes(InfoNode& root)
|
||||
{
|
||||
bool notInitiatedOnRoot = root.physicalNodes.empty();
|
||||
if (notInitiatedOnRoot) {
|
||||
// Assume leaf nodes directly under the root node
|
||||
std::unordered_map<unsigned int, double> physicalNodes;
|
||||
unsigned int maxPhysicalId = 0;
|
||||
unsigned int minPhysicalId = std::numeric_limits<unsigned int>::max();
|
||||
for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
physicalNodes[node.physicalId] += node.data.flow;
|
||||
minPhysicalId = std::min(minPhysicalId, node.physicalId);
|
||||
maxPhysicalId = std::max(maxPhysicalId, node.physicalId);
|
||||
}
|
||||
|
||||
// Re-index physical nodes if necessary
|
||||
std::map<unsigned int, unsigned int> toZeroBasedIndex;
|
||||
if (maxPhysicalId - minPhysicalId + 1 > m_numPhysicalNodes) {
|
||||
unsigned int zeroBasedPhysicalId = 0;
|
||||
for (const auto& physNode : physicalNodes) {
|
||||
toZeroBasedIndex.insert(std::make_pair(physNode.first, zeroBasedPhysicalId++));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& physNode : physicalNodes) {
|
||||
unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[physNode.first] : (physNode.first - minPhysicalId);
|
||||
root.physicalNodes.emplace_back(zeroBasedIndex, physNode.second);
|
||||
}
|
||||
}
|
||||
auto firstLeafIt = root.begin_leaf_nodes();
|
||||
auto depth = firstLeafIt.depth();
|
||||
bool notInitiatedOnLeafNodes = firstLeafIt->physicalNodes.empty();
|
||||
if (notInitiatedOnLeafNodes) {
|
||||
Log(3) << "MemMapEquation::initPhysicalNodesOnOriginalNetwork()...\n";
|
||||
std::set<unsigned int> setOfPhysicalNodes;
|
||||
unsigned int maxPhysicalId = 0;
|
||||
unsigned int minPhysicalId = std::numeric_limits<unsigned int>::max();
|
||||
for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
setOfPhysicalNodes.insert(node.physicalId);
|
||||
maxPhysicalId = std::max(maxPhysicalId, node.physicalId);
|
||||
minPhysicalId = std::min(minPhysicalId, node.physicalId);
|
||||
}
|
||||
|
||||
m_numPhysicalNodes = setOfPhysicalNodes.size();
|
||||
|
||||
// Re-index physical nodes if necessary
|
||||
std::map<unsigned int, unsigned int> toZeroBasedIndex;
|
||||
if (maxPhysicalId - minPhysicalId + 1 > m_numPhysicalNodes) {
|
||||
unsigned int zeroBasedPhysicalId = 0;
|
||||
for (unsigned int physIndex : setOfPhysicalNodes) {
|
||||
toZeroBasedIndex.insert(std::make_pair(physIndex, zeroBasedPhysicalId++));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it(root.begin_leaf_nodes()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[node.physicalId] : (node.physicalId - minPhysicalId);
|
||||
node.physicalNodes.emplace_back(zeroBasedIndex, node.data.flow);
|
||||
}
|
||||
|
||||
// If leaf nodes was not directly under root, make sure leaf modules have
|
||||
// physical nodes defined also
|
||||
if (depth > 1) {
|
||||
for (auto it(root.begin_leaf_modules()); !it.isEnd(); ++it) {
|
||||
InfoNode& module = *it;
|
||||
std::map<unsigned int, double> physToFlow;
|
||||
for (auto& node : module) {
|
||||
for (PhysData& physData : node.physicalNodes) {
|
||||
physToFlow[physData.physNodeIndex] += physData.sumFlowFromM2Node;
|
||||
}
|
||||
}
|
||||
for (auto& physFlow : physToFlow) {
|
||||
module.physicalNodes.emplace_back(physFlow.first, physFlow.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Either a sub-network (without modules) or the whole network with reconstructed tree
|
||||
if (depth == 1) {
|
||||
// new sub-network
|
||||
Log(3) << "MemMapEquation::initPhysicalNodesOnSubNetwork()...\n";
|
||||
std::set<unsigned int> setOfPhysicalNodes;
|
||||
unsigned int maxPhysNodeIndex = 0;
|
||||
unsigned int minPhysNodeIndex = std::numeric_limits<unsigned int>::max();
|
||||
|
||||
// Collect all physical nodes in this sub network
|
||||
for (InfoNode& node : root) {
|
||||
for (PhysData& physData : node.physicalNodes) {
|
||||
setOfPhysicalNodes.insert(physData.physNodeIndex);
|
||||
maxPhysNodeIndex = std::max(maxPhysNodeIndex, physData.physNodeIndex);
|
||||
minPhysNodeIndex = std::min(minPhysNodeIndex, physData.physNodeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
m_numPhysicalNodes = setOfPhysicalNodes.size();
|
||||
|
||||
// Re-index physical nodes if needed (not required when reconstructing tree)
|
||||
if (maxPhysNodeIndex >= m_numPhysicalNodes) {
|
||||
std::map<unsigned int, unsigned int> toZeroBasedIndex;
|
||||
if (maxPhysNodeIndex - minPhysNodeIndex + 1 > m_numPhysicalNodes) {
|
||||
unsigned int zeroBasedPhysicalId = 0;
|
||||
for (unsigned int physIndex : setOfPhysicalNodes) {
|
||||
toZeroBasedIndex.insert(std::make_pair(physIndex, zeroBasedPhysicalId++));
|
||||
}
|
||||
}
|
||||
|
||||
for (InfoNode& node : root) {
|
||||
for (PhysData& physData : node.physicalNodes) {
|
||||
unsigned int zeroBasedIndex = !toZeroBasedIndex.empty() ? toZeroBasedIndex[physData.physNodeIndex] : (physData.physNodeIndex - minPhysNodeIndex);
|
||||
physData.physNodeIndex = zeroBasedIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// whole network with reconstructed tree
|
||||
for (auto it(root.begin_leaf_modules()); !it.isEnd(); ++it) {
|
||||
InfoNode& module = *it;
|
||||
std::map<unsigned int, double> physToFlow;
|
||||
for (auto& node : module) {
|
||||
for (PhysData& physData : node.physicalNodes) {
|
||||
physToFlow[physData.physNodeIndex] += physData.sumFlowFromM2Node;
|
||||
}
|
||||
}
|
||||
for (auto& physFlow : physToFlow) {
|
||||
module.physicalNodes.emplace_back(physFlow.first, physFlow.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemMapEquation::initPartitionOfPhysicalNodes(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
Log(4) << "MemMapEquation::initPartitionOfPhysicalNodes()...\n";
|
||||
m_physToModuleToMemNodes.clear();
|
||||
m_physToModuleToMemNodes.resize(m_numPhysicalNodes);
|
||||
|
||||
for (auto& n : nodes) {
|
||||
InfoNode& node = *n;
|
||||
unsigned int moduleIndex = node.index; // Assume unique module index for all nodes in this initiation phase
|
||||
|
||||
for (PhysData& physData : node.physicalNodes) {
|
||||
m_physToModuleToMemNodes[physData.physNodeIndex].insert(m_physToModuleToMemNodes[physData.physNodeIndex].end(),
|
||||
std::make_pair(moduleIndex, MemNodeSet(1, physData.sumFlowFromM2Node)));
|
||||
}
|
||||
}
|
||||
|
||||
m_memoryContributionsAdded = false;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
void MemMapEquation::calculateCodelength(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
calculateCodelengthTerms(nodes);
|
||||
|
||||
calculateNodeFlow_log_nodeFlow();
|
||||
|
||||
calculateCodelengthFromCodelengthTerms();
|
||||
}
|
||||
|
||||
void MemMapEquation::calculateNodeFlow_log_nodeFlow()
|
||||
{
|
||||
nodeFlow_log_nodeFlow = 0.0;
|
||||
for (unsigned int i = 0; i < m_numPhysicalNodes; ++i) {
|
||||
const ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[i];
|
||||
for (const auto& moduleToMemNode : moduleToMemNodes)
|
||||
nodeFlow_log_nodeFlow += infomath::plogp(moduleToMemNode.second.sumFlow);
|
||||
}
|
||||
}
|
||||
|
||||
double MemMapEquation::calcCodelength(const InfoNode& parent) const
|
||||
{
|
||||
if (parent.isLeafModule()) {
|
||||
return calcCodelengthOnModuleOfLeafNodes(parent);
|
||||
}
|
||||
// Use first-order model on index codebook
|
||||
return Base::calcCodelengthOnModuleOfModules(parent);
|
||||
}
|
||||
|
||||
double MemMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const
|
||||
{
|
||||
if (parent.numPhysicalNodes() == 0) {
|
||||
return Base::calcCodelength(parent); // Infomap root node
|
||||
}
|
||||
|
||||
// TODO: For ordinary networks, flow should be used instead of enter flow
|
||||
// for leaf nodes, what about memory networks? sumFlowFromM2Node vs sumEnterFlowFromM2Node?
|
||||
double parentFlow = parent.data.flow;
|
||||
double parentExit = parent.data.exitFlow;
|
||||
double totalParentFlow = parentFlow + parentExit;
|
||||
if (totalParentFlow < 1e-16)
|
||||
return 0.0;
|
||||
|
||||
double indexLength = 0.0;
|
||||
|
||||
for (const PhysData& physData : parent.physicalNodes) {
|
||||
indexLength -= infomath::plogp(physData.sumFlowFromM2Node / totalParentFlow);
|
||||
}
|
||||
indexLength -= infomath::plogp(parentExit / totalParentFlow);
|
||||
|
||||
indexLength *= totalParentFlow;
|
||||
|
||||
return indexLength;
|
||||
}
|
||||
|
||||
void MemMapEquation::addMemoryContributions(InfoNode& current,
|
||||
MemDeltaFlow& oldModuleDelta,
|
||||
VectorMap<MemDeltaFlow>& moduleDeltaFlow)
|
||||
{
|
||||
// Overlapping modules
|
||||
/*
|
||||
* delta = old.first + new.first + old.second - new.second.
|
||||
* Two cases: (p(x) = plogp(x))
|
||||
* Moving to a module that already have that physical node: (old: p1, p2, new p3, moving p2 -> old:p1, new p2,p3)
|
||||
* Then old.second = new.second = plogp(physicalNodeSize) -> cancelation -> delta = p(p1) - p(p1+p2) + p(p2+p3) - p(p3)
|
||||
* Moving to a module that not have that physical node: (old: p1, p2, new -, moving p2 -> old: p1, new: p2)
|
||||
* Then new.first = new.second = 0 -> delta = p(p1) - p(p1+p2) + p(p2).
|
||||
*/
|
||||
auto& physicalNodes = current.physicalNodes;
|
||||
unsigned int numPhysicalNodes = physicalNodes.size();
|
||||
for (unsigned int i = 0; i < numPhysicalNodes; ++i) {
|
||||
PhysData& physData = physicalNodes[i];
|
||||
ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex];
|
||||
for (const auto& moduleToMemNode : moduleToMemNodes) {
|
||||
unsigned int moduleIndex = moduleToMemNode.first;
|
||||
auto& memNodeSet = moduleToMemNode.second;
|
||||
if (moduleIndex == current.index) // From where the multiple assigned node is moved
|
||||
{
|
||||
double oldPhysFlow = memNodeSet.sumFlow;
|
||||
double newPhysFlow = memNodeSet.sumFlow - physData.sumFlowFromM2Node;
|
||||
oldModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
|
||||
oldModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node);
|
||||
} else // To where the multiple assigned node is moved
|
||||
{
|
||||
double oldPhysFlow = memNodeSet.sumFlow;
|
||||
double newPhysFlow = memNodeSet.sumFlow + physData.sumFlowFromM2Node;
|
||||
|
||||
double sumDeltaPlogpPhysFlow = infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
|
||||
double sumPlogpPhysFlow = infomath::plogp(physData.sumFlowFromM2Node);
|
||||
moduleDeltaFlow.add(moduleIndex, MemDeltaFlow(moduleIndex, 0.0, 0.0, sumDeltaPlogpPhysFlow, sumPlogpPhysFlow));
|
||||
}
|
||||
}
|
||||
}
|
||||
m_memoryContributionsAdded = true;
|
||||
}
|
||||
|
||||
double MemMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current,
|
||||
MemDeltaFlow& oldModuleDelta,
|
||||
MemDeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers)
|
||||
{
|
||||
double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
|
||||
|
||||
double delta_nodeFlow_log_nodeFlow = oldModuleDelta.sumDeltaPlogpPhysFlow + newModuleDelta.sumDeltaPlogpPhysFlow + oldModuleDelta.sumPlogpPhysFlow - newModuleDelta.sumPlogpPhysFlow;
|
||||
|
||||
return deltaL - delta_nodeFlow_log_nodeFlow;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
void MemMapEquation::updateCodelengthOnMovingNode(InfoNode& current,
|
||||
MemDeltaFlow& oldModuleDelta,
|
||||
MemDeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers)
|
||||
{
|
||||
Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
|
||||
if (m_memoryContributionsAdded)
|
||||
updatePhysicalNodes(current, oldModuleDelta.module, newModuleDelta.module);
|
||||
else
|
||||
addMemoryContributionsAndUpdatePhysicalNodes(current, oldModuleDelta, newModuleDelta);
|
||||
|
||||
double delta_nodeFlow_log_nodeFlow = oldModuleDelta.sumDeltaPlogpPhysFlow + newModuleDelta.sumDeltaPlogpPhysFlow + oldModuleDelta.sumPlogpPhysFlow - newModuleDelta.sumPlogpPhysFlow;
|
||||
|
||||
nodeFlow_log_nodeFlow += delta_nodeFlow_log_nodeFlow;
|
||||
moduleCodelength -= delta_nodeFlow_log_nodeFlow;
|
||||
codelength -= delta_nodeFlow_log_nodeFlow;
|
||||
}
|
||||
|
||||
void MemMapEquation::updatePhysicalNodes(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex)
|
||||
{
|
||||
// For all multiple assigned nodes
|
||||
for (const auto& physData : current.physicalNodes) {
|
||||
ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex];
|
||||
|
||||
// Remove contribution to old module
|
||||
auto overlapIt = moduleToMemNodes.find(oldModuleIndex);
|
||||
if (overlapIt == moduleToMemNodes.end())
|
||||
throw std::length_error(io::Str() << "Couldn't find old module " << oldModuleIndex << " in physical node " << physData.physNodeIndex);
|
||||
|
||||
MemNodeSet& oldMemNodeSet = overlapIt->second;
|
||||
oldMemNodeSet.sumFlow -= physData.sumFlowFromM2Node;
|
||||
if (--oldMemNodeSet.numMemNodes == 0)
|
||||
moduleToMemNodes.erase(overlapIt);
|
||||
|
||||
// Add contribution to new module
|
||||
overlapIt = moduleToMemNodes.find(bestModuleIndex);
|
||||
if (overlapIt == moduleToMemNodes.end()) {
|
||||
moduleToMemNodes.insert(std::make_pair(bestModuleIndex, MemNodeSet(1, physData.sumFlowFromM2Node)));
|
||||
} else {
|
||||
MemNodeSet& newMemNodeSet = overlapIt->second;
|
||||
++newMemNodeSet.numMemNodes;
|
||||
newMemNodeSet.sumFlow += physData.sumFlowFromM2Node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemMapEquation::addMemoryContributionsAndUpdatePhysicalNodes(InfoNode& current, MemDeltaFlow& oldModuleDelta, MemDeltaFlow& newModuleDelta)
|
||||
{
|
||||
unsigned int oldModuleIndex = oldModuleDelta.module;
|
||||
unsigned int bestModuleIndex = newModuleDelta.module;
|
||||
|
||||
// For all multiple assigned nodes
|
||||
for (const auto& physData : current.physicalNodes) {
|
||||
ModuleToMemNodes& moduleToMemNodes = m_physToModuleToMemNodes[physData.physNodeIndex];
|
||||
|
||||
// Remove contribution to old module
|
||||
auto overlapIt = moduleToMemNodes.find(oldModuleIndex);
|
||||
if (overlapIt == moduleToMemNodes.end())
|
||||
throw std::length_error("Couldn't find old module among physical node assignments.");
|
||||
|
||||
MemNodeSet& oldMemNodeSet = overlapIt->second;
|
||||
double oldPhysFlow = oldMemNodeSet.sumFlow;
|
||||
double newPhysFlow = oldMemNodeSet.sumFlow - physData.sumFlowFromM2Node;
|
||||
oldModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
|
||||
oldModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node);
|
||||
oldMemNodeSet.sumFlow -= physData.sumFlowFromM2Node;
|
||||
if (--oldMemNodeSet.numMemNodes == 0)
|
||||
moduleToMemNodes.erase(overlapIt);
|
||||
|
||||
// Add contribution to new module
|
||||
overlapIt = moduleToMemNodes.find(bestModuleIndex);
|
||||
if (overlapIt == moduleToMemNodes.end()) {
|
||||
moduleToMemNodes.insert(std::make_pair(bestModuleIndex, MemNodeSet(1, physData.sumFlowFromM2Node)));
|
||||
oldPhysFlow = 0.0;
|
||||
newPhysFlow = physData.sumFlowFromM2Node;
|
||||
newModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
|
||||
newModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node);
|
||||
} else {
|
||||
MemNodeSet& newMemNodeSet = overlapIt->second;
|
||||
oldPhysFlow = newMemNodeSet.sumFlow;
|
||||
newPhysFlow = newMemNodeSet.sumFlow + physData.sumFlowFromM2Node;
|
||||
newModuleDelta.sumDeltaPlogpPhysFlow += infomath::plogp(newPhysFlow) - infomath::plogp(oldPhysFlow);
|
||||
newModuleDelta.sumPlogpPhysFlow += infomath::plogp(physData.sumFlowFromM2Node);
|
||||
++newMemNodeSet.numMemNodes;
|
||||
newMemNodeSet.sumFlow += physData.sumFlowFromM2Node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemMapEquation::consolidateModules(std::vector<InfoNode*>& modules)
|
||||
{
|
||||
std::map<unsigned int, std::map<unsigned int, unsigned int>> validate;
|
||||
|
||||
for (unsigned int i = 0; i < m_numPhysicalNodes; ++i) {
|
||||
ModuleToMemNodes& modToMemNodes = m_physToModuleToMemNodes[i];
|
||||
for (const auto& modToMemNode : modToMemNodes) {
|
||||
if (++validate[modToMemNode.first][i] > 1)
|
||||
throw std::domain_error("[InfomapGreedy::consolidateModules] Error updating physical nodes: duplication error");
|
||||
|
||||
modules[modToMemNode.first]->physicalNodes.emplace_back(i, modToMemNode.second.sumFlow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug
|
||||
// ===================================================
|
||||
|
||||
void MemMapEquation::printDebug() const
|
||||
{
|
||||
std::cout << "MemMapEquation::m_numPhysicalNodes: " << m_numPhysicalNodes << "\n";
|
||||
Base::printDebug();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,174 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef MEM_MAPEQUATION_H_
|
||||
#define MEM_MAPEQUATION_H_
|
||||
|
||||
#include "MapEquation.h"
|
||||
#include "FlowData.h"
|
||||
#include "../utils/Log.h"
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InfoNode;
|
||||
struct MemNodeSet;
|
||||
|
||||
class MemMapEquation : private MapEquation<FlowData, MemDeltaFlow> {
|
||||
using Base = MapEquation<FlowData, MemDeltaFlow>;
|
||||
|
||||
public:
|
||||
using FlowDataType = FlowData;
|
||||
using DeltaFlowDataType = MemDeltaFlow;
|
||||
|
||||
// ===================================================
|
||||
// Getters
|
||||
// ===================================================
|
||||
|
||||
using Base::getIndexCodelength;
|
||||
|
||||
using Base::getModuleCodelength;
|
||||
|
||||
using Base::getCodelength;
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
std::ostream& print(std::ostream& out) const override;
|
||||
friend std::ostream& operator<<(std::ostream&, const MemMapEquation&);
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
void init(const Config& config) override;
|
||||
|
||||
void initTree(InfoNode& /*root*/) override { }
|
||||
|
||||
void initNetwork(InfoNode& root) override;
|
||||
|
||||
void initSuperNetwork(InfoNode& root) override;
|
||||
|
||||
void initSubNetwork(InfoNode& root) override;
|
||||
|
||||
void initPartition(std::vector<InfoNode*>& nodes) override;
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
double calcCodelength(const InfoNode& parent) const override;
|
||||
|
||||
void addMemoryContributions(InfoNode& current, MemDeltaFlow& oldModuleDelta, VectorMap<MemDeltaFlow>& moduleDeltaFlow) override;
|
||||
|
||||
double getDeltaCodelengthOnMovingNode(InfoNode& current,
|
||||
MemDeltaFlow& oldModuleDelta,
|
||||
MemDeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers) override;
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
void updateCodelengthOnMovingNode(InfoNode& current,
|
||||
MemDeltaFlow& oldModuleDelta,
|
||||
MemDeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers) override;
|
||||
|
||||
void consolidateModules(std::vector<InfoNode*>& modules) override;
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug
|
||||
// ===================================================
|
||||
|
||||
void printDebug() const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
// ===================================================
|
||||
// Private member functions
|
||||
// ===================================================
|
||||
double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override;
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
void initPhysicalNodes(InfoNode& root);
|
||||
|
||||
void initPartitionOfPhysicalNodes(std::vector<InfoNode*>& nodes);
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
void calculateCodelength(std::vector<InfoNode*>& nodes) override;
|
||||
|
||||
using Base::calculateCodelengthTerms;
|
||||
|
||||
using Base::calculateCodelengthFromCodelengthTerms;
|
||||
|
||||
void calculateNodeFlow_log_nodeFlow();
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
void updatePhysicalNodes(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex);
|
||||
|
||||
void addMemoryContributionsAndUpdatePhysicalNodes(InfoNode& current, MemDeltaFlow& oldModuleDelta, MemDeltaFlow& newModuleDelta);
|
||||
|
||||
public:
|
||||
// ===================================================
|
||||
// Public member variables
|
||||
// ===================================================
|
||||
|
||||
using Base::codelength;
|
||||
using Base::indexCodelength;
|
||||
using Base::moduleCodelength;
|
||||
|
||||
private:
|
||||
// ===================================================
|
||||
// Private member variables
|
||||
// ===================================================
|
||||
|
||||
using Base::enter_log_enter;
|
||||
using Base::enterFlow;
|
||||
using Base::enterFlow_log_enterFlow;
|
||||
using Base::exit_log_exit;
|
||||
using Base::flow_log_flow; // node.(flow + exitFlow)
|
||||
using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same
|
||||
|
||||
// For hierarchical
|
||||
using Base::exitNetworkFlow;
|
||||
using Base::exitNetworkFlow_log_exitNetworkFlow;
|
||||
|
||||
using ModuleToMemNodes = std::map<unsigned int, MemNodeSet>;
|
||||
|
||||
std::vector<ModuleToMemNodes> m_physToModuleToMemNodes; // vector[physicalNodeID] map<moduleID, {#memNodes, sumFlow}>
|
||||
unsigned int m_numPhysicalNodes = 0;
|
||||
bool m_memoryContributionsAdded = false;
|
||||
};
|
||||
|
||||
struct MemNodeSet {
|
||||
MemNodeSet(unsigned int numMemNodes, double sumFlow) : numMemNodes(numMemNodes), sumFlow(sumFlow) { }
|
||||
unsigned int numMemNodes; // use counter to check for zero to avoid round-off errors in sumFlow
|
||||
double sumFlow;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // MEM_MAPEQUATION_H_
|
||||
@@ -0,0 +1,271 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "MetaMapEquation.h"
|
||||
#include "FlowData.h"
|
||||
#include "InfoNode.h"
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
double MetaMapEquation::getModuleCodelength() const
|
||||
{
|
||||
return moduleCodelength + metaCodelength * metaDataRate;
|
||||
}
|
||||
|
||||
double MetaMapEquation::getCodelength() const
|
||||
{
|
||||
return codelength + metaCodelength * metaDataRate;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
std::ostream& MetaMapEquation::print(std::ostream& out) const
|
||||
{
|
||||
return out << indexCodelength << " + " << moduleCodelength << " + " << metaCodelength << " = " << io::toPrecision(getCodelength());
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const MetaMapEquation& mapEq)
|
||||
{
|
||||
return mapEq.print(out);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
void MetaMapEquation::init(const Config& config)
|
||||
{
|
||||
Log(3) << "MetaMapEquation::init()...\n";
|
||||
numMetaDataDimensions = config.numMetaDataDimensions;
|
||||
metaDataRate = config.metaDataRate;
|
||||
weightByFlow = !config.unweightedMetaData;
|
||||
}
|
||||
|
||||
void MetaMapEquation::initTree(InfoNode& root)
|
||||
{
|
||||
Log(3) << "MetaMapEquation::initTree()...\n";
|
||||
initMetaNodes(root);
|
||||
}
|
||||
|
||||
void MetaMapEquation::initNetwork(InfoNode& root)
|
||||
{
|
||||
Log(3) << "MetaMapEquation::initNetwork()...\n";
|
||||
Base::initNetwork(root);
|
||||
m_unweightedNodeFlow = 1.0 / root.childDegree();
|
||||
}
|
||||
|
||||
void MetaMapEquation::initPartition(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
initPartitionOfMetaNodes(nodes);
|
||||
calculateCodelength(nodes);
|
||||
}
|
||||
|
||||
void MetaMapEquation::initMetaNodes(InfoNode& root)
|
||||
{
|
||||
bool notInitiated = root.firstChild->metaCollection.empty();
|
||||
if (notInitiated) {
|
||||
Log(3) << "MetaMapEquation::initMetaNodes()...\n";
|
||||
|
||||
for (auto it = root.begin_post_depth_first(); !it.isEnd(); ++it) {
|
||||
auto& node = *it;
|
||||
if (node.isRoot()) {
|
||||
break;
|
||||
}
|
||||
if (node.isLeaf()) {
|
||||
if (!node.metaData.empty()) {
|
||||
// TODO: Use flow here and move weightByFlow choice to metaCollection, using flowCount?
|
||||
double flow = weightByFlow ? node.data.flow : m_unweightedNodeFlow;
|
||||
node.parent->metaCollection.add(node.metaData[0], flow);
|
||||
} else {
|
||||
throw std::length_error("A node is missing meta data using MetaMapEquation");
|
||||
}
|
||||
} else {
|
||||
node.parent->metaCollection.add(node.metaCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetaMapEquation::initPartitionOfMetaNodes(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
Log(4) << "MetaMapEquation::initPartitionOfMetaNodes()...\n";
|
||||
m_moduleToMetaCollection.clear();
|
||||
|
||||
for (auto& n : nodes) {
|
||||
InfoNode& node = *n;
|
||||
unsigned int moduleIndex = node.index; // Assume unique module index for all nodes in this initiation phase
|
||||
if (node.metaCollection.empty()) {
|
||||
if (!node.metaData.empty()) {
|
||||
double flow = weightByFlow ? node.data.flow : m_unweightedNodeFlow;
|
||||
node.metaCollection.add(node.metaData[0], flow);
|
||||
} else
|
||||
throw std::length_error("A node is missing meta data using MetaMapEquation");
|
||||
}
|
||||
m_moduleToMetaCollection[moduleIndex] = node.metaCollection;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
void MetaMapEquation::calculateCodelength(std::vector<InfoNode*>& nodes)
|
||||
{
|
||||
calculateCodelengthTerms(nodes);
|
||||
|
||||
calculateCodelengthFromCodelengthTerms();
|
||||
|
||||
metaCodelength = 0.0;
|
||||
|
||||
// Treat each node as a single module
|
||||
for (InfoNode* n : nodes) {
|
||||
InfoNode& node = *n;
|
||||
metaCodelength += node.metaCollection.calculateEntropy();
|
||||
}
|
||||
}
|
||||
|
||||
double MetaMapEquation::calcCodelength(const InfoNode& parent) const
|
||||
{
|
||||
return parent.isLeafModule() ? calcCodelengthOnModuleOfLeafNodes(parent) : Base::calcCodelengthOnModuleOfModules(parent);
|
||||
}
|
||||
|
||||
double MetaMapEquation::calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const
|
||||
{
|
||||
double indexLength = Base::calcCodelength(parent);
|
||||
|
||||
// Meta addition
|
||||
MetaCollection metaCollection;
|
||||
for (const InfoNode& node : parent) {
|
||||
if (!node.metaCollection.empty())
|
||||
metaCollection.add(node.metaCollection);
|
||||
else
|
||||
metaCollection.add(node.metaData[0], weightByFlow ? node.data.flow : m_unweightedNodeFlow); // TODO: Initiate to collection and use all dimensions
|
||||
}
|
||||
|
||||
double _metaCodelength = metaCollection.calculateEntropy();
|
||||
|
||||
return indexLength + metaDataRate * _metaCodelength;
|
||||
}
|
||||
|
||||
double MetaMapEquation::getDeltaCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlow& oldModuleDelta,
|
||||
DeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers)
|
||||
{
|
||||
double deltaL = Base::getDeltaCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
|
||||
|
||||
double deltaMetaL = 0.0;
|
||||
|
||||
unsigned int oldModuleIndex = oldModuleDelta.module;
|
||||
unsigned int newModuleIndex = newModuleDelta.module;
|
||||
|
||||
// Remove codelength of old and new module before changes
|
||||
deltaMetaL -= getCurrentModuleMetaCodelength(oldModuleIndex, current, 0);
|
||||
deltaMetaL -= getCurrentModuleMetaCodelength(newModuleIndex, current, 0);
|
||||
// Add codelength of old module with current node removed
|
||||
deltaMetaL += getCurrentModuleMetaCodelength(oldModuleIndex, current, -1);
|
||||
// Add codelength of old module with current node added
|
||||
deltaMetaL += getCurrentModuleMetaCodelength(newModuleIndex, current, 1);
|
||||
|
||||
return deltaL + deltaMetaL * metaDataRate;
|
||||
}
|
||||
|
||||
double MetaMapEquation::getCurrentModuleMetaCodelength(unsigned int module, InfoNode& current, int addRemoveOrNothing)
|
||||
{
|
||||
auto& currentMetaCollection = m_moduleToMetaCollection[module];
|
||||
|
||||
double moduleMetaCodelength = 0.0;
|
||||
|
||||
if (addRemoveOrNothing == 0) {
|
||||
moduleMetaCodelength = currentMetaCollection.calculateEntropy();
|
||||
}
|
||||
// If add or remove, do the change, calculate new codelength and then undo the change
|
||||
else if (addRemoveOrNothing == 1) {
|
||||
currentMetaCollection.add(current.metaCollection);
|
||||
moduleMetaCodelength = currentMetaCollection.calculateEntropy();
|
||||
currentMetaCollection.remove(current.metaCollection);
|
||||
} else {
|
||||
currentMetaCollection.remove(current.metaCollection);
|
||||
moduleMetaCodelength = currentMetaCollection.calculateEntropy();
|
||||
currentMetaCollection.add(current.metaCollection);
|
||||
}
|
||||
|
||||
return moduleMetaCodelength;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
void MetaMapEquation::updateCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlow& oldModuleDelta,
|
||||
DeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers)
|
||||
{
|
||||
Base::updateCodelengthOnMovingNode(current, oldModuleDelta, newModuleDelta, moduleFlowData, moduleMembers);
|
||||
|
||||
double deltaMetaL = 0.0;
|
||||
|
||||
unsigned int oldModuleIndex = oldModuleDelta.module;
|
||||
unsigned int newModuleIndex = newModuleDelta.module;
|
||||
|
||||
// Remove codelength of old and new module before changes
|
||||
deltaMetaL -= getCurrentModuleMetaCodelength(oldModuleIndex, current, 0);
|
||||
deltaMetaL -= getCurrentModuleMetaCodelength(newModuleIndex, current, 0);
|
||||
|
||||
// Update meta data from moving node
|
||||
updateMetaData(current, oldModuleIndex, newModuleIndex);
|
||||
|
||||
// Add codelength of old and new module after changes
|
||||
deltaMetaL += getCurrentModuleMetaCodelength(oldModuleIndex, current, 0);
|
||||
deltaMetaL += getCurrentModuleMetaCodelength(newModuleIndex, current, 0);
|
||||
|
||||
metaCodelength += deltaMetaL;
|
||||
}
|
||||
|
||||
void MetaMapEquation::updateMetaData(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex)
|
||||
{
|
||||
// Remove meta id from old module (can be a set of meta ids when moving submodules in coarse tune)
|
||||
auto& oldMetaCollection = m_moduleToMetaCollection[oldModuleIndex];
|
||||
oldMetaCollection.remove(current.metaCollection);
|
||||
|
||||
// Add meta id to new module
|
||||
auto& newMetaCollection = m_moduleToMetaCollection[bestModuleIndex];
|
||||
newMetaCollection.add(current.metaCollection);
|
||||
}
|
||||
|
||||
void MetaMapEquation::consolidateModules(std::vector<InfoNode*>& modules)
|
||||
{
|
||||
for (auto& module : modules) {
|
||||
if (module == nullptr)
|
||||
continue;
|
||||
module->metaCollection = m_moduleToMetaCollection[module->index];
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug
|
||||
// ===================================================
|
||||
|
||||
void MetaMapEquation::printDebug() const
|
||||
{
|
||||
std::cout << "MetaMapEquation\n";
|
||||
Base::printDebug();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,185 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef META_MAPEQUATION_H_
|
||||
#define META_MAPEQUATION_H_
|
||||
|
||||
#include "MapEquation.h"
|
||||
#include "FlowData.h"
|
||||
#include "../utils/Log.h"
|
||||
#include "../utils/MetaCollection.h"
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InfoNode;
|
||||
|
||||
class MetaMapEquation : private MapEquation<> {
|
||||
using Base = MapEquation<>;
|
||||
|
||||
public:
|
||||
using FlowDataType = FlowData;
|
||||
using DeltaFlowDataType = DeltaFlow;
|
||||
|
||||
// ===================================================
|
||||
// Getters
|
||||
// ===================================================
|
||||
|
||||
using Base::getIndexCodelength;
|
||||
|
||||
double getModuleCodelength() const override;
|
||||
|
||||
double getCodelength() const override;
|
||||
|
||||
double getMetaCodelength(bool unweighted = false) const
|
||||
{
|
||||
return unweighted ? metaCodelength : metaDataRate * metaCodelength;
|
||||
};
|
||||
|
||||
// ===================================================
|
||||
// IO
|
||||
// ===================================================
|
||||
|
||||
std::ostream& print(std::ostream& out) const override;
|
||||
friend std::ostream& operator<<(std::ostream&, const MetaMapEquation&);
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
void init(const Config& config) override;
|
||||
|
||||
void initTree(InfoNode& root) override;
|
||||
|
||||
void initNetwork(InfoNode& root) override;
|
||||
|
||||
using Base::initSuperNetwork;
|
||||
|
||||
using Base::initSubNetwork;
|
||||
|
||||
void initPartition(std::vector<InfoNode*>& nodes) override;
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
double calcCodelength(const InfoNode& parent) const override;
|
||||
|
||||
using Base::addMemoryContributions;
|
||||
|
||||
double getDeltaCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlow& oldModuleDelta,
|
||||
DeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers) override;
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
void updateCodelengthOnMovingNode(InfoNode& current,
|
||||
DeltaFlow& oldModuleDelta,
|
||||
DeltaFlow& newModuleDelta,
|
||||
std::vector<FlowData>& moduleFlowData,
|
||||
std::vector<unsigned int>& moduleMembers) override;
|
||||
|
||||
void consolidateModules(std::vector<InfoNode*>& modules) override;
|
||||
|
||||
#if 0
|
||||
// ===================================================
|
||||
// Debug
|
||||
// ===================================================
|
||||
|
||||
void printDebug() const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
// ===================================================
|
||||
// Private member functions
|
||||
// ===================================================
|
||||
double calcCodelengthOnModuleOfLeafNodes(const InfoNode& parent) const override;
|
||||
|
||||
// ===================================================
|
||||
// Init
|
||||
// ===================================================
|
||||
|
||||
void initMetaNodes(InfoNode& root);
|
||||
|
||||
void initPartitionOfMetaNodes(std::vector<InfoNode*>& nodes);
|
||||
|
||||
// ===================================================
|
||||
// Codelength
|
||||
// ===================================================
|
||||
|
||||
void calculateCodelength(std::vector<InfoNode*>& nodes) override;
|
||||
|
||||
using Base::calculateCodelengthTerms;
|
||||
|
||||
using Base::calculateCodelengthFromCodelengthTerms;
|
||||
|
||||
// ===================================================
|
||||
// Consolidation
|
||||
// ===================================================
|
||||
|
||||
void updateMetaData(InfoNode& current, unsigned int oldModuleIndex, unsigned int bestModuleIndex);
|
||||
|
||||
public:
|
||||
// ===================================================
|
||||
// Public member variables
|
||||
// ===================================================
|
||||
|
||||
using Base::codelength;
|
||||
using Base::indexCodelength;
|
||||
using Base::moduleCodelength;
|
||||
|
||||
private:
|
||||
// ===================================================
|
||||
// Private member functions
|
||||
// ===================================================
|
||||
|
||||
/**
|
||||
* Get meta codelength of module of current node
|
||||
* @param addRemoveOrNothing +1, -1 or 0 to calculate codelength
|
||||
* as if current node was added, removed or untouched in current module
|
||||
*/
|
||||
double getCurrentModuleMetaCodelength(unsigned int module, InfoNode& current, int addRemoveOrNothing);
|
||||
|
||||
// ===================================================
|
||||
// Private member variables
|
||||
// ===================================================
|
||||
|
||||
using Base::enter_log_enter;
|
||||
using Base::enterFlow;
|
||||
using Base::enterFlow_log_enterFlow;
|
||||
using Base::exit_log_exit;
|
||||
using Base::flow_log_flow; // node.(flow + exitFlow)
|
||||
using Base::nodeFlow_log_nodeFlow; // constant while the leaf network is the same
|
||||
|
||||
// For hierarchical
|
||||
using Base::exitNetworkFlow;
|
||||
using Base::exitNetworkFlow_log_exitNetworkFlow;
|
||||
|
||||
// For meta data
|
||||
using ModuleMetaMap = std::map<unsigned int, MetaCollection>; // moduleId -> (metaId -> count)
|
||||
|
||||
ModuleMetaMap m_moduleToMetaCollection;
|
||||
|
||||
unsigned int numMetaDataDimensions = 0;
|
||||
double metaDataRate = 1.0;
|
||||
bool weightByFlow = true;
|
||||
double metaCodelength = 0.0;
|
||||
double m_unweightedNodeFlow = 0.0;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // META_MAPEQUATION_H_
|
||||
@@ -0,0 +1,329 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "StateNetwork.h"
|
||||
#include "../utils/FlowCalculator.h"
|
||||
#include "../utils/Log.h"
|
||||
#include "../io/SafeFile.h"
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addStateNode(const StateNode& node)
|
||||
{
|
||||
auto ret = m_nodes.insert(StateNetwork::NodeMap::value_type(node.id, node));
|
||||
if (ret.second) {
|
||||
// If state node didn't exist, also create the associated physical node
|
||||
addPhysicalNode(node.physicalId);
|
||||
if (node.id != node.physicalId) {
|
||||
m_haveMemoryInput = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addStateNode(unsigned int id, unsigned int physId)
|
||||
{
|
||||
m_higherOrderInputMethodCalled = true;
|
||||
return addStateNode(StateNode(id, physId));
|
||||
}
|
||||
|
||||
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addNode(unsigned int id)
|
||||
{
|
||||
return addStateNode(StateNode(id));
|
||||
}
|
||||
|
||||
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addNode(unsigned int id, double weight)
|
||||
{
|
||||
auto ret = addNode(id);
|
||||
auto& node = ret.first->second;
|
||||
node.weight = weight;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addNode(unsigned int id, std::string name)
|
||||
{
|
||||
m_names[id] = std::move(name);
|
||||
return addNode(id);
|
||||
}
|
||||
|
||||
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addNode(unsigned int id, std::string name, double weight)
|
||||
{
|
||||
m_names[id] = std::move(name);
|
||||
return addNode(id, weight);
|
||||
}
|
||||
|
||||
StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId)
|
||||
{
|
||||
auto& physNode = m_physNodes[physId];
|
||||
physNode.physId = physId;
|
||||
m_sumNodeWeight += 1.0;
|
||||
return physNode;
|
||||
}
|
||||
|
||||
StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, double weight)
|
||||
{
|
||||
auto& physNode = addPhysicalNode(physId);
|
||||
physNode.weight = weight;
|
||||
m_sumNodeWeight += weight;
|
||||
return physNode;
|
||||
}
|
||||
|
||||
StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, const std::string& name)
|
||||
{
|
||||
auto& physNode = addPhysicalNode(physId);
|
||||
m_names[physId] = name;
|
||||
m_sumNodeWeight += 1.0;
|
||||
return physNode;
|
||||
}
|
||||
|
||||
StateNetwork::PhysNode& StateNetwork::addPhysicalNode(unsigned int physId, double weight, const std::string& name)
|
||||
{
|
||||
auto& physNode = addPhysicalNode(physId);
|
||||
physNode.weight = weight;
|
||||
m_sumNodeWeight += weight;
|
||||
m_names[physId] = name;
|
||||
return physNode;
|
||||
}
|
||||
|
||||
std::pair<std::map<unsigned int, std::string>::iterator, bool> StateNetwork::addName(unsigned int id, const std::string& name)
|
||||
{
|
||||
return m_names.insert(std::make_pair(id, name));
|
||||
}
|
||||
|
||||
bool StateNetwork::addLink(unsigned int sourceId, unsigned int targetId, double weight)
|
||||
{
|
||||
if (weight < m_config.weightThreshold || weight <= 0) {
|
||||
++m_numLinksIgnoredByWeightThreshold;
|
||||
m_totalLinkWeightIgnored += weight;
|
||||
return false;
|
||||
}
|
||||
m_totalLinkWeightAdded += weight;
|
||||
|
||||
if (sourceId == targetId) {
|
||||
++m_numSelfLinksFound;
|
||||
if (m_config.noSelfLinks) {
|
||||
return false;
|
||||
}
|
||||
++m_numSelfLinks;
|
||||
m_sumSelfLinkWeight += weight;
|
||||
}
|
||||
|
||||
addNode(sourceId);
|
||||
addNode(targetId);
|
||||
|
||||
++m_numLinks;
|
||||
m_sumLinkWeight += weight;
|
||||
|
||||
bool addedNewLink = true;
|
||||
|
||||
// Aggregate link weights if they are defined more than once
|
||||
auto& outLinks = m_nodeLinkMap[sourceId];
|
||||
if (outLinks.empty()) {
|
||||
outLinks[targetId] = weight;
|
||||
} else {
|
||||
auto ret = outLinks.insert(std::make_pair(StateNode(targetId), LinkData(weight)));
|
||||
auto& linkData = ret.first->second;
|
||||
if (!ret.second) {
|
||||
linkData.weight += weight;
|
||||
++m_numAggregatedLinks;
|
||||
--m_numLinks;
|
||||
addedNewLink = false;
|
||||
} else {
|
||||
linkData.weight = weight;
|
||||
}
|
||||
}
|
||||
m_outWeights[sourceId] += weight;
|
||||
return addedNewLink;
|
||||
}
|
||||
|
||||
bool StateNetwork::addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight)
|
||||
{
|
||||
return addLink(sourceId, targetId, static_cast<double>(weight));
|
||||
}
|
||||
|
||||
bool StateNetwork::removeLink(unsigned int sourceId, unsigned int targetId)
|
||||
{
|
||||
auto itSource = m_nodeLinkMap.find(sourceId);
|
||||
if (itSource == m_nodeLinkMap.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& targetMap = itSource->second;
|
||||
auto itTarget = targetMap.find(targetId);
|
||||
|
||||
if (itTarget == targetMap.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double weight = itTarget->second.weight;
|
||||
|
||||
targetMap.erase(itTarget);
|
||||
if (targetMap.empty()) {
|
||||
m_nodeLinkMap.erase(itSource);
|
||||
} else {
|
||||
--m_numAggregatedLinks;
|
||||
}
|
||||
|
||||
--m_numLinks;
|
||||
m_sumLinkWeight -= weight;
|
||||
m_totalLinkWeightAdded -= weight;
|
||||
|
||||
if (sourceId == targetId) {
|
||||
--m_numSelfLinksFound;
|
||||
--m_numSelfLinks;
|
||||
m_sumSelfLinkWeight -= weight;
|
||||
}
|
||||
|
||||
m_outWeights[sourceId] -= weight;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StateNetwork::undirectedToDirected()
|
||||
{
|
||||
// Collect links in separate data structure to not risk iterating newly added links
|
||||
if (!m_config.isUndirectedFlow()) {
|
||||
return false;
|
||||
}
|
||||
std::deque<StateLink> oppositeLinks;
|
||||
for (auto& linkIt : m_nodeLinkMap) {
|
||||
unsigned int sourceId = linkIt.first.id;
|
||||
const auto& subLinks = linkIt.second;
|
||||
for (auto& subIt : subLinks) {
|
||||
unsigned int targetId = subIt.first.id;
|
||||
if (targetId == sourceId) {
|
||||
continue; // Self-links are treated as directed on undirected networks
|
||||
}
|
||||
double weight = subIt.second.weight;
|
||||
oppositeLinks.emplace_back(targetId, sourceId, weight);
|
||||
}
|
||||
}
|
||||
for (auto& link : oppositeLinks) {
|
||||
addLink(link.source, link.target, link.weight);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StateNetwork::clearLinks()
|
||||
{
|
||||
m_nodeLinkMap.clear();
|
||||
}
|
||||
|
||||
void StateNetwork::clear()
|
||||
{
|
||||
m_nodes.clear();
|
||||
m_nodeLinkMap.clear();
|
||||
m_physNodes.clear();
|
||||
m_outWeights.clear();
|
||||
m_names.clear();
|
||||
|
||||
m_haveDirectedInput = false;
|
||||
m_haveMemoryInput = false;
|
||||
m_numStateNodesFound = 0;
|
||||
m_numLinks = 0;
|
||||
m_numSelfLinksFound = 0;
|
||||
m_sumLinkWeight = 0.0;
|
||||
m_numSelfLinks = 0;
|
||||
m_sumSelfLinkWeight = 0.0;
|
||||
m_numAggregatedLinks = 0;
|
||||
m_totalLinkWeightAdded = 0.0;
|
||||
m_numLinksIgnoredByWeightThreshold = 0;
|
||||
m_totalLinkWeightIgnored = 0.0;
|
||||
}
|
||||
|
||||
void StateNetwork::writeStateNetwork(const std::string& filename) const
|
||||
{
|
||||
if (filename.empty())
|
||||
throw std::runtime_error("writeStateNetwork called with empty filename");
|
||||
|
||||
SafeOutFile outFile(filename);
|
||||
|
||||
outFile << "# v" << INFOMAP_VERSION << "\n"
|
||||
<< "# ./Infomap " << m_config.parsedString << "\n";
|
||||
|
||||
if (!m_names.empty()) {
|
||||
outFile << "*Vertices\n";
|
||||
for (auto& nameIt : m_names) {
|
||||
auto& physId = nameIt.first;
|
||||
auto& name = nameIt.second;
|
||||
outFile << physId << " \"" << name << "\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
outFile << "*States\n";
|
||||
outFile << "# stateId physicalId\n";
|
||||
for (const auto& nodeIt : nodes()) {
|
||||
const auto& node = nodeIt.second;
|
||||
outFile << node.id << " " << node.physicalId;
|
||||
// Optional name
|
||||
if (!node.name.empty())
|
||||
outFile << " \"" << node.name << "\"";
|
||||
outFile << "\n";
|
||||
}
|
||||
|
||||
outFile << "*Links\n";
|
||||
for (auto& linkIt : m_nodeLinkMap) {
|
||||
for (auto& subIt : linkIt.second) {
|
||||
outFile << linkIt.first.id << " " << subIt.first.id << " " << subIt.second.weight << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateNetwork::writePajekNetwork(const std::string& filename, bool printFlow) const
|
||||
{
|
||||
if (filename.empty())
|
||||
throw std::runtime_error("writePajekNetwork called with empty filename");
|
||||
|
||||
SafeOutFile outFile(filename);
|
||||
|
||||
outFile << "# v" << INFOMAP_VERSION << "\n"
|
||||
<< "# ./Infomap " << m_config.parsedString << "\n";
|
||||
if (haveMemoryInput())
|
||||
outFile << "# State network as physical network\n";
|
||||
|
||||
outFile << "*Vertices\n";
|
||||
outFile << "#id name " << (printFlow ? "flow" : "weight") << "\n";
|
||||
for (const auto& nodeIt : nodes()) {
|
||||
const auto& node = nodeIt.second;
|
||||
outFile << node.id << " \"";
|
||||
// Name, default to id
|
||||
const auto& nameIt = haveMemoryInput() ? m_names.end() : m_names.find(node.id);
|
||||
if (nameIt != m_names.end())
|
||||
outFile << nameIt->second;
|
||||
else
|
||||
outFile << node.id;
|
||||
outFile << "\" " << (printFlow ? node.flow : node.weight) << "\n";
|
||||
}
|
||||
|
||||
outFile << (m_config.printAsUndirected() ? "*Edges" : "*Arcs") << "\n";
|
||||
outFile << "#source target " << (printFlow ? "flow" : "weight") << "\n";
|
||||
for (auto& linkIt : m_nodeLinkMap) {
|
||||
for (auto& subIt : linkIt.second) {
|
||||
auto& linkData = subIt.second;
|
||||
outFile << linkIt.first.id << " " << subIt.first.id << " " << (printFlow ? linkData.flow : linkData.weight) << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addStateNodeWithAutogeneratedId(unsigned int physId)
|
||||
{
|
||||
// Keys sorted with std::less comparator, so last key is the largest
|
||||
unsigned int stateId = m_nodes.empty() ? 0 : m_nodes.crbegin()->first + 1;
|
||||
return addStateNode(stateId, physId);
|
||||
}
|
||||
|
||||
std::pair<StateNetwork::NodeMap::iterator, bool> StateNetwork::addStateNodeWithDeterministicId(unsigned int physId, unsigned int layerId, unsigned int numLayersLog2)
|
||||
{
|
||||
unsigned int stateId = physId << (numLayersLog2 + 1) | layerId;
|
||||
return addStateNode(stateId, physId);
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,216 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef STATE_NETWORK_H_
|
||||
#define STATE_NETWORK_H_
|
||||
|
||||
#include "../io/Config.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class StateNetwork {
|
||||
public:
|
||||
struct StateNode {
|
||||
unsigned int id = 0;
|
||||
unsigned int physicalId = 0;
|
||||
std::string name;
|
||||
unsigned int layerId = 0;
|
||||
double weight = 1.0;
|
||||
double flow = 0.0;
|
||||
double enterFlow = 0.0;
|
||||
double exitFlow = 0.0;
|
||||
double teleFlow = 0.0;
|
||||
|
||||
StateNode(unsigned int id = 0) : id(id), physicalId(id) { }
|
||||
|
||||
StateNode(unsigned int id, unsigned int physicalId) : id(id), physicalId(physicalId) { }
|
||||
|
||||
StateNode(unsigned int id, unsigned int physicalId, std::string name) : id(id), physicalId(physicalId), name(std::move(name)) { }
|
||||
|
||||
bool operator==(const StateNode& rhs) const { return id == rhs.id; }
|
||||
bool operator!=(const StateNode& rhs) const { return id != rhs.id; }
|
||||
bool operator<(const StateNode& rhs) const { return id < rhs.id; }
|
||||
};
|
||||
|
||||
struct PhysNode {
|
||||
unsigned int physId = 0;
|
||||
double weight = 1.0;
|
||||
PhysNode(unsigned int physId) : physId(physId) { }
|
||||
PhysNode(unsigned int physId, double weight) : physId(physId), weight(weight) { }
|
||||
PhysNode(double weight = 1.0) : weight(weight) { }
|
||||
};
|
||||
|
||||
struct LinkData {
|
||||
double weight = 1.0;
|
||||
double flow = 0.0;
|
||||
|
||||
LinkData(double weight = 1.0) : weight(weight) { }
|
||||
|
||||
LinkData& operator+=(double w)
|
||||
{
|
||||
weight += w;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct StateLink {
|
||||
StateLink(unsigned int sourceIndex = 0, unsigned int targetIndex = 0, double weight = 0.0)
|
||||
: source(sourceIndex),
|
||||
target(targetIndex),
|
||||
weight(weight),
|
||||
flow(weight) { }
|
||||
|
||||
unsigned int source;
|
||||
unsigned int target;
|
||||
double weight;
|
||||
double flow;
|
||||
};
|
||||
|
||||
// Unique state id to state node
|
||||
using NodeMap = std::map<unsigned int, StateNode>;
|
||||
using OutLinkMap = std::map<StateNode, LinkData>;
|
||||
using NodeLinkMap = std::map<StateNode, OutLinkMap>;
|
||||
|
||||
protected:
|
||||
friend class FlowCalculator;
|
||||
// Config
|
||||
Config m_config;
|
||||
// Network
|
||||
bool m_haveDirectedInput = false;
|
||||
bool m_haveMemoryInput = false;
|
||||
bool m_higherOrderInputMethodCalled = false;
|
||||
NodeMap m_nodes; // Nodes indexed by state id (equal physical id for first-order networks)
|
||||
NodeLinkMap m_nodeLinkMap;
|
||||
unsigned int m_numStateNodesFound = 0;
|
||||
double m_sumNodeWeight = 0.0;
|
||||
unsigned int m_numLinks = 0;
|
||||
double m_sumLinkWeight = 0.0;
|
||||
unsigned int m_numSelfLinksFound = 0;
|
||||
unsigned int m_numSelfLinks = 0;
|
||||
double m_sumSelfLinkWeight = 0.0;
|
||||
unsigned int m_numAggregatedLinks = 0;
|
||||
double m_totalLinkWeightAdded = 0.0;
|
||||
unsigned int m_numLinksIgnoredByWeightThreshold = 0;
|
||||
double m_totalLinkWeightIgnored = 0.0;
|
||||
std::map<unsigned int, double> m_outWeights;
|
||||
bool m_haveNodeWeights = false;
|
||||
bool m_haveStateNodeWeights = false;
|
||||
bool m_haveFileInput = false;
|
||||
// Attributes
|
||||
std::map<unsigned int, std::string> m_names;
|
||||
std::map<unsigned int, PhysNode> m_physNodes;
|
||||
|
||||
// Bipartite
|
||||
unsigned int m_bipartiteStartId = 0;
|
||||
|
||||
public:
|
||||
StateNetwork() : m_config(Config()) { }
|
||||
StateNetwork(Config config) : m_config(std::move(config)) { }
|
||||
virtual ~StateNetwork() = default;
|
||||
|
||||
StateNetwork(const StateNetwork&) = delete;
|
||||
StateNetwork& operator=(const StateNetwork&) = delete;
|
||||
StateNetwork(StateNetwork&&) = delete;
|
||||
StateNetwork& operator=(StateNetwork&&) = delete;
|
||||
|
||||
// Config
|
||||
void setConfig(const Config& config) { m_config = config; }
|
||||
|
||||
// Mutators
|
||||
std::pair<NodeMap::iterator, bool> addStateNode(const StateNode& node);
|
||||
std::pair<NodeMap::iterator, bool> addStateNode(unsigned int id, unsigned int physId);
|
||||
std::pair<NodeMap::iterator, bool> addNode(unsigned int id);
|
||||
std::pair<NodeMap::iterator, bool> addNode(unsigned int id, std::string name);
|
||||
std::pair<NodeMap::iterator, bool> addNode(unsigned int id, double weight);
|
||||
std::pair<NodeMap::iterator, bool> addNode(unsigned int id, std::string, double weight);
|
||||
PhysNode& addPhysicalNode(unsigned int physId);
|
||||
PhysNode& addPhysicalNode(unsigned int physId, double weight);
|
||||
PhysNode& addPhysicalNode(unsigned int physId, const std::string& name);
|
||||
PhysNode& addPhysicalNode(unsigned int physId, double weight, const std::string& name);
|
||||
std::pair<std::map<unsigned int, std::string>::iterator, bool> addName(unsigned int id, const std::string&);
|
||||
bool addLink(unsigned int sourceId, unsigned int targetId, double weight = 1.0);
|
||||
bool addLink(unsigned int sourceId, unsigned int targetId, unsigned long weight);
|
||||
|
||||
/**
|
||||
* Remove link
|
||||
* Note: It will not remove nodes if they become dangling
|
||||
*/
|
||||
bool removeLink(unsigned int sourceId, unsigned int targetId);
|
||||
|
||||
// Expand each undirected link to two opposite directed links
|
||||
bool undirectedToDirected();
|
||||
|
||||
/**
|
||||
* Clear all network data and reset to default state.
|
||||
*/
|
||||
virtual void clear();
|
||||
|
||||
/**
|
||||
* Clear link data but keep node data.
|
||||
*/
|
||||
virtual void clearLinks();
|
||||
|
||||
// Getters
|
||||
const NodeMap& nodes() const { return m_nodes; }
|
||||
unsigned int numNodes() const { return m_nodes.size(); }
|
||||
unsigned int numPhysicalNodes() const { return m_physNodes.size(); }
|
||||
double sumNodeWeight() const { return m_sumNodeWeight; }
|
||||
const NodeLinkMap& nodeLinkMap() const { return m_nodeLinkMap; }
|
||||
NodeLinkMap& nodeLinkMap() { return m_nodeLinkMap; }
|
||||
unsigned int numLinks() const { return m_numLinks; }
|
||||
double sumLinkWeight() const { return m_sumLinkWeight; }
|
||||
unsigned int numSelfLinks() const { return m_numSelfLinks; }
|
||||
double sumSelfLinkWeight() const { return m_sumSelfLinkWeight; }
|
||||
// Use convention of counting self-links only once, treating them as directed
|
||||
double sumWeightedDegree() const { return 2 * sumLinkWeight() - (m_config.isUndirectedFlow() ? sumSelfLinkWeight() : 0); }
|
||||
unsigned int sumDegree() const { return 2 * numLinks() - (m_config.isUndirectedFlow() ? numSelfLinks() : 0); }
|
||||
std::map<unsigned int, double>& outWeights() { return m_outWeights; }
|
||||
std::map<unsigned int, std::string>& names() { return m_names; }
|
||||
const std::map<unsigned int, std::string>& names() const { return m_names; }
|
||||
bool haveNodeWeights() const { return m_haveNodeWeights; }
|
||||
bool haveStateNodeWeights() const { return m_haveStateNodeWeights; }
|
||||
bool haveFileInput() const { return m_haveFileInput; }
|
||||
|
||||
virtual const std::map<unsigned int, std::vector<int>>& metaData() const = 0;
|
||||
|
||||
bool haveDirectedInput() const { return m_haveDirectedInput; }
|
||||
bool haveMemoryInput() const { return m_haveMemoryInput; }
|
||||
bool higherOrderInputMethodCalled() const { return m_higherOrderInputMethodCalled; }
|
||||
// Bipartite
|
||||
bool isBipartite() const { return m_bipartiteStartId > 0; }
|
||||
unsigned int bipartiteStartId() const { return m_bipartiteStartId; }
|
||||
void setBipartiteStartId(unsigned int value) { m_bipartiteStartId = value; }
|
||||
|
||||
/**
|
||||
* Write state network to file.
|
||||
*/
|
||||
void writeStateNetwork(const std::string& filename) const;
|
||||
|
||||
/**
|
||||
* Write state network as first-order Pajek network, where
|
||||
* state nodes are treated as physical nodes.
|
||||
* For a non-memory input, the state nodes are equivalent to
|
||||
* physical nodes.
|
||||
*/
|
||||
void writePajekNetwork(const std::string& filename, bool printFlow = false) const;
|
||||
|
||||
protected:
|
||||
std::pair<NodeMap::iterator, bool> addStateNodeWithAutogeneratedId(unsigned int physId);
|
||||
|
||||
std::pair<NodeMap::iterator, bool> addStateNodeWithDeterministicId(unsigned int physId, unsigned int layerId, unsigned int numLayersLog2);
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // STATE_NETWORK_H_
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "InfomapIterator.h"
|
||||
#include "../InfoNode.h"
|
||||
#include <iostream>
|
||||
#include <utility> // std::pair
|
||||
|
||||
namespace infomap {
|
||||
|
||||
InfomapIterator& InfomapIterator::operator++() noexcept
|
||||
{
|
||||
const auto root = m_current->getInfomapRoot();
|
||||
auto current = root ? root : m_current;
|
||||
|
||||
if (current->firstChild) {
|
||||
current = current->firstChild;
|
||||
++m_depth;
|
||||
m_path.push_back(1);
|
||||
} else {
|
||||
// Current node is a leaf
|
||||
// Presupposes that the next pointer can't reach out from the current parent.
|
||||
|
||||
bool tryNext = true;
|
||||
|
||||
while (tryNext) {
|
||||
tryNext = false;
|
||||
|
||||
while (!current->next) {
|
||||
if (current->owner) {
|
||||
current = current->owner;
|
||||
|
||||
if (current == m_root) { // Check if back to beginning
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
tryNext = true;
|
||||
break;
|
||||
}
|
||||
if (current->parent) {
|
||||
current = current->parent;
|
||||
--m_depth;
|
||||
m_path.pop_back();
|
||||
|
||||
if (current == m_root) { // Check if back to beginning
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
} else { // null also if no children in first place
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current = current->next;
|
||||
|
||||
if (!current->isLeaf() && (static_cast<unsigned int>(m_moduleIndexLevel) >= m_depth)) {
|
||||
++m_moduleIndex;
|
||||
}
|
||||
|
||||
++m_path.back();
|
||||
}
|
||||
|
||||
m_current = current;
|
||||
return *this;
|
||||
}
|
||||
|
||||
double InfomapIterator::modularCentrality() const noexcept
|
||||
{
|
||||
if (m_current->parent == nullptr) {
|
||||
// The root node has no modular centrality
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
const auto p_m = m_current->parent->data.flow;
|
||||
const auto p_u = m_current->data.flow;
|
||||
const auto p_diff = p_m - p_u;
|
||||
|
||||
if (p_diff > 0.0) {
|
||||
return -p_diff * std::log2(p_diff / p_m);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// InfomapModuleIterator
|
||||
// -------------------------------------
|
||||
|
||||
InfomapIterator& InfomapModuleIterator::operator++() noexcept
|
||||
{
|
||||
InfomapIterator::operator++();
|
||||
while (!isEnd() && m_current->isLeaf()) {
|
||||
InfomapIterator::operator++();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// InfomapLeafModuleIterator
|
||||
// -------------------------------------
|
||||
|
||||
void InfomapLeafModuleIterator::init() noexcept
|
||||
{
|
||||
while (!isEnd() && !m_current->isLeafModule()) {
|
||||
InfomapIterator::operator++();
|
||||
}
|
||||
}
|
||||
|
||||
InfomapIterator& InfomapLeafModuleIterator::operator++() noexcept
|
||||
{
|
||||
InfomapIterator::operator++();
|
||||
while (!isEnd() && !m_current->isLeafModule()) {
|
||||
InfomapIterator::operator++();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// InfomapLeafIterator
|
||||
// -------------------------------------
|
||||
|
||||
void InfomapLeafIterator::init() noexcept
|
||||
{
|
||||
while (!isEnd() && !m_current->isLeaf()) {
|
||||
InfomapIterator::operator++();
|
||||
}
|
||||
}
|
||||
|
||||
InfomapIterator& InfomapLeafIterator::operator++() noexcept
|
||||
{
|
||||
InfomapIterator::operator++();
|
||||
while (!isEnd() && !m_current->isLeaf()) {
|
||||
InfomapIterator::operator++();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// InfomapIteratorPhysical
|
||||
// -------------------------------------
|
||||
|
||||
InfomapIterator& InfomapIteratorPhysical::operator++() noexcept
|
||||
{
|
||||
if (m_physNodes.empty()) {
|
||||
// Iterate modules
|
||||
InfomapIterator::operator++();
|
||||
if (isEnd()) {
|
||||
return *this;
|
||||
}
|
||||
if (m_current->isLeaf()) {
|
||||
// Copy current iterator to restart after iterating through the leaf nodes
|
||||
auto firstLeafIt = *this;
|
||||
// If on a leaf node, loop through and aggregate to physical nodes
|
||||
while (!isEnd() && m_current->isLeaf()) {
|
||||
auto ret = m_physNodes.insert(std::make_pair(m_current->physicalId, InfoNode(*m_current)));
|
||||
auto& physNode = ret.first->second;
|
||||
if (ret.second) {
|
||||
// New physical node, use same parent as the state leaf node
|
||||
physNode.parent = m_current->parent;
|
||||
} else {
|
||||
// Not inserted, add flow to existing physical node
|
||||
// TODO: If exitFlow should be correct, flow between memory nodes within same physical node should be subtracted.
|
||||
physNode.data += m_current->data;
|
||||
}
|
||||
physNode.stateNodes.push_back(m_current->stateId);
|
||||
InfomapIterator::operator++();
|
||||
}
|
||||
// Store current iterator to continue with after iterating physical leaf nodes
|
||||
m_oldIter = *this;
|
||||
// Reset path/depth/moduleIndex to values for first leaf node
|
||||
m_path = firstLeafIt.m_path;
|
||||
m_depth = firstLeafIt.m_depth;
|
||||
m_moduleIndex = firstLeafIt.m_moduleIndex;
|
||||
// Set current node to the first physical node
|
||||
m_physIter = m_physNodes.begin();
|
||||
m_current = &m_physIter->second;
|
||||
}
|
||||
} else {
|
||||
// Iterate physical nodes instead of leaf state nodes
|
||||
++m_physIter;
|
||||
++m_path.back();
|
||||
if (m_physIter == m_physNodes.end()) {
|
||||
// End of leaf nodes
|
||||
m_physNodes.clear();
|
||||
m_path.pop_back();
|
||||
// reset iterator to the one after the leaf nodes
|
||||
*this = m_oldIter;
|
||||
} else {
|
||||
// Set iterator node to the currently iterated physical node
|
||||
m_current = &m_physIter->second;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// InfomapLeafIteratorPhysical
|
||||
// -------------------------------------
|
||||
|
||||
void InfomapLeafIteratorPhysical::init() noexcept
|
||||
{
|
||||
while (!isEnd() && !m_current->isLeaf()) {
|
||||
InfomapIteratorPhysical::operator++();
|
||||
}
|
||||
}
|
||||
|
||||
InfomapIterator& InfomapLeafIteratorPhysical::operator++() noexcept
|
||||
{
|
||||
InfomapIteratorPhysical::operator++();
|
||||
while (!isEnd() && !m_current->isLeaf()) {
|
||||
InfomapIteratorPhysical::operator++();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// InfomapParentIterator
|
||||
// -------------------------------------
|
||||
|
||||
InfomapParentIterator& InfomapParentIterator::operator++() noexcept
|
||||
{
|
||||
m_current = m_current->parent;
|
||||
if (m_current != nullptr && m_current->owner != nullptr) {
|
||||
m_current = m_current->owner;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,341 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOMAP_ITERATOR_H_
|
||||
#define INFOMAP_ITERATOR_H_
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <cmath>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InfoNode;
|
||||
|
||||
/**
|
||||
* Pre processing depth first iterator that explores sub-Infomap instances
|
||||
* Note:
|
||||
* This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
|
||||
*/
|
||||
struct InfomapIterator {
|
||||
protected:
|
||||
InfoNode* m_root = nullptr;
|
||||
InfoNode* m_current = nullptr;
|
||||
int m_moduleIndexLevel = -1;
|
||||
unsigned int m_moduleIndex = 0;
|
||||
std::deque<unsigned int> m_path; // The tree path to current node (indexing starting from one!)
|
||||
unsigned int m_depth = 0;
|
||||
|
||||
public:
|
||||
InfomapIterator() = default;
|
||||
|
||||
InfomapIterator(InfoNode* nodePointer, int moduleIndexLevel = -1)
|
||||
: m_root(nodePointer), m_current(nodePointer), m_moduleIndexLevel(moduleIndexLevel) { }
|
||||
|
||||
virtual ~InfomapIterator() = default;
|
||||
InfomapIterator(const InfomapIterator&) = default;
|
||||
InfomapIterator& operator=(const InfomapIterator&) = default;
|
||||
InfomapIterator(InfomapIterator&&) noexcept = default;
|
||||
InfomapIterator& operator=(InfomapIterator&&) noexcept = default;
|
||||
|
||||
InfoNode* current() noexcept { return m_current; }
|
||||
|
||||
const InfoNode* current() const noexcept { return m_current; }
|
||||
|
||||
InfoNode& operator*() noexcept { return *m_current; }
|
||||
|
||||
const InfoNode& operator*() const noexcept { return *m_current; }
|
||||
|
||||
InfoNode* operator->() noexcept { return m_current; }
|
||||
|
||||
const InfoNode* operator->() const noexcept { return m_current; }
|
||||
|
||||
bool operator==(const InfomapIterator& other) const noexcept { return m_current == other.m_current; }
|
||||
|
||||
bool operator!=(const InfomapIterator& other) const noexcept { return m_current != other.m_current; }
|
||||
|
||||
virtual InfomapIterator& operator++() noexcept;
|
||||
|
||||
virtual InfomapIterator operator++(int) noexcept
|
||||
{
|
||||
InfomapIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
virtual InfomapIterator& stepForward() noexcept
|
||||
{
|
||||
++(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::deque<unsigned int>& path() const noexcept { return m_path; }
|
||||
|
||||
unsigned int moduleIndex() const noexcept { return m_moduleIndex; }
|
||||
|
||||
unsigned int moduleId() const noexcept { return m_moduleIndex + 1; }
|
||||
|
||||
unsigned int childIndex() const noexcept { return m_path.empty() ? 0 : m_path.back() - 1; }
|
||||
|
||||
unsigned int depth() const noexcept { return m_depth; }
|
||||
|
||||
double modularCentrality() const noexcept;
|
||||
|
||||
bool isEnd() const noexcept { return m_current == nullptr; }
|
||||
};
|
||||
|
||||
struct InfomapModuleIterator : public InfomapIterator {
|
||||
public:
|
||||
InfomapModuleIterator() : InfomapIterator() { }
|
||||
|
||||
InfomapModuleIterator(InfoNode* nodePointer, int moduleIndexLevel = -1) : InfomapIterator(nodePointer, moduleIndexLevel) { }
|
||||
|
||||
~InfomapModuleIterator() override = default;
|
||||
InfomapModuleIterator(const InfomapModuleIterator&) = default;
|
||||
InfomapModuleIterator& operator=(const InfomapModuleIterator&) = default;
|
||||
InfomapModuleIterator(InfomapModuleIterator&&) = default;
|
||||
InfomapModuleIterator& operator=(InfomapModuleIterator&&) = default;
|
||||
|
||||
InfomapIterator& operator++() noexcept override;
|
||||
|
||||
InfomapIterator operator++(int) noexcept override
|
||||
{
|
||||
InfomapModuleIterator copy(*this);
|
||||
++(*this);
|
||||
return std::move(copy);
|
||||
}
|
||||
|
||||
using InfomapIterator::childIndex;
|
||||
using InfomapIterator::current;
|
||||
using InfomapIterator::depth;
|
||||
using InfomapIterator::modularCentrality;
|
||||
using InfomapIterator::path;
|
||||
};
|
||||
|
||||
struct InfomapLeafModuleIterator : public InfomapIterator {
|
||||
public:
|
||||
InfomapLeafModuleIterator() : InfomapIterator() { }
|
||||
|
||||
InfomapLeafModuleIterator(InfoNode* nodePointer, int moduleIndexLevel = -1)
|
||||
: InfomapIterator(nodePointer, moduleIndexLevel) { init(); }
|
||||
|
||||
~InfomapLeafModuleIterator() override = default;
|
||||
InfomapLeafModuleIterator(const InfomapLeafModuleIterator& other) : InfomapIterator(other) { init(); }
|
||||
InfomapLeafModuleIterator& operator=(const InfomapLeafModuleIterator&) = default;
|
||||
InfomapLeafModuleIterator(InfomapLeafModuleIterator&&) = default;
|
||||
InfomapLeafModuleIterator& operator=(InfomapLeafModuleIterator&&) = default;
|
||||
|
||||
/**
|
||||
* Iterate to first leaf module
|
||||
*/
|
||||
void init() noexcept;
|
||||
|
||||
InfomapIterator& operator++() noexcept override;
|
||||
|
||||
InfomapIterator operator++(int) noexcept override
|
||||
{
|
||||
InfomapLeafModuleIterator copy(*this);
|
||||
++(*this);
|
||||
return std::move(copy);
|
||||
}
|
||||
|
||||
using InfomapIterator::childIndex;
|
||||
using InfomapIterator::current;
|
||||
using InfomapIterator::depth;
|
||||
using InfomapIterator::modularCentrality;
|
||||
using InfomapIterator::path;
|
||||
};
|
||||
|
||||
struct InfomapLeafIterator : public InfomapIterator {
|
||||
public:
|
||||
InfomapLeafIterator() : InfomapIterator() { }
|
||||
|
||||
InfomapLeafIterator(InfoNode* nodePointer, int moduleIndexLevel = -1)
|
||||
: InfomapIterator(nodePointer, moduleIndexLevel) { init(); }
|
||||
|
||||
~InfomapLeafIterator() override = default;
|
||||
InfomapLeafIterator(const InfomapLeafIterator& other) : InfomapIterator(other) { init(); }
|
||||
InfomapLeafIterator& operator=(const InfomapLeafIterator&) = default;
|
||||
InfomapLeafIterator(InfomapLeafIterator&&) = default;
|
||||
InfomapLeafIterator& operator=(InfomapLeafIterator&&) = default;
|
||||
|
||||
/**
|
||||
* Iterate to first leaf node
|
||||
*/
|
||||
void init() noexcept;
|
||||
|
||||
InfomapIterator& operator++() noexcept override;
|
||||
|
||||
InfomapIterator operator++(int) noexcept override
|
||||
{
|
||||
InfomapLeafIterator copy(*this);
|
||||
++(*this);
|
||||
return std::move(copy);
|
||||
}
|
||||
|
||||
using InfomapIterator::childIndex;
|
||||
using InfomapIterator::current;
|
||||
using InfomapIterator::depth;
|
||||
using InfomapIterator::modularCentrality;
|
||||
using InfomapIterator::path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterate over the whole tree, collecting physical nodes within same leaf modules
|
||||
* Note: The physical nodes are created when entering the parent module and removed
|
||||
* when leaving the module. The tree will not be modified.
|
||||
*/
|
||||
struct InfomapIteratorPhysical : public InfomapIterator {
|
||||
protected:
|
||||
std::map<unsigned int, InfoNode> m_physNodes;
|
||||
std::map<unsigned int, InfoNode>::iterator m_physIter;
|
||||
InfomapIterator m_oldIter;
|
||||
|
||||
public:
|
||||
InfomapIteratorPhysical() : InfomapIterator() { }
|
||||
|
||||
InfomapIteratorPhysical(InfoNode* nodePointer, int moduleIndexLevel = -1)
|
||||
: InfomapIterator(nodePointer, moduleIndexLevel) { }
|
||||
|
||||
~InfomapIteratorPhysical() override = default;
|
||||
InfomapIteratorPhysical(const InfomapIteratorPhysical&) = default;
|
||||
InfomapIteratorPhysical(const InfomapIterator& other) : InfomapIterator(other) { }
|
||||
|
||||
InfomapIteratorPhysical(InfomapIteratorPhysical&&) = default;
|
||||
InfomapIteratorPhysical& operator=(const InfomapIteratorPhysical&) = default;
|
||||
|
||||
// Don't allow moving from this iterator as we use the old iterator in operator++
|
||||
InfomapIteratorPhysical& operator=(InfomapIteratorPhysical&&) = delete;
|
||||
|
||||
InfomapIteratorPhysical& operator=(const InfomapIterator& other)
|
||||
{
|
||||
InfomapIterator::operator=(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfomapIterator& operator++() noexcept override;
|
||||
|
||||
InfomapIterator operator++(int) noexcept override
|
||||
{
|
||||
InfomapIteratorPhysical copy(*this);
|
||||
++(*this);
|
||||
return std::move(copy);
|
||||
}
|
||||
|
||||
using InfomapIterator::childIndex;
|
||||
using InfomapIterator::current;
|
||||
using InfomapIterator::depth;
|
||||
using InfomapIterator::modularCentrality;
|
||||
using InfomapIterator::path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterate over all physical leaf nodes, joining physical nodes within same leaf modules
|
||||
* Note: The physical nodes are created when entering the parent module and removed
|
||||
* when leaving the module. The tree will not be modified.
|
||||
*/
|
||||
struct InfomapLeafIteratorPhysical : public InfomapIteratorPhysical {
|
||||
public:
|
||||
InfomapLeafIteratorPhysical() : InfomapIteratorPhysical() { }
|
||||
|
||||
InfomapLeafIteratorPhysical(InfoNode* nodePointer, int moduleIndexLevel = -1)
|
||||
: InfomapIteratorPhysical(nodePointer, moduleIndexLevel) { init(); }
|
||||
|
||||
InfomapLeafIteratorPhysical(const InfomapLeafIteratorPhysical& other)
|
||||
: InfomapIteratorPhysical(other) { init(); }
|
||||
|
||||
~InfomapLeafIteratorPhysical() override = default;
|
||||
InfomapLeafIteratorPhysical(InfomapLeafIteratorPhysical&&) = default;
|
||||
InfomapLeafIteratorPhysical& operator=(const InfomapLeafIteratorPhysical&) = default;
|
||||
|
||||
// Don't allow moving from this iterator as we use the old iterator in operator++
|
||||
InfomapLeafIteratorPhysical& operator=(InfomapLeafIteratorPhysical&&) = delete;
|
||||
|
||||
/**
|
||||
* Iterate to first leaf node
|
||||
*/
|
||||
void init() noexcept;
|
||||
|
||||
InfomapIterator& operator++() noexcept override;
|
||||
|
||||
InfomapIterator operator++(int) noexcept override
|
||||
{
|
||||
InfomapLeafIteratorPhysical copy(*this);
|
||||
++(*this);
|
||||
return std::move(copy);
|
||||
}
|
||||
|
||||
using InfomapIteratorPhysical::childIndex;
|
||||
using InfomapIteratorPhysical::current;
|
||||
using InfomapIteratorPhysical::depth;
|
||||
using InfomapIteratorPhysical::modularCentrality;
|
||||
using InfomapIteratorPhysical::path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterate parent by parent until it is nullptr,
|
||||
* moving up through possible sub infomap instances
|
||||
* on the way
|
||||
*/
|
||||
struct InfomapParentIterator {
|
||||
protected:
|
||||
InfoNode* m_current = nullptr;
|
||||
|
||||
public:
|
||||
InfomapParentIterator() = default;
|
||||
|
||||
InfomapParentIterator(InfoNode* nodePointer) : m_current(nodePointer) { }
|
||||
|
||||
~InfomapParentIterator() = default;
|
||||
|
||||
InfomapParentIterator(const InfomapParentIterator&) = default;
|
||||
|
||||
InfomapParentIterator& operator=(const InfomapParentIterator&) = default;
|
||||
|
||||
InfomapParentIterator(InfomapParentIterator&&) = default;
|
||||
|
||||
InfomapParentIterator& operator=(InfomapParentIterator&&) = default;
|
||||
|
||||
InfoNode* current() noexcept { return m_current; }
|
||||
|
||||
const InfoNode* current() const noexcept { return m_current; }
|
||||
|
||||
InfoNode& operator*() noexcept { return *m_current; }
|
||||
|
||||
const InfoNode& operator*() const noexcept { return *m_current; }
|
||||
|
||||
InfoNode* operator->() noexcept { return m_current; }
|
||||
|
||||
const InfoNode* operator->() const noexcept { return m_current; }
|
||||
|
||||
bool operator==(const InfomapParentIterator& other) const noexcept { return m_current == other.m_current; }
|
||||
|
||||
bool operator!=(const InfomapParentIterator& other) const noexcept { return m_current != other.m_current; }
|
||||
|
||||
InfomapParentIterator& operator++() noexcept;
|
||||
|
||||
InfomapParentIterator operator++(int) noexcept
|
||||
{
|
||||
InfomapParentIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
InfomapParentIterator& stepForward() noexcept
|
||||
{
|
||||
++(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isEnd() const noexcept { return m_current == nullptr; }
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // INFOMAP_ITERATOR_H_
|
||||
@@ -0,0 +1,32 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef ITER_WRAPPER_H_
|
||||
#define ITER_WRAPPER_H_
|
||||
|
||||
namespace infomap {
|
||||
|
||||
template <typename Iter>
|
||||
class IterWrapper {
|
||||
Iter m_begin, m_end;
|
||||
|
||||
public:
|
||||
IterWrapper(Iter begin, Iter end) : m_begin(begin), m_end(end) { }
|
||||
|
||||
template <typename Container>
|
||||
IterWrapper(Container& container) : m_begin(container.begin()), m_end(container.end()) { }
|
||||
|
||||
Iter begin() noexcept { return m_begin; };
|
||||
|
||||
Iter end() noexcept { return m_end; };
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // ITER_WRAPPER_H_
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOMAP_ITERATORS_H_
|
||||
#define INFOMAP_ITERATORS_H_
|
||||
|
||||
#include "treeIterators.h"
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
/**
|
||||
* Child iterator.
|
||||
*/
|
||||
template <typename NodePointerType> // pointer or const pointer
|
||||
class InfomapChildIterator {
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using value_type = typename iterator_traits<NodePointerType>::value_type;
|
||||
using difference_type = typename iterator_traits<NodePointerType>::difference_type;
|
||||
using reference = typename iterator_traits<NodePointerType>::reference;
|
||||
using pointer = typename iterator_traits<NodePointerType>::pointer;
|
||||
|
||||
protected:
|
||||
NodePointerType m_root = nullptr;
|
||||
NodePointerType m_current = nullptr;
|
||||
|
||||
public:
|
||||
InfomapChildIterator() = default;
|
||||
|
||||
InfomapChildIterator(const NodePointerType& nodePointer)
|
||||
: m_root(nodePointer), m_current(nodePointer) { init(); }
|
||||
|
||||
InfomapChildIterator(const InfomapChildIterator& other)
|
||||
: m_root(other.m_root), m_current(other.m_current) { }
|
||||
|
||||
InfomapChildIterator& operator=(const InfomapChildIterator& other)
|
||||
{
|
||||
m_root = other.m_root;
|
||||
m_current = other.m_current;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
if (m_root != nullptr) {
|
||||
NodePointerType infomapRoot = m_root->getInfomapRoot();
|
||||
if (infomapRoot != nullptr) {
|
||||
m_root = infomapRoot;
|
||||
}
|
||||
}
|
||||
m_current = m_root == nullptr ? nullptr : m_root->firstChild;
|
||||
}
|
||||
|
||||
pointer current() const { return m_current; }
|
||||
|
||||
reference operator*() const { return *m_current; }
|
||||
|
||||
pointer operator->() const { return m_current; }
|
||||
|
||||
bool operator==(const InfomapChildIterator& rhs) const { return m_current == rhs.m_current; }
|
||||
|
||||
bool operator!=(const InfomapChildIterator& rhs) const { return m_current != rhs.m_current; }
|
||||
|
||||
bool isEnd() const { return m_current == nullptr; }
|
||||
|
||||
InfomapChildIterator& operator++()
|
||||
{
|
||||
m_current = m_current->next;
|
||||
if (m_current != nullptr && m_current->parent != m_root) {
|
||||
m_current = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfomapChildIterator operator++(int)
|
||||
{
|
||||
InfomapChildIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
InfomapChildIterator& operator--()
|
||||
{
|
||||
m_current = m_current->previous;
|
||||
if (m_current != nullptr && m_current->parent != m_root) {
|
||||
m_current = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfomapChildIterator operator--(int)
|
||||
{
|
||||
InfomapChildIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pre processing depth first iterator that explores sub-Infomap instances
|
||||
* Note: This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
|
||||
*/
|
||||
template <typename NodePointerType>
|
||||
class InfomapClusterIterator : public DepthFirstIteratorBase<NodePointerType> {
|
||||
protected:
|
||||
using Base = DepthFirstIteratorBase<NodePointerType>;
|
||||
|
||||
unsigned int m_moduleIndex = 0;
|
||||
int m_moduleIndexLevel = -1;
|
||||
|
||||
using Base::m_current;
|
||||
using Base::m_depth;
|
||||
|
||||
public:
|
||||
InfomapClusterIterator() : Base() { }
|
||||
|
||||
InfomapClusterIterator(const NodePointerType& nodePointer, int moduleIndexLevel = -1)
|
||||
: Base(nodePointer), m_moduleIndexLevel(moduleIndexLevel)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
InfomapClusterIterator(const InfomapClusterIterator& other)
|
||||
: Base(other), m_moduleIndex(other.m_moduleIndex), m_moduleIndexLevel(other.m_moduleIndexLevel) { }
|
||||
|
||||
virtual void init() { moveToInfomapRootIfExist(); }
|
||||
|
||||
void moveToInfomapRootIfExist()
|
||||
{
|
||||
if (m_current != nullptr) {
|
||||
NodePointerType infomapRoot = m_current->getInfomapRoot();
|
||||
if (infomapRoot != nullptr) {
|
||||
m_current = infomapRoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InfomapClusterIterator& operator=(const InfomapClusterIterator& other)
|
||||
{
|
||||
Base::operator=(other);
|
||||
m_moduleIndex = other.m_moduleIndex;
|
||||
m_moduleIndexLevel = other.m_moduleIndexLevel;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfomapClusterIterator& operator++()
|
||||
{
|
||||
NodePointerType curr = Base::m_current;
|
||||
|
||||
if (curr->firstChild != nullptr) {
|
||||
curr = curr->firstChild;
|
||||
++m_depth;
|
||||
} else {
|
||||
// Current node is a leaf
|
||||
// Presupposes that the next pointer can't reach out from the current parent.
|
||||
tryNext:
|
||||
while (curr->next == nullptr) {
|
||||
if (curr->parent != nullptr) {
|
||||
curr = curr->parent;
|
||||
--m_depth;
|
||||
if (curr == Base::m_root) // Check if back to beginning
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
if (m_moduleIndexLevel < 0) {
|
||||
if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom
|
||||
++m_moduleIndex;
|
||||
} else if (static_cast<unsigned int>(m_moduleIndexLevel) == m_depth)
|
||||
++m_moduleIndex;
|
||||
} else {
|
||||
NodePointerType infomapOwner = curr->owner;
|
||||
if (infomapOwner != nullptr) {
|
||||
curr = infomapOwner;
|
||||
if (curr == Base::m_root) // Check if back to beginning
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
goto tryNext;
|
||||
} else // null also if no children in first place
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
}
|
||||
curr = curr->next;
|
||||
}
|
||||
m_current = curr;
|
||||
moveToInfomapRootIfExist();
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfomapClusterIterator operator++(int)
|
||||
{
|
||||
InfomapClusterIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
InfomapClusterIterator next()
|
||||
{
|
||||
InfomapClusterIterator copy(*this);
|
||||
return ++copy;
|
||||
}
|
||||
|
||||
InfomapClusterIterator& stepForward()
|
||||
{
|
||||
++(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
unsigned int moduleIndex() const
|
||||
{
|
||||
return m_moduleIndex;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pre processing depth first iterator that explores sub-Infomap instances
|
||||
* Note: This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
|
||||
*/
|
||||
template <typename NodePointerType>
|
||||
class InfomapDepthFirstIterator : public DepthFirstIteratorBase<NodePointerType> {
|
||||
protected:
|
||||
using Base = DepthFirstIteratorBase<NodePointerType>;
|
||||
|
||||
std::deque<unsigned int> m_path; // The child index path to current node
|
||||
unsigned int m_moduleIndex = 0;
|
||||
int m_moduleIndexLevel = -1;
|
||||
|
||||
using Base::m_current;
|
||||
using Base::m_depth;
|
||||
|
||||
public:
|
||||
InfomapDepthFirstIterator() : Base() { }
|
||||
|
||||
InfomapDepthFirstIterator(const NodePointerType& nodePointer, int moduleIndexLevel = -1)
|
||||
: Base(nodePointer),
|
||||
m_moduleIndexLevel(moduleIndexLevel)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
InfomapDepthFirstIterator(const InfomapDepthFirstIterator& other)
|
||||
: Base(other),
|
||||
m_path(other.m_path),
|
||||
m_moduleIndex(other.m_moduleIndex),
|
||||
m_moduleIndexLevel(other.m_moduleIndexLevel)
|
||||
{
|
||||
}
|
||||
|
||||
InfomapDepthFirstIterator& operator=(const InfomapDepthFirstIterator& other)
|
||||
{
|
||||
Base::operator=(other);
|
||||
m_path = other.m_path;
|
||||
m_moduleIndex = other.m_moduleIndex;
|
||||
m_moduleIndexLevel = other.m_moduleIndexLevel;
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual void init()
|
||||
{
|
||||
moveToInfomapRootIfExist();
|
||||
}
|
||||
|
||||
void moveToInfomapRootIfExist()
|
||||
{
|
||||
if (m_current != nullptr) {
|
||||
NodePointerType infomapRoot = m_current->getInfomapRoot();
|
||||
if (infomapRoot != nullptr) {
|
||||
m_current = infomapRoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InfomapDepthFirstIterator& operator++()
|
||||
{
|
||||
NodePointerType curr = Base::m_current;
|
||||
|
||||
if (curr->firstChild != nullptr) {
|
||||
curr = curr->firstChild;
|
||||
++m_depth;
|
||||
m_path.push_back(0);
|
||||
} else {
|
||||
// Current node is a leaf
|
||||
// Presupposes that the next pointer can't reach out from the current parent.
|
||||
tryNext:
|
||||
while (curr->next == nullptr) {
|
||||
if (curr->parent != nullptr) {
|
||||
curr = curr->parent;
|
||||
--m_depth;
|
||||
m_path.pop_back();
|
||||
if (curr == Base::m_root) // Check if back to beginning
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
if (m_moduleIndexLevel < 0) {
|
||||
if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom
|
||||
++m_moduleIndex;
|
||||
} else if (static_cast<unsigned int>(m_moduleIndexLevel) == m_depth)
|
||||
++m_moduleIndex;
|
||||
} else {
|
||||
NodePointerType infomapOwner = curr->owner;
|
||||
if (infomapOwner != nullptr) {
|
||||
curr = infomapOwner;
|
||||
if (curr == Base::m_root) // Check if back to beginning
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
goto tryNext;
|
||||
} else // null also if no children in first place
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
}
|
||||
curr = curr->next;
|
||||
++m_path.back();
|
||||
}
|
||||
m_current = curr;
|
||||
moveToInfomapRootIfExist();
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfomapDepthFirstIterator operator++(int)
|
||||
{
|
||||
InfomapDepthFirstIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
InfomapDepthFirstIterator next()
|
||||
{
|
||||
InfomapDepthFirstIterator copy(*this);
|
||||
return ++copy;
|
||||
}
|
||||
|
||||
InfomapDepthFirstIterator& stepForward()
|
||||
{
|
||||
++(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::deque<unsigned int>& path() const
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
unsigned int moduleIndex() const
|
||||
{
|
||||
return m_moduleIndex;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // INFOMAP_ITERATORS_H_
|
||||
@@ -0,0 +1,628 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef TREE_ITERATORS_H_
|
||||
#define TREE_ITERATORS_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <deque>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
#ifndef ASSERT
|
||||
#include <cassert>
|
||||
#define ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
using std::iterator_traits;
|
||||
|
||||
/**
|
||||
* Child iterator.
|
||||
*/
|
||||
template <typename NodePointerType> // pointer or const pointer
|
||||
class ChildIterator {
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using value_type = typename iterator_traits<NodePointerType>::value_type;
|
||||
using difference_type = typename iterator_traits<NodePointerType>::difference_type;
|
||||
using reference = typename iterator_traits<NodePointerType>::reference;
|
||||
using pointer = typename iterator_traits<NodePointerType>::pointer;
|
||||
|
||||
protected:
|
||||
NodePointerType m_root = nullptr;
|
||||
NodePointerType m_current = nullptr;
|
||||
|
||||
public:
|
||||
ChildIterator() = default;
|
||||
|
||||
ChildIterator(const NodePointerType& nodePointer)
|
||||
: m_root(nodePointer), m_current(nodePointer == nullptr ? nullptr : nodePointer->firstChild) { }
|
||||
|
||||
ChildIterator(const ChildIterator& other)
|
||||
: m_root(other.m_root), m_current(other.m_current) { }
|
||||
|
||||
ChildIterator& operator=(const ChildIterator& other)
|
||||
{
|
||||
m_root = other.m_root;
|
||||
m_current = other.m_current;
|
||||
return *this;
|
||||
}
|
||||
|
||||
pointer current() const { return m_current; }
|
||||
|
||||
reference operator*() const { return *m_current; }
|
||||
|
||||
pointer operator->() const { return m_current; }
|
||||
|
||||
bool operator==(const ChildIterator& rhs) const { return m_current == rhs.m_current; }
|
||||
|
||||
bool operator!=(const ChildIterator& rhs) const { return !(m_current == rhs.m_current); }
|
||||
|
||||
bool isEnd() const { return m_current == nullptr; }
|
||||
|
||||
ChildIterator& operator++()
|
||||
{
|
||||
m_current = m_current->next;
|
||||
if (m_current != nullptr && m_current->parent != m_root) {
|
||||
m_current = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChildIterator operator++(int)
|
||||
{
|
||||
ChildIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
ChildIterator& operator--()
|
||||
{
|
||||
m_current = m_current->previous;
|
||||
if (m_current != nullptr && m_current->parent != m_root) {
|
||||
m_current = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChildIterator operator--(int)
|
||||
{
|
||||
ChildIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Tree iterator.
|
||||
*/
|
||||
template <typename NodePointerType> // pointer or const pointer
|
||||
class TreeIterator {
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = typename iterator_traits<NodePointerType>::value_type;
|
||||
using difference_type = typename iterator_traits<NodePointerType>::difference_type;
|
||||
using reference = typename iterator_traits<NodePointerType>::reference;
|
||||
using pointer = typename iterator_traits<NodePointerType>::pointer;
|
||||
|
||||
protected:
|
||||
NodePointerType m_root = nullptr;
|
||||
NodePointerType m_current = nullptr;
|
||||
int m_moduleIndexLevel = -1;
|
||||
unsigned int m_moduleIndex = 0;
|
||||
std::deque<unsigned int> m_path; // The child index path to current node
|
||||
unsigned int m_depth = 0;
|
||||
|
||||
public:
|
||||
TreeIterator() = default;
|
||||
|
||||
TreeIterator(NodePointerType nodePointer, int moduleIndexLevel = -1)
|
||||
: m_root(nodePointer),
|
||||
m_current(nodePointer),
|
||||
m_moduleIndexLevel(moduleIndexLevel) { }
|
||||
|
||||
TreeIterator(const TreeIterator& other)
|
||||
: m_root(other.m_root),
|
||||
m_current(other.m_current),
|
||||
m_moduleIndexLevel(other.m_moduleIndexLevel),
|
||||
m_moduleIndex(other.m_moduleIndex),
|
||||
m_path(other.m_path),
|
||||
m_depth(other.m_depth) { }
|
||||
|
||||
virtual ~TreeIterator() = default;
|
||||
|
||||
TreeIterator& operator=(const TreeIterator& other)
|
||||
{
|
||||
m_root = other.m_root;
|
||||
m_current = other.m_current;
|
||||
m_moduleIndexLevel = other.m_moduleIndexLevel;
|
||||
m_moduleIndex = other.m_moduleIndex;
|
||||
m_path = other.m_path;
|
||||
m_depth = other.m_depth;
|
||||
return *this;
|
||||
}
|
||||
|
||||
pointer current() const { return m_current; }
|
||||
|
||||
reference operator*() const { return *m_current; }
|
||||
|
||||
pointer operator->() const { return m_current; }
|
||||
|
||||
bool operator==(const TreeIterator& rhs) const { return m_current == rhs.m_current; }
|
||||
|
||||
bool operator!=(const TreeIterator& rhs) const { return !(m_current == rhs.m_current); }
|
||||
|
||||
const std::deque<unsigned int>& path() const { return m_path; }
|
||||
|
||||
unsigned int moduleIndex() const { return m_moduleIndex; }
|
||||
|
||||
unsigned int depth() const { return m_depth; }
|
||||
|
||||
bool isEnd() const { return m_current == nullptr; }
|
||||
|
||||
TreeIterator& operator++()
|
||||
{
|
||||
NodePointerType curr = m_current;
|
||||
NodePointerType infomapRoot = curr->getInfomapRoot();
|
||||
if (infomapRoot != nullptr) {
|
||||
curr = infomapRoot;
|
||||
}
|
||||
|
||||
if (curr->firstChild != nullptr) {
|
||||
curr = curr->firstChild;
|
||||
++m_depth;
|
||||
m_path.push_back(0);
|
||||
} else {
|
||||
// Current node is a leaf
|
||||
// Presupposes that the next pointer can't reach out from the current parent.
|
||||
tryNext:
|
||||
while (curr->next == nullptr) {
|
||||
if (curr->parent != nullptr) {
|
||||
curr = curr->parent;
|
||||
--m_depth;
|
||||
m_path.pop_back();
|
||||
if (curr == m_root) // Check if back to beginning
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
if (m_moduleIndexLevel < 0) {
|
||||
if (curr->isLeafModule()) // TODO: Generalize to -2 for second level to bottom
|
||||
++m_moduleIndex;
|
||||
} else if (static_cast<unsigned int>(m_moduleIndexLevel) == m_depth)
|
||||
++m_moduleIndex;
|
||||
} else {
|
||||
NodePointerType infomapOwner = curr->owner;
|
||||
if (infomapOwner != nullptr) {
|
||||
curr = infomapOwner;
|
||||
if (curr == m_root) // Check if back to beginning
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
goto tryNext;
|
||||
} else // null also if no children in first place
|
||||
{
|
||||
m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
}
|
||||
curr = curr->next;
|
||||
++m_path.back();
|
||||
}
|
||||
m_current = curr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TreeIterator operator++(int)
|
||||
{
|
||||
TreeIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
TreeIterator& stepForward()
|
||||
{
|
||||
++(*this);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Base node iterator.
|
||||
*/
|
||||
template <typename NodePointerType, typename iterator_tag = std::bidirectional_iterator_tag>
|
||||
struct node_iterator_base {
|
||||
using iterator_category = iterator_tag;
|
||||
using value_type = typename iterator_traits<NodePointerType>::value_type;
|
||||
using difference_type = typename iterator_traits<NodePointerType>::difference_type;
|
||||
using reference = typename iterator_traits<NodePointerType>::reference;
|
||||
using pointer = typename iterator_traits<NodePointerType>::pointer;
|
||||
|
||||
node_iterator_base() : m_current(nullptr) { }
|
||||
|
||||
node_iterator_base(const NodePointerType& nodePointer) : m_current(nodePointer) { }
|
||||
|
||||
node_iterator_base(const node_iterator_base& other) : m_current(other.m_current) { }
|
||||
|
||||
node_iterator_base& operator=(const node_iterator_base& other)
|
||||
{
|
||||
m_current = other.m_current;
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual ~node_iterator_base() = default;
|
||||
|
||||
pointer base() const { return m_current; }
|
||||
|
||||
reference operator*() const { return *m_current; }
|
||||
|
||||
pointer operator->() const { return m_current; }
|
||||
|
||||
bool operator==(const node_iterator_base& rhs) const { return m_current == rhs.m_current; }
|
||||
|
||||
bool operator!=(const node_iterator_base& rhs) const { return !(m_current == rhs.m_current); }
|
||||
|
||||
bool isEnd() const { return m_current == nullptr; }
|
||||
|
||||
protected:
|
||||
NodePointerType m_current;
|
||||
};
|
||||
|
||||
template <typename NodePointerType>
|
||||
class DepthFirstIteratorBase : public node_iterator_base<NodePointerType> {
|
||||
using Base = node_iterator_base<NodePointerType>;
|
||||
|
||||
public:
|
||||
DepthFirstIteratorBase() : Base(), m_root(nullptr), m_depth(0) { }
|
||||
|
||||
DepthFirstIteratorBase(const NodePointerType& nodePointer) : Base(nodePointer), m_root(nodePointer), m_depth(0) { }
|
||||
|
||||
DepthFirstIteratorBase(const DepthFirstIteratorBase& other) : Base(other), m_root(other.m_root), m_depth(other.m_depth) { }
|
||||
|
||||
DepthFirstIteratorBase& operator=(const DepthFirstIteratorBase& other)
|
||||
{
|
||||
Base::operator=(other);
|
||||
m_root = other.m_root;
|
||||
m_depth = other.m_depth;
|
||||
return *this;
|
||||
}
|
||||
|
||||
unsigned int depth() const { return m_depth; }
|
||||
|
||||
protected:
|
||||
NodePointerType m_root;
|
||||
unsigned int m_depth;
|
||||
using Base::m_current;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pre processing depth first iterator
|
||||
* Note:
|
||||
* This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
|
||||
*/
|
||||
template <typename NodePointerType, bool pre_t = true>
|
||||
class DepthFirstIterator : public DepthFirstIteratorBase<NodePointerType> {
|
||||
using Base = DepthFirstIteratorBase<NodePointerType>;
|
||||
|
||||
public:
|
||||
DepthFirstIterator() : Base() { }
|
||||
|
||||
DepthFirstIterator(const NodePointerType& nodePointer) : Base(nodePointer) { }
|
||||
|
||||
DepthFirstIterator(const DepthFirstIterator& other) : Base(other) { }
|
||||
|
||||
DepthFirstIterator& operator=(const DepthFirstIterator& other)
|
||||
{
|
||||
Base::operator=(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DepthFirstIterator& operator++()
|
||||
{
|
||||
NodePointerType curr = Base::m_current;
|
||||
if (curr->firstChild != nullptr) {
|
||||
curr = curr->firstChild;
|
||||
++Base::m_depth;
|
||||
} else {
|
||||
// Presupposes that the next pointer can't reach out from the current parent.
|
||||
while (curr->next == nullptr) {
|
||||
curr = curr->parent;
|
||||
--Base::m_depth;
|
||||
if (curr == Base::m_root || curr == nullptr) // 0 if no children in first place
|
||||
{
|
||||
Base::m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
curr = curr->next;
|
||||
}
|
||||
Base::m_current = curr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DepthFirstIterator operator++(int)
|
||||
{
|
||||
auto copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
DepthFirstIterator next()
|
||||
{
|
||||
auto copy(*this);
|
||||
return ++copy;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Post processing depth first iterator
|
||||
* Note:
|
||||
* This iterator presupposes that the next pointer of a node can't reach a node with a different parent.
|
||||
*/
|
||||
template <typename NodePointerType>
|
||||
class DepthFirstIterator<NodePointerType, false> : public DepthFirstIteratorBase<NodePointerType> {
|
||||
using Base = DepthFirstIteratorBase<NodePointerType>;
|
||||
|
||||
public:
|
||||
DepthFirstIterator() : Base() { }
|
||||
|
||||
DepthFirstIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); }
|
||||
|
||||
DepthFirstIterator(const DepthFirstIterator& other) : Base(other) { }
|
||||
|
||||
DepthFirstIterator& operator=(const DepthFirstIterator& other)
|
||||
{
|
||||
Base::operator=(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
if (Base::m_current != nullptr) {
|
||||
while (Base::m_current->firstChild != nullptr) {
|
||||
Base::m_current = Base::m_current->firstChild;
|
||||
++Base::m_depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DepthFirstIterator& operator++()
|
||||
{
|
||||
// The root should be the last node
|
||||
if (Base::m_current == Base::m_root) {
|
||||
Base::m_current = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
NodePointerType curr = Base::m_current;
|
||||
if (curr->next != nullptr) {
|
||||
curr = curr->next;
|
||||
while (curr->firstChild != nullptr) {
|
||||
curr = curr->firstChild;
|
||||
++Base::m_depth;
|
||||
}
|
||||
} else {
|
||||
curr = curr->parent;
|
||||
--Base::m_depth;
|
||||
}
|
||||
|
||||
Base::m_current = curr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
DepthFirstIterator operator++(int)
|
||||
{
|
||||
DepthFirstIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
DepthFirstIterator
|
||||
next()
|
||||
{
|
||||
DepthFirstIterator copy(*this);
|
||||
return ++copy;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaf node iterator
|
||||
*/
|
||||
template <typename NodePointerType>
|
||||
class LeafNodeIterator : public DepthFirstIteratorBase<NodePointerType> {
|
||||
using Base = DepthFirstIteratorBase<NodePointerType>;
|
||||
|
||||
public:
|
||||
LeafNodeIterator() : Base() { }
|
||||
|
||||
LeafNodeIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); }
|
||||
|
||||
LeafNodeIterator(const LeafNodeIterator& other) : Base(other) { }
|
||||
|
||||
LeafNodeIterator& operator=(const LeafNodeIterator& other)
|
||||
{
|
||||
Base::operator=(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
if (Base::m_current != nullptr) {
|
||||
while (Base::m_current->firstChild != nullptr) {
|
||||
Base::m_current = Base::m_current->firstChild;
|
||||
++Base::m_depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LeafNodeIterator& operator++()
|
||||
{
|
||||
ASSERT(Base::m_current != nullptr);
|
||||
while (Base::m_current->next == nullptr || Base::m_current->next->parent != Base::m_current->parent) {
|
||||
Base::m_current = Base::m_current->parent;
|
||||
--Base::m_depth;
|
||||
if (Base::m_current == nullptr)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Base::m_current = Base::m_current->next;
|
||||
|
||||
if (Base::m_current != nullptr) {
|
||||
while (Base::m_current->firstChild != nullptr) {
|
||||
Base::m_current = Base::m_current->firstChild;
|
||||
++Base::m_depth;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
LeafNodeIterator operator++(int)
|
||||
{
|
||||
LeafNodeIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
LeafNodeIterator next()
|
||||
{
|
||||
LeafNodeIterator copy(*this);
|
||||
return ++copy;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaf module iterator
|
||||
*/
|
||||
template <typename NodePointerType>
|
||||
class LeafModuleIterator : public DepthFirstIteratorBase<NodePointerType> {
|
||||
using Base = DepthFirstIteratorBase<NodePointerType>;
|
||||
|
||||
public:
|
||||
LeafModuleIterator() : Base() { }
|
||||
|
||||
LeafModuleIterator(const NodePointerType& nodePointer) : Base(nodePointer) { init(); }
|
||||
|
||||
LeafModuleIterator(const LeafModuleIterator& other) : Base(other) { }
|
||||
|
||||
LeafModuleIterator& operator=(const LeafModuleIterator& other)
|
||||
{
|
||||
Base::operator=(other);
|
||||
init();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
if (Base::m_current != nullptr) {
|
||||
if (Base::m_current->firstChild == nullptr) {
|
||||
Base::m_current = nullptr; // End directly if no module
|
||||
} else {
|
||||
while (Base::m_current->firstChild->firstChild != nullptr) {
|
||||
Base::m_current = Base::m_current->firstChild;
|
||||
++Base::m_depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LeafModuleIterator& operator++()
|
||||
{
|
||||
ASSERT(Base::m_current != nullptr);
|
||||
while (Base::m_current->next == nullptr || Base::m_current->next->parent != Base::m_current->parent) {
|
||||
Base::m_current = Base::m_current->parent;
|
||||
--Base::m_depth;
|
||||
if (Base::m_current == nullptr)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Base::m_current = Base::m_current->next;
|
||||
|
||||
if (Base::m_current != nullptr) {
|
||||
if (Base::m_current->firstChild == nullptr) {
|
||||
Base::m_current = Base::m_current->parent;
|
||||
} else {
|
||||
while (Base::m_current->firstChild->firstChild != nullptr) {
|
||||
Base::m_current = Base::m_current->firstChild;
|
||||
++Base::m_depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
LeafModuleIterator operator++(int)
|
||||
{
|
||||
LeafModuleIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
LeafModuleIterator next()
|
||||
{
|
||||
LeafModuleIterator copy(*this);
|
||||
return ++copy;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sibling iterator.
|
||||
*/
|
||||
template <typename NodePointerType> // pointer or const pointer
|
||||
class SiblingIterator : public node_iterator_base<NodePointerType> {
|
||||
using Base = node_iterator_base<NodePointerType>;
|
||||
|
||||
public:
|
||||
using self_type = SiblingIterator<NodePointerType>;
|
||||
|
||||
SiblingIterator() : Base() { }
|
||||
|
||||
SiblingIterator(const NodePointerType& nodePointer) : Base(nodePointer) { }
|
||||
|
||||
SiblingIterator(const SiblingIterator& other) : Base(other) { }
|
||||
|
||||
SiblingIterator& operator=(const SiblingIterator& other)
|
||||
{
|
||||
Base::operator=(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SiblingIterator& operator++()
|
||||
{
|
||||
ASSERT(Base::m_current != nullptr);
|
||||
Base::m_current = Base::m_current->next;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SiblingIterator operator++(int)
|
||||
{
|
||||
SiblingIterator copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
SiblingIterator& operator--()
|
||||
{
|
||||
ASSERT(Base::m_current != nullptr);
|
||||
Base::m_current = Base::m_current->previous;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SiblingIterator operator--(int)
|
||||
{
|
||||
SiblingIterator copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // TREE_ITERATORS_H_
|
||||
@@ -0,0 +1,189 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "ClusterMap.h"
|
||||
#include "SafeFile.h"
|
||||
#include "../utils/Log.h"
|
||||
#include "../utils/FileURI.h"
|
||||
#include <sstream>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
void ClusterMap::readClusterData(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId)
|
||||
{
|
||||
FileURI file(filename);
|
||||
m_extension = file.getExtension();
|
||||
if (m_extension == "tree" || m_extension == "ftree") {
|
||||
return readTree(filename, includeFlow, layerNodeToStateId);
|
||||
}
|
||||
if (m_extension == "clu") {
|
||||
return readClu(filename, includeFlow, layerNodeToStateId);
|
||||
}
|
||||
throw std::runtime_error(io::Str() << "Input cluster data from file '" << filename << "' is of unknown extension '" << m_extension << "'. Must be 'clu' or 'tree'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample from .tree file
|
||||
# Codelength = 3.46227314 bits.
|
||||
# path flow name physicalId
|
||||
1:1:1 0.0384615 "1" 1
|
||||
1:1:2 0.025641 "2" 2
|
||||
1:1:3 0.0384615 "3" 3
|
||||
1:2:1 0.0384615 "4" 4
|
||||
*/
|
||||
void ClusterMap::readTree(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId)
|
||||
{
|
||||
bool isMultilayer = layerNodeToStateId != nullptr;
|
||||
|
||||
SafeInFile input(filename);
|
||||
std::string line;
|
||||
std::istringstream lineStream;
|
||||
std::istringstream pathStream;
|
||||
m_nodePaths.clear();
|
||||
|
||||
unsigned int lineNr = 0;
|
||||
|
||||
while (!std::getline(input, line).fail()) {
|
||||
++lineNr;
|
||||
if (line.length() == 0)
|
||||
continue;
|
||||
if (line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
if (line[0] == '*') {
|
||||
break;
|
||||
}
|
||||
|
||||
lineStream.clear();
|
||||
lineStream.str(line);
|
||||
|
||||
std::string pathString;
|
||||
double flow;
|
||||
std::string name;
|
||||
unsigned int stateId;
|
||||
unsigned int nodeId;
|
||||
unsigned int layerId;
|
||||
if (!(lineStream >> pathString))
|
||||
throw std::runtime_error(io::Str() << "Couldn't parse tree path from line '" << line << "'");
|
||||
if (!(lineStream >> flow))
|
||||
throw std::runtime_error(io::Str() << "Couldn't parse node flow from line '" << line << "'");
|
||||
// Get the name by extracting the rest of the stream until the first quotation mark and then the last.
|
||||
if (!getline(lineStream, name, '"'))
|
||||
throw std::runtime_error(io::Str() << "Can't parse node name from line " << lineNr << " ('" << line << "').");
|
||||
if (!getline(lineStream, name, '"'))
|
||||
throw std::runtime_error(io::Str() << "Can't parse node name from line " << lineNr << " ('" << line << "').");
|
||||
if (!(lineStream >> stateId))
|
||||
throw std::runtime_error(io::Str() << "Couldn't parse node id from line '" << line << "'");
|
||||
if (lineStream >> nodeId) {
|
||||
m_isHigherOrder = true;
|
||||
} else if (m_isHigherOrder) {
|
||||
throw std::runtime_error(io::Str() << "Missing state id for node on line '" << line << "'.");
|
||||
}
|
||||
if (isMultilayer && !(lineStream >> layerId))
|
||||
throw std::runtime_error(io::Str() << "Couldn't parse layer id from line '" << line << "'");
|
||||
|
||||
bool multilayerNodeFound = false;
|
||||
|
||||
if (isMultilayer) {
|
||||
// get new state id from map
|
||||
auto it = layerNodeToStateId->find(layerId);
|
||||
|
||||
if (it != layerNodeToStateId->end()) {
|
||||
auto nodeIdToStateId = it->second.find(nodeId);
|
||||
if (nodeIdToStateId != it->second.end()) {
|
||||
stateId = nodeIdToStateId->second;
|
||||
multilayerNodeFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMultilayer && !multilayerNodeFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pathStream.clear();
|
||||
pathStream.str(pathString);
|
||||
unsigned int childNumber;
|
||||
|
||||
Path path;
|
||||
while (pathStream >> childNumber) {
|
||||
pathStream.get(); // Extract the delimiting character also
|
||||
if (childNumber == 0)
|
||||
throw std::runtime_error("There is a '0' in the tree path, lowest allowed integer is 1.");
|
||||
path.push_back(childNumber); // Keep 1-based indexing in path
|
||||
}
|
||||
|
||||
m_nodePaths.emplace_back(stateId, path);
|
||||
|
||||
if (includeFlow)
|
||||
m_flowData[stateId] = flow;
|
||||
}
|
||||
}
|
||||
|
||||
void ClusterMap::readClu(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId)
|
||||
{
|
||||
auto isMultilayer = layerNodeToStateId != nullptr;
|
||||
|
||||
Log() << "Read initial partition from '" << filename << "'... " << std::flush;
|
||||
SafeInFile input(filename);
|
||||
std::string line;
|
||||
std::istringstream lineStream;
|
||||
std::map<unsigned int, unsigned int> clusterData;
|
||||
|
||||
while (!std::getline(input, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#' || line[0] == '*')
|
||||
continue;
|
||||
|
||||
lineStream.clear();
|
||||
lineStream.str(line);
|
||||
// # state_id module flow node_id layer_id
|
||||
|
||||
unsigned int stateId;
|
||||
unsigned int nodeId;
|
||||
unsigned int moduleId;
|
||||
unsigned int layerId;
|
||||
|
||||
if (!(lineStream >> stateId >> moduleId))
|
||||
throw std::runtime_error(io::Str() << "Couldn't parse node key and cluster id from line '" << line << "'");
|
||||
|
||||
auto flow = 0.0;
|
||||
if (lineStream >> flow) {
|
||||
if (includeFlow)
|
||||
m_flowData[stateId] = flow;
|
||||
}
|
||||
|
||||
auto multilayerNodeFound = false;
|
||||
if (isMultilayer) {
|
||||
if (!(lineStream >> nodeId))
|
||||
throw std::runtime_error(io::Str() << "Couldn't parse node key from line '" << line << "'");
|
||||
|
||||
if (!(lineStream >> layerId))
|
||||
throw std::runtime_error(io::Str() << "Couldn't parse layer id from line '" << line << "'");
|
||||
|
||||
// get new state id from map
|
||||
auto it = layerNodeToStateId->find(layerId);
|
||||
|
||||
if (it != layerNodeToStateId->end()) {
|
||||
auto nodeIdToStateId = it->second.find(nodeId);
|
||||
if (nodeIdToStateId != it->second.end()) {
|
||||
stateId = nodeIdToStateId->second;
|
||||
multilayerNodeFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMultilayer && !multilayerNodeFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_clusterIds[stateId] = moduleId;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,52 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef CLUSTER_MAP_H_
|
||||
#define CLUSTER_MAP_H_
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
using Path = std::deque<unsigned int>; // 1-based indexing
|
||||
|
||||
using NodePath = std::pair<unsigned int, Path>;
|
||||
|
||||
using NodePaths = std::vector<NodePath>;
|
||||
|
||||
class ClusterMap {
|
||||
public:
|
||||
void readClusterData(const std::string& filename, bool includeFlow = false, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId = nullptr);
|
||||
|
||||
const std::map<unsigned int, unsigned int>& clusterIds() const noexcept
|
||||
{
|
||||
return m_clusterIds;
|
||||
}
|
||||
|
||||
const NodePaths& nodePaths() const noexcept { return m_nodePaths; }
|
||||
|
||||
const std::string& extension() const noexcept { return m_extension; }
|
||||
|
||||
private:
|
||||
void readTree(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId = nullptr);
|
||||
void readClu(const std::string& filename, bool includeFlow, const std::map<unsigned int, std::map<unsigned int, unsigned int>>* layerNodeToStateId = nullptr);
|
||||
|
||||
std::map<unsigned int, unsigned int> m_clusterIds;
|
||||
std::map<unsigned int, double> m_flowData;
|
||||
NodePaths m_nodePaths;
|
||||
std::string m_extension;
|
||||
bool m_isHigherOrder = false;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // CLUSTER_MAP_H_
|
||||
@@ -0,0 +1,264 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "Config.h"
|
||||
#include "ProgramInterface.h"
|
||||
#include "SafeFile.h"
|
||||
#include "../utils/FileURI.h"
|
||||
#include "../utils/Log.h"
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
constexpr int FlowModel::undirected;
|
||||
constexpr int FlowModel::directed;
|
||||
constexpr int FlowModel::undirdir;
|
||||
constexpr int FlowModel::outdirdir;
|
||||
constexpr int FlowModel::rawdir;
|
||||
constexpr int FlowModel::precomputed;
|
||||
|
||||
Config::Config(const std::string& flags, bool isCLI) : isCLI(isCLI)
|
||||
{
|
||||
ProgramInterface api("Infomap",
|
||||
"Implementation of the Infomap clustering algorithm based on the Map Equation (see www.mapequation.org)",
|
||||
INFOMAP_VERSION);
|
||||
|
||||
api.setGroups({ "Input", "Algorithm", "Accuracy", "Output" });
|
||||
|
||||
std::vector<std::string> optionalOutputDir; // Used if !isCLI
|
||||
// --------------------- Input options ---------------------
|
||||
if (isCLI) {
|
||||
api.addNonOptionArgument(networkFile, "network_file", "File containing the network data. Assumes a link list format if no Pajek formatted heading.", "Input");
|
||||
} else {
|
||||
api.addOptionArgument(networkFile, "input", "File containing the network data. Assumes a link list format if no Pajek formatted heading.", ArgType::path, "Input");
|
||||
}
|
||||
|
||||
api.addOptionArgument(skipAdjustBipartiteFlow, "skip-adjust-bipartite-flow", "Skip distributing all flow from the bipartite nodes to the primary nodes.", "Input", true);
|
||||
|
||||
api.addOptionArgument(bipartiteTeleportation, "bipartite-teleportation", "Teleport like the bipartite flow instead of two-step (unipartite) teleportation.", "Input", true);
|
||||
|
||||
api.addOptionArgument(weightThreshold, "weight-threshold", "Limit the number of links to read from the network. Ignore links with less weight than the threshold.", ArgType::number, "Input", 0.0, true);
|
||||
|
||||
bool deprecated_includeSelfLinks = false;
|
||||
api.addOptionArgument(deprecated_includeSelfLinks, 'k', "include-self-links", "DEPRECATED. Include self links by default now, exclude with --no-self-links.", "Input", true).setHidden(true);
|
||||
|
||||
api.addOptionArgument(noSelfLinks, "no-self-links", "Exclude self links in the input network.", "Input", true);
|
||||
|
||||
api.addOptionArgument(nodeLimit, "node-limit", "Limit the number of nodes to read from the network. Ignore links connected to ignored nodes.", ArgType::integer, "Input", 1u, true);
|
||||
|
||||
api.addOptionArgument(matchableMultilayerIds, "matchable-multilayer-ids", "Construct state ids from node and layer ids that are consistent across networks for the same max number of layers. Set to at least the largest layer id among networks to match.", ArgType::integer, "Input", 1u, true);
|
||||
|
||||
api.addOptionArgument(clusterDataFile, 'c', "cluster-data", "Provide an initial two-level (clu format) or multi-layer (tree format) solution.", ArgType::path, "Input");
|
||||
|
||||
api.addOptionArgument(assignToNeighbouringModule, "assign-to-neighbouring-module", "Assign nodes without module assignments (from --cluster-data) to the module assignment of a neighbouring node if possible.", "Input", true);
|
||||
|
||||
api.addOptionArgument(metaDataFile, "meta-data", "Provide meta data (clu format) that should be encoded.", ArgType::path, "Input", true);
|
||||
|
||||
api.addOptionArgument(metaDataRate, "meta-data-rate", "Metadata encoding rate. Default is to encode each step.", ArgType::number, "Input", 0.0, true);
|
||||
|
||||
api.addOptionArgument(unweightedMetaData, "meta-data-unweighted", "Don't weight meta data by node flow.", "Input", true);
|
||||
|
||||
api.addOptionArgument(noInfomap, "no-infomap", "Don't run the optimizer. Useful to calculate codelength of provided cluster data or to print non-modular statistics.", "Input");
|
||||
|
||||
// --------------------- Output options ---------------------
|
||||
|
||||
api.addOptionArgument(outName, "out-name", "Name for the output files, e.g. [output_directory]/[out-name].tree", ArgType::string, "Output", true);
|
||||
|
||||
api.addOptionArgument(noFileOutput, '0', "no-file-output", "Don't write output to file.", "Output", true);
|
||||
|
||||
api.addOptionArgument(printTree, "tree", "Write a tree file with the modular hierarchy. Automatically enabled if no other output is specified.", "Output");
|
||||
|
||||
api.addOptionArgument(printFlowTree, "ftree", "Write a ftree file with the modular hierarchy including aggregated links between (nested) modules. (Used by Network Navigator)", "Output");
|
||||
|
||||
api.addOptionArgument(printClu, "clu", "Write a clu file with the top cluster ids for each node.", "Output");
|
||||
|
||||
api.addOptionArgument(cluLevel, "clu-level", "For clu output, print modules at specified depth from root. Use -1 for bottom level modules.", ArgType::integer, "Output", -1, true);
|
||||
|
||||
api.addOptionArgument(outputFormats, 'o', "output", "Comma-separated output formats without spaces, e.g. -o clu,tree,ftree. Options: clu, tree, ftree, newick, json, csv, network, states, flow.", ArgType::list, "Output", true);
|
||||
|
||||
api.addOptionArgument(hideBipartiteNodes, "hide-bipartite-nodes", "Project bipartite solution to unipartite.", "Output", true);
|
||||
|
||||
api.addOptionArgument(printAllTrials, "print-all-trials", "Print all trials to separate files.", "Output", true);
|
||||
|
||||
// --------------------- Core algorithm options ---------------------
|
||||
api.addOptionArgument(twoLevel, '2', "two-level", "Optimize a two-level partition of the network. Default is multi-level.", "Algorithm");
|
||||
|
||||
std::string flowModelArg;
|
||||
|
||||
api.addOptionArgument(flowModelArg, 'f', "flow-model", "Specify flow model. Options: undirected, directed, undirdir, outdirdir, rawdir, precomputed.", ArgType::option, "Algorithm");
|
||||
|
||||
api.addOptionArgument(directed, 'd', "directed", "Assume directed links. Shorthand for '--flow-model directed'.", "Algorithm");
|
||||
|
||||
api.addOptionArgument(recordedTeleportation, 'e', "recorded-teleportation", "If teleportation is used to calculate the flow, also record it when minimizing codelength.", "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(useNodeWeightsAsFlow, "use-node-weights-as-flow", "Use node weights (from api or after names in Pajek format) as flow, normalized to sum to 1", "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(teleportToNodes, "to-nodes", "Teleport to nodes instead of to links, assuming uniform node weights if no such input data.", "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(teleportationProbability, 'p', "teleportation-probability", "Probability of teleporting to a random node or link.", ArgType::probability, "Algorithm", 0.0, 1.0, true);
|
||||
|
||||
api.addOptionArgument(regularized, "regularized", "Effectively add a fully connected Bayesian prior network to not overfit due to missing links. Implies recorded teleportation", "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(regularizationStrength, "regularization-strength", "Adjust relative strength of Bayesian prior network with this multiplier.", ArgType::number, "Algorithm", 0.0, true);
|
||||
|
||||
api.addOptionArgument(entropyBiasCorrection, "entropy-corrected", "Correct for negative entropy bias in small samples (many modules).", "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(entropyBiasCorrectionMultiplier, "entropy-correction-strength", "Increase or decrease the default entropy correction with this factor.", ArgType::number, "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(markovTime, "markov-time", "Scale link flow to change the cost of moving between modules. Higher values results in fewer modules.", ArgType::number, "Algorithm", 0.0, true);
|
||||
|
||||
api.addOptionArgument(variableMarkovTime, "variable-markov-time", "Increase Markov time locally to level out link flow. Reduces risk of overpartitioning sparse areas while keeping high resolution in dense areas.", "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(variableMarkovTimeDamping, "variable-markov-damping", "Damping parameter for variable Markov time, to scale with local effective degree (0) or local entropy (1).", ArgType::number, "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(variableMarkovTimeMinLocalScale, "variable-markov-min-scale", "Minimum local scale for nodes with zero entropy to avoid division by zero. Local Markov time is max scale divided by local scale.", ArgType::number, "Algorithm", true);
|
||||
|
||||
// api.addOptionArgument(markovTimeNoSelfLinks, "markov-time-no-self-links", "For testing.", "Algorithm", true);
|
||||
|
||||
api.addOptionArgument(preferredNumberOfModules, "preferred-number-of-modules", "Penalize solutions the more they differ from this number.", ArgType::integer, "Algorithm", 1u, true);
|
||||
|
||||
api.addOptionArgument(multilayerRelaxRate, "multilayer-relax-rate", "Probability to relax the constraint to move only in the current layer.", ArgType::probability, "Algorithm", 0.0, 1.0, true);
|
||||
|
||||
api.addOptionArgument(multilayerRelaxLimit, "multilayer-relax-limit", "Number of neighboring layers in each direction to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true);
|
||||
|
||||
api.addOptionArgument(multilayerRelaxLimitUp, "multilayer-relax-limit-up", "Number of neighboring layers with higher id to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true);
|
||||
|
||||
api.addOptionArgument(multilayerRelaxLimitDown, "multilayer-relax-limit-down", "Number of neighboring layers with lower id to relax to. If negative, relax to any layer.", ArgType::integer, "Algorithm", -1, true);
|
||||
|
||||
api.addOptionArgument(multilayerRelaxByJensenShannonDivergence, "multilayer-relax-by-jsd", "Relax proportional to the out-link similarity measured by the Jensen-Shannon divergence.", "Algorithm", true);
|
||||
|
||||
// --------------------- Performance and accuracy options ---------------------
|
||||
// api.addOptionArgument(seedToRandomNumberGenerator, 's', "seed", "A seed (integer) to the random number generator for reproducible results.", ArgType::integer, "Accuracy", 1ul);
|
||||
|
||||
api.addOptionArgument(numTrials, 'N', "num-trials", "Number of outer-most loops to run before picking the best solution.", ArgType::integer, "Accuracy", 1u);
|
||||
|
||||
api.addOptionArgument(coreLoopLimit, 'M', "core-loop-limit", "Limit the number of loops that tries to move each node into the best possible module.", ArgType::integer, "Accuracy", 1u, true);
|
||||
|
||||
api.addOptionArgument(levelAggregationLimit, 'L', "core-level-limit", "Limit the number of times the core loops are reapplied on existing modular network to search bigger structures.", ArgType::integer, "Accuracy", 1u, true);
|
||||
|
||||
api.addOptionArgument(tuneIterationLimit, 'T', "tune-iteration-limit", "Limit the number of main iterations in the two-level partition algorithm. 0 means no limit.", ArgType::integer, "Accuracy", 1u, true);
|
||||
|
||||
api.addOptionArgument(minimumCodelengthImprovement, "core-loop-codelength-threshold", "Minimum codelength threshold for accepting a new solution in core loop.", ArgType::number, "Accuracy", 0.0, true);
|
||||
|
||||
api.addOptionArgument(minimumRelativeTuneIterationImprovement, "tune-iteration-relative-threshold", "Set codelength improvement threshold of each new tune iteration to 'f' times the initial two-level codelength.", ArgType::number, "Accuracy", 0.0, true);
|
||||
|
||||
api.addIncrementalOptionArgument(fastHierarchicalSolution, 'F', "fast-hierarchical-solution", "Find top modules fast. Use -FF to keep all fast levels. Use -FFF to skip recursive part.", "Accuracy", true);
|
||||
|
||||
api.addOptionArgument(preferModularSolution, "prefer-modular-solution", "Prefer modular solutions even if they are worse than putting all nodes in one module.", "Accuracy", true);
|
||||
|
||||
api.addOptionArgument(innerParallelization, "inner-parallelization", "Parallelize the inner-most loop for greater speed. This may give some accuracy tradeoff.", "Accuracy", true);
|
||||
|
||||
api.addOptionalNonOptionArguments(optionalOutputDir, "out_directory", "Directory to write the results to.", "Output");
|
||||
|
||||
api.addIncrementalOptionArgument(verbosity, 'v', "verbose", "Verbose output on the console. Add additional 'v' flags to increase verbosity up to -vvv.", "Output");
|
||||
|
||||
api.addOptionArgument(silent, "silent", "No output on the console.", "Output");
|
||||
|
||||
api.parseArgs(flags);
|
||||
|
||||
if (deprecated_includeSelfLinks) {
|
||||
throw std::runtime_error("The --include-self-links flag is deprecated to include self links by default. Use --no-self-links to exclude.");
|
||||
}
|
||||
|
||||
if (!optionalOutputDir.empty())
|
||||
outDirectory = optionalOutputDir[0];
|
||||
|
||||
if (!isCLI && outDirectory.empty())
|
||||
noFileOutput = true;
|
||||
|
||||
if (!noFileOutput && outDirectory.empty() && isCLI) {
|
||||
throw std::runtime_error("Missing out_directory");
|
||||
}
|
||||
|
||||
if (flowModelArg == "directed" || directed) {
|
||||
setFlowModel(FlowModel::directed);
|
||||
} else if (flowModelArg == "undirected") {
|
||||
setFlowModel(FlowModel::undirected);
|
||||
} else if (flowModelArg == "undirdir") {
|
||||
setFlowModel(FlowModel::undirdir);
|
||||
} else if (flowModelArg == "outdirdir") {
|
||||
setFlowModel(FlowModel::outdirdir);
|
||||
} else if (flowModelArg == "rawdir") {
|
||||
setFlowModel(FlowModel::rawdir);
|
||||
} else if (flowModelArg == "precomputed") {
|
||||
setFlowModel(FlowModel::precomputed);
|
||||
} else if (!flowModelArg.empty()) {
|
||||
throw std::runtime_error(io::Str() << "Unrecognized flow model: '" << flowModelArg << "'");
|
||||
}
|
||||
|
||||
if (regularized) {
|
||||
recordedTeleportation = true;
|
||||
}
|
||||
|
||||
if (*--outDirectory.end() != '/')
|
||||
outDirectory.append("/");
|
||||
|
||||
if (haveOutput() && !isDirectoryWritable(outDirectory))
|
||||
throw std::runtime_error(io::Str() << "Can't write to directory '" << outDirectory << "'. Check that the directory exists and that you have write permissions.");
|
||||
|
||||
if (outName.empty()) {
|
||||
outName = !networkFile.empty() ? FileURI(networkFile).getName() : "no-name";
|
||||
}
|
||||
|
||||
if (noInfomap) {
|
||||
numTrials = 1;
|
||||
}
|
||||
|
||||
parsedString = flags;
|
||||
parsedOptions = api.getUsedOptionArguments();
|
||||
|
||||
if (printAllTrials && numTrials < 2) {
|
||||
printAllTrials = false;
|
||||
}
|
||||
|
||||
adaptDefaults();
|
||||
|
||||
Log::init(verbosity, silent, verboseNumberPrecision);
|
||||
}
|
||||
|
||||
void Config::adaptDefaults()
|
||||
{
|
||||
auto outputs = io::split(outputFormats, ',');
|
||||
for (std::string& o : outputs) {
|
||||
if (o == "clu") {
|
||||
printClu = true;
|
||||
} else if (o == "tree") {
|
||||
printTree = true;
|
||||
} else if (o == "ftree") {
|
||||
printFlowTree = true;
|
||||
} else if (o == "newick") {
|
||||
printNewick = true;
|
||||
} else if (o == "json") {
|
||||
printJson = true;
|
||||
} else if (o == "csv") {
|
||||
printCsv = true;
|
||||
} else if (o == "network") {
|
||||
printPajekNetwork = true;
|
||||
} else if (o == "flow") {
|
||||
printFlowNetwork = true;
|
||||
} else if (o == "states") {
|
||||
printStateNetwork = true;
|
||||
} else {
|
||||
throw std::runtime_error(io::Str() << "Unrecognized output format: '" << o << "'.");
|
||||
}
|
||||
}
|
||||
|
||||
// Of no output format specified, use tree as default (if not used as a library).
|
||||
if (isCLI && !haveModularResultOutput()) {
|
||||
printTree = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, FlowModel f)
|
||||
{
|
||||
return out << flowModelToString(f);
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,268 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef CONFIG_H_
|
||||
#define CONFIG_H_
|
||||
|
||||
#include "../utils/Date.h"
|
||||
#include "../version.h"
|
||||
#include "ProgramInterface.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
struct FlowModel {
|
||||
static constexpr int undirected = 0;
|
||||
static constexpr int directed = 1;
|
||||
static constexpr int undirdir = 2;
|
||||
static constexpr int outdirdir = 3;
|
||||
static constexpr int rawdir = 4;
|
||||
static constexpr int precomputed = 5;
|
||||
|
||||
int value = 0;
|
||||
|
||||
FlowModel(int val) : value(val) { }
|
||||
FlowModel& operator=(int val)
|
||||
{
|
||||
value = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator int&() { return value; }
|
||||
operator int() const { return value; }
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, FlowModel f);
|
||||
|
||||
inline const char* flowModelToString(const FlowModel& flowModel)
|
||||
{
|
||||
switch (flowModel) {
|
||||
case FlowModel::directed:
|
||||
return "directed";
|
||||
case FlowModel::undirdir:
|
||||
return "undirdir";
|
||||
case FlowModel::outdirdir:
|
||||
return "outdirdir";
|
||||
case FlowModel::rawdir:
|
||||
return "rawdir";
|
||||
case FlowModel::precomputed:
|
||||
return "precomputed";
|
||||
case FlowModel::undirected:
|
||||
default:
|
||||
return "undirected";
|
||||
}
|
||||
}
|
||||
|
||||
struct Config {
|
||||
// Input
|
||||
bool isCLI = false;
|
||||
std::string networkFile;
|
||||
std::vector<std::string> additionalInput;
|
||||
bool stateInput = false;
|
||||
bool stateOutput = false;
|
||||
bool multilayerInput = false;
|
||||
double weightThreshold = 0.0;
|
||||
bool bipartite = false;
|
||||
bool skipAdjustBipartiteFlow = false;
|
||||
bool bipartiteTeleportation = false;
|
||||
bool noSelfLinks = false; // Replaces includeSelfLinks
|
||||
unsigned int nodeLimit = 0;
|
||||
unsigned int matchableMultilayerIds = 0;
|
||||
std::string clusterDataFile;
|
||||
std::string metaDataFile;
|
||||
double metaDataRate = 1.0;
|
||||
bool unweightedMetaData = false;
|
||||
unsigned int numMetaDataDimensions = 0;
|
||||
bool clusterDataIsHard = false; // FIXME Not used
|
||||
bool assignToNeighbouringModule = false;
|
||||
bool noInfomap = false;
|
||||
|
||||
FlowModel flowModel = FlowModel::undirected;
|
||||
bool flowModelIsSet = false;
|
||||
bool directed = false;
|
||||
bool useNodeWeightsAsFlow = false;
|
||||
bool teleportToNodes = false;
|
||||
double markovTime = 1.0;
|
||||
bool variableMarkovTime = false;
|
||||
double variableMarkovTimeDamping = 1.0; // 0 for linear scaling, 1 for log scaled.
|
||||
double variableMarkovTimeMinLocalScale = 1; // Correspond to two links in undirected unweighted networks. Avoids division by zero.
|
||||
bool markovTimeNoSelfLinks = false;
|
||||
double multilayerRelaxRate = 0.15;
|
||||
int multilayerRelaxLimit = -1; // Amount of layers allowed to jump up or down
|
||||
int multilayerRelaxLimitUp = -1; // One-sided limit to higher layers
|
||||
int multilayerRelaxLimitDown = -1; // One-sided limit to lower layers
|
||||
double multilayerJSRelaxRate = 0.15;
|
||||
bool multilayerRelaxByJensenShannonDivergence = false;
|
||||
int multilayerJSRelaxLimit = -1;
|
||||
|
||||
// Clustering
|
||||
bool twoLevel = false;
|
||||
bool noCoarseTune = false;
|
||||
bool recordedTeleportation = false;
|
||||
bool regularized = false; // Add a Bayesian prior network with recorded teleportation (sets recordedTeleportation and teleportToNodes to true)
|
||||
double regularizationStrength = 1.0; // Scale Bayesian prior constant ln(N)/N with this factor
|
||||
double teleportationProbability = 0.15;
|
||||
unsigned int preferredNumberOfModules = 0;
|
||||
bool entropyBiasCorrection = false;
|
||||
double entropyBiasCorrectionMultiplier = 1;
|
||||
/* unsigned long seedToRandomNumberGenerator = 123; */
|
||||
|
||||
// Performance and accuracy
|
||||
unsigned int numTrials = 1;
|
||||
double minimumCodelengthImprovement = 1e-10;
|
||||
double minimumSingleNodeCodelengthImprovement = 1e-16;
|
||||
bool randomizeCoreLoopLimit = false;
|
||||
unsigned int coreLoopLimit = 10;
|
||||
unsigned int levelAggregationLimit = 0;
|
||||
unsigned int tuneIterationLimit = 0; // Iterations of fine-tune/coarse-tune in two-level partition
|
||||
double minimumRelativeTuneIterationImprovement = 1e-5;
|
||||
bool onlySuperModules = false;
|
||||
unsigned int fastHierarchicalSolution = 0;
|
||||
bool preferModularSolution = false;
|
||||
bool innerParallelization = false;
|
||||
|
||||
// Output
|
||||
std::string outDirectory;
|
||||
std::string outName;
|
||||
std::string outputFormats;
|
||||
bool printTree = false;
|
||||
bool printFlowTree = false;
|
||||
bool printNewick = false;
|
||||
bool printJson = false;
|
||||
bool printCsv = false;
|
||||
bool printClu = false;
|
||||
bool printAllTrials = false;
|
||||
int cluLevel = 1; // Write modules at specified depth from root. 1, 2, ... or -1 for bottom level
|
||||
bool printFlowNetwork = false;
|
||||
bool printPajekNetwork = false;
|
||||
bool printStateNetwork = false;
|
||||
bool noFileOutput = false;
|
||||
unsigned int verbosity = 0;
|
||||
unsigned int verboseNumberPrecision = 9;
|
||||
bool silent = false;
|
||||
bool hideBipartiteNodes = false;
|
||||
|
||||
// Other
|
||||
Date startDate;
|
||||
std::string version = INFOMAP_VERSION;
|
||||
std::string parsedString;
|
||||
std::vector<ParsedOption> parsedOptions;
|
||||
infomap::interruptionHandlerFn *interruptionHandler = NULL;
|
||||
|
||||
Config() = default;
|
||||
|
||||
explicit Config(const std::string& flags, bool isCLI = false);
|
||||
|
||||
Config& cloneAsNonMain(const Config& other)
|
||||
{
|
||||
isCLI = other.isCLI;
|
||||
networkFile = other.networkFile;
|
||||
additionalInput = other.additionalInput;
|
||||
stateInput = other.stateInput;
|
||||
stateOutput = other.stateOutput;
|
||||
multilayerInput = other.multilayerInput;
|
||||
weightThreshold = other.weightThreshold;
|
||||
bipartite = other.bipartite;
|
||||
skipAdjustBipartiteFlow = other.skipAdjustBipartiteFlow;
|
||||
bipartiteTeleportation = other.bipartiteTeleportation;
|
||||
noSelfLinks = other.noSelfLinks;
|
||||
nodeLimit = other.nodeLimit;
|
||||
matchableMultilayerIds = other.matchableMultilayerIds;
|
||||
metaDataRate = other.metaDataRate;
|
||||
unweightedMetaData = other.unweightedMetaData;
|
||||
numMetaDataDimensions = other.numMetaDataDimensions;
|
||||
assignToNeighbouringModule = other.assignToNeighbouringModule;
|
||||
noInfomap = other.noInfomap;
|
||||
flowModel = other.flowModel;
|
||||
flowModelIsSet = other.flowModelIsSet;
|
||||
directed = other.directed;
|
||||
useNodeWeightsAsFlow = other.useNodeWeightsAsFlow;
|
||||
teleportToNodes = other.teleportToNodes;
|
||||
markovTime = other.markovTime;
|
||||
variableMarkovTime = other.variableMarkovTime;
|
||||
variableMarkovTimeDamping = other.variableMarkovTimeDamping;
|
||||
markovTimeNoSelfLinks = other.markovTimeNoSelfLinks;
|
||||
multilayerRelaxRate = other.multilayerRelaxRate;
|
||||
multilayerRelaxLimit = other.multilayerRelaxLimit;
|
||||
multilayerRelaxLimitUp = other.multilayerRelaxLimitUp;
|
||||
multilayerRelaxLimitDown = other.multilayerRelaxLimitDown;
|
||||
multilayerJSRelaxRate = other.multilayerJSRelaxRate;
|
||||
multilayerRelaxByJensenShannonDivergence = other.multilayerRelaxByJensenShannonDivergence;
|
||||
multilayerJSRelaxLimit = other.multilayerJSRelaxLimit;
|
||||
twoLevel = other.twoLevel;
|
||||
noCoarseTune = other.noCoarseTune;
|
||||
recordedTeleportation = other.recordedTeleportation;
|
||||
regularized = other.regularized;
|
||||
regularizationStrength = other.regularizationStrength;
|
||||
teleportationProbability = other.teleportationProbability;
|
||||
entropyBiasCorrection = other.entropyBiasCorrection;
|
||||
entropyBiasCorrectionMultiplier = other.entropyBiasCorrectionMultiplier;
|
||||
// seedToRandomNumberGenerator = other.seedToRandomNumberGenerator;
|
||||
minimumCodelengthImprovement = other.minimumCodelengthImprovement;
|
||||
minimumSingleNodeCodelengthImprovement = other.minimumSingleNodeCodelengthImprovement;
|
||||
randomizeCoreLoopLimit = other.randomizeCoreLoopLimit;
|
||||
minimumRelativeTuneIterationImprovement = other.minimumRelativeTuneIterationImprovement;
|
||||
preferModularSolution = other.preferModularSolution;
|
||||
innerParallelization = other.innerParallelization;
|
||||
outDirectory = other.outDirectory;
|
||||
outName = other.outName;
|
||||
outputFormats = other.outputFormats;
|
||||
verbosity = other.verbosity;
|
||||
verboseNumberPrecision = other.verboseNumberPrecision;
|
||||
startDate = other.startDate;
|
||||
version = other.version;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void adaptDefaults();
|
||||
|
||||
void setStateInput() { stateInput = true; }
|
||||
|
||||
void setStateOutput() { stateOutput = true; }
|
||||
|
||||
void setMultilayerInput() { multilayerInput = true; }
|
||||
|
||||
void setFlowModel(FlowModel value)
|
||||
{
|
||||
flowModel = value;
|
||||
flowModelIsSet = true;
|
||||
}
|
||||
|
||||
bool isUndirectedClustering() const { return flowModel == FlowModel::undirected; }
|
||||
|
||||
bool isUndirectedFlow() const { return flowModel == FlowModel::undirected || flowModel == FlowModel::undirdir; }
|
||||
|
||||
bool printAsUndirected() const { return isUndirectedClustering(); }
|
||||
|
||||
bool isMultilayerNetwork() const { return multilayerInput || !additionalInput.empty(); }
|
||||
bool isBipartite() const { return bipartite; }
|
||||
|
||||
bool haveMemory() const { return stateInput; }
|
||||
bool printStates() const { return stateOutput; }
|
||||
|
||||
bool haveMetaData() const { return !metaDataFile.empty() || numMetaDataDimensions != 0; }
|
||||
|
||||
bool haveOutput() const { return !noFileOutput; }
|
||||
|
||||
bool haveModularResultOutput() const
|
||||
{
|
||||
return printTree || printFlowTree || printNewick || printJson || printCsv || printClu;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // CONFIG_H_
|
||||
@@ -0,0 +1,990 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "Network.h"
|
||||
#include "../io/SafeFile.h"
|
||||
#include "../utils/FileURI.h"
|
||||
#include "../utils/Log.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
using std::make_pair;
|
||||
|
||||
void Network::init()
|
||||
{
|
||||
initValidHeadings();
|
||||
m_multilayerStateIdBitShift = static_cast<unsigned int>(std::ceil(std::log2(m_config.matchableMultilayerIds)));
|
||||
}
|
||||
|
||||
void Network::initValidHeadings()
|
||||
{
|
||||
auto& headingsPajek = m_validHeadings["pajek"];
|
||||
headingsPajek.insert("*vertices");
|
||||
headingsPajek.insert("*edges");
|
||||
headingsPajek.insert("*arcs");
|
||||
|
||||
auto& headingsLinklist = m_validHeadings["link-list"];
|
||||
headingsLinklist.insert("*links");
|
||||
headingsLinklist.insert("*edges");
|
||||
headingsLinklist.insert("*arcs");
|
||||
|
||||
auto& headingsBipartite = m_validHeadings["bipartite"];
|
||||
headingsBipartite.insert("*vertices");
|
||||
headingsBipartite.insert("*bipartite");
|
||||
|
||||
auto& headingsStates = m_validHeadings["states"];
|
||||
headingsStates.insert("*vertices");
|
||||
headingsStates.insert("*states");
|
||||
headingsStates.insert("*edges");
|
||||
headingsStates.insert("*arcs");
|
||||
headingsStates.insert("*links");
|
||||
headingsStates.insert("*contexts");
|
||||
auto& ignoreHeadingsStates = m_ignoreHeadings["states"];
|
||||
ignoreHeadingsStates.insert("*edges");
|
||||
ignoreHeadingsStates.insert("*contexts");
|
||||
|
||||
auto& headingsMultilayer = m_validHeadings["multilayer"];
|
||||
headingsMultilayer.insert("*vertices");
|
||||
headingsMultilayer.insert("*multiplex");
|
||||
headingsMultilayer.insert("*multilayer");
|
||||
headingsMultilayer.insert("*intra");
|
||||
headingsMultilayer.insert("*inter");
|
||||
|
||||
auto& headingsGeneral = m_validHeadings["general"];
|
||||
headingsGeneral.insert("*vertices");
|
||||
headingsGeneral.insert("*states");
|
||||
headingsGeneral.insert("*multilayer");
|
||||
headingsGeneral.insert("*intra");
|
||||
headingsGeneral.insert("*inter");
|
||||
headingsGeneral.insert("*paths");
|
||||
headingsGeneral.insert("*edges");
|
||||
headingsGeneral.insert("*arcs");
|
||||
headingsGeneral.insert("*links");
|
||||
headingsGeneral.insert("*contexts");
|
||||
headingsGeneral.insert("*bipartite");
|
||||
auto& ignoreHeadingsGeneral = m_ignoreHeadings["general"];
|
||||
ignoreHeadingsGeneral.insert("*contexts");
|
||||
}
|
||||
|
||||
void Network::clear()
|
||||
{
|
||||
StateNetwork::clear();
|
||||
m_networks.clear();
|
||||
m_interLinks.clear();
|
||||
m_layerNodeToStateId.clear();
|
||||
m_sumIntraOutWeight.clear();
|
||||
m_layers.clear();
|
||||
m_numInterLayerLinks = 0;
|
||||
m_numIntraLayerLinks = 0;
|
||||
|
||||
// Bipartite
|
||||
m_bipartiteStartId = 0;
|
||||
|
||||
// Meta data
|
||||
m_metaData.clear();
|
||||
m_numMetaDataColumns = 0;
|
||||
}
|
||||
|
||||
void Network::readInputData(std::string filename, bool accumulate)
|
||||
{
|
||||
if (!accumulate) {
|
||||
clear();
|
||||
}
|
||||
if (filename.empty())
|
||||
filename = m_config.networkFile;
|
||||
if (filename.empty()) {
|
||||
throw std::runtime_error("No input file to read network");
|
||||
}
|
||||
FileURI networkFilename(filename, false);
|
||||
|
||||
parseNetwork(filename);
|
||||
printSummary();
|
||||
}
|
||||
|
||||
void Network::parseNetwork(const std::string& filename)
|
||||
{
|
||||
Log() << "Parsing " << (m_config.isUndirectedFlow() ? "undirected" : "directed") << " network from file '" << filename << "'...\n";
|
||||
|
||||
parseNetwork(filename, m_validHeadings["general"], m_ignoreHeadings["general"]);
|
||||
}
|
||||
|
||||
void Network::parseNetwork(const std::string& filename, const InsensitiveStringSet& validHeadings, const InsensitiveStringSet& ignoreHeadings, const std::string& startHeading)
|
||||
{
|
||||
m_haveFileInput = true;
|
||||
SafeInFile input(filename);
|
||||
|
||||
// Parse standard links by default until possible heading is reached
|
||||
std::string heading = startHeading.length() > 0 ? startHeading : parseLinks(input);
|
||||
|
||||
while (heading.length() > 0 && heading[0] == '*') {
|
||||
std::string headingLowerCase = io::tolower(io::firstWord(heading));
|
||||
if (validHeadings.count(headingLowerCase) == 0) {
|
||||
throw std::runtime_error(io::Str() << "Unrecognized heading in network file: '" << headingLowerCase << "'.");
|
||||
}
|
||||
if (ignoreHeadings.count(headingLowerCase) > 0) {
|
||||
heading = ignoreSection(input, headingLowerCase);
|
||||
} else if (headingLowerCase == "*vertices") {
|
||||
heading = parseVertices(input, heading);
|
||||
} else if (headingLowerCase == "*states") {
|
||||
heading = parseStateNodes(input, heading);
|
||||
} else if (headingLowerCase == "*edges") {
|
||||
if (!m_config.isUndirectedFlow())
|
||||
Log() << "\n --> Notice: Links marked as undirected but parsed as directed.\n";
|
||||
heading = parseLinks(input);
|
||||
} else if (headingLowerCase == "*arcs") {
|
||||
if (m_config.isUndirectedFlow())
|
||||
Log() << "\n --> Notice: Links marked as directed but parsed as undirected.\n";
|
||||
heading = parseLinks(input);
|
||||
} else if (headingLowerCase == "*links") {
|
||||
heading = parseLinks(input);
|
||||
} else if (headingLowerCase == "*multilayer" || headingLowerCase == "*multiplex") {
|
||||
heading = parseMultilayerLinks(input);
|
||||
} else if (headingLowerCase == "*intra") {
|
||||
heading = parseMultilayerIntraLinks(input);
|
||||
} else if (headingLowerCase == "*inter") {
|
||||
heading = parseMultilayerInterLinks(input);
|
||||
} else if (headingLowerCase == "*bipartite") {
|
||||
heading = parseBipartiteLinks(input, heading);
|
||||
} else {
|
||||
heading = ignoreSection(input, headingLowerCase);
|
||||
}
|
||||
}
|
||||
|
||||
postProcessInputData();
|
||||
Log() << "Done!\n";
|
||||
}
|
||||
|
||||
void Network::postProcessInputData()
|
||||
{
|
||||
if (!m_networks.empty()) {
|
||||
generateStateNetworkFromMultilayer();
|
||||
}
|
||||
|
||||
if (!haveMemoryInput()) {
|
||||
// If no memory input, add physical nodes as state nodes to not miss unconnected nodes
|
||||
for (auto& it : m_physNodes) {
|
||||
addNode(it.second.physId, it.second.weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Network::readMetaData(const std::string& filename)
|
||||
{
|
||||
Log() << "Parsing meta data from '" << filename << "'...\n";
|
||||
SafeInFile input(filename);
|
||||
std::string line;
|
||||
while (!std::getline(input, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
|
||||
m_extractor.clear();
|
||||
m_extractor.str(line);
|
||||
|
||||
unsigned int nodeId;
|
||||
if (!(m_extractor >> nodeId))
|
||||
throw std::runtime_error(io::Str() << "Can't parse node id from line '" << line << "'");
|
||||
|
||||
std::vector<int> metaData;
|
||||
unsigned int metaId;
|
||||
while (m_extractor >> metaId) {
|
||||
metaData.push_back(metaId);
|
||||
}
|
||||
if (metaData.empty())
|
||||
throw std::runtime_error(io::Str() << "Can't parse any meta data from line '" << line << "'");
|
||||
|
||||
addMetaData(nodeId, metaData);
|
||||
}
|
||||
Log() << " -> Parsed " << m_numMetaDataColumns << " columns of meta data for " << m_metaData.size() << " nodes.\n";
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// HELPER METHODS
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::string Network::parseVertices(std::ifstream& file, const std::string& /*heading*/)
|
||||
{
|
||||
Log() << " Parsing vertices...\n"
|
||||
<< std::flush;
|
||||
std::string line;
|
||||
while (!std::getline(file, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
|
||||
m_extractor.clear();
|
||||
m_extractor.str(line);
|
||||
|
||||
unsigned int id = 0;
|
||||
if (!(m_extractor >> id))
|
||||
throw std::runtime_error(io::Str() << "Can't parse node id from line '" << line << "'");
|
||||
|
||||
auto nameStart = line.find_first_of('\"');
|
||||
auto nameEnd = line.find_last_of('\"');
|
||||
std::string name;
|
||||
if (nameStart < nameEnd) {
|
||||
name = std::string(line.begin() + nameStart + 1, line.begin() + nameEnd);
|
||||
line = line.substr(nameEnd + 1);
|
||||
m_extractor.clear();
|
||||
m_extractor.str(line);
|
||||
} else {
|
||||
if (!(m_extractor >> name))
|
||||
throw std::runtime_error(io::Str() << "Can't parse node name from line '" << line << "'");
|
||||
}
|
||||
double weight = 1.0;
|
||||
if ((m_extractor >> weight)) {
|
||||
m_haveNodeWeights = true;
|
||||
if (weight < 0)
|
||||
throw std::runtime_error(io::Str() << "Negative node weight (" << weight << ") from line '" << line << "'");
|
||||
}
|
||||
|
||||
addPhysicalNode(id, weight, name);
|
||||
}
|
||||
Log() << " -> " << m_physNodes.size() << " physical nodes added\n";
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string Network::parseStateNodes(std::ifstream& file, const std::string& /*heading*/)
|
||||
{
|
||||
m_higherOrderInputMethodCalled = true;
|
||||
Log() << " Parsing state nodes...\n"
|
||||
<< std::flush;
|
||||
std::string line;
|
||||
while (!std::getline(file, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
|
||||
StateNode stateNode;
|
||||
parseStateNode(line, stateNode);
|
||||
|
||||
addStateNode(stateNode);
|
||||
addPhysicalNode(stateNode.physicalId);
|
||||
|
||||
++m_numStateNodesFound;
|
||||
}
|
||||
Log() << " -> " << m_numStateNodesFound << " state nodes added\n";
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string Network::parseLinks(std::ifstream& file)
|
||||
{
|
||||
// This is the default action, so check for links before printing
|
||||
bool parsingLinks = false;
|
||||
std::string line;
|
||||
while (!std::getline(file, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
|
||||
if (!parsingLinks) {
|
||||
parsingLinks = true;
|
||||
Log() << " Parsing links...\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
unsigned int n1, n2;
|
||||
double weight;
|
||||
parseLink(line, n1, n2, weight);
|
||||
|
||||
addLink(n1, n2, weight);
|
||||
}
|
||||
if (parsingLinks)
|
||||
Log() << " -> " << m_numLinks << " links\n";
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string Network::parseMultilayerLinks(std::ifstream& file)
|
||||
{
|
||||
Log() << " Parsing multilayer links...\n"
|
||||
<< std::flush;
|
||||
|
||||
if (m_config.matchableMultilayerIds > 0) {
|
||||
Log() << " Creating matchable state ids using: nodeId << (log2(" << m_config.matchableMultilayerIds << ") + 1) | layerId\n";
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (!std::getline(file, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
|
||||
unsigned int layer1, n1, layer2, n2;
|
||||
double weight;
|
||||
parseMultilayerLink(line, layer1, n1, layer2, n2, weight);
|
||||
|
||||
// TODO: This explicit multilayer format can allow undirected but not the inter/intra format, clear?
|
||||
addMultilayerLink(layer1, n1, layer2, n2, weight);
|
||||
}
|
||||
Log() << " -> " << (m_numIntraLayerLinks + m_numInterLayerLinks) << " links in " << m_layers.size() << " layers\n";
|
||||
Log() << " -> " << m_numIntraLayerLinks << " intra-layer links\n";
|
||||
Log() << " -> " << m_numInterLayerLinks << " inter-layer links\n";
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string Network::parseMultilayerIntraLinks(std::ifstream& file)
|
||||
{
|
||||
Log() << " Parsing intra-layer links...\n"
|
||||
<< std::flush;
|
||||
|
||||
if (m_config.matchableMultilayerIds > 0) {
|
||||
Log() << " Creating matchable state ids using: nodeId << (log2(" << m_config.matchableMultilayerIds << ") + 1) | layerId\n";
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (!std::getline(file, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
|
||||
unsigned int layer, n1, n2;
|
||||
double weight;
|
||||
parseMultilayerIntraLink(line, layer, n1, n2, weight);
|
||||
|
||||
addMultilayerIntraLink(layer, n1, n2, weight);
|
||||
}
|
||||
Log() << " -> " << m_numIntraLayerLinks << " intra-layer links\n";
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string Network::parseMultilayerInterLinks(std::ifstream& file)
|
||||
{
|
||||
Log() << " Parsing inter-layer links...\n"
|
||||
<< std::flush;
|
||||
std::string line;
|
||||
while (!std::getline(file, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
|
||||
unsigned int layer1, n, layer2;
|
||||
double weight;
|
||||
parseMultilayerInterLink(line, layer1, n, layer2, weight);
|
||||
|
||||
addMultilayerInterLink(layer1, n, layer2, weight);
|
||||
}
|
||||
Log() << " -> " << m_numInterLayerLinks << " inter-layer links\n";
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string Network::parseBipartiteLinks(std::ifstream& file, const std::string& heading)
|
||||
{
|
||||
Log() << " Parsing bipartite links...\n";
|
||||
// Extract break point for bipartite links
|
||||
m_extractor.clear();
|
||||
m_extractor.str(heading);
|
||||
std::string tmp;
|
||||
if (!(m_extractor >> tmp >> m_bipartiteStartId))
|
||||
throw std::runtime_error(io::Str() << "Can't parse bipartite start id from line '" << heading << "'");
|
||||
|
||||
Log() << " -> Using bipartite start id " << m_bipartiteStartId << "\n";
|
||||
m_config.bipartite = true;
|
||||
std::string line;
|
||||
while (!std::getline(file, line).fail()) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
|
||||
unsigned int n1, n2;
|
||||
double weight;
|
||||
parseLink(line, n1, n2, weight);
|
||||
bool sourceIsFeature = n1 >= m_bipartiteStartId;
|
||||
bool targetIsFeature = n2 >= m_bipartiteStartId;
|
||||
if (sourceIsFeature == targetIsFeature) {
|
||||
throw std::runtime_error(io::Str() << "Bipartite link '" << line << "' must cross bipartite start id " << m_bipartiteStartId << ".");
|
||||
}
|
||||
addLink(n1, n2, weight);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string Network::ignoreSection(std::ifstream& file, const std::string& heading)
|
||||
{
|
||||
Log() << "(Ignoring section " << heading << ") ";
|
||||
std::string line;
|
||||
while (!std::getline(file, line).fail()) {
|
||||
if (line[0] == '*')
|
||||
break;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
void Network::parseStateNode(const std::string& line, StateNetwork::StateNode& stateNode)
|
||||
{
|
||||
m_extractor.clear();
|
||||
m_extractor.str(line);
|
||||
if (!(m_extractor >> stateNode.id >> stateNode.physicalId))
|
||||
throw std::runtime_error(io::Str() << "Can't parse any state node from line '" << line << "'");
|
||||
|
||||
// Optional name enclosed in double quotes
|
||||
auto nameStart = line.find_first_of('\"', m_extractor.tellg());
|
||||
auto nameEnd = line.find_last_of('\"');
|
||||
if (nameStart < nameEnd) {
|
||||
stateNode.name = std::string(line.begin() + nameStart + 1, line.begin() + nameEnd);
|
||||
m_extractor.seekg(nameEnd + 1);
|
||||
}
|
||||
// Optional weight, default to 1.0
|
||||
if ((m_extractor >> stateNode.weight)) {
|
||||
m_haveStateNodeWeights = true;
|
||||
if (stateNode.weight < 0)
|
||||
throw std::runtime_error(io::Str() << "Negative state node weight (" << stateNode.weight << ") from line '" << line << "'");
|
||||
}
|
||||
}
|
||||
|
||||
void Network::parseLink(const std::string& line, unsigned int& n1, unsigned int& n2, double& weight)
|
||||
{
|
||||
m_extractor.clear();
|
||||
m_extractor.str(line);
|
||||
if (!(m_extractor >> n1 >> n2))
|
||||
throw std::runtime_error(io::Str() << "Can't parse link data from line '" << line << "'");
|
||||
(m_extractor >> weight) || (weight = 1.0);
|
||||
}
|
||||
|
||||
void Network::parseMultilayerLink(const std::string& line, unsigned int& layer1, unsigned int& n1, unsigned int& layer2, unsigned int& n2, double& weight)
|
||||
{
|
||||
m_extractor.clear();
|
||||
m_extractor.str(line);
|
||||
if (!(m_extractor >> layer1 >> n1 >> layer2 >> n2))
|
||||
throw std::runtime_error(io::Str() << "Can't parse multilayer link data from line '" << line << "'");
|
||||
(m_extractor >> weight) || (weight = 1.0);
|
||||
}
|
||||
|
||||
void Network::parseMultilayerIntraLink(const std::string& line, unsigned int& layer, unsigned int& n1, unsigned int& n2, double& weight)
|
||||
{
|
||||
m_extractor.clear();
|
||||
m_extractor.str(line);
|
||||
if (!(m_extractor >> layer >> n1 >> n2))
|
||||
throw std::runtime_error(io::Str() << "Can't parse intra-multilayer link data from line '" << line << "'");
|
||||
(m_extractor >> weight) || (weight = 1.0);
|
||||
}
|
||||
|
||||
void Network::parseMultilayerInterLink(const std::string& line, unsigned int& layer1, unsigned int& n, unsigned int& layer2, double& weight)
|
||||
{
|
||||
m_extractor.clear();
|
||||
m_extractor.str(line);
|
||||
if (!(m_extractor >> layer1 >> n >> layer2))
|
||||
throw std::runtime_error(io::Str() << "Can't parse inter-multilayer link data from line '" << line << "'");
|
||||
(m_extractor >> weight) || (weight = 1.0);
|
||||
if (layer1 == layer2)
|
||||
throw std::runtime_error(io::Str() << "Inter-layer link from line '" << line << "' doesn't go between different layers.");
|
||||
// TODO: Same as intra-layer self-link?
|
||||
}
|
||||
|
||||
void Network::printSummary()
|
||||
{
|
||||
Log() << "-------------------------------------\n";
|
||||
if (haveMemoryInput()) {
|
||||
Log() << " -> " << numNodes() << " state nodes\n";
|
||||
Log() << " -> " << numPhysicalNodes() << " physical nodes\n";
|
||||
} else {
|
||||
if (m_bipartiteStartId > 0)
|
||||
Log() << " -> " << numNodes() << " bipartite nodes\n";
|
||||
else
|
||||
Log() << " -> " << numNodes() << " nodes\n";
|
||||
}
|
||||
Log() << " -> " << numLinks() << " links with total weight " << m_totalLinkWeightAdded << "\n";
|
||||
if (m_numLinksIgnoredByWeightThreshold > 0) {
|
||||
Log() << " -> " << m_numLinksIgnoredByWeightThreshold << " links ignored by weight threshold with total weight " << m_totalLinkWeightIgnored << " (" << io::toPrecision(m_totalLinkWeightIgnored / (m_totalLinkWeightIgnored + m_totalLinkWeightAdded) * 100, 1, true) << "%)\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Network::addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight)
|
||||
{
|
||||
m_higherOrderInputMethodCalled = true;
|
||||
if (weight < m_config.weightThreshold) {
|
||||
++m_numLinksIgnoredByWeightThreshold;
|
||||
m_totalLinkWeightIgnored += weight;
|
||||
return;
|
||||
}
|
||||
unsigned int stateId1 = addMultilayerNode(layer1, n1);
|
||||
unsigned int stateId2 = addMultilayerNode(layer2, n2);
|
||||
|
||||
if (stateId1 == stateId2) {
|
||||
// TODO: Handle self-links?
|
||||
}
|
||||
|
||||
if (layer1 == layer2) {
|
||||
++m_numIntraLayerLinks;
|
||||
m_sumIntraOutWeight[layer1][n1] += weight; // TODO: Not used? Add on target also if undirected (not inter/intra format)?
|
||||
} else {
|
||||
++m_numInterLayerLinks;
|
||||
}
|
||||
|
||||
addLink(stateId1, stateId2, weight);
|
||||
}
|
||||
|
||||
void Network::generateStateNetworkFromMultilayer()
|
||||
{
|
||||
// As inter-layer links is directed to neighbouring nodes in target layer,
|
||||
// the symmetry is broken so we need directed links for inter-layer flow
|
||||
m_haveDirectedInput = true;
|
||||
if (m_config.isUndirectedFlow()) {
|
||||
// TODO: Don't allow undirdir/outdirdir/rawdir?
|
||||
// Expand each undirected intra-layer link to two opposite directed links
|
||||
Log() << " -> Expanding undirected links to directed...\n";
|
||||
for (auto& layerIt : m_networks) {
|
||||
auto& network = layerIt.second;
|
||||
network.undirectedToDirected();
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_interLinks.empty()) {
|
||||
generateStateNetworkFromMultilayerWithInterLinks();
|
||||
} else {
|
||||
generateStateNetworkFromMultilayerWithSimulatedInterLinks();
|
||||
}
|
||||
m_networks.clear();
|
||||
m_interLinks.clear();
|
||||
}
|
||||
|
||||
void Network::generateStateNetworkFromMultilayerWithInterLinks()
|
||||
{
|
||||
Log() << "Generating state network from multilayer networks with inter-layer links...\n"
|
||||
<< std::flush;
|
||||
// First add intra-layer links
|
||||
for (auto& layerIt : m_networks) {
|
||||
unsigned int layer1 = layerIt.first;
|
||||
auto& network = layerIt.second;
|
||||
for (auto& linkIt : network.nodeLinkMap()) {
|
||||
auto& source = linkIt.first;
|
||||
const auto& subLinks = linkIt.second;
|
||||
for (auto& subIt : subLinks) {
|
||||
auto& target = subIt.first;
|
||||
double linkWeight = subIt.second.weight;
|
||||
addMultilayerLink(layer1, source.physicalId, layer1, target.physicalId, linkWeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log() << "Connecting layers...\n";
|
||||
// Connect layers with inter-layer links spread out in target layer
|
||||
for (auto& it : m_interLinks) {
|
||||
auto& layerNode = it.first;
|
||||
unsigned int layer1 = layerNode.layer;
|
||||
unsigned int physId = layerNode.node;
|
||||
unsigned int stateId1 = addMultilayerNode(layer1, physId);
|
||||
for (auto& it2 : it.second) {
|
||||
unsigned int layer2 = it2.first;
|
||||
double interWeight = it2.second;
|
||||
auto& targetNetwork = m_networks[layer2];
|
||||
|
||||
std::map<StateNode, std::map<StateNode, LinkData>>& targetLinks = targetNetwork.nodeLinkMap();
|
||||
auto& outlinks = targetLinks[StateNode(physId)];
|
||||
if (outlinks.empty()) {
|
||||
continue;
|
||||
}
|
||||
auto& targetOutWeights = targetNetwork.outWeights();
|
||||
double sumIntraOutWeightTargetLayer = targetOutWeights[physId];
|
||||
|
||||
for (auto& outLink : outlinks) {
|
||||
auto& targetPhysId = outLink.first.physicalId;
|
||||
auto& linkData = outLink.second;
|
||||
double intraWeight = linkData.weight;
|
||||
unsigned int stateId2i = addMultilayerNode(layer2, targetPhysId);
|
||||
|
||||
double weight = sumIntraOutWeightTargetLayer == 0.0 ? 0.0 : interWeight * intraWeight / sumIntraOutWeightTargetLayer;
|
||||
|
||||
addLink(stateId1, stateId2i, weight);
|
||||
++m_numInterLayerLinks; // TODO: Count all as one?
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_config.isUndirectedFlow()) {
|
||||
// For undirected inter-layer links, expand and add in other direction too
|
||||
for (auto& it : m_interLinks) {
|
||||
auto& layerNode = it.first;
|
||||
unsigned int layer2 = layerNode.layer;
|
||||
unsigned int physId = layerNode.node;
|
||||
auto& targetNetwork = m_networks[layer2];
|
||||
std::map<StateNode, std::map<StateNode, LinkData>>& targetLinks = targetNetwork.nodeLinkMap();
|
||||
auto& outlinks = targetLinks[StateNode(physId)];
|
||||
if (outlinks.empty()) {
|
||||
continue;
|
||||
}
|
||||
auto& targetOutWeights = targetNetwork.outWeights();
|
||||
double sumIntraOutWeightTargetLayer = targetOutWeights[physId];
|
||||
for (auto& it2 : it.second) {
|
||||
unsigned int layer1 = it2.first;
|
||||
double interWeight = it2.second;
|
||||
unsigned int stateId1 = addMultilayerNode(layer1, physId);
|
||||
|
||||
for (auto& outLink : outlinks) {
|
||||
auto& targetPhysId = outLink.first.physicalId;
|
||||
auto& linkData = outLink.second;
|
||||
double intraWeight = linkData.weight;
|
||||
unsigned int stateId2i = addMultilayerNode(layer2, targetPhysId);
|
||||
|
||||
double weight = sumIntraOutWeightTargetLayer == 0.0 ? 0.0 : interWeight * intraWeight / sumIntraOutWeightTargetLayer;
|
||||
|
||||
addLink(stateId1, stateId2i, weight);
|
||||
++m_numInterLayerLinks; // TODO: Count all as one?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Network::generateStateNetworkFromMultilayerWithSimulatedInterLinks()
|
||||
{
|
||||
Log() << "Generating state network from multilayer networks with simulated inter-layer links...\n"
|
||||
<< std::flush;
|
||||
double relaxRate = m_config.multilayerRelaxRate;
|
||||
|
||||
int maxRelaxLimit = m_networks.size();
|
||||
int relaxLimitSymmetric = m_config.multilayerRelaxLimit < 0 ? maxRelaxLimit : m_config.multilayerRelaxLimit;
|
||||
int relaxLimitDown = m_config.multilayerRelaxLimitDown < 0 ? relaxLimitSymmetric : std::min(relaxLimitSymmetric, m_config.multilayerRelaxLimitDown);
|
||||
int relaxLimitUp = m_config.multilayerRelaxLimitUp < 0 ? relaxLimitSymmetric : std::min(relaxLimitSymmetric, m_config.multilayerRelaxLimitUp);
|
||||
auto haveUpOrDownLimit = m_config.multilayerRelaxLimitDown >= 0 || m_config.multilayerRelaxLimitUp >= 0;
|
||||
|
||||
Log() << "-> " << m_networks.size() << " networks\n";
|
||||
Log() << "-> Relax rate: " << relaxRate << "\n";
|
||||
if (haveUpOrDownLimit) {
|
||||
Log() << "-> Relax limit up: " << relaxLimitUp << (relaxLimitUp == maxRelaxLimit ? " (no limit)\n" : "\n");
|
||||
Log() << "-> Relax limit down: " << relaxLimitDown << (relaxLimitDown == maxRelaxLimit ? " (no limit)\n" : "\n");
|
||||
} else if (m_config.multilayerRelaxLimit >= 0) {
|
||||
Log() << "-> Relax limit: " << m_config.multilayerRelaxLimit << "\n";
|
||||
}
|
||||
|
||||
auto withinRelaxLimit = [relaxLimitDown, relaxLimitUp](auto& layer1, auto& layer2) {
|
||||
int diff = layer1 - layer2;
|
||||
return layer1 >= layer2 ? diff <= relaxLimitDown : -diff <= relaxLimitUp;
|
||||
};
|
||||
|
||||
if (m_config.multilayerRelaxByJensenShannonDivergence) {
|
||||
Log() << "-> Using Jensen-Shannon Divergence\n";
|
||||
|
||||
for (unsigned int nodeId = 0; nodeId <= m_maxNodeIdInIntraLayerNetworks; ++nodeId) {
|
||||
unsigned int layer2from = 0;
|
||||
|
||||
// Calculate Jensen-Shannon similarity between all layers such that layer1 >= layer2,
|
||||
// and then use its symmetry for layer2 > layer1
|
||||
std::map<unsigned int, std::map<unsigned int, double>> jsRelaxWeights;
|
||||
std::map<unsigned int, double> jsTotWeight;
|
||||
|
||||
for (unsigned int layer1 = 0; layer1 < m_networks.size(); ++layer1) {
|
||||
unsigned int layer2to = layer1 + 1;
|
||||
// Limit possible jumps to close by layers
|
||||
if (m_config.multilayerRelaxLimit >= 0) {
|
||||
layer2from = ((int)layer1 - m_config.multilayerRelaxLimit) < 0 ? 0 : layer1 - m_config.multilayerRelaxLimit;
|
||||
}
|
||||
|
||||
auto& layer1LinkMap = m_networks[layer1].nodeLinkMap();
|
||||
auto& layer1OutLinks = layer1LinkMap[StateNode(nodeId)];
|
||||
// Skip dangling nodes, because they have no information to calculate similarity
|
||||
if (layer1OutLinks.empty())
|
||||
continue;
|
||||
|
||||
double sumOutLinkWeightLayer1 = m_networks[layer1].outWeights()[nodeId];
|
||||
|
||||
for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2) {
|
||||
auto& layer2LinkMap = m_networks[layer2].nodeLinkMap();
|
||||
auto& layer2OutLinks = layer2LinkMap[StateNode(nodeId)];
|
||||
if (layer2OutLinks.empty())
|
||||
continue;
|
||||
|
||||
double sumOutLinkWeightLayer2 = m_networks[layer2].outWeights()[nodeId];
|
||||
|
||||
bool intersect;
|
||||
double div = calculateJensenShannonDivergence(intersect, layer1OutLinks, sumOutLinkWeightLayer1, layer2OutLinks, sumOutLinkWeightLayer2);
|
||||
double jsWeight = 1.0 - div;
|
||||
if (intersect && (jsWeight >= m_config.multilayerJSRelaxLimit)) {
|
||||
jsTotWeight[layer1] += jsWeight;
|
||||
jsRelaxWeights[layer1][layer2] = jsWeight;
|
||||
if (layer1 != layer2) {
|
||||
jsTotWeight[layer2] += jsWeight;
|
||||
jsRelaxWeights[layer2][layer1] = jsWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second loop over all pairs of layers
|
||||
unsigned int layer2to = m_networks.size();
|
||||
|
||||
for (unsigned int layer1 = 0; layer1 < m_networks.size(); ++layer1) {
|
||||
// Limit possible jumps to close by layers
|
||||
if (m_config.multilayerRelaxLimit >= 0) {
|
||||
layer2from = ((int)layer1 - m_config.multilayerRelaxLimit) < 0 ? 0 : layer1 - m_config.multilayerRelaxLimit;
|
||||
layer2to = (layer1 + m_config.multilayerRelaxLimit) > m_networks.size() ? m_networks.size() : layer1 + m_config.multilayerRelaxLimit;
|
||||
}
|
||||
|
||||
double sumOutLinkWeightLayer1 = m_networks[layer1].outWeights()[nodeId];
|
||||
|
||||
auto jsRelaxWeightsLayer1It = jsRelaxWeights.find(layer1);
|
||||
auto jsTotWeightIt = jsTotWeight.find(layer1);
|
||||
|
||||
// Create inter-links to the intra-connected nodes in other layers
|
||||
for (unsigned int layer2 = layer2from; layer2 < layer2to; ++layer2) {
|
||||
if (jsRelaxWeightsLayer1It != jsRelaxWeights.end()) {
|
||||
auto jsRelaxWeightsIt = jsRelaxWeightsLayer1It->second.find(layer2);
|
||||
if (jsRelaxWeightsIt != jsRelaxWeightsLayer1It->second.end()) {
|
||||
bool isIntra = layer2 == layer1;
|
||||
|
||||
// Create inter-links to the outgoing nodes in the target layer
|
||||
double linkWeightNormalizationFactor;
|
||||
if (isIntra) {
|
||||
linkWeightNormalizationFactor = 1;
|
||||
} else {
|
||||
linkWeightNormalizationFactor = jsRelaxWeightsIt->second * relaxRate / (1.0 - relaxRate) * sumOutLinkWeightLayer1 / jsTotWeightIt->second;
|
||||
}
|
||||
|
||||
auto& targetLinks = m_networks[layer2].nodeLinkMap();
|
||||
auto& targetOutlinks = targetLinks[StateNode(nodeId)];
|
||||
if (targetOutlinks.empty()) {
|
||||
continue;
|
||||
}
|
||||
for (auto& outLink : targetOutlinks) {
|
||||
auto& n2 = outLink.first.physicalId;
|
||||
auto& linkData = outLink.second;
|
||||
double intraWeight = linkData.weight;
|
||||
// Add intra link weight as teleport weight to source node
|
||||
unsigned int stateId1 = addMultilayerNode(layer1, nodeId, intraWeight);
|
||||
unsigned int stateId2i = addMultilayerNode(layer2, n2, 0.0);
|
||||
|
||||
double weight = intraWeight == 0.0 ? 0.0 : linkWeightNormalizationFactor * intraWeight;
|
||||
addLink(stateId1, stateId2i, weight);
|
||||
++m_numInterLayerLinks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& it1 : m_networks) {
|
||||
auto layer1 = it1.first;
|
||||
auto& network1 = it1.second;
|
||||
|
||||
for (auto& n1It : network1.nodes()) {
|
||||
auto& n1 = n1It.first;
|
||||
unsigned int stateId1 = addMultilayerNode(layer1, n1);
|
||||
|
||||
double sumOutLinkWeightLayer1 = network1.outWeights()[n1];
|
||||
double sumOutWeightAllLayers = 0.0;
|
||||
|
||||
for (auto& it2 : m_networks) {
|
||||
auto layer2 = it2.first;
|
||||
if (!withinRelaxLimit(layer1, layer2)) {
|
||||
continue;
|
||||
}
|
||||
auto& network2 = it2.second;
|
||||
sumOutWeightAllLayers += network2.outWeights()[n1];
|
||||
}
|
||||
|
||||
if (sumOutWeightAllLayers <= 0) {
|
||||
continue;
|
||||
}
|
||||
for (auto& it2 : m_networks) {
|
||||
auto layer2 = it2.first;
|
||||
if (!withinRelaxLimit(layer1, layer2)) {
|
||||
continue;
|
||||
}
|
||||
auto& network2 = it2.second;
|
||||
bool isIntra = layer2 == layer1;
|
||||
|
||||
double linkWeightNormalizationFactor = relaxRate / sumOutWeightAllLayers;
|
||||
if (isIntra) {
|
||||
linkWeightNormalizationFactor += (1.0 - relaxRate) / sumOutLinkWeightLayer1;
|
||||
}
|
||||
|
||||
auto& targetLinks = network2.nodeLinkMap();
|
||||
auto& targetOutlinks = targetLinks[StateNode(n1)];
|
||||
if (targetOutlinks.empty()) {
|
||||
continue;
|
||||
}
|
||||
for (auto& outLink : targetOutlinks) {
|
||||
auto& n2 = outLink.first.physicalId;
|
||||
auto& linkData = outLink.second;
|
||||
double intraWeight = linkData.weight;
|
||||
unsigned int stateId2i = addMultilayerNode(layer2, n2);
|
||||
|
||||
double weight = intraWeight == 0.0 ? 0.0 : linkWeightNormalizationFactor * intraWeight;
|
||||
addLink(stateId1, stateId2i, weight);
|
||||
++m_numInterLayerLinks; // TODO: Count all as one?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double Network::calculateJensenShannonDivergence(bool& intersect, const OutLinkMap& layer1OutLinks, double sumOutLinkWeightLayer1, const OutLinkMap& layer2OutLinks, double sumOutLinkWeightLayer2)
|
||||
{
|
||||
intersect = false;
|
||||
double h1 = 0.0; // The entropy rate of the node in the first layer
|
||||
double h2 = 0.0; // The entropy rate of the node in the second layer
|
||||
double h12 = 0.0; // The entropy rate of the lumped node
|
||||
// The out-link weights of the nodes
|
||||
double ow1 = sumOutLinkWeightLayer1;
|
||||
double ow2 = sumOutLinkWeightLayer2;
|
||||
// Normalized weights over node in layer 1 and 2
|
||||
double pi1 = ow1 / (ow1 + ow2);
|
||||
double pi2 = ow2 / (ow1 + ow2);
|
||||
|
||||
auto layer1OutLinkIt = layer1OutLinks.begin();
|
||||
auto layer2OutLinkIt = layer2OutLinks.begin();
|
||||
auto layer1OutLinkItEnd = layer1OutLinks.end();
|
||||
auto layer2OutLinkItEnd = layer2OutLinks.end();
|
||||
while (layer1OutLinkIt != layer1OutLinkItEnd && layer2OutLinkIt != layer2OutLinkItEnd) {
|
||||
int diff = layer1OutLinkIt->first.id - layer2OutLinkIt->first.id;
|
||||
if (diff < 0) {
|
||||
// If the first state node has a link that the second has not
|
||||
double p1 = layer1OutLinkIt->second.weight / ow1;
|
||||
h1 -= p1 * log2(p1);
|
||||
double p12 = pi1 * layer1OutLinkIt->second.weight / ow1;
|
||||
h12 -= p12 * log2(p12);
|
||||
layer1OutLinkIt++;
|
||||
} else if (diff > 0) {
|
||||
// If the second state node has a link that the second has not
|
||||
double p2 = layer2OutLinkIt->second.weight / ow2;
|
||||
h2 -= p2 * log2(p2);
|
||||
double p12 = pi2 * layer2OutLinkIt->second.weight / ow2;
|
||||
h12 -= p12 * log2(p12);
|
||||
layer2OutLinkIt++;
|
||||
} else { // If both state nodes have the link
|
||||
intersect = true;
|
||||
double p1 = layer1OutLinkIt->second.weight / ow1;
|
||||
h1 -= p1 * log2(p1);
|
||||
double p2 = layer2OutLinkIt->second.weight / ow2;
|
||||
h2 -= p2 * log2(p2);
|
||||
double p12 = pi1 * layer1OutLinkIt->second.weight / ow1 + pi2 * layer2OutLinkIt->second.weight / ow2;
|
||||
h12 -= p12 * log2(p12);
|
||||
layer1OutLinkIt++;
|
||||
layer2OutLinkIt++;
|
||||
}
|
||||
}
|
||||
|
||||
while (layer1OutLinkIt != layer1OutLinkItEnd) {
|
||||
// If the first state node has a link that the second has not
|
||||
double p1 = layer1OutLinkIt->second.weight / ow1;
|
||||
h1 -= p1 * log2(p1);
|
||||
double p12 = pi1 * layer1OutLinkIt->second.weight / ow1;
|
||||
h12 -= p12 * log2(p12);
|
||||
layer1OutLinkIt++;
|
||||
}
|
||||
|
||||
while (layer2OutLinkIt != layer2OutLinkItEnd) {
|
||||
// If the second state node has a link that the second has not
|
||||
double p2 = layer2OutLinkIt->second.weight / ow2;
|
||||
h2 -= p2 * log2(p2);
|
||||
double p12 = pi2 * layer2OutLinkIt->second.weight / ow2;
|
||||
h12 -= p12 * log2(p12);
|
||||
layer2OutLinkIt++;
|
||||
}
|
||||
|
||||
double div = (pi1 + pi2) * h12 - pi1 * h1 - pi2 * h2;
|
||||
|
||||
// Fix precision problems
|
||||
if (div < 0.0)
|
||||
div = 0.0;
|
||||
else if (div > 1.0)
|
||||
div = 1.0;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
void Network::simulateInterLayerLinks()
|
||||
{
|
||||
}
|
||||
|
||||
void Network::addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight)
|
||||
{
|
||||
m_higherOrderInputMethodCalled = true;
|
||||
bool added = m_networks[layer].addLink(n1, n2, weight);
|
||||
if (added) {
|
||||
++m_numIntraLayerLinks;
|
||||
m_maxNodeIdInIntraLayerNetworks = std::max(m_maxNodeIdInIntraLayerNetworks, std::max(n1, n2));
|
||||
}
|
||||
}
|
||||
|
||||
void Network::addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight)
|
||||
{
|
||||
if (layer1 == layer2) {
|
||||
throw std::runtime_error(io::Str() << "Inter-layer link (layer1, node, layer2): " << layer1 << ", " << n << ", " << layer2 << " must have layer1 != layer2");
|
||||
}
|
||||
m_higherOrderInputMethodCalled = true;
|
||||
|
||||
auto& interLinks = m_interLinks[LayerNode(layer1, n)];
|
||||
auto it = interLinks.find(layer2);
|
||||
|
||||
if (it == interLinks.end()) {
|
||||
++m_numInterLayerLinks;
|
||||
}
|
||||
interLinks[layer2] += interWeight;
|
||||
}
|
||||
|
||||
unsigned int Network::addMultilayerNode(unsigned int layerId, unsigned int physicalId, double weight)
|
||||
{
|
||||
m_higherOrderInputMethodCalled = true;
|
||||
|
||||
// Create state node if not already exist, return state node id
|
||||
auto& layerIt = m_layerNodeToStateId[layerId];
|
||||
auto it = layerIt.find(physicalId);
|
||||
|
||||
if (it != layerIt.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool matchableMultilayerIds = m_config.matchableMultilayerIds != 0;
|
||||
|
||||
if (matchableMultilayerIds && layerId > m_config.matchableMultilayerIds) {
|
||||
throw std::runtime_error(io::Str() << "Cannot add node with layer " << layerId << " to network with matchable multilayer ids using largest layer id " << m_config.matchableMultilayerIds);
|
||||
}
|
||||
|
||||
auto ret = matchableMultilayerIds
|
||||
? addStateNodeWithDeterministicId(physicalId, layerId, m_multilayerStateIdBitShift)
|
||||
: addStateNodeWithAutogeneratedId(physicalId);
|
||||
auto& stateNode = ret.first->second;
|
||||
stateNode.layerId = layerId;
|
||||
stateNode.weight = weight;
|
||||
m_layerNodeToStateId[layerId][physicalId] = stateNode.id;
|
||||
m_layers.insert(layerId);
|
||||
return stateNode.id;
|
||||
}
|
||||
|
||||
void Network::addMetaData(unsigned int nodeId, int meta)
|
||||
{
|
||||
std::vector<int> metaData(1, meta);
|
||||
addMetaData(nodeId, metaData);
|
||||
}
|
||||
|
||||
void Network::addMetaData(unsigned int nodeId, const std::vector<int>& metaData)
|
||||
{
|
||||
m_metaData[nodeId] = metaData;
|
||||
if (m_numMetaDataColumns == 0) {
|
||||
m_numMetaDataColumns = metaData.size();
|
||||
} else if (metaData.size() != m_numMetaDataColumns) {
|
||||
throw std::runtime_error(io::Str() << "Must have same number of dimensions in meta data, error trying to add meta data '" << io::stringify(metaData, ",") << "' on node " << nodeId << ".");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,214 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef NETWORK_H_
|
||||
#define NETWORK_H_
|
||||
|
||||
#include "Config.h"
|
||||
#include "../core/StateNetwork.h"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <locale>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
struct LayerNode;
|
||||
|
||||
class Network : public StateNetwork {
|
||||
private:
|
||||
// Helpers
|
||||
std::istringstream m_extractor;
|
||||
|
||||
// Multilayer
|
||||
std::map<unsigned int, Network> m_networks; // intra-layer links
|
||||
std::map<LayerNode, std::map<unsigned int, double>> m_interLinks;
|
||||
// { layer -> { physId -> stateId }}
|
||||
std::map<unsigned int, std::map<unsigned int, unsigned int>> m_layerNodeToStateId;
|
||||
std::map<unsigned int, std::map<unsigned int, double>> m_sumIntraOutWeight;
|
||||
std::set<unsigned int> m_layers;
|
||||
unsigned int m_numInterLayerLinks = 0;
|
||||
unsigned int m_numIntraLayerLinks = 0;
|
||||
unsigned int m_maxNodeIdInIntraLayerNetworks = 0;
|
||||
|
||||
unsigned int m_multilayerStateIdBitShift = 0;
|
||||
|
||||
// Meta data
|
||||
std::map<unsigned int, std::vector<int>> m_metaData;
|
||||
unsigned int m_numMetaDataColumns = 0;
|
||||
|
||||
using InsensitiveStringSet = std::set<std::string, io::InsensitiveCompare>;
|
||||
|
||||
std::map<std::string, InsensitiveStringSet> m_ignoreHeadings;
|
||||
std::map<std::string, InsensitiveStringSet> m_validHeadings; // {
|
||||
// { "pajek", {"*Vertices", "*Edges", "*Arcs"} },
|
||||
// { "link-list", {"*Links"} },
|
||||
// { "bipartite", {"*Vertices", "*Bipartite"} },
|
||||
// { "general", {"*Vertices", "*States", "*Edges", "*Arcs", "*Links", "*Context"} }
|
||||
// };
|
||||
|
||||
public:
|
||||
Network() : StateNetwork() { init(); }
|
||||
explicit Network(const Config& config) : StateNetwork(config) { init(); }
|
||||
explicit Network(const std::string& flags) : StateNetwork(Config(flags)) { init(); }
|
||||
~Network() override = default;
|
||||
|
||||
Network(const Network&) = delete;
|
||||
Network& operator=(const Network&) = delete;
|
||||
Network(Network&&) = delete;
|
||||
Network& operator=(Network&&) = delete;
|
||||
|
||||
void clear() override;
|
||||
|
||||
/**
|
||||
* Parse network data from file and generate network
|
||||
* @param filename input network
|
||||
* @param accumulate add to possibly existing network data (default), else clear before.
|
||||
*/
|
||||
virtual void readInputData(std::string filename = "", bool accumulate = true);
|
||||
|
||||
/**
|
||||
* Init categorical meta data on all nodes from a file with the following format:
|
||||
* # nodeId metaData
|
||||
* 1 1
|
||||
* 2 1
|
||||
* 3 2
|
||||
* 4 2
|
||||
* 5 3
|
||||
* @param filename input filename for metadata
|
||||
*/
|
||||
virtual void readMetaData(const std::string& filename);
|
||||
|
||||
unsigned int numMetaDataColumns() const { return m_numMetaDataColumns; }
|
||||
const std::map<unsigned int, std::vector<int>>& metaData() const override { return m_metaData; }
|
||||
|
||||
bool isMultilayerNetwork() const { return !m_layerNodeToStateId.empty(); }
|
||||
const std::map<unsigned int, std::map<unsigned int, unsigned int>>& layerNodeToStateId() const { return m_layerNodeToStateId; }
|
||||
|
||||
void postProcessInputData();
|
||||
void generateStateNetworkFromMultilayer();
|
||||
void generateStateNetworkFromMultilayerWithInterLinks();
|
||||
void generateStateNetworkFromMultilayerWithSimulatedInterLinks();
|
||||
void simulateInterLayerLinks();
|
||||
|
||||
/**
|
||||
* Create state node corresponding to this multilayer node if not already exist
|
||||
* @return state node id
|
||||
*/
|
||||
unsigned int addMultilayerNode(unsigned int layerId, unsigned int physicalId, double weight = 1.0);
|
||||
|
||||
void addMultilayerLink(unsigned int layer1, unsigned int n1, unsigned int layer2, unsigned int n2, double weight);
|
||||
|
||||
/**
|
||||
* Create an intra-layer link
|
||||
*/
|
||||
void addMultilayerIntraLink(unsigned int layer, unsigned int n1, unsigned int n2, double weight);
|
||||
|
||||
/**
|
||||
* Create links between (layer1,n) and (layer2,m) for all m connected to n in layer 2.
|
||||
* The weight is distributed proportionally.
|
||||
* TODO: This is done later..
|
||||
*/
|
||||
void addMultilayerInterLink(unsigned int layer1, unsigned int n, unsigned int layer2, double interWeight);
|
||||
|
||||
void addMetaData(unsigned int nodeId, int meta);
|
||||
|
||||
void addMetaData(unsigned int nodeId, const std::vector<int>& metaData);
|
||||
|
||||
private:
|
||||
void init();
|
||||
void initValidHeadings();
|
||||
|
||||
void parseNetwork(const std::string& filename);
|
||||
void parseNetwork(const std::string& filename, const InsensitiveStringSet& validHeadings, const InsensitiveStringSet& ignoreHeadings, const std::string& startHeading = "");
|
||||
|
||||
// Helper methods
|
||||
|
||||
/**
|
||||
* Parse vertices under the heading
|
||||
* @return The line after the vertices
|
||||
*/
|
||||
std::string parseVertices(std::ifstream& file, const std::string& heading);
|
||||
std::string parseStateNodes(std::ifstream& file, const std::string& heading);
|
||||
|
||||
std::string parseLinks(std::ifstream& file);
|
||||
|
||||
/**
|
||||
* Parse multilayer links from a *multilayer section
|
||||
*/
|
||||
std::string parseMultilayerLinks(std::ifstream& file);
|
||||
|
||||
/**
|
||||
* Parse multilayer links from an *intra section
|
||||
*/
|
||||
std::string parseMultilayerIntraLinks(std::ifstream& file);
|
||||
|
||||
/**
|
||||
* Parse multilayer links from an *inter section
|
||||
*/
|
||||
std::string parseMultilayerInterLinks(std::ifstream& file);
|
||||
|
||||
std::string parseBipartiteLinks(std::ifstream& file, const std::string& heading);
|
||||
|
||||
static std::string ignoreSection(std::ifstream& file, const std::string& heading);
|
||||
|
||||
void parseStateNode(const std::string& line, StateNetwork::StateNode& stateNode);
|
||||
|
||||
/**
|
||||
* Parse a string of link data.
|
||||
* If no weight data can be extracted, the default value 1.0 will be used.
|
||||
* @throws an error if not both node ids can be extracted.
|
||||
*/
|
||||
void parseLink(const std::string& line, unsigned int& n1, unsigned int& n2, double& weight);
|
||||
|
||||
/**
|
||||
* Parse a string of multilayer link data.
|
||||
* If no weight data can be extracted, the default value 1.0 will be used.
|
||||
* @throws an error if not both node and layer ids can be extracted.
|
||||
*/
|
||||
void parseMultilayerLink(const std::string& line, unsigned int& layer1, unsigned int& n1, unsigned int& layer2, unsigned int& n2, double& weight);
|
||||
|
||||
/**
|
||||
* Parse a string of intra-multilayer link data.
|
||||
* If no weight data can be extracted, the default value 1.0 will be used.
|
||||
* @throws an error if not both node and layer ids can be extracted.
|
||||
*/
|
||||
void parseMultilayerIntraLink(const std::string& line, unsigned int& layer, unsigned int& n1, unsigned int& n2, double& weight);
|
||||
|
||||
/**
|
||||
* Parse a string of inter-multilayer link data.
|
||||
* If no weight data can be extracted, the default value 1.0 will be used.
|
||||
* @throws an error if not both node and layer ids can be extracted.
|
||||
*/
|
||||
void parseMultilayerInterLink(const std::string& line, unsigned int& layer1, unsigned int& n, unsigned int& layer2, double& weight);
|
||||
|
||||
static double calculateJensenShannonDivergence(bool& intersect, const OutLinkMap& layer1OutLinks, double sumOutLinkWeightLayer1, const OutLinkMap& layer2OutLinks, double sumOutLinkWeightLayer2);
|
||||
|
||||
void printSummary();
|
||||
};
|
||||
|
||||
struct LayerNode {
|
||||
unsigned int layer, node;
|
||||
explicit LayerNode(unsigned int layer = 0, unsigned int node = 0) : layer(layer), node(node) { }
|
||||
|
||||
bool operator<(const LayerNode other) const
|
||||
{
|
||||
return layer == other.layer ? node < other.node : layer < other.layer;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // NETWORK_H_
|
||||
@@ -0,0 +1,629 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "Output.h"
|
||||
#include "../core/InfomapBase.h"
|
||||
#include "../core/StateNetwork.h"
|
||||
#include "../io/SafeFile.h"
|
||||
|
||||
namespace infomap {
|
||||
|
||||
std::string getOutputFilename(const InfomapBase& im, const std::string& filename, const std::string& ext, bool states)
|
||||
{
|
||||
if (!filename.empty()) {
|
||||
return filename;
|
||||
}
|
||||
|
||||
auto defaultFilename = im.outDirectory + im.outName;
|
||||
|
||||
if (im.haveMemory() && states) {
|
||||
defaultFilename += "_states";
|
||||
}
|
||||
|
||||
return defaultFilename + ext;
|
||||
}
|
||||
|
||||
std::string getOutputFileHeader(const InfomapBase& im, const StateNetwork& network, bool states)
|
||||
{
|
||||
std::string bipartiteInfo = io::Str() << "\n# bipartite start id " << network.bipartiteStartId();
|
||||
return io::Str() << "# v" << INFOMAP_VERSION << "\n"
|
||||
<< "# ./Infomap " << im.parsedString << "\n"
|
||||
<< "# started at " << im.getStartDate() << "\n"
|
||||
<< "# completed in " << im.getElapsedTime().getElapsedTimeInSec() << " s\n"
|
||||
<< "# partitioned into " << im.maxTreeDepth() << " levels with " << im.numTopModules() << " top modules\n"
|
||||
<< "# codelength " << im.codelength() << " bits\n"
|
||||
<< "# relative codelength savings " << im.getRelativeCodelengthSavings() * 100 << "%\n"
|
||||
<< "# flow model " << flowModelToString(im.flowModel)
|
||||
<< (im.haveMemory() ? "\n# higher order" : "")
|
||||
<< (im.haveMemory() ? states ? "\n# state level" : "\n# physical level" : "")
|
||||
<< (network.isBipartite() ? bipartiteInfo : "");
|
||||
}
|
||||
|
||||
std::string getNodeName(const std::map<unsigned int, std::string>& names, const InfoNode& node)
|
||||
{
|
||||
try {
|
||||
return names.at(node.physicalId);
|
||||
} catch (...) {
|
||||
return io::stringify(node.physicalId);
|
||||
}
|
||||
}
|
||||
|
||||
std::string writeClu(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states, int moduleIndexLevel)
|
||||
{
|
||||
auto outputFilename = getOutputFilename(im, filename, ".clu", states);
|
||||
SafeOutFile outFile { outputFilename };
|
||||
|
||||
outFile << std::setprecision(9);
|
||||
outFile << getOutputFileHeader(im, network, states) << "\n";
|
||||
outFile << "# module level " << moduleIndexLevel << "\n";
|
||||
outFile << std::resetiosflags(std::ios::floatfield) << std::setprecision(6);
|
||||
|
||||
if (states) {
|
||||
outFile << "# state_id module flow node_id";
|
||||
if (im.isMultilayerNetwork())
|
||||
outFile << " layer_id";
|
||||
outFile << '\n';
|
||||
} else {
|
||||
outFile << "# node_id module flow\n";
|
||||
}
|
||||
|
||||
const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes;
|
||||
const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0;
|
||||
|
||||
if (im.haveMemory() && !states) {
|
||||
for (auto it(im.iterTreePhysical(moduleIndexLevel)); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outFile << node.physicalId << " " << it.moduleId() << " " << node.data.flow << "\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto it(im.iterTree(moduleIndexLevel)); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (states) {
|
||||
outFile << node.stateId << " " << it.moduleId() << " " << node.data.flow << " " << node.physicalId;
|
||||
if (im.isMultilayerNetwork())
|
||||
outFile << " " << node.layerId;
|
||||
outFile << "\n";
|
||||
} else
|
||||
outFile << node.physicalId << " " << it.moduleId() << " " << node.data.flow << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
void writeTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states)
|
||||
{
|
||||
auto oldPrecision = outStream.precision();
|
||||
outStream << std::setprecision(9);
|
||||
outStream << getOutputFileHeader(im, network, states) << "\n";
|
||||
outStream << std::setprecision(6);
|
||||
|
||||
if (states) {
|
||||
outStream << "# path flow name state_id node_id";
|
||||
if (im.isMultilayerNetwork())
|
||||
outStream << " layer_id";
|
||||
outStream << '\n';
|
||||
} else {
|
||||
outStream << "# path flow name node_id\n";
|
||||
}
|
||||
|
||||
const auto shouldHideBipartiteNodes = !im.printFlowTree && im.isBipartite() && im.hideBipartiteNodes;
|
||||
const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0;
|
||||
|
||||
// TODO: Make a general iterator where merging physical nodes depend on a parameter rather than type to be able to DRY here
|
||||
if (im.haveMemory() && !states) {
|
||||
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& path = it.path();
|
||||
|
||||
outStream << io::stringify(path, ":") << " " << node.data.flow << " \"" << getNodeName(network.names(), node) << "\" " << node.physicalId << '\n';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& path = it.path();
|
||||
|
||||
outStream << io::stringify(path, ":") << " " << node.data.flow << " \"" << getNodeName(network.names(), node) << "\" ";
|
||||
|
||||
if (states) {
|
||||
outStream << node.stateId << " " << node.physicalId;
|
||||
if (im.isMultilayerNetwork())
|
||||
outStream << " " << node.layerId;
|
||||
outStream << '\n';
|
||||
} else {
|
||||
outStream << node.physicalId << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outStream << std::setprecision(oldPrecision);
|
||||
}
|
||||
|
||||
using Link = std::pair<unsigned int, unsigned int>;
|
||||
using LinkMap = std::map<Link, double>;
|
||||
|
||||
std::map<std::string, LinkMap> aggregateModuleLinks(InfomapBase& im, bool states)
|
||||
{
|
||||
// Aggregate links between each module. Rest is aggregated as exit flow
|
||||
|
||||
// Links on nodes within sub infomap instances doesn't have links outside the root
|
||||
// so iterate over links on main instance and map to infomap tree iterator
|
||||
bool mergePhysicalNodes = im.haveMemory() && !states;
|
||||
|
||||
// Map state id to parent in infomap tree iterator
|
||||
std::map<unsigned int, InfoNode*> stateIdToParent;
|
||||
std::map<unsigned int, unsigned int> stateIdToChildIndex;
|
||||
|
||||
if (mergePhysicalNodes) {
|
||||
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
|
||||
if (it->isLeaf()) {
|
||||
for (auto stateId : it->stateNodes) {
|
||||
stateIdToParent[stateId] = it->parent;
|
||||
stateIdToChildIndex[stateId] = it.childIndex();
|
||||
}
|
||||
} else {
|
||||
// Use stateId to store depth on modules to simplify link aggregation
|
||||
it->stateId = it.depth();
|
||||
it->index = it.childIndex();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
|
||||
if (it->isLeaf()) {
|
||||
stateIdToParent[it->stateId] = it->parent;
|
||||
stateIdToChildIndex[it->stateId] = it.childIndex();
|
||||
} else {
|
||||
// Use stateId to store depth on modules to simplify link aggregation
|
||||
it->stateId = it.depth();
|
||||
it->index = it.childIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, LinkMap> moduleLinks;
|
||||
|
||||
for (auto& leaf : im.leafNodes()) {
|
||||
for (auto& link : leaf->outEdges()) {
|
||||
double flow = link->data.flow;
|
||||
InfoNode* sourceParent = stateIdToParent[link->source->stateId];
|
||||
InfoNode* targetParent = stateIdToParent[link->target->stateId];
|
||||
|
||||
auto sourceDepth = sourceParent->calculatePath().size() + 1;
|
||||
auto targetDepth = targetParent->calculatePath().size() + 1;
|
||||
|
||||
auto sourceChildIndex = stateIdToChildIndex[link->source->stateId];
|
||||
auto targetChildIndex = stateIdToChildIndex[link->target->stateId];
|
||||
|
||||
auto sourceParentIt = InfomapParentIterator(sourceParent);
|
||||
auto targetParentIt = InfomapParentIterator(targetParent);
|
||||
|
||||
// Iterate to same depth
|
||||
// First raise target
|
||||
while (targetDepth > sourceDepth) {
|
||||
++targetParentIt;
|
||||
--targetDepth;
|
||||
}
|
||||
|
||||
// Raise source to same depth
|
||||
while (sourceDepth > targetDepth) {
|
||||
++sourceParentIt;
|
||||
--sourceDepth;
|
||||
}
|
||||
|
||||
auto currentDepth = sourceDepth;
|
||||
// Add link if same parent
|
||||
|
||||
while (currentDepth > 0) {
|
||||
if (sourceParentIt == targetParentIt) {
|
||||
// Skip self-links
|
||||
if (sourceChildIndex != targetChildIndex) {
|
||||
auto parentId = io::stringify(sourceParentIt->calculatePath(), ":");
|
||||
auto& linkMap = moduleLinks[parentId];
|
||||
linkMap[std::make_pair(sourceChildIndex + 1, targetChildIndex + 1)] += flow;
|
||||
}
|
||||
}
|
||||
|
||||
sourceChildIndex = sourceParentIt->index;
|
||||
targetChildIndex = targetParentIt->index;
|
||||
|
||||
++sourceParentIt;
|
||||
++targetParentIt;
|
||||
|
||||
--currentDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moduleLinks;
|
||||
}
|
||||
|
||||
void writeTreeLinks(InfomapBase& im, std::ostream& outStream, bool states)
|
||||
{
|
||||
auto oldPrecision = outStream.precision();
|
||||
outStream << std::setprecision(6);
|
||||
|
||||
auto moduleLinks = aggregateModuleLinks(im, states);
|
||||
|
||||
outStream << "*Links " << (im.isUndirectedFlow() ? "undirected" : "directed") << "\n";
|
||||
outStream << "#*Links path enterFlow exitFlow numEdges numChildren\n";
|
||||
|
||||
// Use stateId to store depth on modules to optimize link aggregation
|
||||
for (auto it(im.iterModules()); !it.isEnd(); ++it) {
|
||||
auto parentId = io::stringify(it.path(), ":");
|
||||
auto& module = *it;
|
||||
auto& links = moduleLinks[parentId];
|
||||
|
||||
outStream << "*Links " << (parentId.empty() ? "root" : parentId) << " " << module.data.enterFlow << " " << module.data.exitFlow << " " << links.size() << " " << module.infomapChildDegree() << "\n";
|
||||
|
||||
for (auto itLink : links) {
|
||||
unsigned int sourceId = itLink.first.first;
|
||||
unsigned int targetId = itLink.first.second;
|
||||
double flow = itLink.second;
|
||||
outStream << sourceId << " " << targetId << " " << flow << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
outStream << std::setprecision(oldPrecision);
|
||||
}
|
||||
|
||||
void writeNewickTree(InfomapBase& im, std::ostream& outStream, bool states)
|
||||
{
|
||||
auto oldPrecision = outStream.precision();
|
||||
outStream << std::setprecision(6);
|
||||
|
||||
auto isRoot = true;
|
||||
unsigned int lastDepth = 0;
|
||||
std::vector<double> flowStack;
|
||||
|
||||
auto writeNewickNode = [&](const InfoNode& node, unsigned int depth) {
|
||||
if (depth > lastDepth || isRoot) {
|
||||
outStream << "(";
|
||||
flowStack.push_back(node.data.flow);
|
||||
if (node.isLeaf())
|
||||
outStream << (states ? node.stateId : node.physicalId) << ":" << node.data.flow;
|
||||
} else if (depth == lastDepth) {
|
||||
outStream << ",";
|
||||
flowStack[flowStack.size() - 1] = node.data.flow;
|
||||
if (node.isLeaf()) {
|
||||
outStream << (states ? node.stateId : node.physicalId) << ":" << node.data.flow;
|
||||
}
|
||||
} else {
|
||||
// depth < lastDepth
|
||||
while (flowStack.size() > depth + 1) {
|
||||
flowStack.pop_back();
|
||||
outStream << "):" << flowStack.back();
|
||||
}
|
||||
flowStack[flowStack.size() - 1] = node.data.flow;
|
||||
outStream << ",";
|
||||
}
|
||||
lastDepth = depth;
|
||||
isRoot = false;
|
||||
};
|
||||
|
||||
// TODO: Make a general iterator where merging physical nodes depend on a parameter rather than type to be able to DRY here
|
||||
if (im.haveMemory() && !states) {
|
||||
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
|
||||
writeNewickNode(*it, it.depth());
|
||||
}
|
||||
} else {
|
||||
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
|
||||
writeNewickNode(*it, it.depth());
|
||||
}
|
||||
}
|
||||
while (flowStack.size() > 1) {
|
||||
flowStack.pop_back();
|
||||
outStream << "):" << flowStack.back();
|
||||
}
|
||||
outStream << ");\n";
|
||||
outStream << std::setprecision(oldPrecision);
|
||||
}
|
||||
|
||||
void writeJsonTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states, bool writeLinks)
|
||||
{
|
||||
auto oldPrecision = outStream.precision();
|
||||
|
||||
outStream << "{";
|
||||
|
||||
outStream << "\"version\":\"v" << INFOMAP_VERSION << "\","
|
||||
<< "\"args\":\"" << im.parsedString << "\","
|
||||
<< "\"startedAt\":\"" << im.getStartDate() << "\","
|
||||
<< "\"completedIn\":" << im.getElapsedTime().getElapsedTimeInSec() << ","
|
||||
<< "\"codelength\":" << im.codelength() << ","
|
||||
<< "\"numLevels\":" << im.maxTreeDepth() << ","
|
||||
<< "\"numTopModules\":" << im.numTopModules() << ","
|
||||
<< "\"relativeCodelengthSavings\":" << im.getRelativeCodelengthSavings() << ","
|
||||
<< "\"directed\":" << (im.isUndirectedFlow() ? "false" : "true") << ","
|
||||
<< "\"flowModel\": \"" << flowModelToString(im.flowModel) << "\","
|
||||
<< "\"higherOrder\":" << (im.haveMemory() ? "true" : "false") << ",";
|
||||
|
||||
if (im.haveMemory()) {
|
||||
outStream << "\"stateLevel\":" << (states ? "true" : "false") << ",";
|
||||
}
|
||||
|
||||
if (im.isBipartite()) {
|
||||
outStream << "\"bipartiteStartId\":" << network.bipartiteStartId() << ",";
|
||||
}
|
||||
|
||||
outStream << std::setprecision(6);
|
||||
|
||||
outStream << "\"nodes\":[";
|
||||
|
||||
const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes;
|
||||
const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0;
|
||||
|
||||
auto metaData = network.metaData();
|
||||
auto writeMeta = [&metaData](auto& outStream, auto nodeId) {
|
||||
outStream << "\"metadata\":{";
|
||||
auto meta = metaData[nodeId];
|
||||
for (unsigned int i = 0; i < meta.size(); ++i) {
|
||||
outStream << '"' << i << "\":"
|
||||
<< '"' << meta[i] << '"'; // metadata class as string to highlight that this is a categorical variable
|
||||
if (i < meta.size() - 1)
|
||||
outStream << ',';
|
||||
}
|
||||
outStream << "},";
|
||||
};
|
||||
|
||||
// don't append a comma after the last entry
|
||||
auto first = true;
|
||||
|
||||
if (im.haveMemory() && !states) {
|
||||
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto path = io::stringify(it.path(), ",");
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
outStream << ",";
|
||||
}
|
||||
|
||||
outStream << "{"
|
||||
<< "\"path\":[" << path << "],"
|
||||
<< "\"name\":\"" << getNodeName(network.names(), node) << "\","
|
||||
<< "\"flow\":" << node.data.flow << ","
|
||||
<< "\"mec\":" << it.modularCentrality() << ","
|
||||
<< "\"id\":" << node.physicalId << "}";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto multilevelModules = im.getMultilevelModules(states);
|
||||
|
||||
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
outStream << ",";
|
||||
}
|
||||
|
||||
const auto path = io::stringify(it.path(), ", ");
|
||||
const auto modules = im.haveModules() ? io::stringify(multilevelModules.at(states ? node.stateId : node.physicalId), ", ") : "1";
|
||||
|
||||
outStream << "{"
|
||||
<< "\"path\":[" << path << "],"
|
||||
<< "\"modules\":[" << modules << "],"
|
||||
<< "\"name\":\"" << getNodeName(network.names(), node) << "\","
|
||||
<< "\"flow\":" << node.data.flow << ","
|
||||
<< "\"mec\":" << it.modularCentrality() << ",";
|
||||
|
||||
// can't currently use both memory and meta map equation
|
||||
if (im.haveMetaData() && !states) {
|
||||
writeMeta(outStream, node.physicalId);
|
||||
}
|
||||
|
||||
if (states) {
|
||||
outStream << "\"stateId\":" << node.stateId << ",";
|
||||
if (im.isMultilayerNetwork())
|
||||
outStream << "\"layerId\":" << node.layerId << ",";
|
||||
}
|
||||
|
||||
outStream << "\"id\":" << node.physicalId << "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outStream << "],"; // tree
|
||||
|
||||
// -------------
|
||||
// Write modules
|
||||
// -------------
|
||||
|
||||
// Uses stateId to store depth on modules to optimize link aggregation
|
||||
auto moduleLinks = aggregateModuleLinks(im, states);
|
||||
|
||||
first = true;
|
||||
|
||||
outStream << "\"modules\":[";
|
||||
|
||||
for (auto it(im.iterModules()); !it.isEnd(); ++it) {
|
||||
const auto parentId = io::stringify(it.path(), ":");
|
||||
const auto& module = *it;
|
||||
const auto& links = moduleLinks[parentId];
|
||||
const auto path = io::stringify(it.path(), ",");
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
outStream << ",";
|
||||
}
|
||||
|
||||
outStream << "{";
|
||||
|
||||
outStream << "\"path\":[" << (parentId.empty() ? "0" : path) << "],"
|
||||
<< "\"enterFlow\":" << module.data.enterFlow << ','
|
||||
<< "\"exitFlow\":" << module.data.exitFlow << ','
|
||||
<< "\"numEdges\":" << links.size() << ','
|
||||
<< "\"numChildren\":" << module.infomapChildDegree() << ','
|
||||
<< "\"codelength\":" << module.codelength;
|
||||
|
||||
if (writeLinks) {
|
||||
outStream << ","
|
||||
<< "\"links\":[";
|
||||
|
||||
auto firstLink = true;
|
||||
|
||||
for (auto itLink : links) {
|
||||
if (firstLink) {
|
||||
firstLink = false;
|
||||
} else {
|
||||
outStream << ",";
|
||||
}
|
||||
|
||||
unsigned int sourceId = itLink.first.first;
|
||||
unsigned int targetId = itLink.first.second;
|
||||
double flow = itLink.second;
|
||||
outStream << "{\"source\":" << sourceId << ",\"target\":" << targetId << ",\"flow\":" << flow << "}";
|
||||
}
|
||||
outStream << "]"; // links
|
||||
}
|
||||
|
||||
outStream << "}";
|
||||
}
|
||||
|
||||
outStream << "]"; // modules
|
||||
|
||||
outStream << "}";
|
||||
|
||||
outStream << std::setprecision(oldPrecision);
|
||||
}
|
||||
|
||||
void writeCsvTree(InfomapBase& im, const StateNetwork& network, std::ostream& outStream, bool states)
|
||||
{
|
||||
auto oldPrecision = outStream.precision();
|
||||
outStream << std::setprecision(6);
|
||||
|
||||
outStream << "path,flow,name,";
|
||||
|
||||
if (im.haveMemory() && !states) {
|
||||
outStream << "node_id\n";
|
||||
} else {
|
||||
if (states) {
|
||||
outStream << "state_id,";
|
||||
if (im.isMultilayerNetwork())
|
||||
outStream << "layer_id,";
|
||||
}
|
||||
outStream << "node_id\n";
|
||||
}
|
||||
|
||||
const auto shouldHideBipartiteNodes = im.isBipartite() && im.hideBipartiteNodes;
|
||||
const auto bipartiteStartId = shouldHideBipartiteNodes ? network.bipartiteStartId() : 0;
|
||||
|
||||
if (im.haveMemory() && !states) {
|
||||
for (auto it(im.iterTreePhysical()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto path = io::stringify(it.path(), ":");
|
||||
outStream << path << ',' << node.data.flow << ",\"" << getNodeName(network.names(), node) << "\"," << node.physicalId << '\n';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto it(im.iterTree()); !it.isEnd(); ++it) {
|
||||
InfoNode& node = *it;
|
||||
if (node.isLeaf()) {
|
||||
if (shouldHideBipartiteNodes && node.physicalId >= bipartiteStartId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto path = io::stringify(it.path(), ":");
|
||||
outStream << path << ',' << node.data.flow << ",\"" << getNodeName(network.names(), node) << "\",";
|
||||
|
||||
if (states) {
|
||||
outStream << node.stateId << ',';
|
||||
if (im.isMultilayerNetwork())
|
||||
outStream << node.layerId << ',';
|
||||
}
|
||||
|
||||
outStream << node.physicalId << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outStream << std::setprecision(oldPrecision);
|
||||
}
|
||||
|
||||
std::string writeTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states)
|
||||
{
|
||||
auto outputFilename = getOutputFilename(im, filename, ".tree", states);
|
||||
SafeOutFile outFile { outputFilename };
|
||||
writeTree(im, network, outFile, states);
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
std::string writeFlowTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states)
|
||||
{
|
||||
auto outputFilename = getOutputFilename(im, filename, ".ftree", states);
|
||||
SafeOutFile outFile { outputFilename };
|
||||
writeTree(im, network, outFile, states);
|
||||
writeTreeLinks(im, outFile, states);
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
std::string writeNewickTree(InfomapBase& im, const std::string& filename, bool states)
|
||||
{
|
||||
auto outputFilename = getOutputFilename(im, filename, ".nwk", states);
|
||||
SafeOutFile outFile { outputFilename };
|
||||
writeNewickTree(im, outFile, states);
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
std::string writeJsonTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states, bool writeLinks)
|
||||
{
|
||||
auto outputFilename = getOutputFilename(im, filename, ".json", states);
|
||||
SafeOutFile outFile { outputFilename };
|
||||
writeJsonTree(im, network, outFile, states, writeLinks);
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
std::string writeCsvTree(InfomapBase& im, const StateNetwork& network, const std::string& filename, bool states)
|
||||
{
|
||||
auto outputFilename = getOutputFilename(im, filename, ".csv", states);
|
||||
SafeOutFile outFile { outputFilename };
|
||||
writeCsvTree(im, network, outFile, states);
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef OUTPUT_H_
|
||||
#define OUTPUT_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InfomapBase;
|
||||
class StateNetwork;
|
||||
|
||||
std::string writeTree(InfomapBase&, const StateNetwork&, const std::string&, bool states);
|
||||
|
||||
std::string writeFlowTree(InfomapBase&, const StateNetwork&, const std::string&, bool states);
|
||||
|
||||
std::string writeNewickTree(InfomapBase&, const std::string&, bool states);
|
||||
|
||||
std::string writeJsonTree(InfomapBase&, const StateNetwork&, const std::string&, bool states, bool writeLinks);
|
||||
|
||||
std::string writeCsvTree(InfomapBase&, const StateNetwork&, const std::string&, bool states);
|
||||
|
||||
std::string writeClu(InfomapBase&, const StateNetwork&, const std::string&, bool states, int moduleIndexLevel);
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // OUTPUT_H_
|
||||
@@ -0,0 +1,362 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "ProgramInterface.h"
|
||||
#include "../utils/Log.h"
|
||||
|
||||
#include "igraph_error.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
const std::string ArgType::integer = "integer";
|
||||
const std::string ArgType::number = "number";
|
||||
const std::string ArgType::string = "string";
|
||||
const std::string ArgType::path = "path";
|
||||
const std::string ArgType::probability = "probability";
|
||||
const std::string ArgType::option = "option";
|
||||
const std::string ArgType::list = "list";
|
||||
|
||||
const std::unordered_map<std::string, char> ArgType::toShort = {
|
||||
{ "integer", 'n' },
|
||||
{ "number", 'f' },
|
||||
{ "string", 's' },
|
||||
{ "path", 'p' },
|
||||
{ "probability", 'P' },
|
||||
{ "option", 'o' },
|
||||
{ "list", 'l' },
|
||||
};
|
||||
|
||||
ProgramInterface::ProgramInterface(std::string name, std::string shortDescription, std::string version)
|
||||
: m_programName(std::move(name)),
|
||||
m_shortProgramDescription(std::move(shortDescription)),
|
||||
m_programVersion(std::move(version))
|
||||
{
|
||||
addIncrementalOptionArgument(m_displayHelp, 'h', "help", "Prints this help message. Use -hh to show advanced options.", "About");
|
||||
addOptionArgument(m_displayVersion, 'V', "version", "Display program version information.", "About");
|
||||
addOptionArgument(m_printJsonParameters, "print-json-parameters", "Print Infomap parameters in JSON.", "About").setHidden(true);
|
||||
}
|
||||
|
||||
/* Modification for igraph: Disable functions that call forbidden
|
||||
* functions such as exit(). */
|
||||
#if 0
|
||||
void ProgramInterface::exitWithUsage(bool showAdvanced) const
|
||||
{
|
||||
Log() << "Name:\n";
|
||||
Log() << " " << m_programName << " - " << m_shortProgramDescription << '\n';
|
||||
Log() << "\nUsage:\n";
|
||||
Log() << " " << m_executableName;
|
||||
for (auto& nonOptionArgument : m_nonOptionArguments)
|
||||
if (showAdvanced || !nonOptionArgument->isAdvanced)
|
||||
Log() << " " << nonOptionArgument->variableName;
|
||||
if (!m_optionArguments.empty())
|
||||
Log() << " [options]";
|
||||
Log() << '\n';
|
||||
|
||||
if (!m_programDescription.empty())
|
||||
Log() << "\nDescription:\n " << m_programDescription << '\n';
|
||||
|
||||
for (auto& nonOptionArgument : m_nonOptionArguments)
|
||||
if (showAdvanced || !nonOptionArgument->isAdvanced)
|
||||
Log() << "\n[" << nonOptionArgument->variableName << "]\n " << nonOptionArgument->description << '\n';
|
||||
|
||||
if (!m_optionArguments.empty())
|
||||
Log() << "\n[options]\n";
|
||||
|
||||
// First stringify the options part to get the maximum length
|
||||
std::deque<std::string> optionStrings(m_optionArguments.size());
|
||||
std::string::size_type maxLength = 0;
|
||||
for (unsigned int i = 0; i < m_optionArguments.size(); ++i) {
|
||||
auto& opt = *m_optionArguments[i];
|
||||
bool haveShort = opt.shortName != '\0';
|
||||
std::string optArgShort = opt.requireArgument ? (io::Str() << "<" << ArgType::toShort.at(opt.argumentName) << ">") : opt.incrementalArgument ? "[+]"
|
||||
: std::string(3, ' ');
|
||||
std::string optArgLong = opt.requireArgument ? (io::Str() << "<" << opt.argumentName << ">") : opt.incrementalArgument ? "[+]"
|
||||
: std::string(3, ' ');
|
||||
std::string shortOption = haveShort ? (io::Str() << " -" << opt.shortName << optArgShort) : std::string(7, ' ');
|
||||
optionStrings[i] = io::Str() << shortOption << " --" << opt.longName << " " << optArgLong;
|
||||
if (optionStrings[i].length() > maxLength)
|
||||
maxLength = optionStrings[i].length();
|
||||
}
|
||||
|
||||
std::vector<std::string> groups { "About" };
|
||||
for (auto& group : m_groups) {
|
||||
if (group != "About")
|
||||
groups.push_back(group);
|
||||
}
|
||||
if (m_groups.empty())
|
||||
groups.emplace_back("All");
|
||||
|
||||
for (const auto& group : groups) {
|
||||
if (group != "All") {
|
||||
Log() << "\n"
|
||||
<< group << "\n";
|
||||
Log() << std::string(group.length(), '-') << "\n";
|
||||
}
|
||||
for (unsigned int i = 0; i < m_optionArguments.size(); ++i) {
|
||||
auto& opt = *m_optionArguments[i];
|
||||
if (group == "All" || opt.group == group) {
|
||||
std::string::size_type numSpaces = maxLength + 3 - optionStrings[i].length();
|
||||
if (showAdvanced || !opt.isAdvanced) {
|
||||
Log() << optionStrings[i] << std::string(numSpaces, ' ') << opt.description;
|
||||
if (!opt.printNumericValue().empty())
|
||||
Log() << " (Default: " << opt.printNumericValue() << ")";
|
||||
Log() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Log() << '\n';
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
void ProgramInterface::exitWithVersionInformation() const
|
||||
{
|
||||
Log() << m_programName << " version " << m_programVersion;
|
||||
#ifdef _OPENMP
|
||||
Log() << " compiled with OpenMP";
|
||||
#endif
|
||||
Log() << '\n';
|
||||
Log() << "See www.mapequation.org for terms of use.\n";
|
||||
std::exit(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ProgramInterface::exitWithError(const std::string& message) const
|
||||
{
|
||||
/* Modification for igraph: This function must never be called
|
||||
* when using Infomap through igraph. The function is disabled
|
||||
* to eliminate forbidden references to exit() and std::cerr. */
|
||||
IGRAPH_FATALF("Infomap called exitWithError() with message '%s'.",
|
||||
message.c_str());
|
||||
#if 0
|
||||
Log() << m_programName << " version " << m_programVersion;
|
||||
#ifdef _OPENMP
|
||||
Log() << " compiled with OpenMP";
|
||||
#endif
|
||||
Log() << std::endl;
|
||||
std::cerr << message << std::endl;
|
||||
Log() << "Usage: " << m_executableName;
|
||||
for (auto& nonOptionArgument : m_nonOptionArguments)
|
||||
if (!nonOptionArgument->isAdvanced)
|
||||
Log() << " " << nonOptionArgument->variableName;
|
||||
if (!m_optionArguments.empty())
|
||||
Log() << " [options]";
|
||||
Log() << ". Run with option '-h' for more information.\n";
|
||||
std::exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string toJson(const std::string& key, const std::string& value)
|
||||
{
|
||||
return io::Str() << '"' << key << "\": \"" << value << '"';
|
||||
}
|
||||
|
||||
std::string toJson(const std::string& key, bool value)
|
||||
{
|
||||
return io::Str() << '"' << key << "\": " << (value ? "true" : "false");
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
std::string toJson(const std::string& key, Value value)
|
||||
{
|
||||
return io::Str() << '"' << key << "\": " << value;
|
||||
}
|
||||
|
||||
std::string toJson(const Option& opt)
|
||||
{
|
||||
return io::Str() << "{ "
|
||||
<< toJson("long", std::string(io::Str() << "--" << opt.longName)) << ", "
|
||||
<< toJson("short", opt.shortName != '\0' ? std::string(io::Str() << "-" << opt.shortName) : "") << ", "
|
||||
<< toJson("description", opt.description) << ", "
|
||||
<< toJson("group", opt.group) << ", "
|
||||
<< toJson("required", opt.requireArgument) << ", "
|
||||
<< toJson("advanced", opt.isAdvanced) << ", "
|
||||
<< toJson("incremental", opt.incrementalArgument) << ", "
|
||||
<< (opt.requireArgument
|
||||
? (io::Str() << toJson("longType", opt.argumentName) << ", "
|
||||
<< toJson("shortType", std::string(1, ArgType::toShort.at(opt.argumentName))) << ", "
|
||||
<< toJson("default", opt.printValue()))
|
||||
: toJson("default", false))
|
||||
<< " }";
|
||||
}
|
||||
|
||||
/* Modification for igraph: Disable functions that call forbidden
|
||||
* functions such as exit(). */
|
||||
#if 0
|
||||
void ProgramInterface::exitWithJsonParameters() const
|
||||
{
|
||||
Log() << "{\n \"parameters\": [\n";
|
||||
|
||||
for (unsigned int i = 0; i < m_optionArguments.size(); ++i) {
|
||||
auto& opt = *m_optionArguments[i];
|
||||
if (opt.hidden)
|
||||
continue;
|
||||
Log() << " " << toJson(opt);
|
||||
if (i < m_optionArguments.size() - 1) {
|
||||
Log() << ",\n";
|
||||
} else {
|
||||
Log() << "\n";
|
||||
}
|
||||
}
|
||||
Log() << " ]\n}";
|
||||
|
||||
std::exit(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ProgramInterface::parseArgs(const std::string& args)
|
||||
{
|
||||
// Map the options on short and long name, and check for duplication
|
||||
std::map<char, Option*> shortOptionMap;
|
||||
std::map<std::string, Option*> longOptionMap;
|
||||
for (auto& optionArgument : m_optionArguments) {
|
||||
auto& opt = *optionArgument;
|
||||
if (opt.shortName != '\0') {
|
||||
auto it = shortOptionMap.find(opt.shortName);
|
||||
if (it != shortOptionMap.end())
|
||||
throw std::runtime_error(io::Str() << "Duplication of option '" << opt.shortName << "'");
|
||||
shortOptionMap.insert(std::make_pair(opt.shortName, &opt));
|
||||
}
|
||||
|
||||
auto it = longOptionMap.find(opt.longName);
|
||||
if (it != longOptionMap.end())
|
||||
throw std::runtime_error(io::Str() << "Duplication of option \"" << opt.longName << "\"");
|
||||
longOptionMap.insert(std::make_pair(opt.longName, &opt));
|
||||
}
|
||||
|
||||
// Split the flags on whitespace
|
||||
std::vector<std::string> flags;
|
||||
std::istringstream argStream(args);
|
||||
|
||||
{
|
||||
std::string arg;
|
||||
while (!(argStream >> arg).fail())
|
||||
flags.push_back(arg);
|
||||
}
|
||||
|
||||
std::deque<std::string> nonOpts;
|
||||
try {
|
||||
for (unsigned int i = 0; i < flags.size(); ++i) {
|
||||
bool flagValue = true;
|
||||
unsigned int numArgsLeft = flags.size() - i - 1;
|
||||
|
||||
const std::string& arg = flags[i];
|
||||
if (arg.length() == 0)
|
||||
throw std::runtime_error("Illegal argument ''");
|
||||
|
||||
if (arg[0] != '-') {
|
||||
nonOpts.push_back(arg);
|
||||
} else {
|
||||
if (arg.length() < 2)
|
||||
throw std::runtime_error("Illegal argument '-'");
|
||||
|
||||
if (arg[1] == '-') {
|
||||
// Long option
|
||||
if (arg.length() < 3)
|
||||
throw std::runtime_error("Illegal argument '--'");
|
||||
std::string longOpt = arg.substr(2);
|
||||
auto it = longOptionMap.find(longOpt);
|
||||
if (it == longOptionMap.end()) {
|
||||
// Unrecognized option, check if it negates a recognised option with the '--no-' prefix
|
||||
if (longOpt.compare(0, 3, "no-") == 0 && longOptionMap.find(std::string(longOpt, 3)) != longOptionMap.end()) {
|
||||
longOpt = std::string(longOpt, 3);
|
||||
it = longOptionMap.find(longOpt);
|
||||
flagValue = false;
|
||||
} else {
|
||||
throw std::runtime_error(io::Str() << "Unrecognized option: '--" << longOpt << "'");
|
||||
}
|
||||
}
|
||||
auto& opt = *it->second;
|
||||
if (!opt.requireArgument || opt.incrementalArgument)
|
||||
opt.set(flagValue);
|
||||
else {
|
||||
if (numArgsLeft == 0)
|
||||
throw std::runtime_error(io::Str() << "Option '" << opt.longName << "' requires argument");
|
||||
++i;
|
||||
if (!opt.parse(flags[i]))
|
||||
throw std::runtime_error(io::Str() << "Cannot parse '" << flags[i] << "' as argument to option '" << opt.longName << "'. ");
|
||||
}
|
||||
} else {
|
||||
// Short option(s)
|
||||
for (unsigned int j = 1; j < arg.length(); ++j) {
|
||||
char o = arg[j];
|
||||
unsigned int numCharsLeft = arg.length() - j - 1;
|
||||
auto it = shortOptionMap.find(o);
|
||||
if (it == shortOptionMap.end())
|
||||
throw std::runtime_error(io::Str() << "Unrecognized option: '-" << o << "'");
|
||||
auto& opt = *it->second;
|
||||
if (!opt.requireArgument || opt.incrementalArgument)
|
||||
opt.set(flagValue);
|
||||
else {
|
||||
std::string optArg;
|
||||
if (numCharsLeft > 0) {
|
||||
optArg = arg.substr(j + 1);
|
||||
j = arg.length() - 1;
|
||||
} else if (numArgsLeft) {
|
||||
++i;
|
||||
optArg = flags[i];
|
||||
} else
|
||||
throw std::runtime_error(io::Str() << "Option '" << opt.longName << "' requires argument");
|
||||
|
||||
if (!opt.parse(optArg))
|
||||
throw std::runtime_error(io::Str() << "Cannot parse '" << optArg << "' as argument to option '" << opt.longName << "'. ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Modification for igraph: Disable calls to functions that
|
||||
* needed to be removed because they referenced exit(). */
|
||||
#if 0
|
||||
if (m_displayHelp > 0)
|
||||
exitWithUsage(m_displayHelp > 1);
|
||||
if (m_displayVersion)
|
||||
exitWithVersionInformation();
|
||||
if (m_printJsonParameters)
|
||||
exitWithJsonParameters();
|
||||
#endif
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
exitWithError(e.what());
|
||||
}
|
||||
|
||||
if (nonOpts.size() < numRequiredArguments())
|
||||
exitWithError("Missing required arguments.");
|
||||
|
||||
unsigned int i = 0;
|
||||
unsigned int numVectorArguments = nonOpts.size() - (m_nonOptionArguments.size() - 1);
|
||||
while (!nonOpts.empty()) {
|
||||
std::string arg = nonOpts.front();
|
||||
nonOpts.pop_front();
|
||||
if (m_nonOptionArguments[i]->isOptionalVector && numVectorArguments == 0)
|
||||
++i;
|
||||
if (!m_nonOptionArguments[i]->parse(arg))
|
||||
exitWithError("Argument error.");
|
||||
if (!m_nonOptionArguments[i]->isOptionalVector || --numVectorArguments == 0)
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ParsedOption> ProgramInterface::getUsedOptionArguments() const
|
||||
{
|
||||
std::vector<ParsedOption> opts;
|
||||
unsigned int numFlags = m_optionArguments.size();
|
||||
for (unsigned int i = 0; i < numFlags; ++i) {
|
||||
auto& opt = *m_optionArguments[i];
|
||||
if (opt.used && opt.longName != "negate-next")
|
||||
opts.emplace_back(opt);
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,423 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef PROGRAM_INTERFACE_H_
|
||||
#define PROGRAM_INTERFACE_H_
|
||||
|
||||
#include "../utils/convert.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class InterruptException : public std::exception {
|
||||
public:
|
||||
InterruptException() {};
|
||||
};
|
||||
|
||||
typedef bool interruptionHandlerFn(void);
|
||||
|
||||
struct ArgType {
|
||||
static const std::string integer;
|
||||
static const std::string number;
|
||||
static const std::string string;
|
||||
static const std::string path;
|
||||
static const std::string probability;
|
||||
static const std::string option;
|
||||
static const std::string list;
|
||||
static const std::unordered_map<std::string, char> toShort;
|
||||
};
|
||||
|
||||
struct Option {
|
||||
Option(char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, bool requireArgument = false, std::string argName = "")
|
||||
: shortName(shortName),
|
||||
longName(std::move(longName)),
|
||||
description(std::move(desc)),
|
||||
group(std::move(group)),
|
||||
isAdvanced(isAdvanced),
|
||||
requireArgument(requireArgument),
|
||||
incrementalArgument(false),
|
||||
argumentName(std::move(argName)) { }
|
||||
|
||||
virtual ~Option() = default;
|
||||
|
||||
Option(const Option&) = default;
|
||||
Option& operator=(const Option&) = default;
|
||||
Option(Option&&) = default;
|
||||
Option& operator=(Option&&) = default;
|
||||
|
||||
virtual bool parse(std::string const&)
|
||||
{
|
||||
used = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void set(bool value)
|
||||
{
|
||||
used = true;
|
||||
negated = !value;
|
||||
};
|
||||
|
||||
Option& setHidden(bool value)
|
||||
{
|
||||
hidden = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual std::ostream& printValue(std::ostream& out) const { return out; }
|
||||
virtual std::string printValue() const { return ""; }
|
||||
virtual std::string printNumericValue() const { return ""; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const Option& option)
|
||||
{
|
||||
out << option.longName;
|
||||
if (option.requireArgument) {
|
||||
out << " = ";
|
||||
option.printValue(out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
char shortName;
|
||||
std::string longName;
|
||||
std::string description;
|
||||
std::string group;
|
||||
bool isAdvanced;
|
||||
bool requireArgument;
|
||||
bool incrementalArgument;
|
||||
std::string argumentName;
|
||||
bool hidden = false;
|
||||
bool used = false;
|
||||
bool negated = false;
|
||||
};
|
||||
|
||||
struct IncrementalOption : Option {
|
||||
IncrementalOption(unsigned int& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced)
|
||||
: Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, false), target(target)
|
||||
{
|
||||
incrementalArgument = true;
|
||||
}
|
||||
|
||||
bool parse(std::string const& value) override
|
||||
{
|
||||
Option::parse(value);
|
||||
return ++target;
|
||||
}
|
||||
|
||||
void set(bool value) override
|
||||
{
|
||||
Option::set(value);
|
||||
if (value) {
|
||||
++target;
|
||||
} else if (target > 0) {
|
||||
--target;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& printValue(std::ostream& out) const override { return out << target; }
|
||||
std::string printValue() const override { return io::Str() << target; }
|
||||
|
||||
unsigned int& target;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ArgumentOption : Option {
|
||||
ArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName)
|
||||
: Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, true, std::move(argName)), target(target) { }
|
||||
|
||||
bool parse(std::string const& value) override
|
||||
{
|
||||
Option::parse(value);
|
||||
return io::stringToValue(value, target);
|
||||
}
|
||||
|
||||
std::string printValue() const override { return io::Str() << target; }
|
||||
std::ostream& printValue(std::ostream& out) const override { return out << target; }
|
||||
std::string printNumericValue() const override { return TypeInfo<T>::isNumeric() ? printValue() : ""; }
|
||||
|
||||
T& target;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct LowerBoundArgumentOption : ArgumentOption<T> {
|
||||
LowerBoundArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName, T minValue)
|
||||
: ArgumentOption<T>(target, shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, std::move(argName)), minValue(minValue) { }
|
||||
|
||||
bool parse(std::string const& value) override
|
||||
{
|
||||
auto ok = ArgumentOption<T>::parse(value);
|
||||
if (ArgumentOption<T>::target < minValue) return false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
T minValue;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct LowerUpperBoundArgumentOption : LowerBoundArgumentOption<T> {
|
||||
LowerUpperBoundArgumentOption(T& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced, std::string argName, T minValue, T maxValue)
|
||||
: LowerBoundArgumentOption<T>(target, shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, std::move(argName), minValue), maxValue(maxValue) { }
|
||||
|
||||
bool parse(std::string const& value) override
|
||||
{
|
||||
auto ok = LowerBoundArgumentOption<T>::parse(value);
|
||||
if (LowerBoundArgumentOption<T>::target > maxValue) return false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
T maxValue;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ArgumentOption<bool> : Option {
|
||||
ArgumentOption(bool& target, char shortName, std::string longName, std::string desc, std::string group, bool isAdvanced)
|
||||
: Option(shortName, std::move(longName), std::move(desc), std::move(group), isAdvanced, false), target(target) { }
|
||||
|
||||
bool parse(std::string const& value) override
|
||||
{
|
||||
Option::parse(value);
|
||||
return target = true;
|
||||
}
|
||||
|
||||
void set(bool value) override
|
||||
{
|
||||
Option::set(value);
|
||||
target = value;
|
||||
}
|
||||
|
||||
std::ostream& printValue(std::ostream& out) const override { return out << target; }
|
||||
std::string printValue() const override { return io::Str() << target; }
|
||||
std::string printNumericValue() const override { return ""; }
|
||||
|
||||
bool& target;
|
||||
};
|
||||
|
||||
struct ParsedOption {
|
||||
explicit ParsedOption(const Option& opt)
|
||||
: shortName(opt.shortName),
|
||||
longName(opt.longName),
|
||||
description(opt.description),
|
||||
group(opt.group),
|
||||
isAdvanced(opt.isAdvanced),
|
||||
requireArgument(opt.requireArgument),
|
||||
incrementalArgument(opt.incrementalArgument),
|
||||
argumentName(opt.argumentName),
|
||||
negated(opt.negated),
|
||||
value(opt.printValue()) { }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const ParsedOption& option)
|
||||
{
|
||||
if (option.negated)
|
||||
out << "no ";
|
||||
out << option.longName;
|
||||
if (option.requireArgument)
|
||||
out << " = " << option.value;
|
||||
return out;
|
||||
}
|
||||
|
||||
char shortName;
|
||||
std::string longName;
|
||||
std::string description;
|
||||
std::string group;
|
||||
bool isAdvanced;
|
||||
bool requireArgument;
|
||||
bool incrementalArgument;
|
||||
std::string argumentName;
|
||||
bool negated;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct TargetBase {
|
||||
TargetBase(std::string variableName, std::string desc, std::string group, bool isAdvanced)
|
||||
: variableName(std::move(variableName)), description(std::move(desc)), group(std::move(group)), isOptionalVector(false), isAdvanced(isAdvanced) { }
|
||||
|
||||
virtual ~TargetBase() = default;
|
||||
|
||||
TargetBase(const TargetBase&) = default;
|
||||
TargetBase& operator=(const TargetBase&) = default;
|
||||
TargetBase(TargetBase&&) = default;
|
||||
TargetBase& operator=(TargetBase&&) = default;
|
||||
|
||||
virtual bool parse(std::string const& value) = 0;
|
||||
|
||||
std::string variableName;
|
||||
std::string description;
|
||||
std::string group;
|
||||
bool isOptionalVector = false;
|
||||
bool isAdvanced;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Target : TargetBase {
|
||||
Target(T& target, std::string variableName, std::string desc, std::string group, bool isAdvanced)
|
||||
: TargetBase(std::move(variableName), std::move(desc), std::move(group), isAdvanced), target(target) { }
|
||||
|
||||
bool parse(std::string const& value) override
|
||||
{
|
||||
return io::stringToValue(value, target);
|
||||
}
|
||||
|
||||
T& target;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct OptionalTargets : TargetBase {
|
||||
OptionalTargets(std::vector<T>& target, std::string variableName, std::string desc, std::string group, bool isAdvanced)
|
||||
: TargetBase(std::move(variableName), std::move(desc), std::move(group), isAdvanced), targets(target)
|
||||
{
|
||||
isOptionalVector = true;
|
||||
}
|
||||
|
||||
bool parse(std::string const& value) override
|
||||
{
|
||||
T target;
|
||||
bool ok = io::stringToValue(value, target);
|
||||
if (ok)
|
||||
targets.push_back(target);
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::vector<T>& targets;
|
||||
};
|
||||
|
||||
class ProgramInterface {
|
||||
public:
|
||||
ProgramInterface(std::string name, std::string shortDescription, std::string version);
|
||||
|
||||
void setGroups(std::vector<std::string> groups) { m_groups = std::move(groups); }
|
||||
|
||||
template <typename T>
|
||||
void addNonOptionArgument(T& target, std::string variableName, std::string desc, std::string group, bool isAdvanced = false)
|
||||
{
|
||||
m_nonOptionArguments.emplace_back(new Target<T>(target, std::move(variableName), std::move(desc), std::move(group), isAdvanced));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void addOptionalNonOptionArguments(std::vector<T>& target, std::string variableName, std::string desc, std::string group, bool isAdvanced = false)
|
||||
{
|
||||
if (m_numOptionalNonOptionArguments != 0)
|
||||
throw std::runtime_error("Can't have two non-option vector arguments");
|
||||
++m_numOptionalNonOptionArguments;
|
||||
m_nonOptionArguments.emplace_back(new OptionalTargets<T>(target, std::move(variableName), std::move(desc), std::move(group), isAdvanced));
|
||||
}
|
||||
|
||||
Option& addOptionArgument(char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false)
|
||||
{
|
||||
auto* o = new Option(shortName, std::move(longName), std::move(description), std::move(group), isAdvanced);
|
||||
m_optionArguments.emplace_back(o);
|
||||
return *o;
|
||||
}
|
||||
|
||||
Option& addIncrementalOptionArgument(unsigned int& target, char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false)
|
||||
{
|
||||
auto* o = new IncrementalOption(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced);
|
||||
m_optionArguments.emplace_back(o);
|
||||
return *o;
|
||||
}
|
||||
|
||||
Option& addOptionArgument(bool& target, char shortName, std::string longName, std::string description, std::string group, bool isAdvanced = false)
|
||||
{
|
||||
auto* o = new ArgumentOption<bool>(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced);
|
||||
m_optionArguments.emplace_back(o);
|
||||
return *o;
|
||||
}
|
||||
|
||||
// Without shortName
|
||||
Option& addOptionArgument(bool& target, std::string longName, std::string description, std::string group, bool isAdvanced = false)
|
||||
{
|
||||
return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(group), isAdvanced);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, bool isAdvanced = false)
|
||||
{
|
||||
auto* o = new ArgumentOption<T>(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName));
|
||||
m_optionArguments.emplace_back(o);
|
||||
return *o;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, bool isAdvanced = false)
|
||||
{
|
||||
auto* o = new LowerBoundArgumentOption<T>(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName), minValue);
|
||||
m_optionArguments.emplace_back(o);
|
||||
return *o;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Option& addOptionArgument(T& target, char shortName, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, T maxValue, bool isAdvanced = false)
|
||||
{
|
||||
auto* o = new LowerUpperBoundArgumentOption<T>(target, shortName, std::move(longName), std::move(description), std::move(group), isAdvanced, std::move(argumentName), minValue, maxValue);
|
||||
m_optionArguments.emplace_back(o);
|
||||
return *o;
|
||||
}
|
||||
|
||||
// Without shortName
|
||||
template <typename T>
|
||||
Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, bool isAdvanced = false)
|
||||
{
|
||||
return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), isAdvanced);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, bool isAdvanced = false)
|
||||
{
|
||||
return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), minValue, isAdvanced);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Option& addOptionArgument(T& target, std::string longName, std::string description, std::string argumentName, std::string group, T minValue, T maxValue, bool isAdvanced = false)
|
||||
{
|
||||
return addOptionArgument(target, '\0', std::move(longName), std::move(description), std::move(argumentName), std::move(group), minValue, maxValue, isAdvanced);
|
||||
}
|
||||
|
||||
void parseArgs(const std::string& args);
|
||||
|
||||
std::vector<ParsedOption> getUsedOptionArguments() const;
|
||||
|
||||
unsigned int numRequiredArguments() const { return m_nonOptionArguments.size() - m_numOptionalNonOptionArguments; }
|
||||
|
||||
private:
|
||||
/* Modification for igraph: Disable functions that call forbidden
|
||||
* functions such as exit(). */
|
||||
#if 0
|
||||
void exitWithUsage(bool showAdvanced) const;
|
||||
void exitWithVersionInformation() const;
|
||||
#endif
|
||||
void exitWithError(const std::string& message) const;
|
||||
#if 0
|
||||
void exitWithJsonParameters() const;
|
||||
#endif
|
||||
|
||||
std::deque<std::unique_ptr<Option>> m_optionArguments;
|
||||
std::deque<std::unique_ptr<TargetBase>> m_nonOptionArguments;
|
||||
std::string m_programName = "Infomap";
|
||||
std::string m_shortProgramDescription;
|
||||
std::string m_programVersion;
|
||||
std::string m_programDescription;
|
||||
std::vector<std::string> m_groups;
|
||||
std::string m_executableName = "Infomap";
|
||||
unsigned int m_displayHelp = 0;
|
||||
bool m_displayVersion = false;
|
||||
bool m_printJsonParameters = false;
|
||||
|
||||
unsigned int m_numOptionalNonOptionArguments = 0;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // PROGRAM_INTERFACE_H_
|
||||
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef SAFEFILE_H_
|
||||
#define SAFEFILE_H_
|
||||
|
||||
#include "../utils/convert.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
|
||||
/**
|
||||
* A wrapper for the C++ file stream class that automatically closes
|
||||
* the file stream when the destructor is called. Allocate it on the
|
||||
* stack to have it automatically closed when going out of scope.
|
||||
*
|
||||
* Note:
|
||||
* In C++, the only code that can be guaranteed to be executed after an
|
||||
* exception is thrown are the destructors of objects residing on the stack.
|
||||
*
|
||||
* You can exploit that fact to avoid resource leaks by tying all resources
|
||||
* to the lifespan of an object allocated on the stack. This technique is
|
||||
* called Resource Acquisition Is Initialization (RAII).
|
||||
*
|
||||
*/
|
||||
class SafeInFile : public ifstream {
|
||||
public:
|
||||
SafeInFile(const std::string& filename, ios_base::openmode mode = ios_base::in)
|
||||
: ifstream(filename, mode)
|
||||
{
|
||||
if (fail())
|
||||
throw std::runtime_error(io::Str() << "Error opening file '" << filename << "'. Check that the path points to a file and that you have read permissions.");
|
||||
}
|
||||
|
||||
~SafeInFile() override
|
||||
{
|
||||
if (is_open())
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
class SafeOutFile : public ofstream {
|
||||
public:
|
||||
SafeOutFile(const std::string& filename, ios_base::openmode mode = ios_base::out)
|
||||
: ofstream(filename, mode)
|
||||
{
|
||||
if (fail())
|
||||
throw std::runtime_error(io::Str() << "Error opening file '" << filename << "'. Check that the directory you are writing to exists and that you have write permissions.");
|
||||
}
|
||||
|
||||
~SafeOutFile() override
|
||||
{
|
||||
if (is_open())
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
inline bool isDirectoryWritable(const std::string& dir)
|
||||
{
|
||||
std::string path = io::Str() << dir << "_1nf0m4p_.tmp";
|
||||
bool ok = true;
|
||||
try {
|
||||
SafeOutFile out(path);
|
||||
} catch (const std::runtime_error&) {
|
||||
ok = false;
|
||||
}
|
||||
if (ok)
|
||||
std::remove(path.c_str());
|
||||
return ok;
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // SAFEFILE_H_
|
||||
@@ -0,0 +1,69 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef DATE_H_
|
||||
#define DATE_H_
|
||||
|
||||
#include <ctime>
|
||||
#include <cmath>
|
||||
#include <ostream>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class ElapsedTime {
|
||||
public:
|
||||
ElapsedTime(double elapsedTime = 0.0) : m_elapsedTime(elapsedTime) { }
|
||||
|
||||
double getSeconds() const { return m_elapsedTime; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const ElapsedTime& elapsedTime)
|
||||
{
|
||||
auto temp = static_cast<unsigned int>(std::floor(elapsedTime.getSeconds()));
|
||||
if (temp > 60) {
|
||||
if (temp > 3600) {
|
||||
if (temp > 86400) {
|
||||
out << temp / 86400 << "d ";
|
||||
temp %= 86400;
|
||||
}
|
||||
out << temp / 3600 << "h ";
|
||||
temp %= 3600;
|
||||
}
|
||||
out << temp / 60 << "m ";
|
||||
temp %= 60;
|
||||
out << temp << "s";
|
||||
} else {
|
||||
out << temp << "s";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
double m_elapsedTime;
|
||||
};
|
||||
|
||||
class Date {
|
||||
public:
|
||||
friend std::ostream& operator<<(std::ostream& out, const Date& date)
|
||||
{
|
||||
struct std::tm t = *localtime(&date.m_timeOfCreation);
|
||||
return out << "" << (t.tm_year + 1900) << (t.tm_mon < 9 ? "-0" : "-") << (t.tm_mon + 1) << (t.tm_mday < 10 ? "-0" : "-") << t.tm_mday << (t.tm_hour < 10 ? " 0" : " ") << t.tm_hour << (t.tm_min < 10 ? ":0" : ":") << t.tm_min << (t.tm_sec < 10 ? ":0" : ":") << t.tm_sec << "";
|
||||
}
|
||||
|
||||
ElapsedTime operator-(const Date& date) const
|
||||
{
|
||||
return { difftime(m_timeOfCreation, date.m_timeOfCreation) };
|
||||
}
|
||||
|
||||
private:
|
||||
std::time_t m_timeOfCreation = time(nullptr);
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // DATE_H_
|
||||
@@ -0,0 +1,50 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "FileURI.h"
|
||||
#include "convert.h"
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace infomap {
|
||||
|
||||
FileURI::FileURI(string filename, bool requireExtension)
|
||||
: m_filename(std::move(filename)), m_requireExtension(requireExtension)
|
||||
{
|
||||
auto getErrorMessage = [](const auto& name, auto requireExt) {
|
||||
string s = io::Str() << "Filename '" << name << "' must match the pattern \"[dir/]name" << (requireExt ? ".extension\"" : "[.extension]\"");
|
||||
return s;
|
||||
};
|
||||
|
||||
auto name = m_filename;
|
||||
auto pos = m_filename.find_last_of('/');
|
||||
if (pos != string::npos) {
|
||||
if (pos == m_filename.length()) // File could not end with slash
|
||||
throw std::invalid_argument(getErrorMessage(m_filename, m_requireExtension));
|
||||
m_directory = m_filename.substr(0, pos + 1); // Include the last slash in the directory
|
||||
name = m_filename.substr(pos + 1); // No slash in the name
|
||||
} else {
|
||||
m_directory = "";
|
||||
}
|
||||
|
||||
pos = name.find_last_of('.');
|
||||
if (pos == string::npos || pos == 0 || pos == name.length() - 1) {
|
||||
if (pos != string::npos || m_requireExtension)
|
||||
throw std::invalid_argument(getErrorMessage(m_filename, m_requireExtension));
|
||||
m_name = name;
|
||||
m_extension = "";
|
||||
} else {
|
||||
m_name = name.substr(0, pos);
|
||||
m_extension = name.substr(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,54 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef FILEURI_H_
|
||||
#define FILEURI_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
/**
|
||||
* Filename class to simplify handling of parts of a filename.
|
||||
* If a path is path/to/file.ext, the member methods of this class give these parts:
|
||||
* getFilename -> "path/to/file.ext"
|
||||
* getName -> "file"
|
||||
* getExtension -> "ext"
|
||||
* Can throw std::invalid_argument on creation.
|
||||
*/
|
||||
class FileURI {
|
||||
public:
|
||||
FileURI() = default;
|
||||
|
||||
explicit FileURI(std::string filename, bool requireExtension = false);
|
||||
|
||||
const std::string& getFilename() const { return m_filename; }
|
||||
|
||||
const std::string& getName() const { return m_name; }
|
||||
|
||||
const std::string& getExtension() const { return m_extension; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const FileURI& file)
|
||||
{
|
||||
return out << file.getFilename();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_filename;
|
||||
bool m_requireExtension = false;
|
||||
|
||||
std::string m_directory;
|
||||
std::string m_name;
|
||||
std::string m_extension;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // FILEURI_H_
|
||||
@@ -0,0 +1,898 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "FlowCalculator.h"
|
||||
#include "../utils/Log.h"
|
||||
#include "../utils/infomath.h"
|
||||
#include "../core/StateNetwork.h"
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
template <typename T>
|
||||
inline void normalize(std::vector<T>& v, const T sum) noexcept
|
||||
{
|
||||
for (auto& numerator : v) {
|
||||
numerator /= sum;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void normalize(std::vector<T>& v) noexcept
|
||||
{
|
||||
const auto sum = std::accumulate(cbegin(v), cend(v), T {});
|
||||
normalize(v, sum);
|
||||
}
|
||||
|
||||
FlowCalculator::FlowCalculator(StateNetwork& network, const Config& config)
|
||||
: numNodes(network.numNodes())
|
||||
{
|
||||
Log() << "Calculating global network flow using flow model '" << config.flowModel << "'... " << std::flush;
|
||||
|
||||
// Prepare data in sequence containers for fast access of individual elements
|
||||
// Map to zero-based dense indexing
|
||||
nodeFlow.assign(numNodes, 0.0);
|
||||
nodeTeleportWeights.assign(numNodes, 0.0); // Fraction of teleportation flow landing on node i
|
||||
|
||||
nodeOutDegree.assign(numNodes, 0);
|
||||
sumLinkOutWeight.assign(numNodes, 0.0);
|
||||
|
||||
unsigned int nodeIndex = 0;
|
||||
const auto& nodeLinkMap = network.nodeLinkMap();
|
||||
|
||||
if (network.isBipartite()) {
|
||||
// Preserve node order
|
||||
for (const auto& node : network.nodes()) {
|
||||
const auto nodeId = node.second.id;
|
||||
nodeIndexMap[nodeId] = nodeIndex++;
|
||||
}
|
||||
|
||||
auto bipartiteStartId = network.bipartiteStartId();
|
||||
bipartiteStartIndex = nodeIndexMap[bipartiteStartId];
|
||||
|
||||
} else {
|
||||
if (config.flowModel != FlowModel::directed) {
|
||||
// Preserve node order
|
||||
for (const auto& node : network.nodes()) {
|
||||
const auto nodeId = node.second.id;
|
||||
nodeIndexMap[nodeId] = nodeIndex++;
|
||||
}
|
||||
} else {
|
||||
// Store dangling nodes out-of-order,
|
||||
// with dangling nodes first to optimize calculation of dangling rank
|
||||
|
||||
for (const auto& node : network.nodes()) {
|
||||
const auto isDangling = nodeLinkMap.find(node.second) == nodeLinkMap.end();
|
||||
if (!isDangling) continue;
|
||||
|
||||
const auto& nodeId = node.second.id;
|
||||
nodeIndexMap[nodeId] = nodeIndex++;
|
||||
}
|
||||
|
||||
nonDanglingStartIndex = nodeIndex;
|
||||
|
||||
for (const auto& node : network.nodes()) {
|
||||
const auto isDangling = nodeLinkMap.find(node.second) == nodeLinkMap.end();
|
||||
if (isDangling) continue;
|
||||
|
||||
const auto& nodeId = node.second.id;
|
||||
nodeIndexMap[nodeId] = nodeIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flowLinks.resize(network.numLinks(), { 0, 0, 0.0 });
|
||||
sumLinkWeight = network.sumLinkWeight();
|
||||
sumWeightedDegree = network.sumWeightedDegree();
|
||||
|
||||
if (network.isBipartite()) {
|
||||
const auto bipartiteStartId = network.bipartiteStartId();
|
||||
|
||||
for (const auto& node : nodeLinkMap) {
|
||||
const auto sourceIsFeature = node.first.id >= bipartiteStartId;
|
||||
if (sourceIsFeature) continue;
|
||||
bipartiteLinkStartIndex += node.second.size();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int linkIndex = 0;
|
||||
unsigned int featureLinkIndex = bipartiteLinkStartIndex; // bipartite case
|
||||
|
||||
for (const auto& node : nodeLinkMap) {
|
||||
const auto sourceId = node.first.id;
|
||||
const auto sourceIndex = nodeIndexMap[sourceId];
|
||||
|
||||
for (const auto& link : node.second) {
|
||||
const auto targetId = link.first.id;
|
||||
const auto targetIndex = nodeIndexMap[targetId];
|
||||
const auto linkWeight = link.second.weight;
|
||||
|
||||
++nodeOutDegree[sourceIndex];
|
||||
sumLinkOutWeight[sourceIndex] += linkWeight;
|
||||
nodeFlow[sourceIndex] += linkWeight / sumWeightedDegree;
|
||||
|
||||
if (network.isBipartite() && sourceId >= network.bipartiteStartId()) {
|
||||
// Link from feature node to ordinary node
|
||||
flowLinks[featureLinkIndex].source = sourceIndex;
|
||||
flowLinks[featureLinkIndex].target = targetIndex;
|
||||
flowLinks[featureLinkIndex].flow = linkWeight;
|
||||
++featureLinkIndex;
|
||||
} else {
|
||||
// Ordinary link, or unipartite
|
||||
flowLinks[linkIndex].source = sourceIndex;
|
||||
flowLinks[linkIndex].target = targetIndex;
|
||||
flowLinks[linkIndex].flow = linkWeight;
|
||||
++linkIndex;
|
||||
}
|
||||
|
||||
if (sourceIndex != targetIndex) {
|
||||
if (config.isUndirectedFlow()) {
|
||||
++nodeOutDegree[targetIndex];
|
||||
sumLinkOutWeight[targetIndex] += linkWeight;
|
||||
}
|
||||
if (config.flowModel != FlowModel::outdirdir) {
|
||||
nodeFlow[targetIndex] += linkWeight / sumWeightedDegree;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool normalizeNodeFlow = false;
|
||||
|
||||
switch (config.flowModel) {
|
||||
case FlowModel::undirected:
|
||||
if (config.regularized) {
|
||||
calcUndirectedRegularizedFlow(network, config);
|
||||
} else {
|
||||
calcUndirectedFlow();
|
||||
}
|
||||
break;
|
||||
case FlowModel::directed:
|
||||
if (network.isBipartite() && config.bipartiteTeleportation) {
|
||||
calcDirectedBipartiteFlow(network, config);
|
||||
} else {
|
||||
if (config.regularized) {
|
||||
calcDirectedRegularizedFlow(network, config);
|
||||
} else {
|
||||
calcDirectedFlow(network, config);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FlowModel::undirdir:
|
||||
case FlowModel::outdirdir:
|
||||
calcDirdirFlow(config);
|
||||
normalizeNodeFlow = true;
|
||||
break;
|
||||
case FlowModel::rawdir:
|
||||
calcRawdirFlow();
|
||||
normalizeNodeFlow = true;
|
||||
break;
|
||||
case FlowModel::precomputed:
|
||||
usePrecomputedFlow(network, config);
|
||||
normalizeNodeFlow = true;
|
||||
break;
|
||||
}
|
||||
|
||||
finalize(network, config, normalizeNodeFlow);
|
||||
}
|
||||
|
||||
void FlowCalculator::calcUndirectedFlow() noexcept
|
||||
{
|
||||
Log() << "\n -> Using undirected links.";
|
||||
|
||||
// Flow is outgoing transition probability times source node flow
|
||||
// = w_ij / s_ij * s_ij / sum(s_ij) = w_ij / sum(s_ij)
|
||||
// Count twice for non-loops to cover flow in both directions
|
||||
// Assuming convention to treat self-links as directed
|
||||
|
||||
for (auto& link : flowLinks) {
|
||||
link.flow /= sumWeightedDegree;
|
||||
if (link.source != link.target) {
|
||||
link.flow *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlowCalculator::calcDirdirFlow(const Config& config) noexcept
|
||||
{
|
||||
if (config.flowModel == FlowModel::outdirdir)
|
||||
Log() << "\n -> Counting only ingoing links.";
|
||||
else
|
||||
Log() << "\n -> Using undirected links, switching to directed after steady state.";
|
||||
|
||||
// Take one last power iteration
|
||||
const std::vector<double> nodeFlowSteadyState(nodeFlow);
|
||||
nodeFlow.assign(numNodes, 0.0);
|
||||
|
||||
for (const auto& link : flowLinks) {
|
||||
nodeFlow[link.target] += nodeFlowSteadyState[link.source] * link.flow / sumLinkOutWeight[link.source];
|
||||
}
|
||||
|
||||
double sumNodeFlow = std::accumulate(cbegin(nodeFlow), cend(nodeFlow), 0.0);
|
||||
|
||||
// Update link data to represent flow instead of weight
|
||||
for (auto& link : flowLinks) {
|
||||
link.flow *= nodeFlowSteadyState[link.source] / sumLinkOutWeight[link.source] / sumNodeFlow;
|
||||
}
|
||||
}
|
||||
|
||||
void FlowCalculator::calcRawdirFlow() noexcept
|
||||
{
|
||||
Log() << "\n -> Using directed links with raw flow.";
|
||||
Log() << "\n -> Total link weight: " << sumLinkWeight << ".";
|
||||
|
||||
// Treat the link weights as flow (after global normalization) and
|
||||
// do one power iteration to set the node flow
|
||||
nodeFlow.assign(numNodes, 0.0);
|
||||
|
||||
for (auto& link : flowLinks) {
|
||||
link.flow /= sumLinkWeight;
|
||||
nodeFlow[link.target] += link.flow;
|
||||
}
|
||||
}
|
||||
|
||||
void FlowCalculator::usePrecomputedFlow(const StateNetwork& network, const Config& config)
|
||||
{
|
||||
Log() << "\n -> Using directed links with precomputed flow from input data.";
|
||||
Log() << "\n -> Total link flow: " << sumLinkWeight << ".";
|
||||
|
||||
if (network.haveFileInput()) {
|
||||
if (network.haveMemoryInput() && !network.haveStateNodeWeights()) {
|
||||
Log() << std::endl;
|
||||
throw std::runtime_error("Missing node flow in input data. Should be passed as a third field under a *States section.");
|
||||
}
|
||||
if (!network.haveMemoryInput() && !network.haveNodeWeights()) {
|
||||
Log() << std::endl;
|
||||
throw std::runtime_error("Missing node flow in input data. Should be passed as a third field under a *Vertices section.");
|
||||
}
|
||||
}
|
||||
|
||||
// Treat the link weights as flow
|
||||
nodeFlow.assign(numNodes, 0.0);
|
||||
double sumFlow = 0.0;
|
||||
|
||||
for (const auto& nodeIt : network.nodes()) {
|
||||
auto& node = nodeIt.second;
|
||||
nodeFlow[nodeIndexMap[node.id]] = node.weight;
|
||||
sumFlow += node.weight;
|
||||
}
|
||||
Log() << "\n -> Total node flow: " << sumFlow << ".";
|
||||
|
||||
if (infomath::isEqual(sumFlow, 0)) {
|
||||
throw std::runtime_error("Missing node flow. Set it on the node weight property.");
|
||||
}
|
||||
if (!infomath::isEqual(sumFlow, 1)) {
|
||||
if (infomath::isEqual(sumFlow, numNodes) && infomath::isEqual(nodeFlow[0], 1)) {
|
||||
Log() << "\n Warning: Node flow sums to the number of nodes, is node flow provided or is default node weights used? Normalizing.";
|
||||
} else {
|
||||
Log() << "\n Warning: Node flow sums to " << sumFlow << ", normalizing.";
|
||||
}
|
||||
for (unsigned int i = 0; i < numNodes; ++i) {
|
||||
nodeFlow[i] /= sumFlow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IterationResult {
|
||||
double alpha;
|
||||
double beta;
|
||||
};
|
||||
|
||||
template <typename Iteration>
|
||||
IterationResult powerIterate(double alpha, Iteration&& iter)
|
||||
{
|
||||
unsigned int iterations = 0;
|
||||
double beta = 1.0 - alpha;
|
||||
double err = 0.0;
|
||||
|
||||
do {
|
||||
double oldErr = err;
|
||||
err = iter(iterations, alpha, beta);
|
||||
|
||||
// Perturb the system if equilibrium
|
||||
if (std::abs(err - oldErr) < 1e-17) {
|
||||
alpha += 1.0e-12;
|
||||
beta = 1.0 - alpha;
|
||||
}
|
||||
|
||||
++iterations;
|
||||
} while (iterations < 200 && (err > 1.0e-15 || iterations < 50));
|
||||
|
||||
Log() << "\n -> PageRank calculation done in " << iterations << " iterations.\n";
|
||||
|
||||
return { alpha, beta };
|
||||
}
|
||||
|
||||
void FlowCalculator::calcDirectedFlow(const StateNetwork& network, const Config& config) noexcept
|
||||
{
|
||||
Log() << "\n -> Using " << (config.recordedTeleportation ? "recorded" : "unrecorded") << " teleportation to " << (config.teleportToNodes ? "nodes" : "links") << ". " << std::flush;
|
||||
|
||||
// Calculate the teleport rate distribution
|
||||
if (config.teleportToNodes) {
|
||||
double sumNodeWeights = 0.0;
|
||||
|
||||
for (const auto& nodeIt : network.nodes()) {
|
||||
auto& node = nodeIt.second;
|
||||
nodeTeleportWeights[nodeIndexMap[node.id]] = node.weight;
|
||||
sumNodeWeights += node.weight;
|
||||
}
|
||||
|
||||
normalize(nodeTeleportWeights, sumNodeWeights);
|
||||
} else {
|
||||
// Teleport to links
|
||||
|
||||
// Teleport proportionally to out-degree, or in-degree if recorded teleportation.
|
||||
for (const auto& link : flowLinks) {
|
||||
auto toNode = config.recordedTeleportation ? link.target : link.source;
|
||||
nodeTeleportWeights[toNode] += link.flow / sumLinkWeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize link weights with respect to its source nodes total out-link weight;
|
||||
for (auto& link : flowLinks) {
|
||||
if (sumLinkOutWeight[link.source] > 0) {
|
||||
link.flow /= sumLinkOutWeight[link.source];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> nodeFlowTmp(numNodes, 0.0);
|
||||
double danglingRank;
|
||||
|
||||
// Calculate PageRank
|
||||
const auto iteration = [&](const auto iter, const double alpha, const double beta) {
|
||||
danglingRank = std::accumulate(cbegin(nodeFlow), cbegin(nodeFlow) + nonDanglingStartIndex, 0.0);
|
||||
|
||||
// Flow from teleportation
|
||||
const auto teleportationFlow = alpha + beta * danglingRank;
|
||||
for (unsigned int i = 0; i < numNodes; ++i) {
|
||||
nodeFlowTmp[i] = teleportationFlow * nodeTeleportWeights[i];
|
||||
}
|
||||
|
||||
// Flow from links
|
||||
for (const auto& link : flowLinks) {
|
||||
nodeFlowTmp[link.target] += beta * link.flow * nodeFlow[link.source];
|
||||
}
|
||||
|
||||
// Update node flow from the power iteration above and check if converged
|
||||
double nodeFlowDiff = -1.0; // Start with -1.0 so we don't have to subtract it later
|
||||
double error = 0.0;
|
||||
for (unsigned int i = 0; i < numNodes; ++i) {
|
||||
nodeFlowDiff += nodeFlowTmp[i];
|
||||
error += std::abs(nodeFlowTmp[i] - nodeFlow[i]);
|
||||
}
|
||||
|
||||
nodeFlow = nodeFlowTmp;
|
||||
|
||||
// Normalize if needed
|
||||
if (std::abs(nodeFlowDiff) > 1.0e-10) {
|
||||
Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") ";
|
||||
normalize(nodeFlow, nodeFlowDiff + 1.0);
|
||||
}
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
const auto result = powerIterate(config.teleportationProbability, iteration);
|
||||
|
||||
double sumNodeRank = 1.0;
|
||||
double beta = result.beta;
|
||||
|
||||
if (!config.recordedTeleportation) {
|
||||
// Take one last power iteration excluding the teleportation
|
||||
// and normalize node flow
|
||||
sumNodeRank = 1.0 - danglingRank;
|
||||
nodeFlow.assign(numNodes, 0.0);
|
||||
|
||||
for (const auto& link : flowLinks) {
|
||||
nodeFlow[link.target] += link.flow * nodeFlowTmp[link.source] / sumNodeRank;
|
||||
}
|
||||
|
||||
beta = 1.0;
|
||||
}
|
||||
|
||||
// Update the links with their global flow from the PageRank values.
|
||||
// Note: beta is set to 1 if unrecorded teleportation
|
||||
for (auto& link : flowLinks) {
|
||||
link.flow *= beta * nodeFlowTmp[link.source] / sumNodeRank;
|
||||
}
|
||||
}
|
||||
|
||||
void FlowCalculator::calcDirectedRegularizedFlow(const StateNetwork& network, const Config& config) noexcept
|
||||
{
|
||||
Log() << "\n -> Using recorded teleportation to nodes according to a fully connected Bayesian prior. " << std::flush;
|
||||
|
||||
// Calculate node weights w_i = s_i/k_i, where s_i is the node strength (weighted degree) and k_i the (unweighted) degree
|
||||
unsigned int N = network.numNodes();
|
||||
|
||||
std::vector<unsigned int> k_out(N, 0);
|
||||
std::vector<unsigned int> k_in(N, 0);
|
||||
std::vector<double> s_out(N, 0);
|
||||
std::vector<double> s_in(N, 0);
|
||||
double sum_s = sumWeightedDegree;
|
||||
unsigned int sum_k = network.sumDegree();
|
||||
double average_weight = sum_s / sum_k;
|
||||
|
||||
for (auto& link : flowLinks) {
|
||||
k_out[link.source] += 1;
|
||||
s_out[link.source] += link.flow;
|
||||
k_in[link.target] += 1;
|
||||
s_in[link.target] += link.flow;
|
||||
}
|
||||
|
||||
double min_u_out = std::numeric_limits<double>::max();
|
||||
double min_u_in = std::numeric_limits<double>::max();
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
if (k_out[i] > 0) {
|
||||
min_u_out = std::min(min_u_out, s_out[i] / k_out[i]);
|
||||
}
|
||||
if (k_in[i] > 0) {
|
||||
min_u_in = std::min(min_u_in, s_in[i] / k_in[i]);
|
||||
}
|
||||
}
|
||||
|
||||
auto u_out = [s_out, k_out, min_u_out](auto i) { return k_out[i] == 0 ? min_u_out : s_out[i] / k_out[i]; };
|
||||
auto u_in = [s_in, k_in, min_u_in](auto i) { return k_in[i] == 0 ? min_u_in : s_in[i] / k_in[i]; };
|
||||
|
||||
unsigned int numNodesAsTeleportationTargets = config.noSelfLinks ? N - 1 : N;
|
||||
double lambda = config.regularizationStrength * std::log(N) / numNodesAsTeleportationTargets;
|
||||
double u_t = average_weight;
|
||||
|
||||
double sum_u_in = 0.0;
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
sum_u_in += u_in(i);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
nodeTeleportWeights[i] = u_in(i) / sum_u_in;
|
||||
}
|
||||
|
||||
std::function<double(unsigned int)> t_out_withoutSelfLinks = [lambda, u_t, u_out, u_in, sum_u_in](unsigned int i) { return lambda / u_t * u_out(i) * (sum_u_in - u_in(i)); };
|
||||
std::function<double(unsigned int)> t_out_withSelfLinks = [lambda, u_t, u_out, sum_u_in](unsigned int i) { return lambda / u_t * u_out(i) * sum_u_in; };
|
||||
auto t_out = config.noSelfLinks ? t_out_withoutSelfLinks : t_out_withSelfLinks;
|
||||
|
||||
std::vector<double> alpha(N, 0);
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
auto t_i = t_out(i);
|
||||
alpha[i] = t_i / (s_out[i] + t_i); // = 1 for dangling nodes
|
||||
if (config.noSelfLinks) {
|
||||
// Inflate to adjust for no self-teleportation
|
||||
// TODO: Check possible side-effects
|
||||
alpha[i] /= 1 - nodeTeleportWeights[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize link weights with respect to its source nodes total out-link weight;
|
||||
for (auto& link : flowLinks) {
|
||||
if (sumLinkOutWeight[link.source] > 0) {
|
||||
link.flow /= sumLinkOutWeight[link.source];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> nodeFlowTmp(numNodes, 0.0);
|
||||
|
||||
// Calculate PageRank
|
||||
const auto iteration = [&](const auto iter) {
|
||||
double teleTmp = 0.0;
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
teleTmp += alpha[i] * nodeFlow[i];
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
nodeFlowTmp[i] = nodeTeleportWeights[i] * (config.noSelfLinks ? (teleTmp - alpha[i] * nodeFlow[i]) : teleTmp);
|
||||
}
|
||||
|
||||
// Flow from links
|
||||
for (const auto& link : flowLinks) {
|
||||
double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1);
|
||||
nodeFlowTmp[link.target] += beta * link.flow * nodeFlow[link.source];
|
||||
}
|
||||
|
||||
// Update node flow from the power iteration above and check if converged
|
||||
double nodeFlowDiff = -1.0; // Start with -1.0 so we don't have to subtract it later
|
||||
double error = 0.0;
|
||||
for (unsigned int i = 0; i < numNodes; ++i) {
|
||||
nodeFlowDiff += nodeFlowTmp[i];
|
||||
error += std::abs(nodeFlowTmp[i] - nodeFlow[i]);
|
||||
}
|
||||
|
||||
nodeFlow = nodeFlowTmp;
|
||||
|
||||
// Normalize if needed
|
||||
if (std::abs(nodeFlowDiff) > 1.0e-10) {
|
||||
Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") ";
|
||||
normalize(nodeFlow, nodeFlowDiff + 1.0);
|
||||
}
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
unsigned int iterations = 0;
|
||||
double err = 0.0;
|
||||
|
||||
do {
|
||||
double oldErr = err;
|
||||
err = iteration(iterations);
|
||||
|
||||
// Perturb the system if equilibrium
|
||||
if (std::abs(err - oldErr) < 1e-15) {
|
||||
}
|
||||
|
||||
++iterations;
|
||||
} while (iterations < 200 && (err > 1.0e-15 || iterations < 50));
|
||||
|
||||
Log() << "\n -> PageRank calculation done in " << iterations << " iterations.\n";
|
||||
|
||||
double sumNodeRank = 1.0;
|
||||
for (auto& link : flowLinks) {
|
||||
double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1);
|
||||
link.flow *= beta * nodeFlow[link.source] / sumNodeRank;
|
||||
}
|
||||
|
||||
nodeTeleportFlow.assign(numNodes, 0.0);
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
nodeTeleportFlow[i] = nodeFlow[i] * alpha[i];
|
||||
}
|
||||
}
|
||||
|
||||
void FlowCalculator::calcUndirectedRegularizedFlow(const StateNetwork& network, const Config& config) noexcept
|
||||
{
|
||||
Log() << "\n -> Using recorded teleportation to nodes according to a fully connected Bayesian prior. " << std::flush;
|
||||
|
||||
// Calculate node weights w_i = s_i/k_i, where s_i is the node strength (weighted degree) and k_i the (unweighted) degree
|
||||
unsigned int N = network.numNodes();
|
||||
std::vector<unsigned int> k(N, 0);
|
||||
std::vector<double> s(N, 0);
|
||||
double sum_s = sumWeightedDegree;
|
||||
unsigned int sum_k = network.sumDegree();
|
||||
double average_weight = sum_s / sum_k;
|
||||
|
||||
for (auto& link : flowLinks) {
|
||||
k[link.source] += 1;
|
||||
s[link.source] += link.flow;
|
||||
if (link.source != link.target) {
|
||||
k[link.target] += 1;
|
||||
s[link.target] += link.flow;
|
||||
}
|
||||
}
|
||||
|
||||
double min_u = std::numeric_limits<double>::max();
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
if (k[i] > 0) {
|
||||
min_u = std::min(min_u, s[i] / k[i]);
|
||||
}
|
||||
}
|
||||
|
||||
auto u = [s, k, min_u](auto i) { return k[i] == 0 ? min_u : s[i] / k[i]; };
|
||||
|
||||
unsigned int numNodesAsTeleportationTargets = config.noSelfLinks ? N - 1 : N;
|
||||
double lambda = config.regularizationStrength * std::log(N) / numNodesAsTeleportationTargets;
|
||||
double u_t = average_weight;
|
||||
|
||||
double sum_u = 0.0;
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
sum_u += u(i);
|
||||
}
|
||||
|
||||
// nodeTeleportWeights is the fraction of teleportation flow landing on each node. This is proportional to u_in
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
nodeTeleportWeights[i] = u(i) / sum_u;
|
||||
}
|
||||
|
||||
std::function<double(unsigned int)> t_withoutSelfLinks = [lambda, u_t, u, sum_u](unsigned int i) { return lambda / u_t * u(i) * (sum_u - u(i)); };
|
||||
std::function<double(unsigned int)> t_withSelfLinks = [lambda, u_t, u, sum_u](unsigned int i) { return lambda / u_t * u(i) * sum_u; };
|
||||
auto t = config.noSelfLinks ? t_withoutSelfLinks : t_withSelfLinks;
|
||||
|
||||
std::vector<double> alpha(N, 0);
|
||||
double sum_t = 0.0;
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
auto t_i = t(i);
|
||||
alpha[i] = t_i / (s[i] + t_i);
|
||||
if (config.noSelfLinks) {
|
||||
// Inflate to adjust for no self-teleportation
|
||||
// TODO: No later side effects of cheating here? Need to normalize targets instead?
|
||||
alpha[i] /= 1 - nodeTeleportWeights[i];
|
||||
}
|
||||
sum_t += t_i;
|
||||
}
|
||||
|
||||
for (auto& link : flowLinks) {
|
||||
if (sumLinkOutWeight[link.source] > 0) {
|
||||
link.flow /= sumLinkOutWeight[link.source];
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
nodeFlow[i] = (s[i] + t(i)) / (sum_s + sum_t);
|
||||
}
|
||||
|
||||
nodeTeleportFlow.assign(numNodes, 0.0);
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
nodeTeleportFlow[i] = nodeFlow[i] * alpha[i];
|
||||
}
|
||||
|
||||
for (auto& link : flowLinks) {
|
||||
// TODO: Side effect from inflating alpha, need real alpha here.
|
||||
double beta = 1 - alpha[link.source] * (config.noSelfLinks ? 1 - nodeTeleportWeights[link.source] : 1);
|
||||
link.flow *= beta * nodeFlow[link.source] * 2;
|
||||
}
|
||||
}
|
||||
|
||||
void FlowCalculator::calcDirectedBipartiteFlow(const StateNetwork& network, const Config& config) noexcept
|
||||
{
|
||||
Log() << "\n -> Using bipartite " << (config.recordedTeleportation ? "recorded" : "unrecorded") << " teleportation to " << (config.teleportToNodes ? "nodes" : "links") << ". " << std::flush;
|
||||
|
||||
const auto bipartiteStartId = network.bipartiteStartId();
|
||||
|
||||
if (config.teleportToNodes) {
|
||||
for (const auto& nodeIt : network.nodes()) {
|
||||
auto& node = nodeIt.second;
|
||||
if (node.id < bipartiteStartId) {
|
||||
nodeTeleportWeights[nodeIndexMap[node.id]] = node.weight;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Teleport proportionally to out-degree, or in-degree if recorded teleportation.
|
||||
// Two-step degree: sum of products between incoming and outgoing links from bipartite nodes
|
||||
|
||||
if (config.recordedTeleportation) {
|
||||
for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) {
|
||||
// target is an ordinary node
|
||||
nodeTeleportWeights[link->target] += link->flow;
|
||||
}
|
||||
} else {
|
||||
// Unrecorded teleportation
|
||||
|
||||
for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) {
|
||||
// source is an ordinary node
|
||||
nodeTeleportWeights[link->source] += link->flow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
normalize(nodeTeleportWeights);
|
||||
|
||||
nodeFlow = nodeTeleportWeights;
|
||||
|
||||
// Normalize link weights with respect to its source nodes total out-link weight;
|
||||
for (auto& link : flowLinks) {
|
||||
if (sumLinkOutWeight[link.source] > 0) {
|
||||
link.flow /= sumLinkOutWeight[link.source];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned int> danglingIndices;
|
||||
for (size_t i = 0; i < numNodes; ++i) {
|
||||
if (nodeOutDegree[i] == 0) {
|
||||
danglingIndices.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> nodeFlowTmp(numNodes, 0.0);
|
||||
double danglingRank;
|
||||
|
||||
// Calculate two-step PageRank
|
||||
const auto iteration = [&](const auto iter, const double alpha, const double beta) {
|
||||
danglingRank = 0.0;
|
||||
for (const auto& i : danglingIndices) {
|
||||
danglingRank += nodeFlow[i];
|
||||
}
|
||||
|
||||
// Flow from teleportation
|
||||
const auto teleportationFlow = alpha + beta * danglingRank;
|
||||
for (unsigned int i = 0; i < bipartiteStartIndex; ++i) {
|
||||
nodeFlowTmp[i] = teleportationFlow * nodeTeleportWeights[i];
|
||||
}
|
||||
|
||||
for (unsigned int i = bipartiteStartIndex; i < numNodes; ++i) {
|
||||
nodeFlowTmp[i] = 0.0;
|
||||
}
|
||||
|
||||
// Flow from links
|
||||
// First step
|
||||
for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) {
|
||||
nodeFlow[link->target] += beta * link->flow * nodeFlow[link->source];
|
||||
}
|
||||
|
||||
// Second step back to primary nodes
|
||||
for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) {
|
||||
nodeFlowTmp[link->target] += link->flow * nodeFlow[link->source];
|
||||
}
|
||||
|
||||
// Update node flow from the power iteration above and check if converged
|
||||
double nodeFlowDiff = -1.0;
|
||||
double error = 0.0;
|
||||
for (unsigned int i = 0; i < bipartiteStartIndex; ++i) {
|
||||
nodeFlowDiff += nodeFlowTmp[i];
|
||||
error += std::abs(nodeFlowTmp[i] - nodeFlow[i]);
|
||||
}
|
||||
|
||||
nodeFlow = nodeFlowTmp;
|
||||
|
||||
// Normalize if needed
|
||||
if (std::abs(nodeFlowDiff) > 1.0e-10) {
|
||||
Log() << "(Normalizing ranks after " << iter << " power iterations with error " << nodeFlowDiff << ") ";
|
||||
normalize(nodeFlow, nodeFlowDiff + 1.0);
|
||||
}
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
const auto result = powerIterate(config.teleportationProbability, iteration);
|
||||
|
||||
double sumNodeRank = 1.0;
|
||||
double beta = result.beta;
|
||||
|
||||
if (!config.recordedTeleportation) {
|
||||
// Take one last power iteration excluding the teleportation (and normalize node flow to sum 1.0)
|
||||
sumNodeRank = 1.0 - danglingRank;
|
||||
nodeFlow.assign(numNodes, 0.0);
|
||||
|
||||
for (auto link = begin(flowLinks); link != begin(flowLinks) + bipartiteLinkStartIndex; ++link) {
|
||||
nodeFlowTmp[link->target] += link->flow * nodeFlowTmp[link->source];
|
||||
}
|
||||
// Second step back to primary nodes
|
||||
for (auto link = begin(flowLinks) + bipartiteLinkStartIndex; link != end(flowLinks); ++link) {
|
||||
nodeFlow[link->target] += link->flow * nodeFlowTmp[link->source];
|
||||
}
|
||||
|
||||
beta = 1.0;
|
||||
}
|
||||
|
||||
// Update the links with their global flow from the PageRank values.
|
||||
// Note: beta is set to 1 if unrecorded teleportation
|
||||
for (auto& link : flowLinks) {
|
||||
link.flow *= beta * nodeFlowTmp[link.source] / sumNodeRank;
|
||||
}
|
||||
}
|
||||
|
||||
void FlowCalculator::finalize(StateNetwork& network, const Config& config, bool normalizeNodeFlow) noexcept
|
||||
{
|
||||
unsigned int N = network.numNodes();
|
||||
// TODO: Skip bipartite flow adjustment for directed / rawdir / .. ?
|
||||
if (network.isBipartite()) {
|
||||
Log() << "\n -> Using bipartite links.";
|
||||
|
||||
if (!config.skipAdjustBipartiteFlow && !config.bipartiteTeleportation) {
|
||||
// Only links between ordinary nodes and feature nodes in bipartite network
|
||||
// Don't code feature nodes -> distribute all flow from those to ordinary nodes
|
||||
for (auto& link : flowLinks) {
|
||||
auto sourceIsFeature = link.source >= bipartiteStartIndex;
|
||||
|
||||
if (sourceIsFeature) {
|
||||
nodeFlow[link.target] += link.flow;
|
||||
nodeFlow[link.source] = 0.0; // Doesn't matter if done multiple times on each node.
|
||||
} else {
|
||||
nodeFlow[link.source] += link.flow;
|
||||
nodeFlow[link.target] = 0.0; // Doesn't matter if done multiple times on each node.
|
||||
}
|
||||
// TODO: Should flow double before moving to nodes, does it cancel out in normalization?
|
||||
|
||||
// Markov time 2 on the full network will correspond to markov time 1 between the real nodes.
|
||||
link.flow *= 2;
|
||||
}
|
||||
// TODO: Should flow double before moving to nodes, does it cancel out in normalization?
|
||||
|
||||
normalizeNodeFlow = true;
|
||||
|
||||
} else if (config.bipartiteTeleportation) {
|
||||
for (auto& link : flowLinks) {
|
||||
// Markov time 2 on the full network will correspond to markov time 1 between the real nodes.
|
||||
link.flow *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.useNodeWeightsAsFlow) {
|
||||
Log() << "\n -> Using node weights as flow.";
|
||||
|
||||
for (auto& nodeIt : network.nodes()) {
|
||||
auto& node = nodeIt.second;
|
||||
nodeFlow[nodeIndexMap[node.id]] = node.weight;
|
||||
}
|
||||
|
||||
normalizeNodeFlow = true;
|
||||
}
|
||||
|
||||
if (normalizeNodeFlow) {
|
||||
normalize(nodeFlow);
|
||||
}
|
||||
|
||||
// Write back flow to network
|
||||
double sumNodeFlow = 0.0;
|
||||
double sumLinkFlow = 0.0;
|
||||
unsigned int linkIndex = 0;
|
||||
auto featureLinkIndex = bipartiteLinkStartIndex;
|
||||
|
||||
for (auto& node : network.m_nodeLinkMap) {
|
||||
for (auto& link : node.second) {
|
||||
auto& linkData = link.second;
|
||||
if (network.isBipartite() && node.first.id >= network.bipartiteStartId()) {
|
||||
linkData.flow = flowLinks[featureLinkIndex++].flow;
|
||||
} else {
|
||||
linkData.flow = flowLinks[linkIndex++].flow;
|
||||
}
|
||||
sumLinkFlow += linkData.flow;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& nodeIt : network.m_nodes) {
|
||||
auto& node = nodeIt.second;
|
||||
const auto nodeIndex = nodeIndexMap[node.id];
|
||||
node.flow = nodeFlow[nodeIndex];
|
||||
node.weight = nodeTeleportWeights[nodeIndex];
|
||||
node.teleFlow = !nodeTeleportFlow.empty() ? nodeTeleportFlow[nodeIndex] : nodeFlow[nodeIndex] * (nodeOutDegree[nodeIndex] == 0 ? 1 : config.teleportationProbability);
|
||||
node.enterFlow = node.flow;
|
||||
node.exitFlow = node.flow;
|
||||
|
||||
if (!config.noSelfLinks) {
|
||||
// Remove self-teleportation flow
|
||||
node.enterFlow -= node.teleFlow * node.weight;
|
||||
node.exitFlow -= node.teleFlow * node.weight;
|
||||
|
||||
// Remove self-link flow
|
||||
unsigned int norm = config.isUndirectedFlow() ? 2 : 1;
|
||||
auto& outLinks = network.m_nodeLinkMap[node.id];
|
||||
for (auto& link : outLinks) {
|
||||
auto& linkData = link.second;
|
||||
if (node.id == link.first.id) {
|
||||
node.enterFlow -= linkData.flow / norm;
|
||||
node.exitFlow -= linkData.flow / norm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sumNodeFlow += node.flow;
|
||||
}
|
||||
|
||||
// Enter/exit flow
|
||||
if (!config.isUndirectedClustering() && !config.regularized) {
|
||||
enterFlow.assign(N, 0);
|
||||
exitFlow.assign(N, 0);
|
||||
double alpha = config.teleportationProbability;
|
||||
double sumDanglingFlow = 0.0;
|
||||
for (unsigned int i = 0; i < N; ++i) {
|
||||
if (nodeOutDegree[i] == 0) {
|
||||
sumDanglingFlow += nodeFlow[i];
|
||||
}
|
||||
}
|
||||
for (auto& nodeIt : network.m_nodes) {
|
||||
auto& node = nodeIt.second;
|
||||
const auto sourceIndex = nodeIndexMap[node.id];
|
||||
auto& outLinks = network.m_nodeLinkMap[node.id];
|
||||
double danglingFlow = outLinks.empty() ? node.flow : 0.0;
|
||||
if (config.recordedTeleportation) {
|
||||
// Don't let self-teleportation add to the enter/exit flow (i.e. multiply with (1.0 - node.data.teleportWeight))
|
||||
exitFlow[sourceIndex] += alpha * node.flow * (1.0 - node.weight);
|
||||
enterFlow[sourceIndex] += (alpha * (1.0 - node.flow) + (1 - alpha) * (sumDanglingFlow - danglingFlow)) * node.weight;
|
||||
}
|
||||
for (auto& link : outLinks) {
|
||||
auto& linkData = link.second;
|
||||
const auto targetIndex = nodeIndexMap[link.first.id];
|
||||
exitFlow[sourceIndex] += linkData.flow;
|
||||
enterFlow[targetIndex] += linkData.flow;
|
||||
}
|
||||
}
|
||||
for (auto& nodeIt : network.m_nodes) {
|
||||
auto& node = nodeIt.second;
|
||||
const auto nodeIndex = nodeIndexMap[node.id];
|
||||
node.enterFlow = enterFlow[nodeIndex];
|
||||
node.exitFlow = exitFlow[nodeIndex];
|
||||
}
|
||||
}
|
||||
|
||||
Log() << "\n => Sum node flow: " << sumNodeFlow << ", sum link flow: " << sumLinkFlow << "\n";
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,75 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef FLOW_CALCULATOR_H_
|
||||
#define FLOW_CALCULATOR_H_
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
struct Config;
|
||||
class StateNetwork;
|
||||
|
||||
namespace detail {
|
||||
struct FlowLink {
|
||||
unsigned int source;
|
||||
unsigned int target;
|
||||
double flow;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Calculate flow on network based on different flow models
|
||||
*/
|
||||
class FlowCalculator {
|
||||
public:
|
||||
FlowCalculator(StateNetwork&, const Config&);
|
||||
|
||||
private:
|
||||
void calcUndirectedFlow() noexcept;
|
||||
void calcDirectedFlow(const StateNetwork&, const Config&) noexcept;
|
||||
void calcUndirectedRegularizedFlow(const StateNetwork&, const Config&) noexcept;
|
||||
void calcDirectedRegularizedFlow(const StateNetwork&, const Config&) noexcept;
|
||||
void calcDirectedBipartiteFlow(const StateNetwork&, const Config&) noexcept;
|
||||
void calcDirdirFlow(const Config&) noexcept;
|
||||
void calcRawdirFlow() noexcept;
|
||||
void usePrecomputedFlow(const StateNetwork&, const Config&);
|
||||
|
||||
void finalize(StateNetwork&, const Config&, bool) noexcept;
|
||||
|
||||
unsigned int numNodes;
|
||||
unsigned int nonDanglingStartIndex = 0;
|
||||
unsigned int bipartiteStartIndex = 0;
|
||||
unsigned int bipartiteLinkStartIndex = 0;
|
||||
|
||||
double sumLinkWeight = 0;
|
||||
double sumWeightedDegree = 0;
|
||||
|
||||
std::map<unsigned int, unsigned int> nodeIndexMap;
|
||||
std::vector<double> nodeFlow;
|
||||
std::vector<double> nodeTeleportWeights;
|
||||
std::vector<double> nodeTeleportFlow;
|
||||
std::vector<double> enterFlow;
|
||||
std::vector<double> exitFlow;
|
||||
std::vector<double> sumLinkOutWeight;
|
||||
std::vector<unsigned int> nodeOutDegree;
|
||||
using FlowLink = detail::FlowLink;
|
||||
std::vector<FlowLink> flowLinks;
|
||||
};
|
||||
|
||||
inline void calculateFlow(StateNetwork& network, const Config& config)
|
||||
{
|
||||
FlowCalculator(network, config);
|
||||
}
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // FLOW_CALCULATOR_H_
|
||||
@@ -0,0 +1,17 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
namespace infomap {
|
||||
|
||||
unsigned int Log::s_verboseLevel = 0;
|
||||
bool Log::s_silent = false;
|
||||
|
||||
} // namespace infomap
|
||||
@@ -0,0 +1,110 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LOG_H_
|
||||
#define LOG_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <iomanip>
|
||||
#include <type_traits>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
struct hideIf;
|
||||
|
||||
class Log {
|
||||
using ostreamFuncPtr = std::add_pointer_t<std::ostream&(std::ostream&)>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Log when level is below or equal Log::verboseLevel()
|
||||
* and maxLevel is above or equal Log::verboseLevel()
|
||||
*/
|
||||
explicit Log(unsigned int level = 0, unsigned int maxLevel = std::numeric_limits<int>::max())
|
||||
: m_level(level), m_maxLevel(maxLevel), m_visible(isVisible(m_level, m_maxLevel)) { }
|
||||
|
||||
bool isVisible() const { return isVisible(m_level, m_maxLevel); }
|
||||
|
||||
void hide(bool value) { m_visible = !value && isVisible(); }
|
||||
|
||||
Log& operator<<(const hideIf&) { return *this; }
|
||||
|
||||
template <typename T>
|
||||
Log& operator<<(const T& data)
|
||||
{
|
||||
#if 0
|
||||
if (m_visible)
|
||||
m_ostream << data;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
Log& operator<<(ostreamFuncPtr f)
|
||||
{
|
||||
#if 0
|
||||
if (m_visible)
|
||||
m_ostream << f;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void init(unsigned int verboseLevel, bool silent, unsigned int numberPrecision)
|
||||
{
|
||||
setVerboseLevel(verboseLevel);
|
||||
setSilent(silent);
|
||||
Log() << std::setprecision(static_cast<int>(numberPrecision));
|
||||
}
|
||||
|
||||
static bool isVisible(unsigned int level, unsigned int maxLevel)
|
||||
{
|
||||
return !s_silent && s_verboseLevel >= level && s_verboseLevel <= maxLevel;
|
||||
}
|
||||
|
||||
static void setVerboseLevel(unsigned int level) { s_verboseLevel = level; }
|
||||
|
||||
static void setSilent(bool silent) { s_silent = silent; }
|
||||
|
||||
static bool isSilent() { return s_silent; }
|
||||
|
||||
/* precision() is patched in igraph to avoid references to std::cout */
|
||||
static std::streamsize precision() { return 6; }
|
||||
|
||||
static std::streamsize precision(std::streamsize)
|
||||
{
|
||||
return precision();
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int m_level;
|
||||
unsigned int m_maxLevel;
|
||||
bool m_visible;
|
||||
#if 0
|
||||
std::ostream& m_ostream = std::cout;
|
||||
#endif
|
||||
|
||||
static unsigned int s_verboseLevel;
|
||||
static bool s_silent;
|
||||
};
|
||||
|
||||
struct hideIf {
|
||||
explicit hideIf(bool value) : hide(value) { }
|
||||
|
||||
friend Log& operator<<(Log& out, const hideIf& manip)
|
||||
{
|
||||
out.hide(manip.hide);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool hide;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // LOG_H_
|
||||
@@ -0,0 +1,153 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef META_COLLECTION_H_
|
||||
#define META_COLLECTION_H_
|
||||
|
||||
#include "infomath.h"
|
||||
#include <ostream>
|
||||
#include <map>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
struct FlowCount {
|
||||
FlowCount() = default;
|
||||
|
||||
explicit FlowCount(double flow)
|
||||
: flow(flow), count(1) { }
|
||||
|
||||
FlowCount& operator+=(const FlowCount& o)
|
||||
{
|
||||
flow += o.flow;
|
||||
count += o.count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FlowCount& operator+=(double f)
|
||||
{
|
||||
flow += f;
|
||||
++count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FlowCount& operator-=(const FlowCount& o)
|
||||
{
|
||||
flow -= o.flow;
|
||||
count -= o.count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FlowCount& operator-=(double f)
|
||||
{
|
||||
flow -= f;
|
||||
--count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const FlowCount& o)
|
||||
{
|
||||
return out << o.flow << "/" << o.count;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
flow = 0.0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
bool empty() const { return count == 0; }
|
||||
double flow = 0.0;
|
||||
unsigned int count = 0;
|
||||
};
|
||||
|
||||
using MetaToFlowCount = std::map<unsigned int, FlowCount>; // metaId -> (flow,count)
|
||||
|
||||
class MetaCollection {
|
||||
protected:
|
||||
FlowCount m_total;
|
||||
MetaToFlowCount m_metaToFlowCount;
|
||||
|
||||
public:
|
||||
unsigned int size() const { return m_metaToFlowCount.size(); }
|
||||
|
||||
bool empty() const { return m_metaToFlowCount.empty(); }
|
||||
|
||||
MetaToFlowCount::iterator begin() { return m_metaToFlowCount.begin(); }
|
||||
MetaToFlowCount::iterator end() { return m_metaToFlowCount.end(); }
|
||||
MetaToFlowCount::const_iterator begin() const { return m_metaToFlowCount.begin(); }
|
||||
MetaToFlowCount::const_iterator end() const { return m_metaToFlowCount.end(); }
|
||||
|
||||
void add(unsigned int meta, double flow = 1.0)
|
||||
{
|
||||
m_total += flow;
|
||||
m_metaToFlowCount[meta] += flow;
|
||||
}
|
||||
|
||||
void add(unsigned int meta, const FlowCount& flow)
|
||||
{
|
||||
m_total += flow;
|
||||
m_metaToFlowCount[meta] += flow;
|
||||
}
|
||||
|
||||
void add(const MetaCollection& other)
|
||||
{
|
||||
for (auto& it : other) {
|
||||
auto metaId = it.first;
|
||||
auto& flowCount = it.second;
|
||||
add(metaId, flowCount);
|
||||
}
|
||||
}
|
||||
|
||||
void remove(unsigned int meta, const FlowCount& flow)
|
||||
{
|
||||
m_total -= flow;
|
||||
auto& metaFlowCount = m_metaToFlowCount[meta];
|
||||
metaFlowCount -= flow;
|
||||
if (metaFlowCount.empty())
|
||||
m_metaToFlowCount.erase(meta);
|
||||
}
|
||||
|
||||
void remove(const MetaCollection& other)
|
||||
{
|
||||
for (auto& it : other) {
|
||||
auto metaId = it.first;
|
||||
auto& flowCount = it.second;
|
||||
remove(metaId, flowCount);
|
||||
}
|
||||
}
|
||||
|
||||
double calculateEntropy()
|
||||
{
|
||||
double metaCodelength = 0.0;
|
||||
for (auto& it : m_metaToFlowCount) {
|
||||
metaCodelength -= infomath::plogp(it.second.flow / m_total.flow);
|
||||
}
|
||||
return m_total.flow * metaCodelength;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_total.reset();
|
||||
m_metaToFlowCount.clear();
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const MetaCollection& m)
|
||||
{
|
||||
out << "<( " << m.m_total << ": ";
|
||||
for (auto& it : m.m_metaToFlowCount) {
|
||||
out << "(" << it.first << "," << it.second << ") ";
|
||||
}
|
||||
out << ")>";
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // META_COLLECTION_H_
|
||||
@@ -0,0 +1,45 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef RANDOM_H_
|
||||
#define RANDOM_H_
|
||||
|
||||
#include <igraph_random.h>
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class Random {
|
||||
igraph_rng_t* m_randGen;
|
||||
|
||||
public:
|
||||
Random() : m_randGen(igraph_rng_default()) { }
|
||||
|
||||
unsigned int randInt(unsigned int min, unsigned int max)
|
||||
{
|
||||
return igraph_rng_get_integer(m_randGen, min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random permutation of indices of the size of the input vector
|
||||
*/
|
||||
void getRandomizedIndexVector(std::vector<unsigned int>& randomOrder)
|
||||
{
|
||||
unsigned int size = randomOrder.size();
|
||||
for (unsigned int i = 0; i < size; ++i)
|
||||
randomOrder[i] = i;
|
||||
for (unsigned int i = 0; i < size; ++i)
|
||||
std::swap(randomOrder[i], randomOrder[i + randInt(0, size - i - 1)]);
|
||||
}
|
||||
};
|
||||
} // namespace infomap
|
||||
|
||||
#endif // RANDOM_H_
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef STOPWATCH_H_
|
||||
#define STOPWATCH_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
#include <ostream>
|
||||
#include <cmath>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
class Stopwatch {
|
||||
public:
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
using TimeType = std::chrono::time_point<Clock>;
|
||||
|
||||
explicit Stopwatch(bool startImmediately)
|
||||
: m_start(now()), m_stop(now()), m_running(false)
|
||||
{
|
||||
if (startImmediately) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
m_start = Clock::now();
|
||||
m_running = true;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (m_running)
|
||||
m_start = Clock::now();
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (m_running) {
|
||||
m_stop = Clock::now();
|
||||
m_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
TimeType getCurrentTimePoint() const
|
||||
{
|
||||
return m_running ? Clock::now() : m_stop;
|
||||
}
|
||||
|
||||
double getElapsedTimeInSec() const
|
||||
{
|
||||
std::chrono::duration<double> diff = getCurrentTimePoint() - m_start;
|
||||
return diff.count();
|
||||
}
|
||||
|
||||
double getElapsedTimeInMilliSec() const
|
||||
{
|
||||
std::chrono::duration<double, std::milli> diff = getCurrentTimePoint() - m_start;
|
||||
return diff.count();
|
||||
}
|
||||
|
||||
static TimeType now()
|
||||
{
|
||||
return Clock::now();
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const Stopwatch& stopwatch)
|
||||
{
|
||||
auto temp = static_cast<unsigned int>(std::floor(stopwatch.getElapsedTimeInMilliSec()));
|
||||
if (temp > 60'000) {
|
||||
if (temp > 3600'000) {
|
||||
if (temp > 86'400'000) {
|
||||
out << temp / 86'400'000 << "d ";
|
||||
temp %= 86'400'000;
|
||||
}
|
||||
out << temp / 3600'000 << "h ";
|
||||
temp %= 3600'000;
|
||||
}
|
||||
out << temp / 60'000 << "m ";
|
||||
temp %= 60'000;
|
||||
out << temp * 1.0 / 1000 << "s";
|
||||
} else {
|
||||
out << stopwatch.getElapsedTimeInSec() << "s";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
TimeType m_start, m_stop;
|
||||
bool m_running;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // STOPWATCH_H_
|
||||
@@ -0,0 +1,82 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef VECTOR_MAP_H_
|
||||
#define VECTOR_MAP_H_
|
||||
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
template <typename T>
|
||||
class VectorMap {
|
||||
public:
|
||||
VectorMap(unsigned int capacity = 0)
|
||||
: m_capacity(capacity),
|
||||
m_values(capacity),
|
||||
m_redirect(capacity, 0),
|
||||
m_maxOffset(std::numeric_limits<unsigned int>::max() - 1 - capacity) { }
|
||||
|
||||
void startRound()
|
||||
{
|
||||
if (m_size > 0) {
|
||||
m_offset += m_capacity;
|
||||
m_size = 0;
|
||||
}
|
||||
if (m_offset > m_maxOffset) {
|
||||
m_redirect.assign(m_capacity, 0);
|
||||
m_offset = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void add(unsigned int index, T value)
|
||||
{
|
||||
if (isSet(index)) {
|
||||
m_values[m_redirect[index] - m_offset] += value;
|
||||
} else {
|
||||
m_redirect[index] = m_offset + m_size;
|
||||
m_values[m_size] = value;
|
||||
++m_size;
|
||||
}
|
||||
}
|
||||
|
||||
bool isSet(unsigned int index)
|
||||
{
|
||||
return m_redirect[index] >= m_offset;
|
||||
}
|
||||
|
||||
unsigned int size()
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
T& operator[](unsigned int index)
|
||||
{
|
||||
return m_values[m_redirect[index] - m_offset];
|
||||
}
|
||||
|
||||
std::vector<T>& values()
|
||||
{
|
||||
return m_values;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int m_capacity = 0;
|
||||
std::vector<T> m_values;
|
||||
std::vector<unsigned int> m_redirect;
|
||||
unsigned int m_maxOffset = std::numeric_limits<unsigned int>::max() - 1;
|
||||
unsigned int m_offset = 1;
|
||||
unsigned int m_size = 0;
|
||||
};
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // VECTOR_MAP_H_
|
||||
@@ -0,0 +1,216 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef CONVERT_H_
|
||||
#define CONVERT_H_
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <locale> // std::locale, std::tolower
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace infomap {
|
||||
|
||||
template <typename T>
|
||||
struct TypeInfo {
|
||||
static bool isNumeric() { return false; }
|
||||
};
|
||||
template <>
|
||||
struct TypeInfo<bool> {
|
||||
static bool isNumeric() { return false; }
|
||||
};
|
||||
template <>
|
||||
struct TypeInfo<int> {
|
||||
static bool isNumeric() { return true; }
|
||||
};
|
||||
template <>
|
||||
struct TypeInfo<unsigned int> {
|
||||
static bool isNumeric() { return true; }
|
||||
};
|
||||
template <>
|
||||
struct TypeInfo<double> {
|
||||
static bool isNumeric() { return true; }
|
||||
};
|
||||
|
||||
namespace io {
|
||||
|
||||
inline std::string tolower(std::string str)
|
||||
{
|
||||
std::locale loc;
|
||||
for (char& c : str)
|
||||
c = std::tolower(c, loc);
|
||||
return str;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::string stringify(T& x)
|
||||
{
|
||||
std::ostringstream o;
|
||||
if (!(o << x))
|
||||
throw std::runtime_error((o << "stringify(" << x << ")", o.str()));
|
||||
return o.str();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string stringify(bool& x)
|
||||
{
|
||||
return x ? "true" : "false";
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
inline std::string stringify(const Container& cont, const std::string& delimiter)
|
||||
{
|
||||
std::ostringstream o;
|
||||
if (cont.empty())
|
||||
return "";
|
||||
unsigned int maxIndex = cont.size() - 1;
|
||||
for (unsigned int i = 0; i < maxIndex; ++i) {
|
||||
if (!(o << cont[i]))
|
||||
throw std::runtime_error((o << "stringify(container[" << i << "])", o.str()));
|
||||
o << delimiter;
|
||||
}
|
||||
if (!(o << cont[maxIndex]))
|
||||
throw std::runtime_error((o << "stringify(container[" << maxIndex << "])", o.str()));
|
||||
return o.str();
|
||||
}
|
||||
|
||||
struct InsensitiveCompare {
|
||||
bool operator()(const std::string& a, const std::string& b) const
|
||||
{
|
||||
auto lhs = a.begin();
|
||||
auto rhs = b.begin();
|
||||
|
||||
std::locale loc;
|
||||
for (; lhs != a.end() && rhs != b.end(); ++lhs, ++rhs) {
|
||||
auto lhs_val = std::tolower(*lhs, loc);
|
||||
auto rhs_val = std::tolower(*rhs, loc);
|
||||
|
||||
if (lhs_val != rhs_val)
|
||||
return lhs_val < rhs_val;
|
||||
}
|
||||
|
||||
return (rhs != b.end());
|
||||
}
|
||||
};
|
||||
|
||||
inline std::vector<std::string>& split(const std::string& s, char delim, std::vector<std::string>& items)
|
||||
{
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
while (std::getline(ss, item, delim)) {
|
||||
if (item.length() > 0) {
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> split(const std::string& s, char delim)
|
||||
{
|
||||
std::vector<std::string> items;
|
||||
split(s, delim, items);
|
||||
return items;
|
||||
}
|
||||
|
||||
class Str {
|
||||
public:
|
||||
Str() = default;
|
||||
template <class T>
|
||||
Str& operator<<(const T& t)
|
||||
{
|
||||
m_oss << stringify(t);
|
||||
return *this;
|
||||
}
|
||||
Str& operator<<(std::ostream& (*f)(std::ostream&))
|
||||
{
|
||||
m_oss << f;
|
||||
return *this;
|
||||
}
|
||||
operator std::string() const
|
||||
{
|
||||
return m_oss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostringstream m_oss;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline bool stringToValue(std::string const& str, T& value)
|
||||
{
|
||||
std::istringstream istream(str);
|
||||
return !!(istream >> value);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool stringToValue(std::string const& str, unsigned int& value)
|
||||
{
|
||||
std::istringstream istream(str);
|
||||
int target = 0;
|
||||
istream >> target;
|
||||
if (target < 0) return false;
|
||||
value = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool stringToValue(std::string const& str, unsigned long& value)
|
||||
{
|
||||
std::istringstream istream(str);
|
||||
int target = 0;
|
||||
istream >> target;
|
||||
if (target < 0) return false;
|
||||
value = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string firstWord(const std::string& line)
|
||||
{
|
||||
std::istringstream ss;
|
||||
std::string buf;
|
||||
ss.str(line);
|
||||
ss >> buf;
|
||||
return buf;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::string padValue(T value, const std::string::size_type size, bool rightAligned = true, const char paddingChar = ' ')
|
||||
{
|
||||
std::string valStr = stringify(value);
|
||||
if (size == valStr.size())
|
||||
return valStr;
|
||||
if (size < valStr.size())
|
||||
return valStr.substr(0, size);
|
||||
|
||||
if (!rightAligned)
|
||||
return valStr.append(size - valStr.size(), paddingChar);
|
||||
|
||||
return std::string(size - valStr.size(), paddingChar).append(valStr);
|
||||
}
|
||||
|
||||
inline std::string toPrecision(double value, unsigned int precision = 10, bool fixed = false)
|
||||
{
|
||||
std::ostringstream o;
|
||||
if (fixed)
|
||||
o << std::fixed << std::setprecision(static_cast<int>(precision));
|
||||
else
|
||||
o << std::setprecision(static_cast<int>(precision));
|
||||
if (!(o << value))
|
||||
throw std::runtime_error((o << "stringify(" << value << ")", o.str()));
|
||||
return o.str();
|
||||
}
|
||||
|
||||
} // namespace io
|
||||
|
||||
} // namespace infomap
|
||||
|
||||
#endif // CONVERT_H_
|
||||
@@ -0,0 +1,57 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef INFOMATH_H_
|
||||
#define INFOMATH_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace infomap {
|
||||
namespace infomath {
|
||||
|
||||
using std::log2;
|
||||
|
||||
inline double plogp(double p)
|
||||
{
|
||||
return p > 0.0 ? p * log2(p) : 0.0;
|
||||
}
|
||||
|
||||
inline double isEqual(double a, double b, double tol = 1e-8)
|
||||
{
|
||||
return std::abs(a - b) <= tol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tsallis entropy S_q of a uniform probability distribution of length n
|
||||
*/
|
||||
inline double tsallisEntropyUniform(double n, double q = 1)
|
||||
{
|
||||
if (isEqual(q, 1)) {
|
||||
return std::log2(n);
|
||||
}
|
||||
return 1 / (q - 1) * (1 - pow(n, (1 - q))) / std::log(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate from linear (q = 0) to log (q = 1)
|
||||
* linlog(k, 0) = k
|
||||
* linlog(k, 1) = log2(k)
|
||||
*/
|
||||
inline double linlog(double k, double q = 1)
|
||||
{
|
||||
double baseCorrection = q <= 1 ? (1 - q) * std::log(2) + q : 1;
|
||||
double offsetCorrection = q <= 1 ? 1 - q : 0;
|
||||
return tsallisEntropyUniform(k, q) * baseCorrection + offsetCorrection;
|
||||
}
|
||||
|
||||
} // namespace infomath
|
||||
} // namespace infomap
|
||||
|
||||
#endif // INFOMATH_H_
|
||||
@@ -0,0 +1,19 @@
|
||||
/*******************************************************************************
|
||||
Infomap software package for multi-level network clustering
|
||||
Copyright (c) 2013, 2014 Daniel Edler, Anton Holmgren, Martin Rosvall
|
||||
|
||||
This file is part of the Infomap software package.
|
||||
See file LICENSE_GPLv3.txt for full license details.
|
||||
For more information, see <http://www.mapequation.org>
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef VERSION_H_
|
||||
#define VERSION_H_
|
||||
|
||||
namespace infomap {
|
||||
|
||||
const char* const INFOMAP_VERSION = "2.8.0";
|
||||
|
||||
}
|
||||
|
||||
#endif // VERSION_H_
|
||||
Reference in New Issue
Block a user