├── .github └── workflows │ ├── build_wheels.yml │ ├── documentation.yml │ └── tests.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── asv.conf.json ├── benchmarks ├── __init__.py ├── ba_sequence.json ├── module.py ├── static.py └── temporal.py ├── docs ├── .gitignore ├── Makefile ├── _static │ ├── .keep │ └── css │ │ └── custom.css ├── algorithms │ ├── assortativity.rst │ ├── edge_degrees.rst │ ├── event_graphs.rst │ ├── graph_properties.rst │ ├── graphicallity.rst │ ├── index.rst │ ├── static_network_reachability.rst │ ├── temporal_network_reachability.rst │ └── vertex_degrees.rst ├── bibliography.rst ├── citing.rst ├── conf.py ├── docutils.conf ├── examples │ ├── index.rst │ ├── isotropic-thread-figure.py │ ├── isotropic-thread.py │ ├── isotropic.pdf │ ├── isotropic.py │ ├── isotropic.rst │ ├── isotropic.svg │ └── temporal_network.rst ├── generation │ ├── barabasi.rst │ ├── complete_graph.rst │ ├── cycle.rst │ ├── degree_seq.rst │ ├── expected_degree_seq.rst │ ├── fully_mixed.rst │ ├── gnp.rst │ ├── index.rst │ ├── link_activation.rst │ ├── node_activation.rst │ ├── path.rst │ ├── regular.rst │ ├── regular_ring_lattice.rst │ ├── square_grid_graph.rst │ └── uniform_hypergraph.rst ├── github.svg ├── index.rst ├── installation.rst ├── io.rst ├── latex_index.rst ├── latex_preamble.rst ├── make.bat ├── operations │ ├── add_remove.rst │ ├── cartesian_product.rst │ ├── graph_union.rst │ ├── index.rst │ ├── occupation.rst │ └── subgraphs.rst ├── preamble.rst ├── pypi.svg ├── quickstart.rst ├── randomness.rst ├── references.bib ├── requirements.txt ├── robots.txt ├── shuffling │ ├── event.rst │ ├── index.rst │ ├── link.rst │ ├── static.rst │ └── timeline.rst └── types.rst ├── examples └── static_network_percolation │ ├── README.md │ ├── figure.svg │ └── static_network_percolation.py ├── flake8 ├── pyproject.toml ├── scripts └── build-wheels.sh ├── src ├── algorithms.cpp ├── algorithms │ ├── assortativity.cpp │ ├── basic_temporal.cpp │ ├── degree.cpp │ ├── density.cpp │ ├── directed_connectivity.cpp │ ├── edge_degree.cpp │ ├── temporal_adjacency.cpp │ └── undirected_connectivity.cpp ├── bind_core.hpp ├── common_edge_properties.hpp ├── components.cpp ├── distributions.cpp ├── generators.cpp ├── implicit_event_graph_components.cpp ├── implicit_event_graphs.cpp ├── interval_sets.cpp ├── io.cpp ├── main.cpp ├── networks.cpp ├── operations.cpp ├── operations │ ├── add_operations.cpp │ ├── edge_occupation.cpp │ ├── remove_operations.cpp │ ├── subgraph.cpp │ └── vertex_occupation.cpp ├── random_activation_networks.cpp ├── random_networks.cpp ├── random_state.cpp ├── reticula │ ├── __init__.py │ ├── generic_attribute.py │ ├── microcanonical_reference_models.py │ └── temporal_adjacency.py ├── scalar_types.cpp ├── scalar_wrapper.hpp ├── simple_generators.cpp ├── static_edges.cpp ├── static_hyperedges.cpp ├── static_microcanonical_reference_models.cpp ├── temporal_adjacency.cpp ├── temporal_clusters.cpp ├── temporal_edges.cpp ├── temporal_hyperedges.cpp ├── temporal_microcanonical_reference_models.cpp ├── type_handles.hpp ├── type_lists.cpp ├── type_str │ ├── common.hpp │ ├── components.hpp │ ├── distributions.hpp │ ├── edges.hpp │ ├── implicit_event_graphs.hpp │ ├── intervals.hpp │ ├── networks.hpp │ ├── random_state.hpp │ ├── scalars.hpp │ ├── temporal_adjacency.hpp │ └── temporal_clusters.hpp ├── type_utils.hpp └── types.cpp └── tests ├── conftest.py ├── test_graph_operations.py └── test_metaclass.py /.github/workflows/build_wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build Wheels 2 | on: [workflow_dispatch] 3 | 4 | env: 5 | CMAKE_BUILD_PARALLEL_LEVEL: 4 6 | 7 | jobs: 8 | build_wheels: 9 | name: Build wheels for ${{ matrix.py }} on ${{ matrix.os }} 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [reticula-build-windows-2022] 15 | py: ["cp310-*", "cp311-*", "cp312-*"] 16 | 17 | steps: 18 | - uses: actions/setup-python@v4 19 | 20 | - name: Collect Workflow Telemetry 21 | uses: runforesight/workflow-telemetry-action@v1 22 | with: 23 | theme: dark 24 | 25 | - uses: actions/checkout@v4 26 | 27 | - name: Set up Visual Studio shell on Windows 28 | uses: egor-tensin/vs-shell@v2 29 | with: 30 | arch: x64 31 | 32 | - name: set x64 cl.exe as CXX 33 | if: matrix.os == 'reticula-build-windows-2022' 34 | run: | 35 | "CXX=$((Get-Command cl).Path)" >> "$env:GITHUB_ENV" 36 | "CC=$((Get-Command cl).Path)" >> "$env:GITHUB_ENV" 37 | 38 | - name: select Xcode version 39 | if: ${{ startsWith(matrix.os, 'macos') }} 40 | run: | 41 | sudo xcode-select -s "/Applications/Xcode_14.3.app" 42 | 43 | - name: Update pip 44 | run: python -m pip install -U pip 45 | 46 | - name: Install cibuildwheel 47 | run: python -m pip install cibuildwheel==2.21.3 48 | 49 | - name: Build Wheels 50 | env: 51 | CIBW_BUILD: ${{ matrix.py }} 52 | run: python -m cibuildwheel --output-dir wheelhouse 53 | 54 | - uses: actions/upload-artifact@v4 55 | with: 56 | path: ./wheelhouse/*.whl 57 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-24.04 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-python@v5 15 | with: 16 | python-version: '3.x' 17 | architecture: 'x64' 18 | 19 | - name: Latex Prerequisits 20 | run: sudo apt-get update && sudo apt-get install -y latexmk texlive-latex-extra 21 | 22 | - name: Python Prerequisits 23 | run: pip install -r docs/requirements.txt 24 | 25 | - name: Build 26 | working-directory: docs/ 27 | run: make html latexpdf 28 | 29 | - name: Copy PDF 30 | working-directory: docs/ 31 | run: cp _build/latex/reticula.pdf _build/html/_static/reticula.pdf 32 | 33 | - name: Upload 34 | id: deployment 35 | uses: actions/upload-pages-artifact@v3 36 | with: 37 | path: docs/_build/html 38 | 39 | 40 | deploy: 41 | permissions: 42 | pages: write 43 | id-token: write 44 | 45 | environment: 46 | name: github-pages 47 | url: ${{ steps.deployment.outputs.page_url }} 48 | 49 | runs-on: ubuntu-latest 50 | needs: build 51 | steps: 52 | - name: Deploy to GitHub Pages 53 | id: deployment 54 | uses: actions/deploy-pages@v4 55 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: [workflow_dispatch] 3 | 4 | env: 5 | CMAKE_BUILD_PARALLEL_LEVEL: 2 6 | SKBUILD_CMAKE_ARGS: "-DCMAKE_C_COMPILER_LAUNCHER=ccache;-DCMAKE_CXX_COMPILER_LAUNCHER=ccache" 7 | CCACHE_NOHASHDIR: "true" 8 | CCACHE_COMPILERCHECK: "content" 9 | 10 | jobs: 11 | run_tests: 12 | name: Running tests 13 | runs-on: ubuntu-24.04 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.12' 21 | architecture: 'x64' 22 | cache: 'pip' 23 | 24 | - name: Update pip 25 | run: python -m pip install -U pip 26 | 27 | - name: install pip-compile 28 | run: python -m pip install pip-tools 29 | 30 | - name: compile dependency list 31 | run: pip-compile --all-build-deps --all-extras --strip-extras pyproject.toml -o comp-req.txt 32 | 33 | - name: install build deps 34 | run: python -m pip install -r comp-req.txt 35 | 36 | - name: Setup ccache 37 | uses: hendrikmuhs/ccache-action@v1.2 38 | with: 39 | key: ${{ github.job }} 40 | verbose: 2 41 | max-size: 5GB 42 | 43 | - name: Build and install reticula 44 | run: python -m pip install .[test] -v 45 | 46 | - name: Run tests 47 | run: python -m pytest . 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build 35 | release 36 | dist 37 | _skbuild/ 38 | *.egg-info/ 39 | *.whl 40 | 41 | # Byte-compiled / optimized / DLL files 42 | __pycache__/ 43 | *.py[cod] 44 | *$py.class 45 | 46 | .hypothesis 47 | 48 | wheelhouse 49 | 50 | .cache 51 | compile_commands.json 52 | .asv 53 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | project(reticula_python LANGUAGES CXX) 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED True) 6 | set(CMAKE_POSITION_INDEPENDENT_CODE True) 7 | 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | reticula 11 | GIT_REPOSITORY https://github.com/reticula-network/reticula.git 12 | GIT_TAG a349e8771342ac689ce38d64d3936b80b46c75ce) 13 | 14 | FetchContent_Declare( 15 | fmt 16 | GIT_REPOSITORY https://github.com/fmtlib/fmt 17 | GIT_TAG 9.1.0) 18 | 19 | FetchContent_Declare( 20 | metal 21 | GIT_REPOSITORY https://github.com/brunocodutra/metal.git 22 | GIT_TAG v2.1.4 23 | EXCLUDE_FROM_ALL) 24 | 25 | find_package(Python 3.10 26 | REQUIRED COMPONENTS Interpreter Development.Module 27 | OPTIONAL_COMPONENTS Development.SABIModule) 28 | 29 | find_package(nanobind CONFIG REQUIRED) 30 | 31 | if (NOT MSVC) 32 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 33 | endif() 34 | 35 | FetchContent_MakeAvailable(reticula fmt metal) 36 | 37 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 38 | 39 | nanobind_add_module(_reticula_ext 40 | NB_DOMAIN reticula 41 | STABLE_ABI 42 | NB_STATIC 43 | NOMINSIZE 44 | 45 | src/main.cpp 46 | 47 | src/types.cpp 48 | src/scalar_types.cpp 49 | src/random_state.cpp 50 | src/distributions.cpp 51 | src/interval_sets.cpp 52 | src/temporal_edges.cpp 53 | src/temporal_hyperedges.cpp 54 | src/static_edges.cpp 55 | src/static_hyperedges.cpp 56 | src/networks.cpp 57 | src/temporal_adjacency.cpp 58 | src/implicit_event_graphs.cpp 59 | src/implicit_event_graph_components.cpp 60 | src/components.cpp 61 | src/temporal_clusters.cpp 62 | src/type_lists.cpp 63 | 64 | src/generators.cpp 65 | src/static_microcanonical_reference_models.cpp 66 | src/temporal_microcanonical_reference_models.cpp 67 | src/simple_generators.cpp 68 | src/random_networks.cpp 69 | src/random_activation_networks.cpp 70 | 71 | src/io.cpp 72 | 73 | src/operations.cpp 74 | src/operations/subgraph.cpp 75 | src/operations/add_operations.cpp 76 | src/operations/remove_operations.cpp 77 | src/operations/edge_occupation.cpp 78 | src/operations/vertex_occupation.cpp 79 | 80 | src/algorithms.cpp 81 | src/algorithms/assortativity.cpp 82 | src/algorithms/degree.cpp 83 | src/algorithms/edge_degree.cpp 84 | src/algorithms/basic_temporal.cpp 85 | src/algorithms/density.cpp 86 | src/algorithms/directed_connectivity.cpp 87 | src/algorithms/temporal_adjacency.cpp 88 | src/algorithms/undirected_connectivity.cpp) 89 | target_link_libraries(_reticula_ext 90 | PRIVATE reticula fmt Metal) 91 | set_target_properties(_reticula_ext 92 | PROPERTIES CXX_VISIBILITY_PRESET hidden 93 | VISIBILITY_INLINES_HIDDEN ON) 94 | 95 | install(TARGETS _reticula_ext DESTINATION reticula) 96 | 97 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") 98 | set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CMAKE_COMMAND} -E time") 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Arash Badie-Modiri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python bindings for [Reticula][reticula] [![Documentations][docs-badge]][docs-website] [![Paper][paper-badge]][paper-link] 2 | 3 | [reticula]: https://github.com/reticula-network/reticula 4 | [paper-badge]: https://img.shields.io/badge/Paper-SoftwareX-informational 5 | [paper-link]: https://www.sciencedirect.com/science/article/pii/S2352711022002199 6 | [docs-badge]: https://img.shields.io/badge/Docs-docs.reticula.network-success 7 | [docs-website]: https://docs.reticula.network 8 | 9 | ## Installation 10 | 11 | The library offers pre-compiled Wheels for x64 Windows, MacOS and Linux. The library 12 | currently supports Python version 3.10 or newer. 13 | 14 | ```console 15 | $ pip install reticula 16 | ``` 17 | 18 | ### Installing from source 19 | Alternatively you can install the library from source: 20 | 21 | Clone the library: 22 | ```console 23 | $ git clone https://github.com/arashbm/reticula-python.git 24 | ``` 25 | 26 | Build the Wheel: 27 | ```console 28 | $ cd reticula-python 29 | $ pip install . 30 | ``` 31 | 32 | Note that compiling from source requires an unbelievable amount (> 40GB) of RAM. 33 | 34 | ## Basic examples 35 | 36 | Generate a random static network and investigate: 37 | ```pycon 38 | >>> import reticula as ret 39 | >>> state = ret.mersenne_twister(42) # create a pseudorandom number generator 40 | >>> g = ret.random_gnp_graph[ret.int64](n=100, p=0.02, random_state=state) 41 | >>> g 42 | 43 | >>> g.vertices() 44 | [0, 1, 2, 3, .... 99] 45 | >>> g.edges() 46 | [undirected_edge[int64](0, 16), undirected_edge[int64](0, 20), 47 | undirected_edge[int64](0, 31), undirected_edge[int64](0, 51), ...] 48 | >>> ret.connected_components(g) 49 | [, , ...] 50 | >>> lcc = max(ret.connected_components(g), key=len) 51 | >>> lcc 52 | 53 | >>> g2 = ret.vertex_induced_subgraph(g, lcc) 54 | >>> g2 55 | 56 | ``` 57 | A more complete example of static network percolation analysis, running on 58 | multiple threads, can be found in 59 | [`examples/static_network_percolation/`](examples/static_network_percolation/) 60 | 61 | Create a random fully-mixed temporal network and calculate simple 62 | (unconstrained) reachability from node 0 at time 0 to all nodes and times. 63 | ```pycon 64 | >>> import reticula as ret 65 | >>> state = ret.mersenne_twister(42) 66 | >>> g = ret.random_fully_mixed_temporal_network[ret.int64](\ 67 | ... size=100, rate=0.01, max_t=1024, random_state=state) 68 | >>> adj = ret.temporal_adjacency.simple[\ 69 | ... ret.undirected_temporal_edge[ret.int64, ret.double]]() 70 | >>> cluster = ret.out_cluster(\ 71 | ... temporal_network=g, temporal_adjacency=adj, vertex=0, time=0.0) 72 | >>> cluster 73 | 76 | >>> cluster.covers(vertex=12, time=100.0) 77 | True 78 | 79 | >>> # Let's see all intervals where vert 15 is reachable from vert 0 at t=0.0: 80 | >>> list(cluster.interval_sets()[15]) 81 | [(3.099055278145548, 1.7976931348623157e+308)] 82 | ``` 83 | 84 | Let's now try limited waiting-time (with $dt = 5.0$) reachability: 85 | ```pycon 86 | >>> import reticula as ret 87 | >>> state = ret.mersenne_twister(42) 88 | >>> g = ret.random_fully_mixed_temporal_network[int64](\ 89 | ... size=100, rate=0.01, max_t=1024, random_state=state) 90 | >>> adj = ret.temporal_adjacency.limited_waiting_time[\ 91 | ... ret.undirected_temporal_edge[ret.int64, ret.double]](dt=5.0) 92 | >>> cluster = ret.out_cluster(\ 93 | ... temporal_network=g, temporal_adjacency=adj, vertex=0, time=0.0) 94 | >>> cluster 95 | 98 | >>> cluster.covers(vertex=15, time=16.0) 99 | True 100 | >>> list(cluster.interval_sets()[15]) 101 | [(3.099055278145548, 200.17866501023616), 102 | (200.39858803326402, 337.96139372380003), 103 | ... 104 | (1017.5258263596586, 1028.9149586273347)] 105 | 106 | >>> # Total "human-hours" of reachability cluster 107 | >>> cluster.mass() 108 | 101747.97444555275 109 | 110 | >>> # Survival time of the reachability cluster 111 | >>> cluster.lifetime() 112 | (0.0, 1028.9972186553928) 113 | ``` 114 | -------------------------------------------------------------------------------- /benchmarks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reticula-network/reticula-python/940782e8eb4b7b7f52e971289d72eed3031654eb/benchmarks/__init__.py -------------------------------------------------------------------------------- /benchmarks/module.py: -------------------------------------------------------------------------------- 1 | class ModuleImport: 2 | """ 3 | Benchmark total import time. 4 | """ 5 | 6 | def timeraw_import_reticula(self): 7 | return """ 8 | import reticula 9 | """ 10 | -------------------------------------------------------------------------------- /benchmarks/temporal.py: -------------------------------------------------------------------------------- 1 | import reticula as ret 2 | 3 | import random 4 | 5 | 6 | class Constrction: 7 | """ 8 | Benchmarking the construction and basic operation of temporal networks. 9 | """ 10 | 11 | def setup(self): 12 | self.n = 2**10 13 | rate = 4/self.n 14 | max_t = 32 15 | gen = ret.mersenne_twister(42) 16 | self.g = ret.random_fully_mixed_temporal_network[ret.int64]( 17 | size=self.n, rate=rate, max_t=max_t, random_state=gen) 18 | self.verts = list(self.g.vertices()) 19 | 20 | self.edges = list(self.g.edges()) 21 | self.tuples = [] 22 | for e in self.edges: 23 | verts = e.incident_verts() 24 | self.tuples.append((verts[0], verts[-1], e.cause_time())) 25 | 26 | rand = random.Random(42) 27 | self.shuffled_edges = rand.sample(self.edges, k=len(self.edges)) 28 | self.shuffled_tuples = [] 29 | for e in self.shuffled_edges: 30 | verts = e.incident_verts() 31 | if rand.random() > 0.5: 32 | self.shuffled_tuples.append( 33 | (verts[0], verts[-1], e.cause_time())) 34 | else: 35 | self.shuffled_tuples.append( 36 | (verts[-1], verts[0], e.cause_time())) 37 | 38 | def time_copy_construction(self): 39 | ret.undirected_temporal_network[ret.int64, ret.double](self.g) 40 | 41 | def time_tuple_construction(self): 42 | ret.undirected_temporal_network[ret.int64, ret.double]( 43 | verts=self.verts, edges=self.tuples) 44 | 45 | def time_shuffled_tuple_construction(self): 46 | ret.undirected_temporal_network[ret.int64, ret.double]( 47 | verts=self.verts, edges=self.shuffled_tuples) 48 | 49 | def time_edge_obj_construction(self): 50 | ret.undirected_temporal_network[ret.int64, ret.double]( 51 | verts=self.verts, edges=self.edges) 52 | 53 | def time_shuffled_edge_obj_construction(self): 54 | ret.undirected_temporal_network[ret.int64, ret.double]( 55 | verts=self.verts, edges=self.shuffled_edges) 56 | 57 | 58 | class Algorithms: 59 | def setup(self): 60 | n = 2**11 61 | rate = 4/n 62 | max_t = 64 63 | gen = ret.mersenne_twister(42) 64 | self.g = ret.random_fully_mixed_temporal_network[ret.int64]( 65 | size=n, rate=rate, max_t=max_t, random_state=gen) 66 | self.link = ret.undirected_edge[ret.int64](0, 1) 67 | 68 | def time_time_window(self): 69 | ret.time_window(self.g) 70 | 71 | def time_static_projection(self): 72 | ret.static_projection(self.g) 73 | 74 | def time_link_timeline(self): 75 | ret.link_timeline(self.g, self.link) 76 | 77 | def time_link_timelines(self): 78 | ret.link_timelines(self.g) 79 | 80 | 81 | class Reachability: 82 | params = [ 83 | ret.temporal_adjacency.simple[ 84 | ret.undirected_temporal_edge[ret.int64, ret.double]](), 85 | ret.temporal_adjacency.limited_waiting_time[ 86 | ret.undirected_temporal_edge[ret.int64, ret.double]](dt=1.0), 87 | ret.temporal_adjacency.exponential[ 88 | ret.undirected_temporal_edge[ret.int64, ret.double]]( 89 | rate=1.0, seed=12345) 90 | ] 91 | 92 | param_names = ["adjacency"] 93 | 94 | def setup(self, adj): 95 | gen = ret.mersenne_twister(42) 96 | 97 | max_t = 32 98 | small_n = 2**6 99 | small_rate = 4/small_n 100 | self.small_g = ret.random_fully_mixed_temporal_network[ret.int64]( 101 | size=small_n, rate=small_rate, max_t=max_t, random_state=gen) 102 | 103 | def time_out_cluster(self, adj): 104 | ret.out_cluster(self.small_g, adj, vertex=0, time=0) 105 | 106 | def time_out_clusters(self, adj): 107 | ret.out_clusters(self.small_g, adj) 108 | 109 | def peakmem_out_clusters(self, adj): 110 | ret.out_clusters(self.small_g, adj) 111 | 112 | def time_out_cluster_sizes(self, adj): 113 | ret.out_cluster_sizes(self.small_g, adj) 114 | 115 | def peakmem_out_cluster_sizes(self, adj): 116 | ret.out_cluster_sizes(self.small_g, adj) 117 | 118 | def time_out_cluster_size_estimates(self, adj): 119 | ret.out_cluster_size_estimates(self.small_g, adj, 120 | time_resolution=0.5, 121 | seed=42) 122 | 123 | def peakmem_out_cluster_size_estimates(self, adj): 124 | ret.out_cluster_size_estimates(self.small_g, adj, 125 | time_resolution=0.5, 126 | seed=42) 127 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reticula-network/reticula-python/940782e8eb4b7b7f52e971289d72eed3031654eb/docs/_static/.keep -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | .math { 2 | white-space: nowrap; 3 | } 4 | -------------------------------------------------------------------------------- /docs/algorithms/graphicallity.rst: -------------------------------------------------------------------------------- 1 | Graphicallity 2 | ============= 3 | 4 | Undirected degree sequence 5 | -------------------------- 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: is_graphic(degree_seq: List[int]) -> bool 13 | 14 | .. tab-item:: C++ 15 | :sync: cpp 16 | 17 | .. cpp:function:: template \ 18 | requires degree_range \ 19 | bool is_graphic(Range&& degree_sequence) 20 | 21 | Checks if the sequence can be the degree sequence of a valid undirected graph, 22 | containing no multi-edges or loops, based on the Erdős--Gallai algorithm 23 | :cite:p:`erdos1960graphs,choudum1986simple`. 24 | 25 | .. code-block:: pycon 26 | 27 | >>> import reticula as ret 28 | >>> ret.is_graphic([2, 2, 1, 1]) 29 | True 30 | >>> ret.is_graphic([2, 2, 1, 2]) 31 | False 32 | 33 | 34 | Directed degree-pair sequence 35 | ----------------------------- 36 | 37 | .. tab-set:: 38 | 39 | .. tab-item:: Python 40 | :sync: python 41 | 42 | .. py:function:: is_digraphic(\ 43 | in_out_degree_sequence: List[Pair[int, int]]) -> bool 44 | 45 | .. tab-item:: C++ 46 | :sync: cpp 47 | 48 | .. cpp:function:: template \ 49 | requires degree_pair_range \ 50 | bool is_digraphic(PairRange&& in_out_degree_sequence) 51 | 52 | 53 | Checks if the sequence can be the degree-pair sequence of a valid directed 54 | graph, containing no multi-edges or loops, based on the algorithm by Kleitman 55 | and Wang :cite:p:`kleitman1973algorithms`. 56 | 57 | A degree pair sequence is a range (list) of pairs (2-tuples) of integers, 58 | where the first element of each item represents in-degree and the second item 59 | out-degree of one vertex. 60 | 61 | .. code-block:: pycon 62 | 63 | >>> import reticula as ret 64 | >>> ret.is_digraphic([(0, 1), (2, 0), (0, 1), (1, 1)]) 65 | True 66 | >>> ret.is_digraphic([(0, 1), (2, 0), (0, 1), (1, 2)]) 67 | False 68 | -------------------------------------------------------------------------------- /docs/algorithms/index.rst: -------------------------------------------------------------------------------- 1 | Algorithms 2 | ========== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | :caption: Degree and degree sequences 7 | 8 | vertex_degrees 9 | edge_degrees 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | :caption: Degree sequences graphicallity 14 | 15 | graphicallity 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | :caption: General properties of static/temporal networks 20 | 21 | graph_properties 22 | assortativity 23 | 24 | 25 | .. toctree:: 26 | :maxdepth: 1 27 | :caption: Reachability in networks 28 | 29 | static_network_reachability 30 | temporal_network_reachability 31 | event_graphs 32 | -------------------------------------------------------------------------------- /docs/bibliography.rst: -------------------------------------------------------------------------------- 1 | .. only:: html or text 2 | 3 | Bibliography 4 | ============ 5 | 6 | .. bibliography:: 7 | -------------------------------------------------------------------------------- /docs/citing.rst: -------------------------------------------------------------------------------- 1 | Citing Reticula 2 | =============== 3 | 4 | If this software was directly or indirectly useful in your reasearch, you can 5 | cite the publication: 6 | 7 | .. code-block:: BibTex 8 | 9 | @article{badie2023reticula, 10 | title={Reticula: A temporal network and hypergraph analysis software package}, 11 | doi = {10.1016/j.softx.2022.101301}, 12 | url = {https://www.sciencedirect.com/science/article/pii/S2352711022002199}, 13 | author={Badie-Modiri, Arash and Kivel{\"a}, Mikko}, 14 | journal={SoftwareX}, 15 | volume={21}, 16 | pages={101301}, 17 | year={2023}, 18 | publisher={Elsevier} 19 | } 20 | 21 | .. code-block:: text 22 | 23 | Badie-Modiri, Arash and Mikko Kivelä. "Reticula: A temporal network and hypergraph analysis software package." SoftwareX 21 (2023): 101301 24 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | import toml 21 | 22 | project_data = toml.load("../pyproject.toml") 23 | 24 | project = "Reticula" 25 | html_show_copyright = False 26 | author = "Arash Badie-Modiri" 27 | 28 | # The full version, including alpha/beta/rc tags 29 | release = project_data["project"]["version"] 30 | 31 | 32 | master_doc = 'index' 33 | 34 | latex_documents = [ 35 | ("latex_index", 'reticula.tex', 36 | project, author, 'howto', True), 37 | ] 38 | 39 | latex_elements = { 40 | 'papersize': 'a4paper', 41 | 'pointsize': '11pt', 42 | 'figure_align': 'htbp', 43 | 'preamble': '', 44 | } 45 | 46 | latex_elements['preamble'] += r""" 47 | \usepackage{amsfonts} 48 | \usepackage{parskip} 49 | \usepackage{microtype} 50 | """ 51 | 52 | # -- General configuration --------------------------------------------------- 53 | 54 | # Add any Sphinx extension module names here, as strings. They can be 55 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 56 | # ones. 57 | extensions = [ 58 | "sphinxcontrib.bibtex", 59 | "sphinx_copybutton", 60 | "sphinx.ext.autosectionlabel", 61 | "sphinx_design", 62 | "sphinxext.opengraph", 63 | "sphinx_sitemap", 64 | ] 65 | 66 | # Add any paths that contain templates here, relative to this directory. 67 | templates_path = ["_templates"] 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This pattern also affects html_static_path and html_extra_path. 72 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", 73 | 'preamble.rst'] 74 | 75 | 76 | primary_domain = None 77 | 78 | nitpicky = True 79 | 80 | rst_prolog = """ 81 | .. role:: py(code) 82 | :language: python 83 | :class: highlight 84 | 85 | .. role:: cpp(code) 86 | :language: cpp 87 | :class: highlight 88 | """ 89 | 90 | 91 | # # REs for Python signatures with types 92 | import re 93 | 94 | typed_py_re = re.compile( 95 | r"""^ ([\w.]*\.)? # class name(s) 96 | (\w+) \s* # thing name 97 | (?: \[\s*(.*?)\s*\])? # optional: type parameters list 98 | (?: \(\s*(.*)\s*\) # optional: arguments 99 | (?:\s* -> \s* (.*))? # return annotation 100 | )? $ # and nothing more 101 | """, 102 | re.VERBOSE, 103 | ) 104 | 105 | import sphinx.domains.python._object as po 106 | 107 | po.py_sig_re = typed_py_re 108 | 109 | autosectionlabel_prefix_document = True 110 | autosectionlabel_maxdepth = 3 111 | 112 | # -- Options for HTML output ------------------------------------------------- 113 | 114 | # The theme to use for HTML and HTML Help pages. See the documentation for 115 | # a list of builtin themes. 116 | # 117 | html_theme = "furo" 118 | 119 | pygments_style = "sphinx" 120 | pygments_dark_style = "monokai" 121 | html_title = "Reticula" 122 | html_baseurl = "https://docs.reticula.network/" 123 | html_extra_path = ["robots.txt"] 124 | 125 | import os.path 126 | 127 | 128 | def read_icon(path: str): 129 | with open(os.path.join(os.path.dirname(__file__), path), "r") as f: 130 | return f.read() 131 | 132 | 133 | html_theme_options = { 134 | "source_repository": "https://github.com/reticula-network/reticula-python", 135 | "source_branch": "main", 136 | "source_directory": "docs/", 137 | "footer_icons": [ 138 | { 139 | "name": "GitHub", 140 | "url": "https://github.com/reticula-network", 141 | "html": read_icon("github.svg"), 142 | "class": "", 143 | }, 144 | { 145 | "name": "PyPi", 146 | "url": "https://pypi.org/project/reticula/", 147 | "html": read_icon("pypi.svg"), 148 | "class": "", 149 | }, 150 | ], 151 | } 152 | 153 | bibtex_bibfiles = ["references.bib"] 154 | bibtex_default_style = "unsrt" 155 | 156 | # Add any paths that contain custom static files (such as style sheets) here, 157 | # relative to this directory. They are copied after the builtin static files, 158 | # so a file named "default.css" will overwrite the builtin "default.css". 159 | html_static_path = ["_static"] 160 | 161 | html_css_files = [ 162 | "css/custom.css", 163 | ] 164 | 165 | # opengraph settings 166 | ogp_site_url = html_baseurl 167 | 168 | # sitemap configs 169 | sitemap_url_scheme = "{link}" 170 | 171 | sitemap_excludes = [ 172 | "latex_index.html", 173 | "latex_preamble.html", 174 | ] 175 | 176 | # copybutton configs 177 | copybutton_exclude = ".linenos, .gp, .go" 178 | -------------------------------------------------------------------------------- /docs/docutils.conf: -------------------------------------------------------------------------------- 1 | [restructuredtext parser] 2 | syntax_highlight = short 3 | -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | isotropic 8 | temporal_network 9 | -------------------------------------------------------------------------------- /docs/examples/isotropic-thread-figure.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor 2 | from functools import partial 3 | 4 | import numpy as np 5 | import reticula as ret 6 | import matplotlib.pyplot as plt 7 | from tqdm import tqdm 8 | 9 | 10 | def lcc_size(n, p): 11 | state = ret.mersenne_twister() 12 | g = ret.random_gnp_graph[ret.int64](n, p, state) 13 | return len(ret.largest_connected_component(g)) 14 | 15 | 16 | n = 1000000 17 | ps = np.linspace(0, 2./n, num=100) 18 | 19 | with ThreadPoolExecutor(max_workers=16) as e: 20 | lccs = list(tqdm(e.map(partial(lcc_size, n), ps), 21 | total=len(ps))) 22 | 23 | fig, ax = plt.subplots(figsize=(6, 4)) 24 | ax.plot(ps, lccs) 25 | ax.ticklabel_format(useMathText=True) 26 | 27 | ax.set_xlabel("Edge probability $p$") 28 | ax.set_ylabel("Largest connected component size") 29 | 30 | fig.tight_layout() 31 | fig.savefig("isotropic.pdf") 32 | fig.savefig("isotropic.svg") 33 | -------------------------------------------------------------------------------- /docs/examples/isotropic-thread.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import reticula as ret 3 | import matplotlib.pyplot as plt 4 | 5 | from concurrent.futures import ThreadPoolExecutor 6 | from functools import partial 7 | 8 | def lcc_size(n, p): 9 | state = ret.mersenne_twister() 10 | g = ret.random_gnp_graph[ret.int64](n, p, state) 11 | return len(ret.largest_connected_component(g)) 12 | 13 | n = 1000000 14 | ps = np.linspace(0, 2./n) 15 | 16 | with ThreadPoolExecutor(max_workers=8) as e: 17 | lccs = e.map(partial(lcc_size, n), ps) 18 | 19 | plt.plot(ps, list(lccs)) 20 | plt.show() 21 | -------------------------------------------------------------------------------- /docs/examples/isotropic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reticula-network/reticula-python/940782e8eb4b7b7f52e971289d72eed3031654eb/docs/examples/isotropic.pdf -------------------------------------------------------------------------------- /docs/examples/isotropic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import reticula as ret 3 | import matplotlib.pyplot as plt 4 | 5 | def lcc_size(n, p): 6 | state = ret.mersenne_twister() 7 | g = ret.random_gnp_graph[ret.int64](n, p, state) 8 | return len(ret.largest_connected_component(g)) 9 | 10 | n = 1000000 11 | ps = np.linspace(0, 2./n) 12 | 13 | lccs = [lcc_size(n, p) for p in ps] 14 | 15 | plt.plot(ps, list(lccs)) 16 | plt.show() 17 | -------------------------------------------------------------------------------- /docs/examples/isotropic.rst: -------------------------------------------------------------------------------- 1 | Isotropic bond percolation in G(n, p) networks 2 | ============================================== 3 | 4 | The goal of this example is to visualise what happens to the largest component 5 | size when you randomly occupying edges in a static, undirected network. 6 | 7 | A :ref:`random G(n, p) network ` can 8 | generates a random network with each edge occupied with probability :math:`p`. 9 | So the exercise boils down to generating :math:`G(n, p)` networks with various 10 | values of :math:`p` and plotting the largest component sizes. 11 | 12 | .. literalinclude:: isotropic.py 13 | :language: python 14 | :linenos: 15 | 16 | This script, after doing a lot of computation, produces a figure that looks 17 | like this, minus the axis labels and other niceties: 18 | 19 | .. figure:: isotropic.* 20 | :alt: A line plot with x axis between 0 to 2e-6 labelled "edge probability 21 | p", and the y axis ranging from 0 to 800000 labelled "largest connected 22 | component size". The y values of the line are close to zero from x=0 to 23 | x=1e-6, then they suddenly start increasing. 24 | 25 | Results of the isotropic bond percolation in random :math:`G(n, p)` graphs. 26 | The evolution of the largest connected component size shows that the 27 | connectivity of the system changes rapidly around :math:`p = 10^{-6}`. 28 | 29 | 30 | While this script definitly works, it is only utilising one CPU core. We can use 31 | more CPU cores (and run the code faster) using multi-processing. This, however, 32 | would be wasteful, as it entails running multiple independent python processes, 33 | each loading the standard library and Reticula in mostly separate places in the 34 | memory. The separate processes also have to communicate with each other by 35 | pickling and unpickling Python objects, which can be inefficient. 36 | 37 | More desirable would be to perform this in multi-threaded setting. This reduces 38 | the memory-use overhead significantly, and mitigates the inter-process 39 | communication issue mentioned above as threads can access shared memory. In 40 | general, this also increases the risk of race conditions. On Reticula, network 41 | and edge types are immutable, so the risk race conditions when sharing a network 42 | object across threads is minimised. 43 | 44 | In this case, there is no need to share anything other than the parameter 45 | :math:`p` across threads. In the next example, we run the function 46 | :py:`lcc_size` on 8 threads. Note that each thread creates its own instance of 47 | pseudorandom number generator using the command 48 | :py:`state = ret.mersenne_twister()`. 49 | 50 | .. literalinclude:: isotropic-thread.py 51 | :language: python 52 | :linenos: 53 | -------------------------------------------------------------------------------- /docs/generation/barabasi.rst: -------------------------------------------------------------------------------- 1 | Random Barabási--Albert network 2 | =============================== 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: random_barabasi_albert_graph[vert_type](\ 10 | n: int, m: int, random_state) \ 11 | -> undirected_network[vert_type] 12 | 13 | .. tab-item:: C++ 14 | :sync: cpp 15 | 16 | .. cpp:function:: template <\ 17 | integer_network_vertex VertT, \ 18 | std::uniform_random_bit_generator Gen> \ 19 | undirected_network \ 20 | random_barabasi_albert_graph(VertT n, VertT m, Gen& generator) 21 | 22 | Generates a random Barabási--Albert network :cite:p:`barabasi1999emergence` of 23 | size :cpp:`n`. :math:`n-m` vertices are added one-by-one and each vertex is 24 | connected to :math:`m` randomly selected existing vertices with probability 25 | proportional to their degrees. The initial "seed" of the network is a set of 26 | :math:`m` isolated vertices. 27 | 28 | The parameter :cpp:`m` needs to be smaller than :cpp:`n` and larger than or 29 | equal to 1, otherwise the function fails by raising a :py:`ValueError` 30 | exception in Python or a :cpp:`std::invalid_argument` exception in C++. 31 | 32 | .. code-block:: pycon 33 | 34 | >>> import reticula as ret 35 | >>> gen = ret.mersenne_twister(42) 36 | >>> ret.random_barabasi_albert_graph[ret.int64](n=128, m=3, random_state=gen) 37 | 38 | -------------------------------------------------------------------------------- /docs/generation/complete_graph.rst: -------------------------------------------------------------------------------- 1 | Complete graphs 2 | =============== 3 | 4 | Undirected complete graph 5 | ------------------------- 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: complete_graph[vert_type](size: int) \ 13 | -> undirected_network[vert_type] 14 | 15 | .. tab-item:: C++ 16 | :sync: cpp 17 | 18 | .. cpp:function:: template \ 19 | undirected_network complete_graph(VertT size) 20 | 21 | 22 | Generates a network of size :cpp:`size` vertices where all possible edges are 23 | present. 24 | 25 | .. code-block:: pycon 26 | 27 | >>> import reticula as ret 28 | >>> ret.complete_graph[ret.int64](size=128) 29 | 30 | 31 | 32 | Complete directed graph 33 | ----------------------- 34 | 35 | .. tab-set:: 36 | 37 | .. tab-item:: Python 38 | :sync: python 39 | 40 | .. py:function:: complete_directed_graph[vert_type](size: int) \ 41 | -> directed_network[vert_type] 42 | 43 | .. tab-item:: C++ 44 | :sync: cpp 45 | 46 | .. cpp:function:: template \ 47 | directed_network complete_directed_graph(VertT size) 48 | 49 | Generates a directed network of size :cpp:`size` vertices where all possible 50 | edges are present. 51 | 52 | .. code-block:: pycon 53 | 54 | >>> import reticula as ret 55 | >>> ret.complete_directed_graph[ret.int64](size=128) 56 | 57 | -------------------------------------------------------------------------------- /docs/generation/cycle.rst: -------------------------------------------------------------------------------- 1 | Cycle graphs 2 | ============ 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: cycle_graph[vert_type](size: int) \ 10 | -> undirected_network[vert_type] 11 | 12 | .. tab-item:: C++ 13 | :sync: cpp 14 | 15 | .. cpp:function:: template \ 16 | undirected_network \ 17 | cycle_graph(VertT size) 18 | 19 | Generates a cycle graph of size :cpp:`size`: A 2-regular graph where each vertex 20 | is connected to the one before and after it. 21 | 22 | .. code-block:: pycon 23 | 24 | >>> import reticula as ret 25 | >>> ret.cycle_graph[ret.int64](size=128) 26 | 27 | -------------------------------------------------------------------------------- /docs/generation/fully_mixed.rst: -------------------------------------------------------------------------------- 1 | Fully-mixed temporal network 2 | ============================ 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: random_fully_mixed_temporal_network[vert_type](\ 10 | size: int, rate: float, max_t: float, random_state) \ 11 | -> undirected_temporal_network[vert_type, double] 12 | 13 | .. tab-item:: C++ 14 | :sync: cpp 15 | 16 | .. cpp:function:: template <\ 17 | integer_network_vertex VertT, \ 18 | std::uniform_random_bit_generator Gen> \ 19 | undirected_temporal_network \ 20 | random_fully_mixed_temporal_network(\ 21 | VertT size, double rate, double max_t, Gen& generator) 22 | 23 | Generates a random temporal network of size :cpp:`size` where each link 24 | (connection between two vertices) is activated on times determined by an 25 | exponential inter-event time distribution at constant rate :cpp:`rate`. 26 | 27 | .. code-block:: pycon 28 | 29 | >>> import reticula as ret 30 | >>> gen = ret.mersenne_twister(42) 31 | >>> ret.random_fully_mixed_temporal_network[ret.int64]( 32 | ... size=128, rate=0.1, max_t=1000, random_state=gen) 33 | 34 | -------------------------------------------------------------------------------- /docs/generation/gnp.rst: -------------------------------------------------------------------------------- 1 | Random Erdős--Rényi network 2 | =========================== 3 | 4 | Undirected Erdős--Rényi network 5 | ------------------------------- 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: random_gnp_graph[vert_type](n: int, p: float, \ 13 | random_state) \ 14 | -> undirected_network[vert_type] 15 | 16 | .. tab-item:: C++ 17 | :sync: cpp 18 | 19 | .. cpp:function:: template <\ 20 | integer_network_vertex VertT, \ 21 | std::uniform_random_bit_generator Gen> \ 22 | undirected_network \ 23 | random_gnp_graph(VertT n, double p, Gen& generator) 24 | 25 | Generates a random :math:`G(n, p)` graph of size :cpp:`n`, where every edge 26 | exists independently with probability :cpp:`p` :cite:p:`batagelj2005efficient`. 27 | 28 | The expected number of edges is :math:`p \frac{n (n-1)}{2}`. 29 | 30 | If the parameter :cpp:`p` is not in the :math:`[0, 1]` range, the function fails 31 | by raising a :py:`ValueError` exception in Python or a 32 | :cpp:`std::invalid_argument` exception in C++. 33 | 34 | .. code-block:: pycon 35 | 36 | >>> import reticula as ret 37 | >>> gen = ret.mersenne_twister(42) 38 | >>> ret.random_gnp_graph[ret.int64](n=128, p=0.05, random_state=gen) 39 | 40 | 41 | Directed Erdős--Rényi network 42 | ----------------------------- 43 | 44 | .. tab-set:: 45 | 46 | .. tab-item:: Python 47 | :sync: python 48 | 49 | .. py:function:: random_directed_gnp_graph[vert_type](n: int, p: float, \ 50 | random_state) \ 51 | -> directed_network[vert_type] 52 | 53 | .. tab-item:: C++ 54 | :sync: cpp 55 | 56 | .. cpp:function:: template <\ 57 | integer_network_vertex VertT, \ 58 | std::uniform_random_bit_generator Gen> \ 59 | directed_network \ 60 | random_directed_gnp_graph(VertT n, double p, Gen& generator) 61 | 62 | 63 | Generates a random directed :math:`G(n, p)` graph of size :cpp:`n`, where every 64 | directed edge exists independently with probability :cpp:`p` 65 | :cite:p:`batagelj2005efficient`. 66 | 67 | Note that unlike in an undirected network :math:`(i, j)` and :math:`(j, i)` 68 | are distinct edges in a directed network, so the expected number of edges is 69 | :math:`p n (n-1)` 70 | 71 | If the parameter :cpp:`p` is not in the :math:`[0, 1]` range, the function fails 72 | by raising a :py:`ValueError` exception in Python or a 73 | :cpp:`std::invalid_argument` exception in C++. 74 | 75 | .. code-block:: pycon 76 | 77 | >>> import reticula as ret 78 | >>> gen = ret.mersenne_twister(42) 79 | >>> ret.random_directed_gnp_graph[ret.int64](n=128, p=0.05, random_state=gen) 80 | 81 | -------------------------------------------------------------------------------- /docs/generation/index.rst: -------------------------------------------------------------------------------- 1 | Generating networks 2 | =================== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | :caption: Simple static networks 7 | 8 | path 9 | cycle 10 | regular_ring_lattice 11 | square_grid_graph 12 | complete_graph 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | :caption: Random static networks 17 | 18 | gnp 19 | barabasi 20 | regular 21 | degree_seq 22 | expected_degree_seq 23 | uniform_hypergraph 24 | 25 | 26 | .. toctree:: 27 | :maxdepth: 1 28 | :caption: Random temporal networks 29 | 30 | fully_mixed 31 | link_activation 32 | node_activation 33 | 34 | The library allows users to generate a variety of synthetic networks. In this 35 | section you can see a different functions for generating deterministic or random 36 | networks based on established models. 37 | -------------------------------------------------------------------------------- /docs/generation/link_activation.rst: -------------------------------------------------------------------------------- 1 | Link activation temporal network 2 | ================================ 3 | 4 | With residual time distribution 5 | ------------------------------- 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: random_link_activation_temporal_network(base_net, max_t, \ 13 | iet_dist, res_dist, random_state, size_hint: int = 0) 14 | 15 | .. tab-item:: C++ 16 | :sync: cpp 17 | 18 | .. cpp:function:: template < \ 19 | temporal_network_edge EdgeT, \ 20 | typename ActivationF, \ 21 | typename ResActivationF, \ 22 | std::uniform_random_bit_generator Gen> \ 23 | network random_link_activation_temporal_network(\ 24 | const network& base_net, \ 25 | typename EdgeT::TimeType max_t, \ 26 | ActivationF&& inter_event_time_edge_activation, \ 27 | ResActivationF&& residual_time_edge_activation, \ 28 | Gen& generator, std::size_t size_hint = 0) 29 | 30 | Generates a random link-activation temporal network by activating the edges in 31 | :cpp:`base_net` first according to the residual time distribution (for the first 32 | activation time) then based on the inter-event time distribution, for time 33 | values from 0 to :cpp:`max_t` (exclusive). 34 | 35 | See the list of distributions :ref:`in the relevant section of the documentation 36 | `. 37 | 38 | The output type depends on the type of :py:`base_net`. If a (un)directed network 39 | or a (un)directed hypernetwork is provided, the output will be a (un)directed 40 | temporal network or a (un)directed temporal hypernetwork. 41 | 42 | With burn-in 43 | ------------ 44 | 45 | .. tab-set:: 46 | 47 | .. tab-item:: Python 48 | :sync: python 49 | 50 | .. py:function:: random_link_activation_temporal_network(base_net, max_t, \ 51 | iet_dist, random_state, size_hint: int = 0) 52 | :no-index: 53 | 54 | .. tab-item:: C++ 55 | :sync: cpp 56 | 57 | .. cpp:function:: template < \ 58 | temporal_network_edge EdgeT, \ 59 | typename ActivationF, \ 60 | std::uniform_random_bit_generator Gen> \ 61 | network random_link_activation_temporal_network(\ 62 | const network& base_net, \ 63 | typename EdgeT::TimeType max_t, \ 64 | ActivationF&& inter_event_time_edge_activation, \ 65 | Gen& generator, std::size_t size_hint = 0) 66 | 67 | Generates a random link-activation temporal network without a residual time 68 | distribution, by burning-in the inter-event time distribution for :cpp:`max_t` 69 | time before recording events. The output events have times values from 0 to 70 | :cpp:`max_t` (exclusive). 71 | 72 | See the list of distributions :ref:`in the relevant section of the documentation 73 | `. 74 | 75 | .. warning:: 76 | This approach might not work on all distributions. It is up to the user to think 77 | about whether this suits their use case. 78 | -------------------------------------------------------------------------------- /docs/generation/node_activation.rst: -------------------------------------------------------------------------------- 1 | Node activation temporal network 2 | ================================ 3 | 4 | With residual time distribution 5 | ------------------------------- 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: random_node_activation_temporal_network(base_net, max_t, \ 13 | iet_dist, res_dist, random_state, size_hint: int = 0) 14 | 15 | .. tab-item:: C++ 16 | :sync: cpp 17 | 18 | .. cpp:function:: template < \ 19 | temporal_network_edge EdgeT, \ 20 | typename ActivationF, \ 21 | typename ResActivationF, \ 22 | std::uniform_random_bit_generator Gen> \ 23 | network random_node_activation_temporal_network(\ 24 | const network& base_net, \ 25 | typename EdgeT::TimeType max_t, \ 26 | ActivationF&& inter_event_time_edge_activation, \ 27 | ResActivationF&& residual_time_edge_activation, \ 28 | Gen& generator, std::size_t size_hint = 0) 29 | 30 | Generates a random node-activation temporal network by activating a each vertex 31 | of :cpp:`base_net` first according to the residual time distribution (for the 32 | first activation time) then based on the inter-event time distribution, for 33 | time values from 0 to :cpp:`max_t` (exclusive). At each activation, a random 34 | edge (out-)incident to that vertex is selected uniformly at random, and that 35 | edge is activated. 36 | 37 | See the list of distributions :ref:`in the relevant section of the documentation 38 | `. 39 | 40 | The output type depends on the type of :py:`base_net`. If a (un)directed network 41 | or a (un)directed hypernetwork is provided, the output will be a (un)directed 42 | temporal network or a (un)directed temporal hypernetwork. 43 | 44 | With burn-in 45 | ------------ 46 | 47 | .. tab-set:: 48 | 49 | .. tab-item:: Python 50 | :sync: python 51 | 52 | .. py:function:: random_node_activation_temporal_network(base_net, max_t, \ 53 | iet_dist, random_state, size_hint: int = 0) 54 | :no-index: 55 | 56 | .. tab-item:: C++ 57 | :sync: cpp 58 | 59 | .. cpp:function:: template < \ 60 | temporal_network_edge EdgeT, \ 61 | typename ActivationF, \ 62 | std::uniform_random_bit_generator Gen> \ 63 | network random_node_activation_temporal_network(\ 64 | const network& base_net, \ 65 | typename EdgeT::TimeType max_t, \ 66 | ActivationF&& inter_event_time_edge_activation, \ 67 | Gen& generator, std::size_t size_hint = 0) 68 | 69 | Generates a random node-activation temporal network without a residual time 70 | distribution, by burning-in the inter-event time distribution for :cpp:`max_t` 71 | time before recording events. The output events have times values from 0 to 72 | :cpp:`max_t` (exclusive). 73 | 74 | Each vertex of :cpp:`base_net` is activated based on the inter-event time 75 | distribution. At each activation, a random edge (out-)incident to that vertex 76 | is selected uniformly at random, and that edge is activated. 77 | 78 | 79 | See the list of distributions :ref:`in the relevant section of the documentation 80 | `. 81 | 82 | .. warning:: 83 | This approach might not work on all distributions. It is up to the user to think 84 | about whether this suits their use case. 85 | -------------------------------------------------------------------------------- /docs/generation/path.rst: -------------------------------------------------------------------------------- 1 | Path graphs 2 | =========== 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: path_graph[vert_type](size: int, periodic: bool = False) \ 10 | -> undirected_network[vert_type] 11 | 12 | .. tab-item:: C++ 13 | :sync: cpp 14 | 15 | .. cpp:function:: template \ 16 | undirected_network \ 17 | path_graph(VertT size, bool periodic = false) 18 | 19 | Generates a chain of :cpp:`size` vertices, each connected consecutively to each 20 | other. If the periodic parameter is set to :cpp:`true`, then the first and the 21 | last vertices are also connected, making it into a cycle graph equivalent to 22 | what is generated through :cpp:func:`cycle_graph`. 23 | 24 | .. code-block:: pycon 25 | 26 | >>> import reticula as ret 27 | >>> ret.path_graph[ret.int64](size=128) 28 | 29 | 30 | >>> ret.path_graph[ret.int64](size=128, periodic=True) 31 | 32 | -------------------------------------------------------------------------------- /docs/generation/regular.rst: -------------------------------------------------------------------------------- 1 | Random k-regular network 2 | ======================== 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: random_regular_graph[vert_type](\ 10 | size: int, degree: int, random_state) \ 11 | -> undirected_network[vert_type] 12 | 13 | .. tab-item:: C++ 14 | :sync: cpp 15 | 16 | .. cpp:function:: template <\ 17 | integer_network_vertex VertT, \ 18 | std::uniform_random_bit_generator Gen> \ 19 | undirected_network \ 20 | random_regular_graph(VertT size, VertT degree, Gen& generator) 21 | 22 | Generates a random :math:`k`-regular network, a random sample out of the space 23 | of all graphs where all nodes have exactly degree equal to :cpp:`degree`. 24 | 25 | .. code-block:: pycon 26 | 27 | >>> import reticula as ret 28 | >>> gen = ret.mersenne_twister(42) 29 | >>> ret.random_regular_graph[ret.int64](size=128, degree=4, random_state=gen) 30 | 31 | 32 | The generation of a random :math:`k`-regular graph becomes more and more 33 | difficult as the density increases. In C++, you can use the :code:`try_` 34 | variant of this function to limit runtime to a limited set of tries: 35 | 36 | .. cpp:function:: template <\ 37 | integer_network_vertex VertT, \ 38 | std::uniform_random_bit_generator Gen> \ 39 | std::optional> \ 40 | try_random_regular_graph(VertT size, VertT degree, Gen& generator, \ 41 | std::size_t max_tries) 42 | 43 | If this function succeeds in :cpp:`max_tries` tries, it will return the 44 | resulting network, otherwise it returns an instance of :cpp:`std::nullopt_t`. 45 | -------------------------------------------------------------------------------- /docs/generation/regular_ring_lattice.rst: -------------------------------------------------------------------------------- 1 | Regular ring lattice 2 | ==================== 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: regular_ring_lattice[vert_type](size: int, degree: int) \ 10 | -> undirected_network[vert_type] 11 | 12 | .. tab-item:: C++ 13 | :sync: cpp 14 | 15 | .. cpp:function:: template \ 16 | undirected_network \ 17 | regular_ring_lattice(VertT size, VertT degree) 18 | 19 | Generates a regular ring lattice of size :cpp:`size` and degree :cpp:`degree`. A 20 | ring-like structure that if arranged in a circle, each vertex would be connected 21 | to :math:`degree/2` vertices before and after it. 22 | 23 | The parameter :cpp:`degree` needs to be an even number and :cpp:`degree` needs 24 | to be smaller than :cpp:`size`, otherwise the function fails by raising a 25 | :py:`ValueError` exception in Python or a :cpp:`std::invalid_argument` exception 26 | in C++. 27 | 28 | .. code-block:: pycon 29 | 30 | >>> import reticula as ret 31 | >>> ret.regular_ring_lattice[ret.int64](size=128, degree=6) 32 | 33 | -------------------------------------------------------------------------------- /docs/generation/square_grid_graph.rst: -------------------------------------------------------------------------------- 1 | Square grid graphs 2 | ================== 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: square_grid_graph[vert_type](\ 10 | side: int, dims: int, periodic: bool = False) \ 11 | -> undirected_network[vert_type] 12 | 13 | .. tab-item:: C++ 14 | :sync: cpp 15 | 16 | .. cpp:function:: template \ 17 | undirected_network \ 18 | square_grid_graph(VertT side, std::size_t dims, bool periodic = false) 19 | 20 | Generates a :cpp:`dims`-dimensional square grid lattice with :cpp:`side` vertices 21 | to each side, creating a network of :math:`side^{dims}` vertices. The parameter 22 | :cpp:`periodic` controls the boundary conditions of the lattice. 23 | 24 | .. code-block:: pycon 25 | 26 | >>> import reticula as ret 27 | >>> ret.square_grid_graph[ret.int64](side=4, dims=2) 28 | 29 | 30 | >>> ret.square_grid_graph[ret.int64](side=4, dims=2, periodic=True) 31 | 32 | -------------------------------------------------------------------------------- /docs/generation/uniform_hypergraph.rst: -------------------------------------------------------------------------------- 1 | Random uniform hypergraphs 2 | ========================== 3 | 4 | Undirected random uniform hypergraph 5 | ------------------------------------ 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: random_uniform_hypergraph[vert_type](\ 13 | size: int, edge_degree: int, edge_prob: float, random_state) \ 14 | -> undirected_hypernetwork[vert_type] 15 | 16 | .. tab-item:: C++ 17 | :sync: cpp 18 | 19 | .. cpp:function:: template \ 21 | undirected_hypernetwork random_uniform_hypergraph(\ 22 | VertT size, VertT edge_degree, double edge_prob, Gen& generator) 23 | 24 | 25 | Generates a random hypergraph of size :cpp:`size` vertices where each of the 26 | possible edges of degree :cpp:`edge_degree` are present independently with 27 | probability :cpp:`edge_prob`. This is the natural extension of Erdős--Rényi 28 | networks to uniform hypergraphs. 29 | 30 | All edges will consist of exactly :cpp:`edge_degree` vertices. 31 | 32 | .. code-block:: pycon 33 | 34 | >>> import reticula as ret 35 | >>> gen = ret.mersenne_twister(42) 36 | >>> ret.random_uniform_hypergraph[ret.int64]( 37 | ... size=128, edge_degree=4, edge_prob=0.1, random_state=gen) 38 | 39 | 40 | 41 | Directed random uniform hypergraph 42 | ---------------------------------- 43 | 44 | .. tab-set:: 45 | 46 | .. tab-item:: Python 47 | :sync: python 48 | 49 | .. py:function:: random_directed_uniform_hypergraph[vert_type](\ 50 | size: int, edge_in_degree: int, edge_out_degree: int, \ 51 | edge_prob: float, random_state) \ 52 | -> directed_hypernetwork[vert_type] 53 | 54 | .. tab-item:: C++ 55 | :sync: cpp 56 | 57 | .. cpp:function:: template \ 59 | directed_hypernetwork random_directed_uniform_hypergraph(\ 60 | VertT size, VertT edge_in_degree, VertT edge_out_degree, \ 61 | double edge_prob, Gen& generator) 62 | 63 | Generates a random hypergraph of size :cpp:`size` vertices where each of the 64 | possible directed edges of in-degree :cpp:`edge_in_degree` and out-degree 65 | :cpp:`edge_out_degree` are present independently with probability 66 | :cpp:`edge_prob`. This is the natural extension of directed Erdős--Rényi 67 | networks to uniform directed hypergraphs. 68 | 69 | All edges will consist of exactly :cpp:`edge_in_degree` mutator vertices (tail 70 | of the arrow) and :cpp:`edge_out_degree` mutated vertices (head of the arrow). 71 | 72 | .. code-block:: pycon 73 | 74 | >>> import reticula as ret 75 | >>> gen = ret.mersenne_twister(42) 76 | >>> ret.random_directed_uniform_hypergraph[ret.int64]( 77 | ... size=128, edge_in_degree=2, edge_out_degree=2, 78 | ... edge_prob=0.01, random_state=gen) 79 | 80 | -------------------------------------------------------------------------------- /docs/github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Reticula: Temporal network and hypergraphs 2 | ========================================== 3 | 4 | .. include:: preamble.rst 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :hidden: 9 | 10 | Home 11 | installation 12 | quickstart 13 | types 14 | io 15 | randomness 16 | generation/index 17 | shuffling/index 18 | operations/index 19 | algorithms/index 20 | examples/index 21 | citing 22 | bibliography 23 | -------------------------------------------------------------------------------- /docs/io.rst: -------------------------------------------------------------------------------- 1 | Input/Output and interoperation 2 | =============================== 3 | 4 | Edgelist input/output 5 | --------------------- 6 | 7 | Reading an edgelist file 8 | ^^^^^^^^^^^^^^^^^^^^^^^^ 9 | 10 | 11 | .. tab-set:: 12 | 13 | .. tab-item:: Python 14 | :sync: python 15 | 16 | .. py:function:: read_edgelist[edge_type](path: str) 17 | 18 | .. tab-item:: C++ 19 | :sync: cpp 20 | 21 | .. cpp:function:: template \ 22 | network read_edgelist(std::filesystem::path path) 23 | 24 | 25 | Dyadic static and temporal networks can be read from space separated files where 26 | each row represents an edge. Note that vertices with no neighbours cannot be 27 | represented in this format. 28 | 29 | Writing an edgelist file 30 | ^^^^^^^^^^^^^^^^^^^^^^^^ 31 | 32 | 33 | .. tab-set:: 34 | 35 | .. tab-item:: Python 36 | :sync: python 37 | 38 | .. py:function:: write_edgelist(network, path: str) 39 | 40 | .. tab-item:: C++ 41 | :sync: cpp 42 | 43 | .. cpp:function:: template \ 44 | void write_edgelist(const network& g, std::filesystem::path path) 45 | 46 | Write a dyadic static and temporal networks in form of space separated files 47 | where each row represents an edge. Note that vertices with no neighbours cannot 48 | be represented in this format. 49 | 50 | Interoperation with NetworkX 51 | ---------------------------- 52 | 53 | .. py:function:: to_networkx(network, create_using = None) 54 | 55 | Creates a NetworkX graph from a dyadic static network. If :py:`created_using` 56 | is not provided, the type of the output is selected based in directionality of 57 | the input :py:`network`. For directed networks, the output would would be of 58 | type :py:`networkx.DiGraph`, and for undirected networks it would produce an 59 | instance of :py:`networkx.Graph`. 60 | 61 | Raises a :py:`ModuleNotFoundError` if NetworkX is not installed or cannot be 62 | imported at the time the function is called. 63 | 64 | 65 | .. py:function:: from_networkx[vertex_type](g) 66 | 67 | Creates a dyadic static network from a NetworkX graph. The directedness of the 68 | network is decided based on whether graph :py:`g` is directed, i.e., whether 69 | :py:`networkx.is_directed(g)` returns :py:`True`. 70 | 71 | Raises a :py:`ModuleNotFoundError` if NetworkX is not installed or cannot be 72 | imported at the time the the function is called. 73 | -------------------------------------------------------------------------------- /docs/latex_index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Reticula 4 | ======== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :hidden: 9 | 10 | latex_preamble 11 | installation 12 | quickstart 13 | types 14 | io 15 | randomness 16 | generation/index 17 | shuffling/index 18 | operations/index 19 | algorithms/index 20 | examples/index 21 | citing 22 | bibliography 23 | -------------------------------------------------------------------------------- /docs/latex_preamble.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Abstract 4 | ======== 5 | 6 | .. only:: latex 7 | 8 | .. include:: preamble.rst 9 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/operations/cartesian_product.rst: -------------------------------------------------------------------------------- 1 | Cartesian product 2 | ================= 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: cartesian_product(\ 10 | g1: undirected_network[vertex_type1], \ 11 | g2: undirected_network[vertex_type2]) \ 12 | -> undirected_network[pair[vertex_type1, vertex_type2]] 13 | 14 | .. tab-item:: C++ 15 | :sync: cpp 16 | 17 | .. cpp:function:: template \ 19 | undirected_network> \ 20 | cartesian_product(\ 21 | const undirected_network& g1, \ 22 | const undirected_network& g2) 23 | 24 | Calculates graph cartesian product of two undirected networks :cpp:`g1` and 25 | :cpp:`g1`. 26 | 27 | .. note:: While in C++ there are no limits on the types of vertices 28 | of the network (as long as they satisfy :cpp:concept:`network_vertex`) 29 | the Python binding only supports certain :ref:`vertex types `. 30 | This function is not implemented for cases that would produce output vertices 31 | too complex to be predefined, i.e., when :py:`vertex_type1` or 32 | :py:`vertex_type2` are not a simple numerical or string type. In these cases 33 | you might want to relabel the vertices of the networks before calling this 34 | function. 35 | -------------------------------------------------------------------------------- /docs/operations/graph_union.rst: -------------------------------------------------------------------------------- 1 | Graph union 2 | =========== 3 | 4 | .. tab-set:: 5 | 6 | .. tab-item:: Python 7 | :sync: python 8 | 9 | .. py:function:: graph_union(g1, g2) 10 | 11 | .. tab-item:: C++ 12 | :sync: cpp 13 | 14 | .. cpp:function:: template \ 15 | network \ 16 | graph_union(const network& g1, const network& g2) 17 | 18 | 19 | Calculates the graph union of two networks :cpp:`g1` and :cpp:`g2`: a new 20 | network containing the union of their sets of vertices and edges. 21 | 22 | .. code-block:: pycon 23 | 24 | >>> import reticula as ret 25 | >>> g1 = ret.cycle_graph[ret.int64](size=4) 26 | >>> g2 = ret.complete_graph[ret.int64](size=4) 27 | >>> g1 28 | 29 | >>> g2 30 | 31 | # union of cycle and complete graph is a complete graph 32 | >>> ret.graph_union(g1, g2) 33 | 34 | -------------------------------------------------------------------------------- /docs/operations/index.rst: -------------------------------------------------------------------------------- 1 | Network operations 2 | ================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | subgraphs 8 | add_remove 9 | occupation 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | 14 | cartesian_product 15 | graph_union 16 | -------------------------------------------------------------------------------- /docs/operations/subgraphs.rst: -------------------------------------------------------------------------------- 1 | Subgraph operations 2 | =================== 3 | 4 | Edge induced subgraphs 5 | ---------------------- 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: edge_induced_subgraph(net, edges) 13 | 14 | .. tab-item:: C++ 15 | :sync: cpp 16 | 17 | .. cpp:function:: template \ 19 | requires std::same_as, EdgeT> \ 20 | network \ 21 | edge_induced_subgraph(const network& net, Range&& edges) 22 | 23 | Returns a copy of a subset of the network :cpp:`net` consisting only of edges 24 | :cpp:`edges` and all their incident vertices. 25 | 26 | .. code-block:: pycon 27 | 28 | >>> import reticula as ret 29 | >>> net = ret.cycle_graph[ret.int64](size=8) 30 | >>> edges = [(0, 1), (1, 2), (2, 3)] 31 | >>> subgraph = ret.edge_induced_subgraph(net, edges) 32 | >>> subgraph 33 | 34 | >>> subgraph.edges() 35 | [undirected_edge[int64](0, 1), undirected_edge[int64](1, 2), undirected_edge[int64](2, 3)] 36 | >>> subgraph.vertices() 37 | [0, 1, 2, 3] 38 | 39 | 40 | Vertex induced subgraphs 41 | ------------------------ 42 | 43 | .. tab-set:: 44 | 45 | .. tab-item:: Python 46 | :sync: python 47 | 48 | .. py:function:: vertex_induced_subgraph(net, verts) 49 | 50 | .. tab-item:: C++ 51 | :sync: cpp 52 | 53 | .. cpp:function:: template \ 55 | requires std::same_as, EdgeT::VetexType> \ 56 | network \ 57 | vertex_induced_subgraph(const network& net, Range&& verts) 58 | 59 | Returns a copy of a subset of the network :cpp:`net` consisting only of vertices 60 | :cpp:`verts` and any edges with all incident vertices in :cpp:`verts`. 61 | 62 | .. code-block:: pycon 63 | 64 | >>> import reticula as ret 65 | >>> net = ret.cycle_graph[ret.int64](size=8) 66 | >>> verts = [0, 1, 2, 5] 67 | >>> subgraph = ret.vertex_induced_subgraph(net, verts) 68 | >>> subgraph 69 | 70 | >>> subgraph.edges() 71 | [undirected_edge[int64](0, 1), undirected_edge[int64](1, 2)] 72 | >>> subgraph.vertices() 73 | [0, 1, 2, 5] 74 | -------------------------------------------------------------------------------- /docs/preamble.rst: -------------------------------------------------------------------------------- 1 | Reticula is a general purpose Python package backed by a powerful C++ library 2 | for fast and efficient analysis of **temporal networks** and **static and 3 | temporal hypergraphs**. Currently, the library supports multiple methods of 4 | generating and randomising networks, using various algorithms to calculate 5 | network properties and running those algorithms safely in a multi-threaded 6 | environment. 7 | 8 | The names Reticula is the plural form of reticulum, a Latin word meaning 9 | network or a network-like (reticulated) structure. 10 | 11 | You can :ref:`install ` the latest version of the 12 | Python package from the Python Package Index, using the command: 13 | 14 | .. code-block:: console 15 | 16 | $ python -m pip install --upgrade reticula 17 | 18 | The Python package is available on most relatively modern Windows, MacOS and 19 | Linux systems. MacOS package provides universal binaries supporting both ARM 20 | and x64 hardware. Support for the ARM architecture on Windows and Linux will be 21 | introduced in future versions. 22 | 23 | Example: Temporal network reachability 24 | -------------------------------------- 25 | 26 | Let us generate a :doc:`random link activation temporal network 27 | ` from a :doc:`random regular static base network 28 | ` and poisson process link activations and analyse 29 | reachability from a specific node at a specific time to all other nodes and 30 | times: 31 | 32 | .. code-block:: python 33 | 34 | import reticula as ret 35 | 36 | # random number generator: 37 | gen = ret.mersenne_twister(42) 38 | 39 | # make a random regular static base netowrk with integer vertices: 40 | g = ret.random_regular_graph[ret.int64](size=100, degree=4, random_state=gen) 41 | 42 | print(g) 43 | # -> 44 | 45 | # make a Poisson random link-activation network with mean inter-event time of 2.0 46 | mean_interevent_time = 2.0 47 | iet = ret.exponential_distribution[ret.double](lmbda=1/mean_interevent_time) 48 | temporal_net = ret.random_link_activation_temporal_network( 49 | base_net=g, max_t=200.0, 50 | iet_dist=iet, res_dist=iet, 51 | random_state=gen) 52 | 53 | print(temporal_net) 54 | # -> 55 | 56 | # calculate reachability: 57 | adj = ret.temporal_adjacency.simple[temporal_net.edge_type()]() 58 | cluster = ret.out_cluster( 59 | temporal_network=temporal_net, 60 | temporal_adjacency=adj, vertex=0, time=0.0) 61 | 62 | # Total number of reachable vertices: 63 | print(cluster.volume()) 64 | # -> 100 65 | 66 | # Is node 12 at time 100 reachable from node 0 at t=0.0? 67 | print(cluster.covers(vertex=12, time=100.0)) 68 | # -> True 69 | 70 | # Let's see all intervals where node 15 is reachable from node 0 at t=0.0: 71 | print(list(cluster.interval_sets()[15])) 72 | # -> [(1.7428016698637445, inf)] 73 | 74 | This can tell us that all 100 vertices are eventually reachble from vertex 0 at 75 | time 0.0 with these configurations, and that, e.g., node 15 first becomes 76 | reachable at time 1.742 and remains reachable after that. 77 | 78 | For a more detailed example of temporal network reachability, check out 79 | :doc:`this dedicated example page `. 80 | 81 | Example: Static network connected component 82 | ------------------------------------------- 83 | 84 | Let us generate :ref:`an Erdős--Rényi network ` and find its :ref:`largest connected component 86 | `: 87 | 88 | .. code-block:: python 89 | 90 | import reticula as ret 91 | 92 | gen = ret.mersenne_twister(42) 93 | g = ret.random_gnp_graph[ret.int64](n=100, p=0.02, random_state=gen) 94 | 95 | lcc = ret.largest_connected_component(g) 96 | print(lcc) 97 | # -> 98 | 99 | More detailed example of static network component size analysis is presented in 100 | :doc:`its dedicated example page `. 101 | -------------------------------------------------------------------------------- /docs/pypi.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==8.1.3 2 | sphinx-copybutton==0.5.2 3 | furo==2024.8.6 4 | sphinxcontrib-bibtex==2.6.3 5 | sphinx_design==0.6.1 6 | sphinxext-opengraph==0.9.1 7 | sphinx-sitemap==2.6.0 8 | toml==0.10.2 9 | -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | 3 | Sitemap: https://docs.reticula.network/sitemap.xml 4 | Disallow: /latex_index.html 5 | Disallow: /latex_preamble.html 6 | -------------------------------------------------------------------------------- /docs/shuffling/event.rst: -------------------------------------------------------------------------------- 1 | Event shuffling 2 | =============== 3 | 4 | Instant-event shuffling 5 | ----------------------- 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: microcanonical_reference_models.instant_event_shuffling(\ 13 | temporal_network, random_state) 14 | 15 | .. tab-item:: C++ 16 | :sync: cpp 17 | 18 | .. cpp:function:: template \ 20 | requires is_dyadic_v \ 21 | network \ 22 | microcanonical_reference_models::instant_event_shuffling(\ 23 | const network& temp, Gen& generator); 24 | 25 | 26 | Produces a random shuffling of the temporal network where each event is 27 | attributed to two randomly selected vertices from the original network. 28 | Equivalent to micocanonical reference model with the canonical name 29 | :math:`P[E]`. The set of vertices, timestamps and the number of events are 30 | conserved. 31 | -------------------------------------------------------------------------------- /docs/shuffling/index.rst: -------------------------------------------------------------------------------- 1 | Randomising networks 2 | ===================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Static network shuffling methods 7 | 8 | static 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :caption: Temporal network shuffling methods 13 | 14 | link 15 | event 16 | timeline 17 | 18 | -------------------------------------------------------------------------------- /docs/shuffling/link.rst: -------------------------------------------------------------------------------- 1 | Temporal Network Link Shuffling 2 | =============================== 3 | 4 | Link shuffling 5 | -------------- 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: microcanonical_reference_models.link_shuffling(\ 13 | temporal_network, random_state) 14 | 15 | .. tab-item:: C++ 16 | :sync: cpp 17 | 18 | .. cpp:function:: template \ 20 | requires is_dyadic_v \ 21 | network microcanonical_reference_models::link_shuffling(\ 22 | const network& temp, Gen& generator) 23 | 24 | 25 | Produces a random shuffling of the temporal network where all events between two 26 | vertices are attributed to two randomly selected vertices from the original 27 | network. Equivalent to micocanonical reference model with the canonical name 28 | :math:`P[p_{\mathcal{L}}(\Theta)]`. 29 | 30 | The set of vertices, timestamps, the number of events and the multiset of 31 | timelines are conserved. 32 | 33 | Connected link shuffling 34 | ------------------------ 35 | 36 | .. tab-set:: 37 | 38 | .. tab-item:: Python 39 | :sync: python 40 | 41 | .. py:function:: microcanonical_reference_models.connected_link_shuffling(\ 42 | temporal_network, random_state) 43 | 44 | .. tab-item:: C++ 45 | :sync: cpp 46 | 47 | .. cpp:function:: template \ 49 | requires is_dyadic_v \ 50 | network \ 51 | microcanonical_reference_models::connected_link_shuffling(\ 52 | const network& temp, Gen& generator) 53 | 54 | Produces a random shuffling of the temporal network where all events between two 55 | vertices are attributed to two randomly selected vertices from the original 56 | network. As opposed to `Link shuffling`, this model preserves the pattern of 57 | (weak) connectivity in the static projection of the original graph, i.e., the 58 | static projection of the output would have the same set of (weakly) connected 59 | components as the input. Generalisation of the micocanonical reference model 60 | with the canonical name :math:`P[I_\lambda, p_{\mathcal{L}}(\Theta)]` to 61 | temporal networks with directed and/or multi-component static projections. 62 | 63 | In addition to the set of components of the static projection, the set of 64 | vertices, timestamps, the number of events and the multiset of timelines 65 | of the temporal network are conserved. 66 | 67 | Topology-constrained link shuffling 68 | ----------------------------------- 69 | 70 | .. tab-set:: 71 | 72 | .. tab-item:: Python 73 | :sync: python 74 | 75 | .. py:function:: \ 76 | microcanonical_reference_models.topology_constrained_link_shuffling(\ 77 | temporal_network, random_state) 78 | 79 | .. tab-item:: C++ 80 | :sync: cpp 81 | 82 | .. cpp:function:: template \ 84 | requires is_dyadic_v \ 85 | network \ 86 | microcanonical_reference_models::topology_constrained_link_shuffling(\ 87 | const network& temp, Gen& generator) 88 | 89 | 90 | Produces a random shuffling of the temporal network where the events are 91 | shuffled by assigning new, uniformly random timetamps and moving it to a 92 | randomly selected link with a non-empty timeline. Equivalent to micocanonical 93 | reference model with the canonical name :math:`P[\mathcal{L}, E]`. 94 | 95 | The set of vertices, total number of events and the static projection of the 96 | temporal network are conserved. 97 | -------------------------------------------------------------------------------- /docs/shuffling/static.rst: -------------------------------------------------------------------------------- 1 | Static Network Link Shuffling 2 | ============================= 3 | 4 | Degree-sequence preserving shuffling 5 | ------------------------------------ 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Python 10 | :sync: python 11 | 12 | .. py:function:: microcanonical_reference_models.degree_sequence_preserving_shuffling(\ 13 | static_network, random_state) 14 | .. py:function:: microcanonical_reference_models.degree_sequence_preserving_shuffling(\ 15 | static_network, random_state, rewires: int) 16 | 17 | .. tab-item:: C++ 18 | :sync: cpp 19 | 20 | .. cpp:function:: template \ 22 | requires is_dyadic_v \ 23 | network microcanonical_reference_models::\ 24 | degree_sequence_preserving_shuffling(\ 25 | const network& temp, Gen& generator) 26 | 27 | .. cpp:function:: template \ 29 | requires is_dyadic_v \ 30 | network microcanonical_reference_models::\ 31 | degree_sequence_preserving_shuffling(\ 32 | const network& temp, Gen& generator, std::size_t rewires) 33 | 34 | Produces a degree-sequence preserving shuffling of the given undirected dyadic 35 | static network. The variant with the ``rewires`` parameter performs the given 36 | number of rewirings. The variant without the ``rewires`` parameter performs a 37 | number of rewirings equal to 100 times the number of edges in the network. 38 | 39 | 40 | Joint degree-sequence preserving shuffling 41 | ------------------------------------------ 42 | 43 | 44 | .. tab-set:: 45 | 46 | .. tab-item:: Python 47 | :sync: python 48 | 49 | .. py:function:: microcanonical_reference_models.joint_degree_sequence_preserving_shuffling(\ 50 | static_network, random_state) 51 | .. py:function:: microcanonical_reference_models.joint_degree_sequence_preserving_shuffling(\ 52 | static_network, random_state, rewires: int) 53 | 54 | .. tab-item:: C++ 55 | :sync: cpp 56 | 57 | .. cpp:function:: template \ 59 | requires is_dyadic_v \ 60 | network microcanonical_reference_models::\ 61 | joint_degree_sequence_preserving_shuffling(\ 62 | const network& temp, Gen& generator) 63 | 64 | .. cpp:function:: template \ 66 | requires is_dyadic_v \ 67 | network microcanonical_reference_models::\ 68 | joint_degree_sequence_preserving_shuffling(\ 69 | const network& temp, Gen& generator, std::size_t rewires) 70 | 71 | Produces a joint degree-sequence preserving shuffling of the given undirected, 72 | dyadic static network. The variant with the ``rewires`` parameter performs the 73 | given number of rewirings. The variant without the ``rewires`` parameter 74 | performs a number of rewirings equal to 100 times the number of edges in the 75 | network. 76 | -------------------------------------------------------------------------------- /examples/static_network_percolation/README.md: -------------------------------------------------------------------------------- 1 | # Random network percolation 2 | 3 | In this example, we study the connectivity phase transition in 4 | [random `G(n, p)` networks][gnp] as controlled by parameter `p`. We are 5 | interested in the evolution of expected and largest connected component size. 6 | Also, we will study two different "susceptibility" measures, one based on small 7 | component size distribution, called `classic_chi` in the code, and one based on 8 | expected change in the expected component size after inducing a miniscule 9 | external field, called `isotropoc_chi`. 10 | 11 | The code tries to utilise all cores on your computer without using separate 12 | Python processes. Remember that while almost all the computations that run 13 | inside Reticulum library functions can be parallelised, the more Python 14 | functions we run in the `cc_analysis` function, which is the multi-threaded part 15 | of your code, [the lower your overall speedup is going to be][amdahl]. In this 16 | example, the networks with a lower `p` values spend less calculation time in the 17 | Reticulum library (i.e. the functions `reticulum.random_gnp_graph` and 18 | `reticulum.connected_components` run faster for less dense network) but slightly 19 | more time in the Python functions since larger number of components means more 20 | calculation time for list comprehensions. 21 | 22 | [gnp]: https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93R%C3%A9nyi_model 23 | [amdahl]: https://en.wikipedia.org/wiki/Amdahl%27s_law 24 | 25 | ## Running the example 26 | 27 | Run this example for 100 random networks of size 10000 nodes: 28 | ``` 29 | $ time python examples/static_network_percolation/static_network_percolation.py --size 10000 --ens 100 /tmp/figure.svg /tmp/report.json 30 | 100%|█████████████████████████████████████████████████████████████| 20000/20000 [01:48<00:00, 183.95it/s] 31 | 32 | real 2m11.495s 33 | user 9m15.110s 34 | sys 0m2.580s 35 | ``` 36 | 37 | Note the difference between user+sys times and the real "clock" time. This 38 | signifies a speedup of around 4.2 times, achieved on an 4-core Intel Core 39 | i7-6700HQ laptop CPU supporting a total of 8 SMT threads. 40 | 41 | The output figure should look something like this: 42 | ![4 plot showing changes in compoent size and susceptibility][fig] 43 | 44 | [fig]: https://raw.githubusercontent.com/reticulum-network/reticulum-python/main/examples/static_network_percolation/figure.svg 45 | -------------------------------------------------------------------------------- /examples/static_network_percolation/static_network_percolation.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | import os 3 | import argparse 4 | import json 5 | 6 | import reticula as ret 7 | import tqdm 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | 11 | def cc_analysis(n: int, p: float, seed: int): 12 | # The Reticula magic is happening in this paragraph: 13 | state = ret.mersenne_twister(seed) 14 | g = ret.random_gnp_graph[ret.int64](n, p, state) 15 | ccs = [len(cc) for cc in ret.connected_components(g)] 16 | 17 | largest_comp_size = max(ccs) 18 | sum_squares = sum([s**2 for s in ccs]) 19 | sum_cubes = sum([s**3 for s in ccs]) 20 | nom = sum_squares - largest_comp_size**2 21 | denom = n - largest_comp_size 22 | classic_chi = nom/denom if denom > 0 else 0.0 23 | expected_comp_size = sum_squares/n 24 | small_field = 1/n 25 | isotropic_chi = 1/small_field * (1/n*sum_squares - 1/(n**2)*sum_cubes) 26 | return largest_comp_size, classic_chi, expected_comp_size, isotropic_chi 27 | 28 | if __name__ == "__main__": 29 | parser = argparse.ArgumentParser( 30 | description="Analysis of connected component size and other " 31 | "isometric percolation properties in G(n, p) networks.") 32 | parser.add_argument("figure", type=str, help="output figure filename") 33 | parser.add_argument("output", type=str, help="output json filename") 34 | parser.add_argument("--size", type=int, default=10000, help="system size") 35 | parser.add_argument("--ens", type=int, default=100, help="ensemble size") 36 | parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), 37 | help="number of parallel workers") 38 | args = parser.parse_args() 39 | 40 | seeds = range(args.ens) 41 | size = args.size 42 | p_values = np.linspace(0.1/(size-1), 6/(size-1), num=200) 43 | 44 | largest_comps = {} 45 | classic_chis = {} 46 | expected_comps = {} 47 | isotropic_chis = {} 48 | with concurrent.futures.ThreadPoolExecutor( 49 | max_workers=args.jobs) as executor: 50 | futures = [] 51 | for p in p_values: 52 | for seed in seeds: 53 | futures.append((p, 54 | executor.submit(cc_analysis, size, p, seed))) 55 | 56 | for p, f in tqdm.tqdm(futures): 57 | largest_comp_size, classic_chi,\ 58 | exp_comp_size, isotropic_chi = f.result() 59 | 60 | if p not in largest_comps: 61 | largest_comps[p] = [] 62 | largest_comps[p].append(largest_comp_size) 63 | 64 | if p not in classic_chis: 65 | classic_chis[p] = [] 66 | classic_chis[p].append(classic_chi) 67 | 68 | if p not in expected_comps: 69 | expected_comps[p] = [] 70 | expected_comps[p].append(exp_comp_size) 71 | 72 | if p not in isotropic_chis: 73 | isotropic_chis[p] = [] 74 | isotropic_chis[p].append(isotropic_chi) 75 | 76 | 77 | fig, axes = plt.subplots(2, 2, figsize=(8, 8), sharex=True, 78 | gridspec_kw={"wspace": 0.3, "hspace": 0.3}) 79 | 80 | ax_max, ax_classic_chi, ax_exp, ax_isotropic_chi = axes.flatten() 81 | 82 | ax_max.plot( 83 | p_values - 1/(size-1), 84 | [np.mean(largest_comps[p]) for p in p_values]) 85 | ax_max.set_xlabel("$p - p_c$") 86 | ax_max.set_ylabel("Largest connected component size") 87 | 88 | ax_classic_chi.plot( 89 | p_values - 1/(size-1), 90 | [np.mean(classic_chis[p]) for p in p_values]) 91 | ax_classic_chi.set_xlabel("$p - p_c$") 92 | ax_classic_chi.set_ylabel("Susceptibility " 93 | "($\\frac{\\sum_{i>0} s_i^2}{\\sum_{i > 0} s_i}$)") 94 | 95 | ax_exp.plot( 96 | p_values - 1/(size-1), 97 | [np.mean(expected_comps[p]) for p in p_values]) 98 | ax_exp.set_xlabel("$p - p_c$") 99 | ax_exp.set_ylabel("Expected component size") 100 | 101 | ax_isotropic_chi.plot( 102 | p_values - 1/(size-1), 103 | [np.mean(isotropic_chis[p]) for p in p_values]) 104 | ax_isotropic_chi.set_xlabel("$p - p_c$") 105 | ax_isotropic_chi.set_ylabel("Susceptibility " 106 | "($\\sum_i s_i^2 - \\frac{1}{n} \\sum_i s_i^3$)") 107 | 108 | fig.savefig(args.figure) 109 | 110 | 111 | report = { 112 | "size": size, 113 | "p_values": list(p_values), 114 | "largest_component_size": 115 | [np.mean(largest_comps[p]) for p in p_values], 116 | "old_school_susceptibility": 117 | [np.mean(classic_chis[p]) for p in p_values], 118 | "expected_component_size": 119 | [np.mean(expected_comps[p]) for p in p_values], 120 | "susceptibility": 121 | [np.mean(isotropic_chis[p]) for p in p_values]} 122 | 123 | with open(args.output, 'w') as out: 124 | json.dump(report, out) 125 | -------------------------------------------------------------------------------- /flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | extend-ignore = E402 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "reticula" 3 | version = "0.13.0" 4 | description="Analyse temporal network and hypergraphs efficiently." 5 | authors = [{name="Arash Badie-Modiri", email="arashbm@gmail.com"}] 6 | requires-python = ">=3.10" 7 | license = {text = "MIT"} 8 | readme = "README.md" 9 | keywords = [ 10 | "Complex Networks", "Networks", "network", 11 | "Graphs", "Graph Theory", "graph", 12 | "Temporal Networks", "temporal network", 13 | "Hypergraphs", "hypergraph", "hyper-graph" 14 | ] 15 | 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Intended Audience :: Education", 19 | "Intended Audience :: Science/Research", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: POSIX :: Linux", 22 | "Programming Language :: C++", 23 | "Programming Language :: Python :: 3", 24 | "Programming Language :: Python :: Implementation :: CPython", 25 | "Topic :: Scientific/Engineering", 26 | "Topic :: Scientific/Engineering :: Physics", 27 | "Topic :: Scientific/Engineering :: Mathematics", 28 | "Topic :: Scientific/Engineering :: Information Analysis" 29 | ] 30 | 31 | [project.urls] 32 | homepage = "https://reticula.network/" 33 | documentation = "https://docs.reticula.network/" 34 | repository = "https://github.com/reticula-network/reticula-python" 35 | bug-tracker= "https://github.com/reticula-network/reticula-python/issues" 36 | 37 | [project.optional-dependencies] 38 | test = ["pytest", "hypothesis", "pytest-xdist"] 39 | 40 | [build-system] 41 | requires = [ 42 | "setuptools>=67", 43 | "wheel", 44 | "scikit-build-core>=0.10.7", 45 | "nanobind==2.2.0" 46 | ] 47 | build-backend = "scikit_build_core.build" 48 | 49 | [tool.scikit-build] 50 | wheel.py-api = "cp312" 51 | build-dir = "build/{wheel_tag}" 52 | 53 | [tool.cibuildwheel] 54 | build-verbosity = 2 55 | 56 | [tool.cibuildwheel.linux] 57 | archs = ["x86_64"] 58 | repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" 59 | 60 | [tool.cibuildwheel.macos] 61 | archs = ["x86_64", "arm64"] 62 | repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}" 63 | 64 | [tool.cibuildwheel.macos.environment] 65 | MACOSX_DEPLOYMENT_TARGET = "10.15" 66 | 67 | [tool.cibuildwheel.windows] 68 | archs = ["AMD64"] 69 | before-build = "pip install delvewheel" 70 | repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" 71 | -------------------------------------------------------------------------------- /scripts/build-wheels.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -ex 4 | 5 | mkdir -p dist 6 | rm -f wheelhouse/* 7 | 8 | for version in python3.8 python3.9 python3.10 python3.11 pypy3.9; do 9 | rm -f dist/* 10 | rm -rf _skbuild 11 | singularity exec --cleanenv --bind .:/reticula-python --pwd /reticula-python \ 12 | docker://quay.io/pypa/manylinux2014_x86_64 \ 13 | ${version} -m pip wheel . -w dist --verbose 14 | singularity exec --cleanenv --bind .:/reticula-python --pwd /reticula-python \ 15 | docker://quay.io/pypa/manylinux2014_x86_64 \ 16 | auditwheel repair --strip dist/*.whl 17 | done 18 | -------------------------------------------------------------------------------- /src/algorithms.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bind_core.hpp" 4 | 5 | #include 6 | 7 | #include "type_str/edges.hpp" 8 | #include "type_str/scalars.hpp" 9 | 10 | #include "type_utils.hpp" 11 | 12 | namespace nb = nanobind; 13 | using namespace nanobind::literals; 14 | 15 | template 16 | struct declare_degree_sequence_algorithms { 17 | void operator()(nb::module_& m) { 18 | m.def("is_graphic", 19 | &reticula::is_graphic>, 20 | "degree_seq"_a, 21 | nb::call_guard()); 22 | 23 | m.def("is_digraphic", 24 | &reticula::is_digraphic>>, 25 | "in_out_degree_seq"_a, 26 | nb::call_guard()); 27 | } 28 | }; 29 | 30 | void declare_typed_assortativity_algorithms(nb::module_& m); 31 | void declare_typed_degree_algorithms(nb::module_& m); 32 | void declare_typed_edge_degree_algorithms(nb::module_& m); 33 | void declare_typed_basic_temporal_network_algorithms(nb::module_& m); 34 | void declare_typed_density_algorithms(nb::module_& m); 35 | void declare_typed_directed_connectivity_algorithms(nb::module_& m); 36 | void declare_typed_temporal_adjacency_algorithms(nb::module_& m); 37 | void declare_typed_undirected_connectivity_algorithms(nb::module_& m); 38 | 39 | void declare_algorithms(nb::module_& m) { 40 | types::run_each< 41 | metal::transform< 42 | metal::lambda, 43 | metal::list>>{}(m); 44 | 45 | declare_typed_assortativity_algorithms(m); 46 | declare_typed_degree_algorithms(m); 47 | declare_typed_edge_degree_algorithms(m); 48 | declare_typed_basic_temporal_network_algorithms(m); 49 | declare_typed_density_algorithms(m); 50 | declare_typed_directed_connectivity_algorithms(m); 51 | declare_typed_temporal_adjacency_algorithms(m); 52 | declare_typed_undirected_connectivity_algorithms(m); 53 | } 54 | -------------------------------------------------------------------------------- /src/algorithms/assortativity.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../bind_core.hpp" 5 | 6 | #include 7 | 8 | #include "../type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_undirected_assortativity_algorithms { 15 | void operator()(nb::module_& m) { 16 | m.def("degree_assortativity", 17 | &reticula::degree_assortativity, 18 | "network"_a, 19 | nb::call_guard()); 20 | 21 | m.def("attribute_assortativity", 22 | &reticula::attribute_assortativity>>, 26 | "network"_a, 27 | "attribute_map"_a, 28 | "default_value"_a, 29 | nb::call_guard()); 30 | m.def("attribute_assortativity", 31 | &reticula::attribute_assortativity>, 33 | "network"_a, 34 | "attribute_fun"_a, 35 | nb::call_guard()); 36 | } 37 | }; 38 | 39 | template 40 | struct declare_directed_assortativity_algorithms { 41 | void operator()(nb::module_& m) { 42 | m.def("in_in_degree_assortativity", 43 | &reticula::in_in_degree_assortativity, 44 | "network"_a, 45 | nb::call_guard()); 46 | m.def("in_out_degree_assortativity", 47 | &reticula::in_out_degree_assortativity, 48 | "network"_a, 49 | nb::call_guard()); 50 | m.def("out_in_degree_assortativity", 51 | &reticula::out_in_degree_assortativity, 52 | "network"_a, 53 | nb::call_guard()); 54 | m.def("out_out_degree_assortativity", 55 | &reticula::out_out_degree_assortativity, 56 | "network"_a, 57 | nb::call_guard()); 58 | 59 | m.def("attribute_assortativity", 60 | &reticula::attribute_assortativity>, 64 | std::unordered_map< 65 | typename EdgeT::VertexType, double, 66 | reticula::hash>>, 67 | "network"_a, 68 | "mutator_attribute_map"_a, 69 | "mutated_attribute_map"_a, 70 | "mutator_default_value"_a, 71 | "mutated_default_value"_a, 72 | nb::call_guard()); 73 | m.def("attribute_assortativity", 74 | &reticula::attribute_assortativity, 76 | std::function>, 77 | "network"_a, 78 | "mutator_attribute_fun"_a, 79 | "mutated_attribute_fun"_a, 80 | nb::call_guard()); 81 | } 82 | }; 83 | 84 | void declare_typed_assortativity_algorithms(nb::module_& m) { 85 | types::run_each< 86 | metal::transform< 87 | metal::lambda, 88 | metal::join< 89 | types::first_order_undirected_edges, 90 | types::second_order_undirected_edges, 91 | types::first_order_undirected_hyperedges>>>{}(m); 92 | 93 | types::run_each< 94 | metal::transform< 95 | metal::lambda, 96 | metal::join< 97 | types::first_order_directed_edges, 98 | types::second_order_directed_edges, 99 | types::first_order_directed_hyperedges>>>{}(m); 100 | } 101 | -------------------------------------------------------------------------------- /src/algorithms/basic_temporal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../bind_core.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include "../type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_basic_temporal_network_algorithms { 15 | void operator()(nb::module_& m) { 16 | m.def("time_window", 17 | &reticula::time_window, 18 | "temporal_network"_a, 19 | nb::call_guard()); 20 | m.def("cause_time_window", 21 | &reticula::cause_time_window, 22 | "temporal_network"_a, 23 | nb::call_guard()); 24 | m.def("effect_time_window", 25 | &reticula::effect_time_window, 26 | "temporal_network"_a, 27 | nb::call_guard()); 28 | 29 | m.def("static_projection", 30 | &reticula::static_projection, 31 | "temporal_network"_a, 32 | nb::call_guard()); 33 | 34 | m.def("link_timeline", 35 | &reticula::link_timeline, 36 | "temporal_network"_a, "static_link"_a, 37 | nb::call_guard()); 38 | m.def("link_timelines", 39 | &reticula::link_timelines, 40 | "temporal_network"_a, 41 | nb::call_guard()); 42 | } 43 | }; 44 | 45 | void declare_typed_basic_temporal_network_algorithms(nb::module_& m) { 46 | types::run_each< 47 | metal::transform< 48 | metal::lambda, 49 | metal::join< 50 | types::first_order_temporal_edges>>>{}(m); 51 | } 52 | -------------------------------------------------------------------------------- /src/algorithms/degree.cpp: -------------------------------------------------------------------------------- 1 | #include "../bind_core.hpp" 2 | 3 | #include 4 | 5 | #include "../type_utils.hpp" 6 | 7 | namespace nb = nanobind; 8 | using namespace nanobind::literals; 9 | 10 | template 11 | struct declare_undirected_degree_algorithms { 12 | void operator()(nb::module_& m) { 13 | m.def("degree", 14 | &reticula::degree, 15 | "network"_a, 16 | "vertex"_a, 17 | nb::call_guard()); 18 | m.def("degree_sequence", 19 | &reticula::degree_sequence, 20 | "network"_a, 21 | nb::call_guard()); 22 | } 23 | }; 24 | 25 | template 26 | struct declare_degree_algorithms { 27 | void operator()(nb::module_& m) { 28 | m.def("in_degree", 29 | &reticula::in_degree, 30 | "network"_a, 31 | "vertex"_a, 32 | nb::call_guard()); 33 | m.def("out_degree", 34 | &reticula::out_degree, 35 | "network"_a, 36 | "vertex"_a, 37 | nb::call_guard()); 38 | m.def("incident_degree", 39 | &reticula::incident_degree, 40 | "network"_a, 41 | "vertex"_a, 42 | nb::call_guard()); 43 | 44 | m.def("in_degree_sequence", 45 | &reticula::in_degree_sequence, 46 | "network"_a, 47 | nb::call_guard()); 48 | m.def("out_degree_sequence", 49 | &reticula::out_degree_sequence, 50 | "network"_a, 51 | nb::call_guard()); 52 | m.def("incident_degree_sequence", 53 | &reticula::incident_degree_sequence, 54 | "network"_a, 55 | nb::call_guard()); 56 | m.def("in_out_degree_pair_sequence", 57 | &reticula::in_out_degree_pair_sequence, 58 | "network"_a, 59 | nb::call_guard()); 60 | } 61 | }; 62 | 63 | template 64 | struct declare_directed_assortativity_algorithms { 65 | void operator()(nb::module_& m) { 66 | m.def("in_in_degree_assortativity", 67 | &reticula::in_in_degree_assortativity, 68 | "network"_a, 69 | nb::call_guard()); 70 | m.def("in_out_degree_assortativity", 71 | &reticula::in_out_degree_assortativity, 72 | "network"_a, 73 | nb::call_guard()); 74 | m.def("out_in_degree_assortativity", 75 | &reticula::out_in_degree_assortativity, 76 | "network"_a, 77 | nb::call_guard()); 78 | m.def("out_out_degree_assortativity", 79 | &reticula::out_out_degree_assortativity, 80 | "network"_a, 81 | nb::call_guard()); 82 | 83 | m.def("attribute_assortativity", 84 | &reticula::attribute_assortativity>, 88 | std::unordered_map< 89 | typename EdgeT::VertexType, double, 90 | reticula::hash>>, 91 | "network"_a, 92 | "mutator_attribute_map"_a, 93 | "mutated_attribute_map"_a, 94 | "mutator_default_value"_a, 95 | "mutated_default_value"_a, 96 | nb::call_guard()); 97 | m.def("attribute_assortativity", 98 | &reticula::attribute_assortativity, 100 | std::function>, 101 | "network"_a, 102 | "mutator_attribute_fun"_a, 103 | "mutated_attribute_fun"_a, 104 | nb::call_guard()); 105 | } 106 | }; 107 | 108 | void declare_typed_degree_algorithms(nb::module_& m) { 109 | types::run_each< 110 | metal::transform< 111 | metal::lambda, 112 | metal::join< 113 | types::first_order_undirected_edges, 114 | types::second_order_undirected_edges, 115 | types::first_order_undirected_hyperedges, 116 | types::first_order_undirected_temporal_edges, 117 | types::first_order_undirected_temporal_hyperedges>>>{}(m); 118 | 119 | types::run_each< 120 | metal::transform< 121 | metal::lambda, 122 | types::all_edge_types>>{}(m); 123 | } 124 | -------------------------------------------------------------------------------- /src/algorithms/density.cpp: -------------------------------------------------------------------------------- 1 | #include "../bind_core.hpp" 2 | 3 | #include 4 | 5 | #include "../type_utils.hpp" 6 | 7 | namespace nb = nanobind; 8 | using namespace nanobind::literals; 9 | 10 | template 11 | struct declare_density_algorithm { 12 | void operator()(nb::module_& m) { 13 | m.def("density", 14 | nb::overload_cast&>( 15 | &reticula::density), 16 | "network"_a, 17 | nb::call_guard()); 18 | } 19 | }; 20 | 21 | void declare_typed_density_algorithms(nb::module_& m) { 22 | types::run_each< 23 | metal::transform< 24 | metal::lambda, 25 | metal::join< 26 | types::first_order_undirected_edges, 27 | types::second_order_undirected_edges, 28 | types::first_order_directed_edges, 29 | types::second_order_directed_edges>>>{}(m); 30 | } 31 | -------------------------------------------------------------------------------- /src/algorithms/directed_connectivity.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../bind_core.hpp" 4 | 5 | #include 6 | 7 | #include "../type_utils.hpp" 8 | 9 | namespace nb = nanobind; 10 | using namespace nanobind::literals; 11 | 12 | template 13 | struct declare_directed_connectivity_algorithms { 14 | void operator()(nb::module_& m) { 15 | m.def("is_acyclic", 16 | &reticula::is_acyclic, 17 | "directed_network"_a, 18 | nb::call_guard()); 19 | 20 | m.def("topological_order", 21 | &reticula::topological_order, 22 | "directed_network"_a, 23 | nb::call_guard()); 24 | 25 | m.def("out_component", 26 | &reticula::out_component, 27 | "directed_network"_a, "vert"_a, "size_hint"_a = 0, 28 | nb::call_guard()); 29 | m.def("out_components", 30 | &reticula::out_components, 31 | "directed_network"_a, 32 | nb::call_guard()); 33 | m.def("out_component_sizes", 34 | &reticula::out_component_sizes, 35 | "directed_network"_a, 36 | nb::call_guard()); 37 | m.def("out_component_size_estimates", 38 | &reticula::out_component_size_estimates, 39 | "directed_network"_a, "seed"_a, 40 | nb::call_guard()); 41 | 42 | m.def("in_component", 43 | &reticula::in_component, 44 | "directed_network"_a, "vert"_a, "size_hint"_a = 0, 45 | nb::call_guard()); 46 | m.def("in_components", 47 | &reticula::in_components, 48 | "directed_network"_a, 49 | nb::call_guard()); 50 | m.def("in_component_sizes", 51 | &reticula::in_component_sizes, 52 | "directed_network"_a, 53 | nb::call_guard()); 54 | m.def("in_component_size_estimates", 55 | &reticula::in_component_size_estimates, 56 | "directed_network"_a, "seed"_a, 57 | nb::call_guard()); 58 | 59 | m.def("weakly_connected_component", 60 | &reticula::weakly_connected_component, 61 | "directed_network"_a, "vert"_a, "size_hint"_a = 0, 62 | nb::call_guard()); 63 | m.def("is_weakly_connected", 64 | &reticula::is_weakly_connected, 65 | "directed_network"_a, 66 | nb::call_guard()); 67 | m.def("largest_weakly_connected_component", 68 | &reticula::largest_weakly_connected_component, 69 | "directed_network"_a, 70 | nb::call_guard()); 71 | m.def("weakly_connected_components", 72 | &reticula::weakly_connected_components, 73 | "directed_network"_a, "singletons"_a = true, 74 | nb::call_guard()); 75 | 76 | m.def("is_reachable", 77 | &reticula::is_reachable, 78 | "network"_a, "source"_a, "destination"_a, 79 | nb::call_guard()); 80 | 81 | m.def("shortest_path_lengths_from", 82 | &reticula::shortest_path_lengths_from, 83 | "network"_a, "source"_a, 84 | nb::call_guard()); 85 | m.def("shortest_path_lengths_to", 86 | &reticula::shortest_path_lengths_to, 87 | "network"_a, "destination"_a, 88 | nb::call_guard()); 89 | } 90 | }; 91 | 92 | void declare_typed_directed_connectivity_algorithms(nb::module_& m) { 93 | types::run_each< 94 | metal::transform< 95 | metal::lambda, 96 | metal::join< 97 | types::first_order_directed_edges, 98 | types::first_order_directed_hyperedges, 99 | types::second_order_directed_edges>>>{}(m); 100 | } 101 | -------------------------------------------------------------------------------- /src/algorithms/edge_degree.cpp: -------------------------------------------------------------------------------- 1 | #include "../bind_core.hpp" 2 | 3 | #include 4 | 5 | #include "../type_utils.hpp" 6 | 7 | namespace nb = nanobind; 8 | using namespace nanobind::literals; 9 | 10 | template 11 | struct declare_undirected_edge_degree_algorithms { 12 | void operator()(nb::module_& m) { 13 | m.def("edge_degree", 14 | &reticula::edge_degree, 15 | "edge"_a, 16 | nb::call_guard()); 17 | m.def("edge_degree_sequence", 18 | &reticula::edge_degree_sequence, 19 | "network"_a, 20 | nb::call_guard()); 21 | } 22 | }; 23 | 24 | template 25 | struct declare_edge_degree_algorithms { 26 | void operator()(nb::module_& m) { 27 | m.def("edge_in_degree", 28 | &reticula::edge_in_degree, 29 | "edge"_a, 30 | nb::call_guard()); 31 | m.def("edge_out_degree", 32 | &reticula::edge_out_degree, 33 | "edge"_a, 34 | nb::call_guard()); 35 | m.def("edge_incident_degree", 36 | &reticula::edge_incident_degree, 37 | "edge"_a, 38 | nb::call_guard()); 39 | 40 | m.def("edge_in_degree_sequence", 41 | &reticula::edge_in_degree_sequence, 42 | "network"_a, 43 | nb::call_guard()); 44 | m.def("edge_out_degree_sequence", 45 | &reticula::edge_out_degree_sequence, 46 | "network"_a, 47 | nb::call_guard()); 48 | m.def("edge_incident_degree_sequence", 49 | &reticula::edge_incident_degree_sequence, 50 | "network"_a, 51 | nb::call_guard()); 52 | m.def("edge_in_out_degree_pair_sequence", 53 | &reticula::edge_in_out_degree_pair_sequence, 54 | "network"_a, 55 | nb::call_guard()); 56 | } 57 | }; 58 | 59 | void declare_typed_edge_degree_algorithms(nb::module_& m) { 60 | types::run_each< 61 | metal::transform< 62 | metal::lambda, 63 | metal::join< 64 | types::first_order_undirected_edges, 65 | types::second_order_undirected_edges, 66 | types::first_order_undirected_hyperedges, 67 | types::first_order_undirected_temporal_edges, 68 | types::first_order_undirected_temporal_hyperedges>>>{}(m); 69 | 70 | types::run_each< 71 | metal::transform< 72 | metal::lambda, 73 | types::all_edge_types>>{}(m); 74 | } 75 | -------------------------------------------------------------------------------- /src/algorithms/temporal_adjacency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../bind_core.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include "../type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_temporal_network_adjacency_algorithms { 15 | void operator()(nb::module_& m) { 16 | using EdgeT = typename AdjT::EdgeType; 17 | m.def("event_graph", 18 | &reticula::event_graph, 19 | "temporal_network"_a, "adjacency"_a, 20 | nb::call_guard()); 21 | 22 | m.def("make_implicit_event_graph", 23 | [](const std::vector& events, const AdjT& adj) { 24 | return reticula::implicit_event_graph(events, adj); 25 | }, "events"_a, "adjacency"_a, 26 | nb::call_guard()); 27 | m.def("make_implicit_event_graph", 28 | [](const reticula::network& temp, const AdjT& adj) { 29 | return reticula::implicit_event_graph( 30 | temp.edges_cause(), adj); 31 | }, "temporal_network"_a, "adjacency"_a, 32 | nb::call_guard()); 33 | 34 | m.def("is_reachable", 35 | &reticula::is_reachable, 36 | "temporal_network"_a, "temporal_adjacency"_a, 37 | "source"_a, "t0"_a, "destination"_a, "t1"_a, 38 | nb::call_guard()); 39 | 40 | m.def("out_cluster", 41 | nb::overload_cast< 42 | const reticula::network&, 43 | const AdjT&, 44 | const typename EdgeT::VertexType&, 45 | typename EdgeT::TimeType>( 46 | &reticula::out_cluster), 47 | "temporal_network"_a, "temporal_adjacency"_a, 48 | "vertex"_a, "time"_a, 49 | nb::call_guard()); 50 | 51 | m.def("out_cluster", 52 | nb::overload_cast< 53 | const reticula::network&, 54 | const AdjT&, 55 | const EdgeT&>( 56 | &reticula::out_cluster), 57 | "temporal_network"_a, "temporal_adjacency"_a, 58 | "event"_a, 59 | nb::call_guard()); 60 | 61 | m.def("out_clusters", 62 | &reticula::out_clusters, 63 | "temporal_network"_a, "temporal_adjacency"_a, 64 | nb::call_guard()); 65 | m.def("out_cluster_sizes", 66 | &reticula::out_cluster_sizes, 67 | "temporal_network"_a, "temporal_adjacency"_a, 68 | nb::call_guard()); 69 | m.def("out_cluster_size_estimates", 70 | &reticula::out_cluster_size_estimates, 71 | "temporal_network"_a, "temporal_adjacency"_a, 72 | "time_resolution"_a, "seed"_a, 73 | nb::call_guard()); 74 | 75 | 76 | m.def("in_cluster", 77 | nb::overload_cast< 78 | const reticula::network&, 79 | const AdjT&, 80 | const typename EdgeT::VertexType&, 81 | typename EdgeT::TimeType>( 82 | &reticula::in_cluster), 83 | "temporal_network"_a, "temporal_adjacency"_a, 84 | "vertex"_a, "time"_a, 85 | nb::call_guard()); 86 | 87 | m.def("in_cluster", 88 | nb::overload_cast< 89 | const reticula::network&, 90 | const AdjT&, 91 | const EdgeT&>( 92 | &reticula::in_cluster), 93 | "temporal_network"_a, "temporal_adjacency"_a, 94 | "event"_a, 95 | nb::call_guard()); 96 | 97 | m.def("in_clusters", 98 | &reticula::in_clusters, 99 | "temporal_network"_a, "temporal_adjacency"_a, 100 | nb::call_guard()); 101 | m.def("in_cluster_sizes", 102 | &reticula::in_cluster_sizes, 103 | "temporal_network"_a, "temporal_adjacency"_a, 104 | nb::call_guard()); 105 | m.def("in_cluster_size_estimates", 106 | &reticula::in_cluster_size_estimates, 107 | "temporal_network"_a, "temporal_adjacency"_a, 108 | "time_resolution"_a, "seed"_a, 109 | nb::call_guard()); 110 | } 111 | }; 112 | 113 | void declare_typed_temporal_adjacency_algorithms(nb::module_& m) { 114 | types::run_each< 115 | metal::transform< 116 | metal::lambda, 117 | types::first_order_temporal_adjacency_types>>{}(m); 118 | } 119 | -------------------------------------------------------------------------------- /src/algorithms/undirected_connectivity.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../bind_core.hpp" 4 | 5 | #include 6 | 7 | #include "../type_utils.hpp" 8 | 9 | namespace nb = nanobind; 10 | using namespace nanobind::literals; 11 | 12 | template 13 | struct declare_undirected_connectivity_algorithms { 14 | void operator()(nb::module_& m) { 15 | m.def("connected_component", 16 | &reticula::connected_component, 17 | "undirected_network"_a, "vert"_a, "size_hint"_a = 0, 18 | nb::call_guard()); 19 | m.def("is_connected", 20 | &reticula::is_connected, 21 | "undirected_network"_a, 22 | nb::call_guard()); 23 | m.def("largest_connected_component", 24 | &reticula::largest_connected_component, 25 | "undirected_network"_a, 26 | nb::call_guard()); 27 | m.def("connected_components", 28 | &reticula::connected_components, 29 | "undirected_network"_a, "singletons"_a = true, 30 | nb::call_guard()); 31 | 32 | m.def("is_reachable", 33 | &reticula::is_reachable, 34 | "network"_a, "source"_a, "destination"_a, 35 | nb::call_guard()); 36 | 37 | m.def("shortest_path_lengths_from", 38 | &reticula::shortest_path_lengths_from, 39 | "network"_a, "source"_a, 40 | nb::call_guard()); 41 | m.def("shortest_path_lengths_to", 42 | &reticula::shortest_path_lengths_to, 43 | "network"_a, "destination"_a, 44 | nb::call_guard()); 45 | } 46 | }; 47 | 48 | void declare_typed_undirected_connectivity_algorithms(nb::module_& m) { 49 | types::run_each< 50 | metal::transform< 51 | metal::lambda, 52 | metal::join< 53 | types::first_order_undirected_edges, 54 | types::first_order_undirected_hyperedges, 55 | types::second_order_undirected_edges>>>{}(m); 56 | } 57 | -------------------------------------------------------------------------------- /src/bind_core.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | -------------------------------------------------------------------------------- /src/common_edge_properties.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "bind_core.hpp" 5 | #include "nanobind/operators.h" 6 | #include "type_str/scalars.hpp" 7 | #include "type_str/edges.hpp" 8 | #include "type_utils.hpp" 9 | #include "type_handles.hpp" 10 | 11 | namespace nb = nanobind; 12 | using namespace nanobind::literals; 13 | 14 | template 15 | nb::class_ define_basic_edge_concept(nb::module_& m) { 16 | nb::class_ cls(m, python_type_str().c_str()); 17 | 18 | cls.def(nb::init(), 19 | "edge"_a, 20 | nb::call_guard()) 21 | .def("mutated_verts", 22 | &EdgeT::mutated_verts, 23 | nb::call_guard()) 24 | .def("mutator_verts", 25 | &EdgeT::mutator_verts, 26 | nb::call_guard()) 27 | .def("incident_verts", 28 | &EdgeT::incident_verts, 29 | nb::call_guard()) 30 | .def("is_incident", 31 | &EdgeT::is_incident, 32 | "vert"_a, 33 | nb::call_guard()) 34 | .def("is_in_incident", 35 | &EdgeT::is_in_incident, 36 | "vert"_a, 37 | nb::call_guard()) 38 | .def("is_out_incident", 39 | &EdgeT::is_out_incident, 40 | "vert"_a, 41 | nb::call_guard()) 42 | .def(nb::self == nb::self, 43 | nb::call_guard()) 44 | .def(nb::self != nb::self, 45 | nb::call_guard()) 46 | .def(nb::self < nb::self, 47 | nb::call_guard()) 48 | .def(nb::hash(nb::self), 49 | nb::call_guard()) 50 | .def("__copy__", [](const EdgeT& self) { 51 | return EdgeT(self); 52 | }).def("__deepcopy__", [](const EdgeT& self, nb::dict) { 53 | return EdgeT(self); 54 | }, "memo"_a) 55 | .def("__repr__", [](const EdgeT& a) { 56 | return fmt::format("{}", a); 57 | }).def_static("__class_repr__", []() { 58 | return fmt::format("", type_str{}()); 59 | }).def_static("__class_name__", []() { 60 | return type_str{}(); 61 | }).def_static("vertex_type", []() { 62 | return types::handle_for(); 63 | }); 64 | 65 | using VertT = typename EdgeT::VertexType; 66 | if constexpr (reticula::temporal_network_edge) { 67 | using TimeT = typename EdgeT::TimeType; 68 | cls.def("cause_time", 69 | &EdgeT::cause_time, 70 | nb::call_guard()) 71 | .def("effect_time", 72 | &EdgeT::effect_time, 73 | nb::call_guard()) 74 | .def("static_projection", 75 | &EdgeT::static_projection, 76 | nb::call_guard()) 77 | .def_static("static_projection_type", []() { 78 | return types::handle_for(); 79 | }) 80 | .def_static("time_type", []() { 81 | return types::handle_for(); 82 | }); 83 | 84 | m.def("adjacent", 85 | nb::overload_cast< 86 | const EdgeT&, const EdgeT&>(&reticula::adjacent), 87 | "edge1"_a, "edge2"_a, 88 | nb::call_guard()); 89 | m.def("effect_lt", 90 | nb::overload_cast< 91 | const EdgeT&, const EdgeT&>(&reticula::effect_lt), 92 | "edge1"_a, "edge2"_a, 93 | nb::call_guard()); 94 | } else { 95 | m.def("adjacent", 96 | nb::overload_cast< 97 | const EdgeT&, const EdgeT&>(&reticula::adjacent), 98 | "edge1"_a, "edge2"_a, 99 | nb::call_guard()); 100 | m.def("effect_lt", 101 | nb::overload_cast( 102 | &reticula::effect_lt), 103 | "edge1"_a, "edge2"_a, 104 | nb::call_guard()); 105 | } 106 | 107 | m.def(fmt::format("is_network_edge_{}", 108 | python_type_str()).c_str(), 109 | []{ return reticula::network_edge; }); 110 | m.def(fmt::format("is_static_edge_{}", 111 | python_type_str()).c_str(), 112 | []{ return reticula::static_network_edge; }); 113 | m.def(fmt::format("is_temporal_edge_{}", 114 | python_type_str()).c_str(), 115 | []{ return reticula::temporal_network_edge; }); 116 | m.def(fmt::format("is_instantaneous_{}", 117 | python_type_str()).c_str(), 118 | []{ return reticula::is_instantaneous_v; }); 119 | m.def(fmt::format("is_undirected_{}", 120 | python_type_str()).c_str(), 121 | []{ return reticula::is_undirected_v; }); 122 | m.def(fmt::format("is_dyadic_{}", 123 | python_type_str()).c_str(), 124 | []{ return reticula::is_dyadic_v; }); 125 | 126 | return cls; 127 | } 128 | -------------------------------------------------------------------------------- /src/generators.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | 3 | namespace nb = nanobind; 4 | 5 | void declare_typed_generators(nb::module_& m); 6 | void declare_typed_random_networks(nb::module_& m); 7 | void declare_typed_activation_networks(nb::module_& m); 8 | 9 | void declare_typed_static_mrrm_algorithms(nb::module_& m); 10 | void declare_typed_temporal_mrrm_algorithms(nb::module_& m); 11 | 12 | void declare_generators(nb::module_& m) { 13 | declare_typed_generators(m); 14 | declare_typed_random_networks(m); 15 | declare_typed_activation_networks(m); 16 | 17 | nb::module_ mrrm_m = m.def_submodule("microcanonical_reference_models"); 18 | declare_typed_temporal_mrrm_algorithms(mrrm_m); 19 | declare_typed_static_mrrm_algorithms(mrrm_m); 20 | } 21 | -------------------------------------------------------------------------------- /src/implicit_event_graph_components.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bind_core.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include "type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_implicit_event_graph_component_algorithms { 15 | void operator()(nb::module_& m) { 16 | using EdgeT = typename AdjT::EdgeType; 17 | m.def("out_component", 18 | &reticula::out_component, 19 | "implicit_event_graph"_a, "root"_a, 20 | nb::call_guard()); 21 | m.def("out_components", 22 | &reticula::out_components, 23 | "implicit_event_graph"_a, 24 | nb::call_guard()); 25 | m.def("out_component_sizes", 26 | &reticula::out_component_sizes, 27 | "implicit_event_graph"_a, 28 | nb::call_guard()); 29 | m.def("out_component_size_estimates", 30 | &reticula::out_component_size_estimates, 31 | "implicit_event_graph"_a, "seed"_a, 32 | nb::call_guard()); 33 | 34 | m.def("in_component", 35 | &reticula::in_component, 36 | "implicit_event_graph"_a, "root"_a, 37 | nb::call_guard()); 38 | m.def("in_components", 39 | &reticula::in_components, 40 | "implicit_event_graph"_a, 41 | nb::call_guard()); 42 | m.def("in_component_sizes", 43 | &reticula::in_component_sizes, 44 | "implicit_event_graph"_a, 45 | nb::call_guard()); 46 | m.def("in_component_size_estimates", 47 | &reticula::in_component_size_estimates, 48 | "implicit_event_graph"_a, "seed"_a, 49 | nb::call_guard()); 50 | 51 | m.def("weakly_connected_component", 52 | &reticula::weakly_connected_component, 53 | "implicit_event_graph"_a, "root"_a, 54 | nb::call_guard()); 55 | m.def("weakly_connected_components", 56 | &reticula::weakly_connected_components, 57 | "implicit_event_graph"_a, "singletons"_a = true, 58 | nb::call_guard()); 59 | } 60 | }; 61 | 62 | void declare_typed_implicit_event_graph_components(nb::module_& m) { 63 | types::run_each< 64 | metal::transform< 65 | metal::lambda, 66 | types::first_order_temporal_adjacency_types>>{}(m); 67 | } 68 | -------------------------------------------------------------------------------- /src/implicit_event_graphs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bind_core.hpp" 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "type_str/implicit_event_graphs.hpp" 10 | #include "type_utils.hpp" 11 | #include "type_handles.hpp" 12 | 13 | namespace nb = nanobind; 14 | using namespace nanobind::literals; 15 | 16 | template 17 | struct declare_implicit_event_graph_class { 18 | void operator()(nb::module_ &m) { 19 | using EdgeT = typename AdjT::EdgeType; 20 | using Net = reticula::implicit_event_graph; 21 | nb::class_( 22 | m, python_type_str().c_str()) 23 | .def(nb::init, AdjT>(), 24 | "events"_a, "temporal_adjacency"_a, 25 | nb::call_guard()) 26 | .def(nb::init< 27 | std::vector, 28 | std::vector, 29 | AdjT>(), 30 | "events"_a, "verts"_a, "temporal_adjacency"_a, 31 | nb::call_guard()) 32 | .def(nb::init, AdjT>(), 33 | "temporal_network"_a, "temporal_adjacency"_a, 34 | nb::call_guard()) 35 | .def("events_cause", 36 | &Net::events_cause, 37 | nb::call_guard()) 38 | .def("events_effect", 39 | &Net::events_effect, 40 | nb::call_guard()) 41 | .def("temporal_net_vertices", 42 | &Net::temporal_net_vertices, 43 | nb::call_guard()) 44 | .def("temporal_adjacency", 45 | &Net::temporal_adjacency, 46 | nb::call_guard()) 47 | .def("time_window", 48 | &Net::time_window, 49 | nb::call_guard()) 50 | .def("predecessors", 51 | &Net::predecessors, 52 | "event"_a, "just_first"_a = true, 53 | nb::call_guard()) 54 | .def("successors", 55 | &Net::successors, 56 | "event"_a, "just_first"_a = true, 57 | nb::call_guard()) 58 | .def("neighbours", 59 | &Net::neighbours, 60 | "event"_a, "just_first"_a = true, 61 | nb::call_guard()) 62 | .def("__copy__", [](const Net& self) { 63 | return Net(self); 64 | }).def("__deepcopy__", [](const Net& self, nb::dict) { 65 | return Net(self); 66 | }, "memo"_a) 67 | .def("__repr__", [](const Net& a) { 68 | return fmt::format("{}", a); 69 | }).def_static("edge_type", []() { 70 | return types::handle_for(); 71 | }).def_static("vertex_type", []() { 72 | return types::handle_for(); 73 | }).def_static("__class_repr__", []() { 74 | return fmt::format("", type_str{}()); 75 | }).def_static("__class_name__", []() { 76 | return type_str{}(); 77 | }); 78 | } 79 | }; 80 | 81 | void declare_typed_implicit_event_graphs(nb::module_& m) { 82 | types::run_each< 83 | metal::transform< 84 | metal::lambda, 85 | types::first_order_temporal_adjacency_types>>{}(m); 86 | } 87 | -------------------------------------------------------------------------------- /src/interval_sets.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "type_str/intervals.hpp" 10 | #include "type_utils.hpp" 11 | #include "type_handles.hpp" 12 | 13 | namespace nb = nanobind; 14 | using namespace nanobind::literals; 15 | 16 | template 17 | struct declare_interval_set_types { 18 | void operator()(nb::module_& m) { 19 | using IntSet = reticula::interval_set; 20 | nb::class_( 21 | m, python_type_str().c_str()) 22 | .def(nb::init<>(), 23 | nb::call_guard()) 24 | .def("insert", 25 | &IntSet::insert, 26 | "start"_a, "end"_a, 27 | nb::call_guard()) 28 | .def("merge", 29 | &IntSet::merge, 30 | "other"_a, 31 | nb::call_guard()) 32 | .def("covers", 33 | &IntSet::covers, 34 | "time"_a, 35 | nb::call_guard()) 36 | .def("cover", 37 | &IntSet::cover, 38 | nb::call_guard()) 39 | .def("__iter__", [](const IntSet& c) { 40 | return nb::make_iterator( 41 | nb::type(), "iterator", 42 | c.begin(), c.end()); 43 | }, nb::keep_alive<0, 1>()) 44 | .def("__contains__", 45 | &IntSet::covers, 46 | "time"_a, 47 | nb::call_guard()) 48 | .def(nb::self == nb::self, 49 | nb::call_guard()) 50 | .def("__copy__", [](const IntSet& self) { 51 | return IntSet(self); 52 | }).def("__deepcopy__", [](const IntSet& self, nb::dict) { 53 | return IntSet(self); 54 | }, "memo"_a) 55 | .def("__repr__", [](const IntSet& c) { 56 | return fmt::format("{}", c); 57 | }).def_static("value_type", []() { 58 | return types::handle_for(); 59 | }).def_static("__class_repr__", []() { 60 | return fmt::format("", type_str{}()); 61 | }) 62 | .def_static("__class_name__", []() { 63 | return type_str{}(); 64 | }); 65 | } 66 | }; 67 | 68 | void declare_typed_interval_sets(nb::module_& m) { 69 | types::run_each< 70 | metal::transform< 71 | metal::lambda, 72 | types::time_types>>{}(m); 73 | } 74 | -------------------------------------------------------------------------------- /src/io.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bind_core.hpp" 4 | 5 | #include 6 | 7 | #include "type_str/edges.hpp" 8 | 9 | #include "type_utils.hpp" 10 | 11 | namespace nb = nanobind; 12 | using namespace nanobind::literals; 13 | 14 | template 15 | struct declare_io_functions { 16 | void operator()(nb::module_& m) { 17 | m.def(fmt::format("read_edgelist_{}", python_type_str()).c_str(), 18 | [](const std::string& path) { 19 | return reticula::read_edgelist(path); 20 | }, "path"_a, 21 | nb::call_guard()); 22 | 23 | m.def("write_edgelist", 24 | [](reticula::network& g, const std::string& path) { 25 | return reticula::write_edgelist(g, path); 26 | }, "network"_a, "path"_a, 27 | nb::call_guard()); 28 | } 29 | }; 30 | 31 | using simple_temporal_type_parameter_combinations = 32 | metal::cartesian; 33 | 34 | void declare_io(nb::module_& m) { 35 | types::run_each< 36 | metal::transform< 37 | metal::lambda, 38 | metal::join< 39 | metal::transform< 40 | metal::lambda, 41 | types::simple_vert_types>, 42 | metal::transform< 43 | metal::lambda, 44 | types::simple_vert_types>, 45 | metal::transform< 46 | metal::partial< 47 | metal::lambda, 48 | metal::lambda>, 49 | simple_temporal_type_parameter_combinations>, 50 | metal::transform< 51 | metal::partial< 52 | metal::lambda, 53 | metal::lambda>, 54 | simple_temporal_type_parameter_combinations>, 55 | metal::transform< 56 | metal::partial< 57 | metal::lambda, 58 | metal::lambda>, 59 | simple_temporal_type_parameter_combinations>>>>{}(m); 60 | } 61 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | 3 | namespace nb = nanobind; 4 | 5 | void declare_types(nb::module_& m); 6 | void declare_generators(nb::module_& m); 7 | void declare_io(nb::module_& m); 8 | void declare_operations(nb::module_& m); 9 | void declare_algorithms(nb::module_& m); 10 | 11 | NB_MODULE(_reticula_ext, m) { 12 | declare_types(m); 13 | declare_generators(m); 14 | declare_io(m); 15 | declare_operations(m); 16 | declare_algorithms(m); 17 | } 18 | -------------------------------------------------------------------------------- /src/networks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bind_core.hpp" 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "type_str/networks.hpp" 10 | #include "type_utils.hpp" 11 | #include "type_handles.hpp" 12 | 13 | namespace nb = nanobind; 14 | using namespace nanobind::literals; 15 | 16 | template 17 | struct declare_network_class { 18 | void operator()(nb::module_ &m) { 19 | using Net = reticula::network; 20 | nb::class_(m, python_type_str().c_str()) 21 | .def(nb::init<>(), 22 | nb::call_guard()) 23 | .def(nb::init>(), 24 | "network"_a, 25 | nb::call_guard()) 26 | .def(nb::init>(), 27 | "edges"_a, 28 | nb::call_guard()) 29 | .def(nb::init< 30 | std::vector, 31 | std::vector>(), 32 | "edges"_a, "verts"_a, 33 | nb::call_guard()) 34 | .def("vertices", 35 | &Net::vertices, 36 | nb::call_guard()) 37 | .def("edges", 38 | &Net::edges, 39 | nb::call_guard()) 40 | .def("edges_cause", 41 | &Net::edges_cause, 42 | nb::call_guard()) 43 | .def("edges_effect", 44 | &Net::edges_effect, 45 | nb::call_guard()) 46 | .def("incident_edges", 47 | &Net::incident_edges, 48 | "vert"_a, 49 | nb::call_guard()) 50 | .def("in_degree", 51 | &Net::in_degree, 52 | "vert"_a, 53 | nb::call_guard()) 54 | .def("out_degree", 55 | &Net::out_degree, 56 | "vert"_a, 57 | nb::call_guard()) 58 | .def("degree", 59 | &Net::degree, 60 | "vert"_a, 61 | nb::call_guard()) 62 | .def("predecessors", 63 | &Net::predecessors, 64 | "vert"_a, 65 | nb::call_guard()) 66 | .def("successors", 67 | &Net::successors, 68 | "vert"_a, 69 | nb::call_guard()) 70 | .def("neighbours", 71 | &Net::neighbours, 72 | "vert"_a, 73 | nb::call_guard()) 74 | .def("in_edges", 75 | nb::overload_cast( 76 | &Net::in_edges, nb::const_), 77 | "vert"_a, 78 | nb::call_guard()) 79 | .def("out_edges", 80 | nb::overload_cast( 81 | &Net::out_edges, nb::const_), 82 | "vert"_a, 83 | nb::call_guard()) 84 | .def("in_edges", 85 | nb::overload_cast<>( 86 | &Net::in_edges, nb::const_), 87 | nb::call_guard()) 88 | .def("out_edges", 89 | nb::overload_cast<>( 90 | &Net::out_edges, nb::const_), 91 | nb::call_guard()) 92 | .def(nb::self == nb::self, 93 | nb::call_guard()) 94 | .def(nb::self != nb::self, 95 | nb::call_guard()) 96 | .def("__copy__", [](const Net& self) { 97 | return Net(self); 98 | }).def("__deepcopy__", [](const Net& self, nb::dict) { 99 | return Net(self); 100 | }, "memo"_a) 101 | .def("__repr__", [](const Net& a) { 102 | return fmt::format("{}", a); 103 | }).def_static("__class_repr__", []() { 104 | return fmt::format("", type_str{}()); 105 | }).def_static("__class_name__", []() { 106 | return type_str{}(); 107 | }).def_static("edge_type", []() { 108 | return types::handle_for(); 109 | }).def_static("vertex_type", []() { 110 | return types::handle_for(); 111 | }); 112 | } 113 | }; 114 | 115 | void declare_typed_networks(nb::module_& m) { 116 | types::run_each< 117 | metal::transform< 118 | metal::lambda, 119 | types::all_edge_types>>{}(m); 120 | } 121 | -------------------------------------------------------------------------------- /src/operations.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | 3 | #include 4 | 5 | #include "type_str/edges.hpp" 6 | #include "type_str/scalars.hpp" 7 | 8 | #include "type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_relabel_nodes { 15 | void operator()(nb::module_& m) { 16 | m.def(fmt::format("relabel_nodes_{}", python_type_str()).c_str(), 17 | &reticula::relabel_nodes, 18 | "network"_a, 19 | nb::call_guard()); 20 | } 21 | }; 22 | 23 | template 24 | struct declare_cartesian_product { 25 | void operator()(nb::module_& m) { 26 | m.def("cartesian_product", 27 | &reticula::cartesian_product, 28 | "undirected_net_1"_a, "undirected_net_2"_a, 29 | nb::call_guard()); 30 | } 31 | }; 32 | 33 | 34 | void declare_typed_add_operation_algorithms(nb::module_& m); 35 | void declare_typed_remove_operation_algorithms(nb::module_& m); 36 | 37 | void declare_typed_edge_occupation_algorithms(nb::module_& m); 38 | void declare_typed_vertex_occupation_algorithms(nb::module_& m); 39 | 40 | void declare_typed_subgraph_algorithms(nb::module_& m); 41 | 42 | void declare_operations(nb::module_& m) { 43 | types::run_each< 44 | metal::transform< 45 | metal::partial< 46 | metal::lambda, 47 | metal::lambda>, 48 | metal::cartesian< 49 | types::integer_vert_types, 50 | types::first_order_vert_types>>>{}(m); 51 | 52 | types::run_each< 53 | metal::transform< 54 | metal::partial< 55 | metal::lambda, 56 | metal::lambda>, 57 | metal::cartesian< 58 | types::simple_vert_types, 59 | types::simple_vert_types>>>{}(m); 60 | 61 | declare_typed_add_operation_algorithms(m); 62 | declare_typed_remove_operation_algorithms(m); 63 | 64 | declare_typed_edge_occupation_algorithms(m); 65 | declare_typed_vertex_occupation_algorithms(m); 66 | 67 | declare_typed_subgraph_algorithms(m); 68 | } 69 | -------------------------------------------------------------------------------- /src/operations/add_operations.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../bind_core.hpp" 5 | 6 | #include 7 | 8 | #include "../type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_add_operation_algorithms { 15 | void operator()(nb::module_& m) { 16 | m.def("graph_union", 17 | &reticula::graph_union, 18 | "g1"_a, "g2"_a, 19 | nb::call_guard()); 20 | 21 | m.def("with_edges", 22 | &reticula::with_edges< 23 | EdgeT, std::vector>, 24 | "network"_a, "edges"_a, 25 | nb::call_guard()); 26 | m.def("with_edges", 27 | &reticula::with_edges< 28 | EdgeT, const reticula::component&>, 29 | "network"_a, "edges"_a, 30 | nb::call_guard()); 31 | 32 | m.def("with_vertices", 33 | &reticula::with_vertices< 34 | EdgeT, std::vector>, 35 | "network"_a, "verts"_a, 36 | nb::call_guard()); 37 | m.def("with_vertices", 38 | &reticula::with_vertices< 39 | EdgeT, const reticula::component&>, 40 | "network"_a, "verts"_a, 41 | nb::call_guard()); 42 | } 43 | }; 44 | 45 | void declare_typed_add_operation_algorithms(nb::module_& m) { 46 | types::run_each< 47 | metal::transform< 48 | metal::lambda, 49 | types::all_edge_types>>{}(m); 50 | } 51 | -------------------------------------------------------------------------------- /src/operations/edge_occupation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "../bind_core.hpp" 7 | 8 | #include 9 | 10 | #include "../type_utils.hpp" 11 | 12 | namespace nb = nanobind; 13 | using namespace nanobind::literals; 14 | 15 | template 16 | struct declare_edge_occupation_algorithms { 17 | void operator()(nb::module_& m) { 18 | m.def("occupy_edges", 19 | &reticula::occupy_edges< 20 | EdgeT, std::unordered_map< 21 | EdgeT, double, 22 | reticula::hash>, Gen>, 23 | "network"_a, "prob_map"_a, "random_state"_a, "default_prob"_a=0.0, 24 | nb::call_guard()); 25 | m.def("occupy_edges", 26 | &reticula::occupy_edges< 27 | EdgeT, std::function, Gen>, 28 | "network"_a, "prob_func"_a, "random_state"_a, 29 | nb::call_guard()); 30 | 31 | m.def("uniformly_occupy_edges", 32 | &reticula::uniformly_occupy_edges, 33 | "network"_a, "occupation_prob"_a, "random_state"_a, 34 | nb::call_guard()); 35 | } 36 | }; 37 | 38 | 39 | void declare_typed_edge_occupation_algorithms(nb::module_& m) { 40 | types::run_each< 41 | metal::transform< 42 | metal::partial< 43 | metal::lambda, 44 | metal::lambda>, 45 | metal::cartesian< 46 | types::all_edge_types, 47 | types::random_state_types>>>{}(m); 48 | } 49 | -------------------------------------------------------------------------------- /src/operations/remove_operations.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../bind_core.hpp" 5 | 6 | #include 7 | 8 | #include "../type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_remove_operation_algorithms { 15 | void operator()(nb::module_& m) { 16 | m.def("without_edges", 17 | &reticula::without_edges< 18 | EdgeT, std::vector>, 19 | "network"_a, "edges"_a, 20 | nb::call_guard()); 21 | m.def("without_edges", 22 | &reticula::without_edges< 23 | EdgeT, const reticula::component&>, 24 | "network"_a, "edges"_a, 25 | nb::call_guard()); 26 | 27 | m.def("without_vertices", 28 | &reticula::without_vertices< 29 | EdgeT, std::vector>, 30 | "network"_a, "verts"_a, 31 | nb::call_guard()); 32 | m.def("without_vertices", 33 | &reticula::without_vertices< 34 | EdgeT, const reticula::component&>, 35 | "network"_a, "verts"_a, 36 | nb::call_guard()); 37 | } 38 | }; 39 | 40 | void declare_typed_remove_operation_algorithms(nb::module_& m) { 41 | types::run_each< 42 | metal::transform< 43 | metal::lambda, 44 | types::all_edge_types>>{}(m); 45 | } 46 | -------------------------------------------------------------------------------- /src/operations/subgraph.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../bind_core.hpp" 5 | 6 | #include 7 | 8 | #include "../type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_subgraph_algorithms { 15 | void operator()(nb::module_& m) { 16 | m.def("vertex_induced_subgraph", 17 | &reticula::vertex_induced_subgraph< 18 | EdgeT, std::vector>, 19 | "network"_a, "verts"_a, 20 | nb::call_guard()); 21 | m.def("vertex_induced_subgraph", 22 | &reticula::vertex_induced_subgraph< 23 | EdgeT, const reticula::component&>, 24 | "network"_a, "verts"_a, 25 | nb::call_guard()); 26 | 27 | m.def("edge_induced_subgraph", 28 | &reticula::edge_induced_subgraph< 29 | EdgeT, std::vector>, 30 | "network"_a, "edges"_a, 31 | nb::call_guard()); 32 | m.def("edge_induced_subgraph", 33 | &reticula::edge_induced_subgraph< 34 | EdgeT, const reticula::component&>, 35 | "network"_a, "edges"_a, 36 | nb::call_guard()); 37 | } 38 | }; 39 | 40 | void declare_typed_subgraph_algorithms(nb::module_& m) { 41 | types::run_each< 42 | metal::transform< 43 | metal::lambda, 44 | types::all_edge_types>>{}(m); 45 | } 46 | -------------------------------------------------------------------------------- /src/operations/vertex_occupation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "../bind_core.hpp" 7 | 8 | #include 9 | 10 | #include "../type_utils.hpp" 11 | 12 | namespace nb = nanobind; 13 | using namespace nanobind::literals; 14 | 15 | template 16 | struct declare_vertex_occupation_algorithms { 17 | void operator()(nb::module_& m) { 18 | m.def("occupy_vertices", 19 | &reticula::occupy_vertices< 20 | EdgeT, std::unordered_map< 21 | typename EdgeT::VertexType, double, 22 | reticula::hash>, Gen>, 23 | "network"_a, "prob_map"_a, "random_state"_a, "default_prob"_a=0.0, 24 | nb::call_guard()); 25 | m.def("occupy_vertices", 26 | &reticula::occupy_vertices< 27 | EdgeT, std::function, Gen>, 28 | "network"_a, "prob_func"_a, "random_state"_a, 29 | nb::call_guard()); 30 | 31 | m.def("uniformly_occupy_vertices", 32 | &reticula::uniformly_occupy_vertices, 33 | "network"_a, "occupation_prob"_a, "random_state"_a, 34 | nb::call_guard()); 35 | } 36 | }; 37 | 38 | 39 | void declare_typed_vertex_occupation_algorithms(nb::module_& m) { 40 | types::run_each< 41 | metal::transform< 42 | metal::partial< 43 | metal::lambda, 44 | metal::lambda>, 45 | metal::cartesian< 46 | types::all_edge_types, 47 | types::random_state_types>>>{}(m); 48 | } 49 | -------------------------------------------------------------------------------- /src/random_activation_networks.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "type_str/scalars.hpp" 7 | #include "type_str/distributions.hpp" 8 | #include "type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template < 14 | reticula::temporal_network_edge EdgeT, 15 | reticula::random_number_distribution Dist, 16 | reticula::random_number_distribution ResDist, 17 | std::uniform_random_bit_generator Gen> 18 | struct declare_activations_with_residual { 19 | void operator()(nb::module_& m) { 20 | m.def("random_link_activation_temporal_network", 21 | &reticula::random_link_activation_temporal_network< 22 | EdgeT, Dist, ResDist, Gen>, 23 | "base_net"_a, "max_t"_a, "iet_dist"_a, "res_dist"_a, 24 | "random_state"_a, "size_hint"_a = 0, 25 | nb::call_guard()); 26 | 27 | m.def("random_node_activation_temporal_network", 28 | &reticula::random_node_activation_temporal_network< 29 | EdgeT, Dist, ResDist, Gen>, 30 | "base_net"_a, "max_t"_a, "iet_dist"_a, "res_dist"_a, 31 | "random_state"_a, "size_hint"_a = 0, 32 | nb::call_guard()); 33 | } 34 | }; 35 | 36 | template < 37 | reticula::temporal_network_edge EdgeT, 38 | reticula::random_number_distribution Dist, 39 | std::uniform_random_bit_generator Gen> 40 | struct declare_activations { 41 | void operator()(nb::module_& m) { 42 | m.def("random_link_activation_temporal_network", 43 | &reticula::random_link_activation_temporal_network< 44 | EdgeT, Dist, Gen>, 45 | "base_net"_a, "max_t"_a, "iet_dist"_a, 46 | "random_state"_a, "size_hint"_a = 0, 47 | nb::call_guard()); 48 | 49 | m.def("random_node_activation_temporal_network", 50 | &reticula::random_node_activation_temporal_network< 51 | EdgeT, Dist, Gen>, 52 | "base_net"_a, "max_t"_a, "iet_dist"_a, 53 | "random_state"_a, "size_hint"_a = 0, 54 | nb::call_guard()); 55 | } 56 | }; 57 | 58 | template 59 | struct type_dists {}; 60 | 61 | template 62 | struct type_dists { 63 | using list = metal::list< 64 | std::geometric_distribution, 65 | reticula::delta_distribution, 66 | std::uniform_int_distribution>; 67 | }; 68 | 69 | template 70 | struct type_dists { 71 | using list = metal::list< 72 | std::exponential_distribution, 73 | reticula::power_law_with_specified_mean, 74 | reticula::residual_power_law_with_specified_mean, 75 | reticula::hawkes_univariate_exponential, 76 | reticula::delta_distribution, 77 | std::uniform_real_distribution>; 78 | }; 79 | 80 | template 81 | struct declare_typed_activations_for_edge { 82 | void operator()(nb::module_& m) { 83 | using time_type = typename EdgeT::TimeType; 84 | using dists = typename type_dists::list; 85 | 86 | types::run_each< 87 | metal::transform< 88 | metal::partial< 89 | metal::lambda, 90 | metal::lambda>, 91 | metal::cartesian< 92 | metal::list, 93 | dists, 94 | types::random_state_types>>>{}(m); 95 | 96 | types::run_each< 97 | metal::transform< 98 | metal::partial< 99 | metal::lambda, 100 | metal::lambda>, 101 | metal::cartesian< 102 | metal::list, 103 | dists, dists, 104 | types::random_state_types>>>{}(m); 105 | } 106 | }; 107 | 108 | void declare_typed_activation_networks(nb::module_& m) { 109 | types::run_each< 110 | metal::transform< 111 | metal::lambda, 112 | metal::join< 113 | types::first_order_undirected_temporal_edges, 114 | types::first_order_undirected_temporal_hyperedges, 115 | types::first_order_directed_temporal_edges, 116 | types::first_order_directed_temporal_hyperedges>>>{}(m); 117 | } 118 | -------------------------------------------------------------------------------- /src/random_state.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bind_core.hpp" 4 | #include 5 | 6 | #include "type_str/random_state.hpp" 7 | 8 | namespace nb = nanobind; 9 | using namespace nanobind::literals; 10 | 11 | void declare_random_states(nb::module_& m) { 12 | nb::class_(m, python_type_str().c_str()) 13 | .def("__init__", [](std::mt19937_64* t){ 14 | new (t) std::mt19937_64{std::random_device{}()}; 15 | }, nb::call_guard()) 16 | .def(nb::init(), 17 | "seed"_a, nb::call_guard()) 18 | .def("__copy__", [](const std::mt19937_64& self) { 19 | return std::mt19937_64(self); 20 | }).def("__deepcopy__", [](const std::mt19937_64& self, nb::dict) { 21 | return std::mt19937_64(self); 22 | }, "memo"_a) 23 | .def("__call__", [](std::mt19937_64& self) { return self(); }) 24 | .def_static("__class_repr__", []() { 25 | return fmt::format("", type_str{}()); 26 | }).def_static("__class_name__", []() { 27 | return type_str{}(); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/reticula/generic_attribute.py: -------------------------------------------------------------------------------- 1 | import typing as _typing 2 | import collections 3 | 4 | 5 | class generic_attribute(collections.abc.Mapping): 6 | def __init__( 7 | self, attr_prefix: str, 8 | arg_names: _typing.Tuple[str, ...], 9 | options: _typing.Iterable[_typing.Tuple[type, ...]], 10 | function_module, api_module_name): 11 | self.options = set(options) 12 | self.attr_prefix = attr_prefix 13 | self.arg_names = arg_names 14 | self.function_module = function_module 15 | self.api_module_name = api_module_name 16 | 17 | def __getitem__(self, keys): 18 | if not isinstance(keys, tuple): 19 | keys = (keys,) 20 | 21 | if len(keys) != len(self.arg_names): 22 | raise AttributeError( 23 | f"Wrong number of type templates: expected " 24 | f"{len(self.arg_names)} template types but received " 25 | f"{len(keys)}.\nThe expected template types are " 26 | f"[{', '.join(self.arg_names)}]") 27 | 28 | attr_name = str(self.attr_prefix) 29 | if not self.options or keys in self.options: 30 | attr_name = str(self.attr_prefix) + "_" + \ 31 | "_".join([k.__name__ for k in keys]) 32 | else: 33 | raise AttributeError( 34 | f"Provided template type {str(keys)} is not a valid " 35 | "option. Valid options are:\n\n" + 36 | self.options_list()) 37 | return self.function_module.__getattribute__(attr_name) 38 | 39 | def options_list(self): 40 | opts = [] 41 | for type_list in self.options: 42 | opts.append( 43 | "[" + ", ".join([t.__class_name__() for t in type_list]) + "]") 44 | opts = sorted(opts, key=lambda s: (s.count("["), s)) 45 | 46 | return "\n".join(opts) 47 | 48 | def __call__(self, *args, **kwargs): 49 | raise TypeError( 50 | "No type information was paased to a generic function or type.\n" 51 | "This usually means that you forgot to add square brackets\n" 52 | "and type information before parentheses, e.g.:\n\n" 53 | f" {self.api_module_name}.{self.attr_prefix}" 54 | f"[{', '.join(self.arg_names)}]" 55 | "\n\nValid options are:\n\n" + self.options_list()) 56 | 57 | def __repr__(self) -> str: 58 | return f"{self.api_module_name}.{self.attr_prefix}"\ 59 | f"[{", ".join(self.arg_names)}]" 60 | 61 | def __len__(self) -> int: 62 | return len(self.options) 63 | 64 | def __iter__(self): 65 | return iter(self.options) 66 | -------------------------------------------------------------------------------- /src/reticula/microcanonical_reference_models.py: -------------------------------------------------------------------------------- 1 | from ._reticula_ext.microcanonical_reference_models import ( 2 | degree_sequence_preserving_shuffling, 3 | joint_degree_sequence_preserving_shuffling, 4 | instant_event_shuffling, link_shuffling, connected_link_shuffling, 5 | topology_constrained_link_shuffling, timeline_shuffling, 6 | weight_constrained_timeline_shuffling, 7 | activity_constrained_timeline_shuffling, inter_event_shuffling) 8 | -------------------------------------------------------------------------------- /src/reticula/temporal_adjacency.py: -------------------------------------------------------------------------------- 1 | import sys as _sys 2 | 3 | from .generic_attribute import generic_attribute as _generic_attribute 4 | from ._reticula_ext import temporal_adjacency as _reticula_ext_adj 5 | from ._reticula_ext import types as _reticula_ext_types 6 | 7 | _temporal_edge_types = set(_reticula_ext_types.temporal_edge_types) 8 | 9 | _generic_adjacency_attrs = [ 10 | "simple", 11 | "limited_waiting_time", 12 | "exponential", 13 | "geometric"] 14 | for _a in _generic_adjacency_attrs: 15 | setattr(_sys.modules[__name__], 16 | _a, _generic_attribute( 17 | attr_prefix=_a, 18 | arg_names=("temporal_edge_types",), 19 | options=((t,) for t in _temporal_edge_types), 20 | function_module=_reticula_ext_adj, 21 | api_module_name=__name__)) 22 | -------------------------------------------------------------------------------- /src/scalar_types.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | #include 3 | #include 4 | 5 | #include "type_str/scalars.hpp" 6 | #include "type_utils.hpp" 7 | #include "scalar_wrapper.hpp" 8 | 9 | namespace nb = nanobind; 10 | using namespace nanobind::literals; 11 | 12 | template 13 | struct declare_scalar_types { 14 | void operator()(nb::module_ &m) { 15 | nb::class_>(m, 16 | python_type_str().c_str()) 17 | .def_static("__class_repr__", []() { 18 | return fmt::format("", type_str{}()); 19 | }).def_static("__class_name__", []() { 20 | return type_str{}(); 21 | }); 22 | } 23 | }; 24 | 25 | void declare_typed_scalar(nb::module_& m) { 26 | using scalar_types = types::unique>; 28 | 29 | // declare network 30 | types::run_each< 31 | metal::transform< 32 | metal::lambda, 33 | metal::join>>{}(m); 34 | } 35 | -------------------------------------------------------------------------------- /src/scalar_wrapper.hpp: -------------------------------------------------------------------------------- 1 | template 2 | struct scalar_wrapper {}; 3 | -------------------------------------------------------------------------------- /src/simple_generators.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | #include 3 | 4 | #include 5 | 6 | #include "type_str/scalars.hpp" 7 | #include "type_str/edges.hpp" 8 | #include "type_utils.hpp" 9 | 10 | namespace nb = nanobind; 11 | using namespace nanobind::literals; 12 | 13 | template 14 | struct declare_simple_generators { 15 | void operator()(nb::module_& m) { 16 | m.def(("square_grid_graph_"+python_type_str()).c_str(), 17 | &reticula::square_grid_graph, 18 | "side"_a, "dims"_a, "periodic"_a = false, 19 | nb::call_guard()); 20 | m.def(("path_graph_"+python_type_str()).c_str(), 21 | &reticula::path_graph, 22 | "size"_a, "periodic"_a = false, 23 | nb::call_guard()); 24 | m.def(("cycle_graph_"+python_type_str()).c_str(), 25 | &reticula::cycle_graph, 26 | "size"_a, 27 | nb::call_guard()); 28 | m.def(("regular_ring_lattice_"+python_type_str()).c_str(), 29 | &reticula::regular_ring_lattice, 30 | "size"_a, "degree"_a, 31 | nb::call_guard()); 32 | m.def(("complete_graph_"+python_type_str()).c_str(), 33 | &reticula::complete_graph, 34 | "size"_a, 35 | nb::call_guard()); 36 | m.def(("complete_directed_graph_"+python_type_str()).c_str(), 37 | &reticula::complete_directed_graph, 38 | "size"_a, 39 | nb::call_guard()); 40 | } 41 | }; 42 | 43 | void declare_typed_generators(nb::module_& m) { 44 | types::run_each< 45 | metal::transform< 46 | metal::lambda, 47 | types::integer_vert_types>>{}(m); 48 | } 49 | -------------------------------------------------------------------------------- /src/static_edges.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "bind_core.hpp" 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "type_str/scalars.hpp" 9 | #include "type_str/edges.hpp" 10 | #include "type_utils.hpp" 11 | #include "common_edge_properties.hpp" 12 | 13 | namespace nb = nanobind; 14 | using namespace nanobind::literals; 15 | 16 | template 17 | struct declare_static_edges { 18 | void operator()(nb::module_& m) { 19 | define_basic_edge_concept>(m) 20 | .def(nb::init(), "v1"_a, "v2"_a) 21 | .def("__init__", []( 22 | reticula::undirected_edge* edge, 23 | std::tuple t) { 24 | new (edge) reticula::undirected_edge{ 25 | std::get<0>(t), std::get<1>(t)}; 26 | }, "tuple"_a, nb::call_guard()); 27 | 28 | nb::implicitly_convertible< 29 | std::tuple, 30 | reticula::undirected_edge>(); 31 | 32 | define_basic_edge_concept>(m) 33 | .def(nb::init(), 34 | "tail"_a, "head"_a, 35 | nb::call_guard()) 36 | .def("__init__", []( 37 | reticula::directed_edge* edge, 38 | std::tuple t) { 39 | new (edge) reticula::directed_edge{ 40 | std::get<0>(t), std::get<1>(t)}; 41 | }, "tuple"_a, nb::call_guard()) 42 | .def("head", 43 | &reticula::directed_edge::head, 44 | nb::call_guard()) 45 | .def("tail", 46 | &reticula::directed_edge::tail, 47 | nb::call_guard()); 48 | 49 | nb::implicitly_convertible< 50 | std::tuple, 51 | reticula::directed_edge>(); 52 | } 53 | }; 54 | 55 | 56 | void declare_typed_static_edges(nb::module_& m) { 57 | types::run_each< 58 | metal::transform< 59 | metal::lambda, 60 | types::all_vert_types>>{}(m); 61 | } 62 | -------------------------------------------------------------------------------- /src/static_hyperedges.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "bind_core.hpp" 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "type_str/scalars.hpp" 9 | #include "type_str/edges.hpp" 10 | #include "type_utils.hpp" 11 | #include "common_edge_properties.hpp" 12 | 13 | namespace nb = nanobind; 14 | using namespace nanobind::literals; 15 | 16 | template 17 | struct declare_static_hyperedges { 18 | void operator()(nb::module_ &m) { 19 | define_basic_edge_concept>(m) 20 | .def(nb::init>(), 21 | "verts"_a, nb::call_guard()); 22 | 23 | nb::implicitly_convertible< 24 | std::vector, 25 | reticula::undirected_hyperedge>(); 26 | 27 | define_basic_edge_concept>(m) 28 | .def(nb::init, std::vector>(), 29 | "tails"_a, "heads"_a, 30 | nb::call_guard()) 31 | .def("__init__", []( 32 | reticula::directed_hyperedge* edge, 33 | std::tuple, std::vector> t) { 34 | new (edge) reticula::directed_hyperedge{ 35 | std::get<0>(t), std::get<1>(t)}; 36 | }, "tuple"_a, nb::call_guard()) 37 | .def("heads", 38 | &reticula::directed_hyperedge::heads, 39 | nb::call_guard()) 40 | .def("tails", 41 | &reticula::directed_hyperedge::tails, 42 | nb::call_guard()); 43 | 44 | nb::implicitly_convertible< 45 | std::pair, std::vector>, 46 | reticula::directed_hyperedge>(); 47 | } 48 | }; 49 | 50 | 51 | void declare_typed_static_hyperedges(nb::module_& m) { 52 | types::run_each< 53 | metal::transform< 54 | metal::lambda, 55 | types::all_vert_types>>{}(m); 56 | } 57 | -------------------------------------------------------------------------------- /src/static_microcanonical_reference_models.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | 3 | #include 4 | 5 | #include "type_utils.hpp" 6 | 7 | namespace nb = nanobind; 8 | using namespace nanobind::literals; 9 | 10 | template < 11 | reticula::undirected_network_edge EdgeT, 12 | std::uniform_random_bit_generator Gen> 13 | struct declare_static_mrrm_algorithms { 14 | void operator()(nb::module_& m) { 15 | m.def("degree_sequence_preserving_shuffling", 16 | nb::overload_cast< 17 | const reticula::network&, Gen&>( 18 | &reticula::mrrms::degree_sequence_preserving_shuffling), 19 | "static_network"_a, "random_state"_a, 20 | nb::call_guard()); 21 | 22 | m.def("degree_sequence_preserving_shuffling", 23 | nb::overload_cast< 24 | const reticula::network&, Gen&, std::size_t>( 25 | &reticula::mrrms::degree_sequence_preserving_shuffling), 26 | "static_network"_a, "random_state"_a, 27 | "rewirings"_a, 28 | nb::call_guard()); 29 | 30 | m.def("joint_degree_sequence_preserving_shuffling", 31 | nb::overload_cast< 32 | const reticula::network&, Gen&>( 33 | &reticula::mrrms::joint_degree_sequence_preserving_shuffling< 34 | EdgeT, Gen>), 35 | "static_network"_a, "random_state"_a, 36 | nb::call_guard()); 37 | 38 | m.def("joint_degree_sequence_preserving_shuffling", 39 | nb::overload_cast< 40 | const reticula::network&, Gen&, std::size_t>( 41 | &reticula::mrrms::joint_degree_sequence_preserving_shuffling< 42 | EdgeT, Gen>), 43 | "static_network"_a, "random_state"_a, 44 | "rewirings"_a, 45 | nb::call_guard()); 46 | } 47 | }; 48 | 49 | void declare_typed_static_mrrm_algorithms(nb::module_& m) { 50 | types::run_each< 51 | metal::transform< 52 | metal::partial< 53 | metal::lambda, 54 | metal::lambda>, 55 | metal::cartesian< 56 | metal::join< 57 | types::first_order_undirected_edges, 58 | types::second_order_undirected_edges>, 59 | types::random_state_types>>>{}(m); 60 | } 61 | -------------------------------------------------------------------------------- /src/temporal_edges.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "type_str/scalars.hpp" 8 | #include "type_str/edges.hpp" 9 | #include "type_utils.hpp" 10 | #include "common_edge_properties.hpp" 11 | 12 | namespace nb = nanobind; 13 | using namespace nanobind::literals; 14 | 15 | template 16 | struct declare_temporal_edges { 17 | void operator()(nb::module_ &m) { 18 | define_basic_edge_concept>(m) 19 | .def(nb::init(), 20 | "v1"_a, "v2"_a, "time"_a, 21 | nb::call_guard()) 22 | .def("__init__", [](reticula::undirected_temporal_edge* edge, 23 | std::tuple t) { 24 | new (edge) reticula::undirected_temporal_edge{ 25 | std::get<0>(t), std::get<1>(t), std::get<2>(t)}; 26 | }, 27 | "tuple"_a, 28 | nb::call_guard()); 29 | 30 | nb::implicitly_convertible< 31 | std::tuple, 32 | reticula::undirected_temporal_edge>(); 33 | 34 | define_basic_edge_concept>(m) 35 | .def(nb::init(), 36 | "tail"_a, "head"_a, "time"_a, 37 | nb::call_guard()) 38 | .def("__init__", []( 39 | reticula::directed_temporal_edge* edge, 40 | std::tuple t) { 41 | new (edge) reticula::directed_temporal_edge{ 42 | std::get<0>(t), std::get<1>(t), std::get<2>(t)}; 43 | }, "tuple"_a, nb::call_guard()) 44 | .def("head", 45 | &reticula::directed_temporal_edge::head, 46 | nb::call_guard()) 47 | .def("tail", 48 | &reticula::directed_temporal_edge::tail, 49 | nb::call_guard()); 50 | 51 | nb::implicitly_convertible< 52 | std::tuple, 53 | reticula::directed_temporal_edge>(); 54 | 55 | define_basic_edge_concept< 56 | reticula::directed_delayed_temporal_edge>(m) 57 | .def(nb::init(), 58 | "tail"_a, "head"_a, "cause_time"_a, "effect_time"_a, 59 | nb::call_guard()) 60 | .def("__init__", []( 61 | reticula::directed_delayed_temporal_edge *edge, 62 | std::tuple t) { 63 | new (edge) reticula::directed_delayed_temporal_edge{ 64 | std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t)}; 65 | }, "tuple"_a, nb::call_guard()) 66 | .def("head", 67 | &reticula::directed_delayed_temporal_edge::head, 68 | nb::call_guard()) 69 | .def("tail", 70 | &reticula::directed_delayed_temporal_edge::tail, 71 | nb::call_guard()); 72 | 73 | nb::implicitly_convertible< 74 | std::tuple, 75 | reticula::directed_delayed_temporal_edge>(); 76 | } 77 | }; 78 | 79 | void declare_typed_temporal_edges(nb::module_& m) { 80 | types::run_each< 81 | metal::transform< 82 | metal::partial< 83 | metal::lambda, 84 | metal::lambda>, 85 | types::first_order_temporal_type_parameter_combinations>>{}(m); 86 | } 87 | -------------------------------------------------------------------------------- /src/temporal_hyperedges.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "type_str/scalars.hpp" 8 | #include "type_str/edges.hpp" 9 | #include "type_utils.hpp" 10 | #include "common_edge_properties.hpp" 11 | 12 | namespace nb = nanobind; 13 | using namespace nanobind::literals; 14 | 15 | template 16 | struct declare_temporal_hyperedges { 17 | void operator()(nb::module_& m) { 18 | define_basic_edge_concept< 19 | reticula::undirected_temporal_hyperedge>(m) 20 | .def(nb::init, TimeT>(), 21 | "verts"_a, "time"_a, 22 | nb::call_guard()) 23 | .def("__init__", []( 24 | reticula::undirected_temporal_hyperedge* edge, 25 | std::tuple, TimeT> t) { 26 | new (edge) reticula::undirected_temporal_hyperedge{ 27 | std::get<0>(t), std::get<1>(t)}; 28 | }, "tuple"_a, 29 | nb::call_guard()); 30 | 31 | nb::implicitly_convertible< 32 | std::pair, TimeT>, 33 | reticula::undirected_temporal_hyperedge>(); 34 | 35 | define_basic_edge_concept< 36 | reticula::directed_temporal_hyperedge>(m) 37 | .def(nb::init, std::vector, TimeT>(), 38 | "tails"_a, "heads"_a, "time"_a, 39 | nb::call_guard()) 40 | .def("__init__", []( 41 | reticula::directed_temporal_hyperedge *edge, 42 | std::tuple, std::vector, TimeT> t) { 43 | new (edge) reticula::directed_temporal_hyperedge{ 44 | std::get<0>(t), std::get<1>(t), std::get<2>(t)}; 45 | }, "tuple"_a, nb::call_guard()) 46 | .def("heads", 47 | &reticula::directed_temporal_hyperedge::heads, 48 | nb::call_guard()) 49 | .def("tails", 50 | &reticula::directed_temporal_hyperedge::tails, 51 | nb::call_guard()); 52 | 53 | nb::implicitly_convertible< 54 | std::tuple, std::vector, TimeT>, 55 | reticula::directed_temporal_hyperedge>(); 56 | 57 | define_basic_edge_concept< 58 | reticula::directed_delayed_temporal_hyperedge>(m) 59 | .def(nb::init, std::vector, TimeT, TimeT>(), 60 | "tails"_a, "heads"_a, "cause_time"_a, "effect_time"_a, 61 | nb::call_guard()) 62 | .def("__init__", []( 63 | reticula::directed_delayed_temporal_hyperedge* edge, 64 | std::tuple, std::vector, TimeT, TimeT> t) { 65 | new (edge) reticula::directed_delayed_temporal_hyperedge{ 66 | std::get<0>(t), std::get<1>(t), 67 | std::get<2>(t), std::get<3>(t)}; 68 | }, "tuple"_a, nb::call_guard()) 69 | .def("heads", 70 | &reticula::directed_delayed_temporal_hyperedge::heads, 71 | nb::call_guard()) 72 | .def("tails", 73 | &reticula::directed_delayed_temporal_hyperedge::tails, 74 | nb::call_guard()); 75 | 76 | nb::implicitly_convertible< 77 | std::tuple, std::vector, TimeT, TimeT>, 78 | reticula::directed_delayed_temporal_hyperedge>(); 79 | } 80 | }; 81 | 82 | void declare_typed_temporal_hyperedges(nb::module_& m) { 83 | types::run_each< 84 | metal::transform< 85 | metal::partial< 86 | metal::lambda, 87 | metal::lambda>, 88 | types::first_order_temporal_type_parameter_combinations>>{}(m); 89 | } 90 | -------------------------------------------------------------------------------- /src/temporal_microcanonical_reference_models.cpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | 3 | #include 4 | 5 | #include "type_utils.hpp" 6 | 7 | namespace nb = nanobind; 8 | using namespace nanobind::literals; 9 | 10 | template < 11 | reticula::temporal_network_edge EdgeT, 12 | std::uniform_random_bit_generator Gen> 13 | struct declare_temporal_mrrm_algorithms { 14 | void operator()(nb::module_& m) { 15 | m.def("instant_event_shuffling", 16 | &reticula::mrrms::instant_event_shuffling, 17 | "temporal_network"_a, "random_state"_a, 18 | nb::call_guard()); 19 | m.def("link_shuffling", 20 | &reticula::mrrms::link_shuffling, 21 | "temporal_network"_a, "random_state"_a, 22 | nb::call_guard()); 23 | m.def("connected_link_shuffling", 24 | &reticula::mrrms::connected_link_shuffling, 25 | "temporal_network"_a, "random_state"_a, 26 | nb::call_guard()); 27 | m.def("topology_constrained_link_shuffling", 28 | &reticula::mrrms::topology_constrained_link_shuffling, 29 | "temporal_network"_a, "random_state"_a, 30 | nb::call_guard()); 31 | 32 | m.def("timeline_shuffling", 33 | nb::overload_cast< 34 | const reticula::network&, Gen&, 35 | typename EdgeT::TimeType, typename EdgeT::TimeType>( 36 | &reticula::mrrms::timeline_shuffling), 37 | "temporal_network"_a, "random_state"_a, "t_start"_a, "t_end"_a, 38 | nb::call_guard()); 39 | m.def("timeline_shuffling", 40 | nb::overload_cast&, Gen&>( 41 | &reticula::mrrms::timeline_shuffling), 42 | "temporal_network"_a, "random_state"_a, 43 | nb::call_guard()); 44 | m.def("weight_constrained_timeline_shuffling", 45 | nb::overload_cast< 46 | const reticula::network&, Gen&, 47 | typename EdgeT::TimeType, typename EdgeT::TimeType>( 48 | &reticula::mrrms::weight_constrained_timeline_shuffling), 49 | "temporal_network"_a, "random_state"_a, "t_start"_a, "t_end"_a, 50 | nb::call_guard()); 51 | m.def("weight_constrained_timeline_shuffling", 52 | nb::overload_cast&, Gen&>( 53 | &reticula::mrrms::weight_constrained_timeline_shuffling), 54 | "temporal_network"_a, "random_state"_a, 55 | nb::call_guard()); 56 | m.def("activity_constrained_timeline_shuffling", 57 | &reticula::mrrms::activity_constrained_timeline_shuffling, 58 | "temporal_network"_a, "random_state"_a, 59 | nb::call_guard()); 60 | m.def("inter_event_shuffling", 61 | &reticula::mrrms::inter_event_shuffling, 62 | "temporal_network"_a, "random_state"_a, 63 | nb::call_guard()); 64 | } 65 | }; 66 | 67 | void declare_typed_temporal_mrrm_algorithms(nb::module_& m) { 68 | types::run_each< 69 | metal::transform< 70 | metal::partial< 71 | metal::lambda, 72 | metal::lambda>, 73 | metal::cartesian< 74 | metal::join< 75 | types::first_order_undirected_temporal_edges, 76 | types::first_order_directed_temporal_edges, 77 | types::first_order_directed_delayed_temporal_edges>, 78 | types::random_state_types>>>{}(m); 79 | } 80 | -------------------------------------------------------------------------------- /src/type_handles.hpp: -------------------------------------------------------------------------------- 1 | #include "bind_core.hpp" 2 | #include "type_str/scalars.hpp" 3 | #include "scalar_wrapper.hpp" 4 | 5 | template typename Template> 6 | struct is_specialization_of : std::false_type {}; 7 | 8 | template