diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e8c4565 --- /dev/null +++ b/.clang-format @@ -0,0 +1,36 @@ +--- +#from https://github.com/pybind/pybind11/blob/master/.clang-format +# See all possible options and defaults with: +# clang-format --style=llvm --dump-config +AccessModifierOffset: -4 +AllowShortLambdasOnASingleLine: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: All +ColumnLimit: 150 +CommentPragmas: 'NOLINT:.*|^ IWYU pragma:' +IncludeBlocks: Regroup +IndentCaseLabels: true +IndentPPDirectives: AfterHash +IndentWidth: 4 +Language: Cpp +SpaceAfterCStyleCast: true +Standard: Cpp11 +StatementMacros: ['PyObject_HEAD'] +TabWidth: 2 +IncludeCategories: + - Regex: '' + Priority: 4 + - Regex: '.*' + Priority: 5 \ No newline at end of file diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 0000000..276e34e --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,81 @@ +# Build on every branch push, tag push, and pull request change: +# From: https://github.com/pypa/cibuildwheel/blob/main/examples/github-deploy.yml +# Also: +# https://github.com/airspeed-velocity/asv/blob/main/.github/workflows/build_wheels.yml +# include [wheel build] in the commit to trigger wheel builds +name: Build wheels +on: [push, pull_request, workflow_dispatch] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + get_commit_message: + name: Get commit message + runs-on: ubuntu-latest + outputs: + message: ${{ steps.commit_message.outputs.message }} + steps: + - name: Checkout pyseldonlib + uses: actions/checkout@v4 + # Gets the correct commit message for pull request + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Get commit message + id: commit_message + run: | + set -xe + COMMIT_MSG=$(git log --no-merges -1 --oneline) + echo "message=$COMMIT_MSG" >> $GITHUB_OUTPUT + echo github.ref ${{ github.ref }} + build_wheels: + name: Build wheels + needs: get_commit_message + if: >- + contains(needs.get_commit_message.outputs.message, '[wheel build]') || + github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && ( ! endsWith(github.ref, 'dev0'))) + runs-on: ${{ matrix.os }} + env: # Needed for C++20 support on MacOS + MACOSX_DEPLOYMENT_TARGET: '12.6' + strategy: + # Ensure that a wheel builder finishes even if another fails + fail-fast: false + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ubuntu-24.04, windows-2022, macos-13, macos-14] + + steps: + - uses: actions/checkout@v4 + - name: Echo build platform + run: echo Building wheels for ${{ matrix.os }} + - name: Build wheels + uses: pypa/cibuildwheel@v2.20.0 + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + needs: get_commit_message + if: >- + contains(needs.get_commit_message.outputs.message, '[wheel build]') || + github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && ( ! endsWith(github.ref, 'dev0'))) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + shell: bash -l {0} + run: pipx run build --sdist + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..ebc91f5 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,54 @@ +name: Test and Documentation workflow + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + shell: micromamba-shell {0} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 # Updated to latest stable version + + - name: micromamba setup + uses: mamba-org/setup-micromamba@v1 + with: + environment-file: environment.yml + generate-run-shell: true + cache-environment: true + init-shell: bash + + - name: Install project dependencies + run: | + meson setup build + pip install . + + - name: Run tests + run: | + pytest tests/ + + - name: Install documentation dependencies + run: | + python -m pip install --upgrade pip + pip install -r docs/requirements.txt + + - name: Build the documentation + run: | + cd docs + sphinx-build -b html . source/_build/html + + - name: Deploy the documentation + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/source/_build/html + keep_files: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 259148f..7cf2109 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,33 @@ +#subprojects +/subprojects/seldon/ +# If we need something, we'll add it with -f +/subprojects/* +/wheelhouse/ + +#vscode +.vscode/ + +# docs +*_build/ +doc/apidocs/ + +#pycache +*__pycache__/ +*_cache/ + +#probability_distribution +output_probability_distributions/ + +#mesonpython +.mesonpy* +notes.txt +TODO.txt +tests/__pycache__/ +tests/outputs/ +.pytest* +output/ +build/ + # Prerequisites *.d @@ -30,3 +60,8 @@ *.exe *.out *.app + +# dist folder +dist/ + +token diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..69575f8 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v12.0.0 + hooks: + - id: clang-format + files: python_bindings/bindings.cpp + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.241 + hooks: + - id: ruff + files: ^tests/|^pyseldonlib/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index f288702..e72bfdd 100644 --- a/LICENSE +++ b/LICENSE @@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. \ No newline at end of file diff --git a/Project.md b/Project.md new file mode 100644 index 0000000..9752a4b --- /dev/null +++ b/Project.md @@ -0,0 +1,58 @@ +# PySeldonlib +![pyseldonlib](https://raw.githubusercontent.com/User-DK/pyseldon/main/res/logotext.png) +PySeldonlib is a Python Package for Opinion Dynamics Simulation, an extension of the [`Seldon Framework`](https://github.com/seldon-code/seldon). It provides: + +- Tools for the simulation of various Opinion Dynamics Models like the classical DeGroot Model, Deffuant Model, Activity Driven Model, etc. +- Tools to create, manipulate, and study complex networks which are either randomly generated or provided by the user. +- A clean and robust interface for conducting the simulations. + +## Opinion Dynamics + +Opinion dynamics is a field of study within the realm of complex systems and sociophysics that explores how opinions, beliefs, and attitudes evolve and spread within social networks. It combines elements of physics, social science, and mathematics to understand the mechanisms driving opinion formation and change under various influences, such as personal convictions, peer pressure, media impact, and societal norms. + +Our work contributes to this interdisciplinary field by providing robust tools for simulation and analysis, aiding in the understanding of complex opinion dynamics phenomena [`Seldon-Code`](https://github.com/seldon-code). + +## DeGroot Model Example + +The DeGroot model is a model of social influence. It describes how agents in a network can reach a consensus by updating their opinions based on the opinions of their neighbors. The DeGroot model is a simple model of social influence that has been studied extensively in the literature. It is used to model a wide range of social phenomena, such as the spread of information, the formation of opinions, and the emergence of social norms. + +Below is an example of reaching consensus in a network using the DeGroot model. We will create a network of 20 agents with random opinions and random connections between them. We will then conduct the simulation. + +### Initial Opinions + +Initial opinions of the agents in the network in the range of [0,1] are shown below: + +![Initial Opinions](https://github.com/User-DK/pyseldon/raw/main/visualisations/ouput_20_agents_10_connections_each/initial.png) + +### Final Opinions + +Final opinions of the agents in the network after the simulation are shown below: + +![Final Opinions](https://github.com/User-DK/pyseldon/raw/main/visualisations/ouput_20_agents_10_connections_each/final.png) + +We can conclude that the agents have reached a consensus after the simulation. + +### Reference +- DeGroot, M. H. (1974). Reaching a Consensus. Journal of the American Statistical Association, 69(345), 118–121. https://doi.org/10.2307/2285509 + + +### Usage + +```python +import pyseldonlib + +pyseldonlib.run_simulation_from_config_file(config_file_path = '/path/to/config/file') +``` + +```python +import pyseldonlib + +model = pyseldonlib.DeGroot_Model(max_iterations=1000, + convergence_tol=1e-6, + rng_seed=120, + other_settings=other_settings) + +output_dir_path = str("./output") + +model.run(output_dir_path) +``` \ No newline at end of file diff --git a/README.md b/README.md index 98397f7..ddb0dc8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,60 @@ -# pyseldon -Python bindings for the Seldon framework +# pyseldonlib +![](https://raw.githubusercontent.com/User-DK/pyseldon/main/res/logotext.png) + +## Python bindings for the Seldon framework + +This work is a part of Google Summer of Code 2024 under the Python Software Organisation. The aim of this project is to create Python bindings for the Seldon framework. The Seldon framework is a C++ Engine for Opinion Dynamics. The bindings and the complete project as a python package will allow users to use the Seldon framework in Python and carry out Simulations. + +### DeGroot Model Example +The DeGroot model is a model of social influence. It describes how agents in a network can reach a consensus by updating their opinions based on the opinions of their neighbors. The DeGroot model is a simple model of social influence that has been studied extensively in the literature. It is used to model a wide range of social phenomena, such as the spread of information, the formation of opinions, and the emergence of social norms. + +And here is the example of reaching consensus in a network using the DeGroot model. +We will create a network of 20 agents with random opinions and random connections between them. We will then conduct the simulation. as can be seen in the notebook file [here](./examples/ouput_20_agents_10_connections_each/degrootmodel.ipynb). + +Initial opinions of the agents in the network in the range of [0,1] are shown below: +![initial opinions](https://github.com/User-DK/pyseldon/raw/main/visualisations/ouput_20_agents_10_connections_each/initial.png) + +Final opinions of the agents in the network after the simulation are shown below: +![final opinions](https://github.com/User-DK/pyseldon/raw/main/visualisations/ouput_20_agents_10_connections_each/final.png) + +And we can conclude that the agents have reached a consensus after the simulation. + +### Reference +- DeGroot, M. H. (1974). Reaching a Consensus. Journal of the American Statistical Association, 69(345), 118–121. https://doi.org/10.2307/2285509 + +visualisations generated with the help of `cytoscape` some of the visualisations normalisation code can be found [here](./visualisations/) + +### Steps to Install and Compile the code + +### Installation +Create a new `micromamba` environment and activate it: +```bash +micromamba create -f environment.yml +micromamba activate pyseldonenv +``` +If you get problem with micromamba can look at the installation documentation [here](https://mamba.readthedocs.io/en/latest/installation/micromamba-installation.html) + +### Build +Set up the build directory with `meson`: +```bash +meson setup build +``` + +### Install Python package +Use `pip` to install the package: +```bash +pip install . +``` + +### Run the tests +Use `pytest` to run the tests: +```bash +pytest tests/ +``` + or + + ```bash +pytest -s tests/ +``` +to see the output of the tests without capturing the std output + diff --git a/bindings_package/__init__.py b/bindings_package/__init__.py new file mode 100644 index 0000000..ea0c4b9 --- /dev/null +++ b/bindings_package/__init__.py @@ -0,0 +1 @@ +from pyseldonlib import seldoncore \ No newline at end of file diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..0db98cc --- /dev/null +++ b/contributing.md @@ -0,0 +1,63 @@ + +# Contributing + +We love pull requests from everyone. By participating in this project, you +agree to abide by the contributor covenant suggested [code of conduct]. + +[code of conduct]: https://github.com/Seldon-code/seldon/blob/main/CODE_OF_CONDUCT.md + +Do make sure to run tests and generally **BE PREPARED** to have your code vetted and +checked. Do **NOT** submit code you would not be prepared to defend or maintain, +unless you are fixing a bug. + +Push to your fork and [submit a pull request][pr]. + +[pr]: https://github.com/Seldon-code/seldon/compare + +At this point you're waiting on us. We like to at least comment on pull requests +within four business days (and, typically, three business day). We may suggest +some changes or improvements or alternatives. + +Some things that will increase the chance that your pull request is accepted: + +- Write tests. +- Follow the commit-style below. + +## Commit Style + +A sample **good commit** is: + +``` +fileName: Thing I did +Some subHeading things + +So this change was to do that thing I thought was good. Also there was this +other person who thought so too, so then I ate a sandwich and we got the code +done. I am writing this but really, honestly, two people did this. + + +Co-authored-by: Joel Doe +``` + +A good commit should have: + +- The name of the file, or the topic or the subject you have changed or the + namespace or the functionality you have added **something:** +- A line describing **something:** +- An _(optional)_ subheading with more details +- An _(optional)_ paragraph or essay on why the change was done and anything else you want to share with the devs. +- **Co-authorship** IS MANDATORY if applicable. Even if you just had a sandwich + with the other person. It won't kill you to share, or to write that. + + + + + +### Commit template +If you are not already using a commit template, consider doing so. The Seldon repository includes a template under `.gitmessage`. + +``` +git config --global commit.template .gitmessage +``` + +You can omit the `--global` to only use the template when committing to Seldon. \ No newline at end of file diff --git a/docs/LICENSE.md b/docs/LICENSE.md new file mode 100644 index 0000000..82f2e51 --- /dev/null +++ b/docs/LICENSE.md @@ -0,0 +1,674 @@ +# GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..5c0c81c --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,75 @@ +import os +import sys + +sys.path.insert(0, os.path.abspath('../pyseldonlib')) + +project = 'pyseldonlib' +copyright = '2024, PySeldon Developers' +author = 'Amrita Goswami, Daivik Karbhari, Moritz Sallermann, Rohit Goswami' +release = '1.0' + +# -- General configuration --------------------------------------------------- + +extensions = [ + 'sphinx.ext.napoleon', # from https://stackoverflow.com/a/66930447/22039471 + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.viewcode', + 'sphinx.ext.coverage', + # 'nbsphinx', + 'sphinxcontrib.bibtex', + 'myst_nb', + 'sphinx_copybutton', + 'sphinx.ext.viewcode', + "numpydoc", +] +bibtex_bibfiles = [ + 'refs.bib', + ] + +jupyter_execute_notebooks = "auto" +autosummary_generate = True +numpydoc_show_class_members = False + +autodoc_default_options = { + 'members': True, + 'undoc-members': True, + 'private-members': True, + 'inherited-members': True, + 'show-inheritance': True, +} + +templates_path = ['source/_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- +html_theme = 'pydata_sphinx_theme' +html_title = "PySeldon" +html_logo = "../res/logotext.png" +html_favicon = "../res/favicon.ico" + +html_theme_options = { + "show_toc_level": 2, + "icon_links": [ + {"name": "Home Page", "url": "https://github.com/seldon-code/pyseldonlib", "icon": "fas fa-home"}, + { + "name": "GitHub", + "url": "https://github.com/seldon-code/pyseldonlib", + "icon": "fab fa-github-square", + }, + ], + # "navbar_end": ["theme-switcher", "navbar-icon-links"], + "pygments_light_style": "tango", + "pygments_dark_style": "monokai", +} + +html_sidebars = { + "**": ["sidebar-nav-bs"], + "index": [], + "install": [], + "quickstart": [], + "contributing": [], + "LICENSE": [], + "examples/index": [], +} \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..0db98cc --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,63 @@ + +# Contributing + +We love pull requests from everyone. By participating in this project, you +agree to abide by the contributor covenant suggested [code of conduct]. + +[code of conduct]: https://github.com/Seldon-code/seldon/blob/main/CODE_OF_CONDUCT.md + +Do make sure to run tests and generally **BE PREPARED** to have your code vetted and +checked. Do **NOT** submit code you would not be prepared to defend or maintain, +unless you are fixing a bug. + +Push to your fork and [submit a pull request][pr]. + +[pr]: https://github.com/Seldon-code/seldon/compare + +At this point you're waiting on us. We like to at least comment on pull requests +within four business days (and, typically, three business day). We may suggest +some changes or improvements or alternatives. + +Some things that will increase the chance that your pull request is accepted: + +- Write tests. +- Follow the commit-style below. + +## Commit Style + +A sample **good commit** is: + +``` +fileName: Thing I did +Some subHeading things + +So this change was to do that thing I thought was good. Also there was this +other person who thought so too, so then I ate a sandwich and we got the code +done. I am writing this but really, honestly, two people did this. + + +Co-authored-by: Joel Doe +``` + +A good commit should have: + +- The name of the file, or the topic or the subject you have changed or the + namespace or the functionality you have added **something:** +- A line describing **something:** +- An _(optional)_ subheading with more details +- An _(optional)_ paragraph or essay on why the change was done and anything else you want to share with the devs. +- **Co-authorship** IS MANDATORY if applicable. Even if you just had a sandwich + with the other person. It won't kill you to share, or to write that. + + + + + +### Commit template +If you are not already using a commit template, consider doing so. The Seldon repository includes a template under `.gitmessage`. + +``` +git config --global commit.template .gitmessage +``` + +You can omit the `--global` to only use the template when committing to Seldon. \ No newline at end of file diff --git a/docs/examples/degrootmodel.md b/docs/examples/degrootmodel.md new file mode 100644 index 0000000..cae1c93 --- /dev/null +++ b/docs/examples/degrootmodel.md @@ -0,0 +1,52 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.4 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# DeGroot Model (Reaching Consensus) + ++++ + +Import the packages + +```{code-cell} ipython3 +import pyseldonlib +import pathlib +import shutil +``` + +Initialize some other settings for the simulation + +```{code-cell} ipython3 +other_settings = pyseldonlib.Other_Settings(n_output_agents=10, + n_output_network= None, + print_progress= True, + output_initial=True, + start_output=1, + number_of_agents = 200, + connections_per_agent = 10) +``` + +Initialize the model with parameters. + +```{code-cell} ipython3 +model = pyseldonlib.DeGroot_Model(max_iterations=1000, + convergence_tol=1e-6, + rng_seed=120, + other_settings=other_settings) +output_dir_path = str("./output") +if pathlib.Path(output_dir_path).exists(): + shutil.rmtree(output_dir_path) +model.run(output_dir_path) +if pathlib.Path(output_dir_path).exists(): + shutil.rmtree(output_dir_path) +``` +### On your local machine you can see a C++ std::out \ No newline at end of file diff --git a/docs/examples/index.rst b/docs/examples/index.rst new file mode 100644 index 0000000..4d69beb --- /dev/null +++ b/docs/examples/index.rst @@ -0,0 +1,9 @@ +Examples +======== +This is a collection of examples that demonstrate how to use the library. + +.. toctree:: + :maxdepth: 4 + + degrootmodel + network \ No newline at end of file diff --git a/docs/examples/network.md b/docs/examples/network.md new file mode 100644 index 0000000..ef8af67 --- /dev/null +++ b/docs/examples/network.md @@ -0,0 +1,38 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.4 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Example Usage of the Network Class from the pyseldonlib Package + +```{code-cell} ipython3 +# Import necessary modules +import pyseldonlib +``` + +```{code-cell} ipython3 +# Initialize the Network object +network = pyseldonlib.Network( + model_string="DeGroot", + neighbour_list=[[1, 2], [0, 2], [0, 1], [4], [3]], + weight_list=[[0.5, 0.5], [0.5, 0.5], [0.5, 0.5], [1], [1]], + direction="Incoming" +) +``` + +```{code-cell} ipython3 +# Print the network details +print(f"Number of agents: {network.n_agents}") +print(f"Edges of 1st index agent: {network.n_edges(1)}") +print(f"Direction: {network.get_direction}") +print(f"Neighbour List: {network.get_neighbours(1)}") +print(f"Weight List: {network.get_weights(1)}") +``` diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..83f05de --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,64 @@ +.. image:: ../res/PySeldon_1.svg + :align: left + :width: 800px + :alt: PySeldonlib + +PySeldonlib Documentation +========================= + +Welcome to the **PySeldonlib** Documentation. PySeldonlib is a Python Package for Opinion Dynamics Simulation, an extension of the `Seldon Framework `_. +It provides: + +- tools for simulation of various Opinion Dynamics Models like the classical DeGroot Model, Deffuant Model, Activity Driven Model, etc. +- tools to create, manipulate and study complex networks which are either randomly generated or provided by the user. +- a clean and robust interface for conducting the simulations. + +Opinion Dynamics +---------------- +Opinion dynamics is a field of study within the realm of complex systems and sociophysics that explores how opinions, beliefs, and attitudes evolve and spread within social networks. It combines elements of physics, social science, and mathematics to understand the mechanisms driving opinion formation and change under various influences, such as personal convictions, peer pressure, media impact, and societal norms. +Our work contributes to this interdisciplinary field by providing robust tools for simulation and analysis, aiding in the understanding of complex opinion dynamics phenomena `Seldon-Code `_. + +DeGroot Model Example +--------------------- +The DeGroot model is a model of social influence. It describes how agents in a network can reach a consensus by updating their opinions based on the opinions of their neighbors. The DeGroot model is a simple model of social influence that has been studied extensively in the literature. It is used to model a wide range of social phenomena, such as the spread of information, the formation of opinions, and the emergence of social norms. + +And here is the example of reaching consensus in a network using the DeGroot model. +We will create a network of 20 agents with random opinions and random connections between them. We will then conduct the simulation. + +Initial opinions of the agents in the network in the range of [0,1] are shown below: + +.. image:: ../visualisations/ouput_20_agents_10_connections_each/initial.png + :align: center + :width: 600px + :alt: Initial Opinions + +Final opinions of the agents in the network after the simulation are shown below: + +.. image:: ../visualisations/ouput_20_agents_10_connections_each/final.png + :align: center + :width: 600px + :alt: Final opinions + +And we can conclude that the agents have reached a consensus after the simulation. + +Reference: +---------- +.. bibliography:: + :style: plain + + DeGroot_1974 + +.. Citing +.. ------ +.. To cite PySeldon + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + install + quickstart + source/reference/index + examples/index + contributing.md + LICENSE.md \ No newline at end of file diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..06b07f4 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,59 @@ +Installation +============= + +Python Package Index (PYPI) +--------------------------- +You can install the package from PYPI using `pip`: + +.. code-block:: bash + + $ pip install pyseldonlib + + +From Source +-------------- + +Create and Activate the Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create a new `micromamba` environment and activate it: + +.. code-block:: bash + + $ micromamba create -f environment.yml + + $ micromamba activate pyseldonenv + + +If you get problem with micromamba can look at the installation documentation `here `_ + +Build +~~~~~ +Set up the build directory with `meson`: + +.. code-block:: bash + + $ meson setup build + + +Install Python package +~~~~~~~~~~~~~~~~~~~~~~~ +Use `pip` to install the package: + +.. code-block:: bash + + $ pip install . + +Run the tests +~~~~~~~~~~~~~ +Use `pytest` to run the tests: + +.. code-block:: bash + + $ pytest tests + +or + +.. code-block:: bash + + $ python -s pytest tests \ No newline at end of file diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..1a48f04 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,82 @@ +Quick Start Guide +================= + +This guide will help you get started with the basics of using the `pyseldonlib` package. + +Installation +------------ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + install + +Usage +----- + +.. code-block:: python + + import pyseldonlib + + pyseldonlib.run_simulation_from_config_file( + config_file_path="path/to/config.toml", + agent_file_path="path/to/agent.csv", + network_file_path="path/to/network.csv", + output_dir_path="path/to/output" + ) + +This will run the simulation as per the configuration file and save the output files in the specified directory. + +Configuration TOML file Format +------------------------------ + +.. code-block:: toml + + [simulation] + model = "DeGroot" + rng_seed = 120 + + [io] + n_output_network = 20 + n_output_agents = 1 + print_progress = false + output_initial = true + start_output = 1 + start_numbering_from = 0 + + [model] + max_iterations = 20 + + [DeGroot] + convergence = 1e-3 + + [network] + number_of_agents = 300 + connections_per_agent = 10 + +Specifications +-------------- + +- **[simulation]** + - `model`: The model to run. Options are DeGroot, Deffuant, ActivityDriven, ActivityDrivenInertial. + - `rng_seed`: Seed for random number generation. If left empty, a random seed is used. + +- **[io]** + - `n_output_network`: Number of iterations between writing network files. + - `n_output_agents`: Number of iterations between writing agent opinion files. + - `print_progress`: Whether to print the iteration time. Default is false. + - `output_initial`: Whether to print the initial opinions and network file from step 0. Default is true. + - `start_output`: Iteration number from which to start writing outputs. + - `start_numbering_from`: Initial step number before the simulation starts. Default is 0. + +- **[model]** + - `max_iterations`: Maximum number of iterations. If not set, the maximum is infinite. + +- **[DeGroot]** + - `convergence`: Convergence threshold for the DeGroot model. + +- **[network]** + - `number_of_agents`: Number of agents in the network. + - `connections_per_agent`: Number of connections each agent has. + + diff --git a/docs/refs.bib b/docs/refs.bib new file mode 100644 index 0000000..3ab35f9 --- /dev/null +++ b/docs/refs.bib @@ -0,0 +1,71 @@ +@article{Baumann_2021, + title={Emergence of Polarized Ideological Opinions in Multidimensional Topic Spaces}, + volume={11}, + ISSN={2160-3308}, + url={http://dx.doi.org/10.1103/PhysRevX.11.011012}, + DOI={10.1103/physrevx.11.011012}, + number={1}, + journal={Physical Review X}, + publisher={American Physical Society (APS)}, + author={Baumann, Fabian and Lorenz-Spreen, Philipp and Sokolov, Igor M. and Starnini, Michele}, + year={2021}, + month={jan} +} + +@article{Baumann_2020, + title={Modeling Echo Chambers and Polarization Dynamics in Social Networks}, + volume={124}, + ISSN={1079-7114}, + url={http://dx.doi.org/10.1103/PhysRevLett.124.048301}, + DOI={10.1103/physrevlett.124.048301}, + number={4}, + journal={Physical Review Letters}, + publisher={American Physical Society (APS)}, + author={Baumann, Fabian and Lorenz-Spreen, Philipp and Sokolov, Igor M. and Starnini, Michele}, + year={2020}, + month={jan} +} + +@article{Clifford_1973, + author = {CLIFFORD, PETER and SUDBURY, AIDAN}, + title = "{A model for spatial conflict}", + journal = {Biometrika}, + volume = {60}, + number = {3}, + pages = {581-588}, + year = {1973}, + month = {12}, + abstract = {Two species compete for territory along their mutual boundary. The species are fairly matched and the result of conflict is the invasion by one of the species of territory held by the other. A simple stochastic model for this process is described and rules are given for the calculation, as a function of time, of the probabilities that individual territories and groups of territories are held by a single species. Asymptotic results are given for the pattern of territories held by a single species, and here a remarkable distinction is seen between the cases of one-dimensional and two-dimensional competition and the case of three-dimensional competition. The process of invasion is contrasted with two relatively benign processes, that in which an exchange of territories is arranged and that in which a spatial alternation of species may be beneficial to the community. The implications of the long-term behaviour of such processes are discussed. It is suggested further that, under certain ideal conditions, it may be possible to estimate the duration of a spatial struggle for life by observing at some time the configuration of territories held by each species.}, + issn = {0006-3444}, + doi = {10.1093/biomet/60.3.581}, + url = {https://doi.org/10.1093/biomet/60.3.581}, + eprint = {https://academic.oup.com/biomet/article-pdf/60/3/581/576759/60-3-581.pdf}, +} + +@article{Deffuant_2000, +author = {Deffuant, Guillaume and Neau, David and Amblard, Frederic and Weisbuch, G\'{e}rard}, +title = {Mixing beliefs among interacting agents}, +journal = {Advances in Complex Systems}, +volume = {03}, +number = {01n04}, +pages = {87-98}, +year = {2000}, +doi = {10.1142/S0219525900000078}, +URL = {https://doi.org/10.1142/S0219525900000078}, +eprint = {https://doi.org/10.1142/S0219525900000078}, +abstract = { We present a model of opinion dynamics in which agents adjust continuous opinions as a result of random binary encounters whenever their difference in opinion is below a given threshold. High thresholds yield convergence of opinions towards an average opinion, whereas low thresholds result in several opinion clusters: members of the same cluster share the same opinion but are no longer influenced by members of other clusters. } +} + +@article{DeGroot_1974, +author = {Morris H. Degroot}, +title = {Reaching a Consensus}, +journal = {Journal of the American Statistical Association}, +volume = {69}, +number = {345}, +pages = {118--121}, +year = {1974}, +publisher = {ASA Website}, +doi = {10.1080/01621459.1974.10480137}, +URL = {https://www.tandfonline.com/doi/abs/10.1080/01621459.1974.10480137}, +eprint = {https://www.tandfonline.com/doi/pdf/10.1080/01621459.1974.10480137} +} \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..9276454 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,10 @@ +sphinx +sphinxcontrib-napoleon +sphinx-autobuild +nbsphinx +sphinx-favicon +sphinx-copybutton +numpydoc +pydata-sphinx-theme +myst-nb +sphinxcontrib-bibtex \ No newline at end of file diff --git a/docs/source/reference/activitydriven.rst b/docs/source/reference/activitydriven.rst new file mode 100644 index 0000000..81ec138 --- /dev/null +++ b/docs/source/reference/activitydriven.rst @@ -0,0 +1,8 @@ +Activity Driven Model +===================== + +.. automodule:: pyseldonlib.ActivityDrivenModel + :members: True + :undoc-members: True + :show-inheritance: True + :inherited-members: True diff --git a/docs/source/reference/deffuant.rst b/docs/source/reference/deffuant.rst new file mode 100644 index 0000000..46fd23a --- /dev/null +++ b/docs/source/reference/deffuant.rst @@ -0,0 +1,8 @@ +Deffuant Model +============== + +.. automodule:: pyseldonlib.DeffuantModel + :members: True + :undoc-members: True + :show-inheritance: True + :inherited-members: True \ No newline at end of file diff --git a/docs/source/reference/deffuantvector.rst b/docs/source/reference/deffuantvector.rst new file mode 100644 index 0000000..1b85b84 --- /dev/null +++ b/docs/source/reference/deffuantvector.rst @@ -0,0 +1,8 @@ +Deffuant Vector Model +====================== + +.. automodule:: pyseldonlib.DeffuantVectorModel + :members: True + :undoc-members: True + :show-inheritance: True + :inherited-members: True \ No newline at end of file diff --git a/docs/source/reference/degroot.rst b/docs/source/reference/degroot.rst new file mode 100644 index 0000000..be9c2ad --- /dev/null +++ b/docs/source/reference/degroot.rst @@ -0,0 +1,8 @@ +DeGroot Model +============= + +.. automodule:: pyseldonlib.DeGrootModel + :members: True + :undoc-members: True + :show-inheritance: True + :inherited-members: True \ No newline at end of file diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst new file mode 100644 index 0000000..8543813 --- /dev/null +++ b/docs/source/reference/index.rst @@ -0,0 +1,13 @@ +Reference +============= + +This is the class and function reference of PySeldon. + +.. toctree:: + :maxdepth: 4 + + introduction + models + network + util + packagereference diff --git a/docs/source/reference/inertial.rst b/docs/source/reference/inertial.rst new file mode 100644 index 0000000..104164f --- /dev/null +++ b/docs/source/reference/inertial.rst @@ -0,0 +1,8 @@ +Activity Driven Inertial Model +============================== + +.. automodule:: pyseldonlib.InertialModel + :members: True + :undoc-members: True + :show-inheritance: True + :inherited-members: True diff --git a/docs/source/reference/introduction.rst b/docs/source/reference/introduction.rst new file mode 100644 index 0000000..332827e --- /dev/null +++ b/docs/source/reference/introduction.rst @@ -0,0 +1,11 @@ +Introduction +============ +Pyseldon provides a simple interface for Opinion Dynamics simulations. It is designed to be easy to use and to provide a simple way to run simulations and analyze the results. +The package provides a set of classes that implement different models of opinion dynamics. It is a compiled package, so it is fast and efficient. The package is written in C++ and binded with Pybind11 to provide a Python interface. +Some of the models that are implemented in the package are: + +- DeGroot Model +- Deffuant Model +- Deffuant Vector Model +- Activity Driven Model +- Inertial Model \ No newline at end of file diff --git a/docs/source/reference/models.rst b/docs/source/reference/models.rst new file mode 100644 index 0000000..b8f86a5 --- /dev/null +++ b/docs/source/reference/models.rst @@ -0,0 +1,12 @@ +Models +====== +The following models are available in the `pyseldonlib` package: + +.. toctree:: + :maxdepth: 4 + + degroot + deffuant + deffuantvector + activitydriven + inertial \ No newline at end of file diff --git a/docs/source/reference/network.rst b/docs/source/reference/network.rst new file mode 100644 index 0000000..374e7de --- /dev/null +++ b/docs/source/reference/network.rst @@ -0,0 +1,8 @@ +network +======= + +.. automodule:: pyseldonlib.network + :members: True + :undoc-members: True + :show-inheritance: True + :inherited-members: True \ No newline at end of file diff --git a/docs/source/reference/packagereference.rst b/docs/source/reference/packagereference.rst new file mode 100644 index 0000000..1bcbd2d --- /dev/null +++ b/docs/source/reference/packagereference.rst @@ -0,0 +1,8 @@ +Complete Api Reference +====================== + +.. automodule:: pyseldonlib.__init__ + :members: True + :undoc-members: True + :show-inheritance: True + :inherited-members: True \ No newline at end of file diff --git a/docs/source/reference/util.rst b/docs/source/reference/util.rst new file mode 100644 index 0000000..ddcb173 --- /dev/null +++ b/docs/source/reference/util.rst @@ -0,0 +1,8 @@ +util +==== + +.. automodule:: pyseldonlib.utils + :members: True + :undoc-members: True + :show-inheritance: True + :inherited-members: True \ No newline at end of file diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..428b279 --- /dev/null +++ b/environment.yml @@ -0,0 +1,24 @@ +name: pyseldonenv +channels: + - conda-forge + +# adapted from the Seldon Code Repository +dependencies: + - meson + - ninja + - python + - pybind11 + - pytest + - meson-python + - fmt + - cmake + - cpp-argparse + - clang-format=18.1.1 + - tomlplusplus + - catch2 + - pkg-config + - pre-commit + - clang-format + - pip + - pip: + - ruff \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..a82663d --- /dev/null +++ b/meson.build @@ -0,0 +1,50 @@ +project('pyseldonlib', 'cpp', + version: '1.0.0', + default_options: ['cpp_std=c++20', 'warning_level=3']) + +_deps = [] + +cppc = meson.get_compiler('cpp') +seldon_subproj = subproject('seldon', default_options: ['default_library=static','build_tests=false', + 'build_exe=false']) +seldon_dep = seldon_subproj.get_variable('seldon_static_dep') + +_deps += seldon_dep + +py = import('python').find_installation('python') +_deps += py.dependency() + +pyb11_dep = [ + py.dependency(), + dependency('pybind11') +] +_deps += pyb11_dep + +py.extension_module( + 'seldoncore', + sources : [ + 'python_bindings/bindings.cpp' + ], + dependencies: _deps, + install: true, + subdir: 'bindings/', +) + +py.install_sources( + [ + 'pyseldonlib/__init__.py', + 'pyseldonlib/_basemodel.py', + 'pyseldonlib/DeGrootModel.py', + 'pyseldonlib/DeffuantModel.py', + 'pyseldonlib/DeffuantVectorModel.py', + 'pyseldonlib/ActivityDrivenModel.py', + 'pyseldonlib/InertialModel.py', + 'pyseldonlib/network.py', + 'pyseldonlib/_othersettings.py', + 'pyseldonlib/_run_simulation.py', + 'pyseldonlib/utils.py', + + ], + pure: false, # do not install next to compiled extension + subdir: 'pyseldonlib' +) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c969307 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,71 @@ +[project] +name = "pyseldonlib" +description="Python Bindings for the Seldon Framework" +authors = [ + {name = "Daivik Karbhari", email="daivikkarbhari01@gmail.com"}, + ] +maintainers = [ + {name = "Amrita Goswami", email = "amrita16thaug646@gmail.com"}, + {name = "Daivik Karbhari", email="daivikkarbhari01@gmail.com"}, + {name = "Moritz Sallermann", email = "moritzsallermann@gmail.com"}, + {name = "Rohit Goswami", email = "rgoswami@ieee.org"}, +] +dependencies=[ + "pybind11>=2.12.0" +] +keywords = [ + "Seldon-Code","Seldon", "Opinion Dynamics", "Opinion Dynamics Simulation", + "Social Influence", "Social Dynamics", + "DeGroot Model", "Inertial Model", "Activity Driven Model", + "Deffuant Model", "Deffuant Vector Model" +] +requires-python = ">=3.9" +readme = 'Project.md' # different for pypi package +license = {text = "GPL v3"} # just mention the license text to not clutter the metadata +dynamic = [ + "version", +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Researchers, Developers, Data Scientists, Students", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Software Development :: Libraries", + "Topic :: Scientific/Engineering :: Simulation, Social Dynamics, Opinion Dynamics", +] + +[project.urls] +"Source Code" = "https://github.com/seldon-code/pyseldonlib" +Documentation = "https://seldon-code.github.io/pyseldonlib/" + +[build-system] +requires = ["pybind11","meson-python","setuptools>=42", "wheel", "setuptools_scm"] +build-backend= "mesonpy" + +[tool.setuptools_scm] + +[tool.setuptools] +packages = ["pyseldon"] + +[tool.meson-python.args] +install = ['--skip-subprojects'] + + +[tool.cibuildwheel] +skip = [ "pp*" ] + +[tool.cibuildwheel.linux] +archs = ["auto64"] +manylinux-x86_64-image = "manylinux2014" +manylinux-aarch64-image = "manylinux2014" +musllinux-x86_64-image = "musllinux_1_2" + +[tool.cibuildwheel.macos] +archs = [ "auto64" ] + +[tool.cibuildwheel.windows] +archs = [ "auto64" ] diff --git a/pyseldonlib/ActivityDrivenModel.py b/pyseldonlib/ActivityDrivenModel.py new file mode 100644 index 0000000..5767961 --- /dev/null +++ b/pyseldonlib/ActivityDrivenModel.py @@ -0,0 +1,342 @@ +""" +In the study of Social Opinion Dynamics, it is crucial to consider multiple factors that significantly influence the formation and spread of opinions within networks of agents. The Activity Driven Model addresses this by simulating the evolution of opinions over time, capturing the complex interactions that lead to consensus formation, polarization, and the influence of external entities such as bots. + +This model is highly configurable, allowing users to explore a wide range of scenarios and behaviors by adjusting parameters related to agent activity, social influence, homophily, and more. It also incorporates recent considerations, such as the role of bots—agents with fixed opinions—who interact with humans on social media platforms, thereby influencing human-machine interactions and their effects on opinion dynamics. + +As the name suggests, the `Activity Driven` Model focuses on the activities of agents, which represent the interactions they perform within a social network. + +The key features of this models are : + +Temporal Dynamics +----------------- +max_iterations: + Limits the total number of simulation steps. + If set to None, the model runs indefinitely, allowing for long-term analysis of opinion evolution. + +dt: + Defines the time step for each iteration, controlling the pace at which the simulation progresses. + Smaller values lead to more granular updates, while larger values speed up the simulation but might miss finer details. + +Agent Behavior and Interaction +------------------------------ +m: + Determines how many agents an active agent interacts with during each time step. + Influences the rate of opinion spreading; higher values mean more interactions and potentially faster consensus or polarization. + +eps: + Sets the minimum activity level for agents, ensuring no agent is completely inactive. + Helps prevent stagnation in the model by keeping all agents engaged at some level. + +gamma: + Controls the distribution of agent activity, typically following a power-law where few agents are very active, and many are less active. + Affects how central or peripheral agents influence the overall opinion dynamics. + +homophily: + Measures the tendency of agents to interact with others who share similar opinions. + High homophily can lead to echo chambers, while low homophily promotes diverse interactions. + +reciprocity: + Determines whether agents are likely to reciprocate interactions, creating more mutual or one-sided connections. + High reciprocity strengthens bidirectional relationships, potentially stabilizing opinion clusters. + +K: + Represents the strength of social influence between agents. + A higher K means opinions are more strongly influenced by interactions, which can accelerate consensus or deepen polarization. + +Social Context and Controversialness +------------------------------------ +alpha: + Controls the degree of controversialness of the issue being simulated. + A higher alpha can lead to more polarized opinions, as agents might have stronger reactions to the issue. + +Bots and External Influence +--------------------------- +n_bots: + Specifies the number of bots in the simulation, which are fixed in their opinions. + Bots influence the network without being influenced, potentially driving opinion shifts or reinforcing certain views. + +bot_m, bot_activity, bot_opinion, bot_homophily: + Define the specific behaviors and characteristics of bots, such as how often they interact or how similar they are to other agents. + These parameters allow bots to mimic or diverge from regular agents, providing a controlled way to study external influence. + +Reluctance and Activity Correlation +----------------------------------- +use_reluctances: + Activates the feature where agents have a reluctance to change their opinions. + Adds complexity by simulating resistance to change, affecting how quickly or slowly opinions evolve. + +reluctance_mean, reluctance_sigma, reluctance_eps: + Define the distribution of reluctance across agents, determining the average resistance and its variability. + These parameters help model heterogeneous populations where some agents are more resistant to change than others. + +covariance_factor: + Introduces a correlation between an agent's activity level and its reluctance, meaning that activity might influence or be influenced by reluctance. + Allows for more realistic scenarios where active agents may be more or less open to changing their opinions, depending on the sign of the covariance. + +Example: +--------- +>>> from pyseldonlib import Activity_Driven_Model +>>> # Create the Activity Driven Model +>>> model = Activity_Driven_Model(max_iterations=1000, convergence_tol=1e-6) +>>> # Run the simulation +>>> model.run("output_dir") +>>> # Access the network +>>> network = model.get_Network() +>>> # Access the opinions of the agents +>>> opinions = model.agents_opinions() +>>> activity = model.agents_activity() +>>> reluctance = model.agents_reluctance() + +Reference +--------- +.. bibliography:: + :style: plain + + Baumann_2020 + Baumann_2021 + +************* +""" + +from bindings import seldoncore +import pathlib +from typing import Optional +from ._basemodel import Base_Model + +from ._othersettings import Other_Settings + + +class Activity_Driven_Model(Base_Model): + """ + Activity Driven Model base class for Simulation. + + Parameters + ----------- + max_iterations : int, default=None + The maximum number of iterations to run the simulation. If None, the simulation runs infinitely. + + dt : float, default=0.01 + The time step for the simulation. + + m : int, default=10 + Number of agents contacted, when the agent is active. + + eps : float, default=0.01 + The minimum activity epsilon. + + gamma : float, default=2.1 + Exponent of activity power law distribution of activities. + + alpha : float default=3.0 + Controversialness of the issue, must be greater than 0. + + homophily : float, default=0.5 + The extent to which similar agents interact with similar other. + Example: If 0.0, agents pick their interaction partners at random. + If 1.0, agents interact only with agents of the same opinion. + + reciprocity : float, default=0.5 + The extent to which agents reciprocate interactions. + Example: If 0.0, agents do not reciprocate interactions. + If 1.0, agents reciprocate all interactions. + + K : float, default=3.0 + Social interaction strength. + + mean_activities : bool, default=False + Whether use the mean value of the powerlaw distribution for the activities of all agents. + + mean_weights : bool, default=False + Whether use the meanfield approximation of the network edges, by default is False. + + n_bots : int, default=0 + Number of bots in the simulation. + + .. note:: + Bots are agents that are not influenced by the opinions of other agents, but they can influence the opinions of other agents. So they have fixed opinions and different parameters, the parameters are specified in the following lists. + + bot_m : list[int], default=[] + Value of m for the bots, If not specified, defaults to `m`. + + bot_activity : list[float], default=[], + The list of bot activities, If not specified, defaults to 0. + + bot_opinion : list[float], default=[] + The fixed opinions of the bots. + + bot_homophily : list[float], default=[] + The list of bot homophily, If not specified, defaults to `homophily`. + + use_reluctances : int, default=False + Whether use reluctances, by default is False and every agent has a reluctance of 1. + + reluctance_mean : float, default=1.0 + Mean of distribution before drawing from a truncated normal distribution. + + reluctance_sigma : float, default=0.25 + Width of normal distribution (before truncating). + + reluctance_eps : float, default=0.01 + Minimum such that the normal distribution is truncated at this value. + + covariance_factor : float, default=0.0 + Covariance Factor, defines the correlation between reluctances and activities. + + rng_seed : int, default=None + The seed for the random number generator. If not provided, a random seed is picked. + + agent_file : str, default=None + The file to read the agents from. If None, the agents are generated randomly. + + network_file : str, default=None + The file to read the network from. If None, the network is generated randomly + + other_settings : Other_Settings, default=None + The other settings for the simulation. If None, the default settings are used. + + Attributes + ----------- + Network : Network (Object) + The network generated by the simulation. + + Opinion : Float + The opinions of the agents or nodes of the network. + """ + + def __init__( + self, + max_iterations: int = None, + dt: float = 0.01, + m: int = 10, + eps: float = 0.01, + gamma: float = 2.1, + alpha: float = 3.0, + homophily: float = 0.5, + reciprocity: float = 0.5, + K: float = 3.0, + mean_activities: bool = False, + mean_weights: bool = False, + n_bots: int = 0, + bot_m: list[int] = [], + bot_activity: list[float] = [], + bot_opinion: list[float] = [], + bot_homophily: list[float] = [], + use_reluctances: int = False, + reluctance_mean: float = 1.0, + reluctance_sigma: float = 0.25, + reluctance_eps: float = 0.01, + covariance_factor: float = 0.0, + rng_seed: Optional[int] = None, + agent_file: Optional[str] = None, + network_file: Optional[str] = None, + other_settings: Other_Settings = None, + ): + # Other settings and Simulation Options are already intialised in super + super().__init__() + if other_settings is not None: + self.other_settings = other_settings + + self._options.model_string = "ActivityDriven" + self._options.model_settings = seldoncore.ActivityDrivenSettings() + self._options.output_settings = self.other_settings.output_settings + self._options.network_settings = self.other_settings.network_settings + self._options.model = seldoncore.Model.ActivityDrivenModel + + self._options.model_settings.max_iterations = max_iterations + self._options.model_settings.dt = dt + self._options.model_settings.m = m + self._options.model_settings.eps = eps + self._options.model_settings.gamma = gamma + self._options.model_settings.alpha = alpha + self._options.model_settings.homophily = homophily + self._options.model_settings.reciprocity = reciprocity + self._options.model_settings.K = K + self._options.model_settings.mean_activities = mean_activities + self._options.model_settings.mean_weights = mean_weights + self._options.model_settings.n_bots = n_bots + self._options.model_settings.bot_m = bot_m + self._options.model_settings.bot_activity = bot_activity + self._options.model_settings.bot_opinion = bot_opinion + self._options.model_settings.bot_homophily = bot_homophily + self._options.model_settings.use_reluctances = use_reluctances + self._options.model_settings.reluctance_mean = reluctance_mean + self._options.model_settings.reluctance_sigma = reluctance_sigma + self._options.model_settings.reluctance_eps = reluctance_eps + self._options.model_settings.covariance_factor = covariance_factor + + if rng_seed is not None: + self._options.rng_seed = rng_seed + + self._simulation = seldoncore.SimulationActivityAgent( + options=self._options, + cli_agent_file=agent_file, + cli_network_file=network_file, + ) + + self._network = self._simulation.network + + def agent_activity(self, index: int = None): + """ + Access the agents activity data from the simulated network. + + Parameters + ----------- + index : int + The index of the agent to access. The index is 0-based. If not provided, all agents are returned. + """ + if index is None: + result = [agent.data.activity for agent in self._simulation.network.agent] + return result + else: + if index < 0 or index >= self.Network.n_agents(): + raise IndexError("Agent index is out of range.") + return self._simulation.network.agent[index].data.activity + + def set_agent_activity(self, index: int, activity: float): + """ + Set the activity of a specific agent. + + Parameters + ---------- + index : int + The index of the agent whose opinion is to be set. + activity : float + The new activity value for the agent. + """ + if index < 0 or index >= self.Network.n_agents(): + raise IndexError("Agent index is out of range.") + + self._simulation.network.agent[index].data.activity = activity + + def agent_reluctance(self, index: int = None): + """ + Access the agents reluctance data from the simulated network. + + Parameters + ----------- + index : int + The index of the agent to access. The index is 0-based. If not provided, all agents are returned. + """ + if index is None: + result = [agent.data.reluctance for agent in self._simulation.network.agent] + return result + else: + if index < 0 or index >= self.Network.n_agents(): + raise IndexError("Agent index is out of range.") + return self._simulation.network.agent[index].data.reluctance + + def set_agent_reluctance(self, index: int, reluctance: float): + """ + Set the reluctance of a specific agent. + + Parameters + ---------- + index : int + The index of the agent whose opinion is to be set. + reluctance : float + The new reluctance value for the agent. + """ + if index < 0 or index >= self.Network.n_agents(): + raise IndexError("Agent index is out of range.") + + self._simulation.network.agent[index].data.reluctance = reluctance diff --git a/pyseldonlib/DeGrootModel.py b/pyseldonlib/DeGrootModel.py new file mode 100644 index 0000000..3267923 --- /dev/null +++ b/pyseldonlib/DeGrootModel.py @@ -0,0 +1,116 @@ +""" +The DeGroot Model is a fundamental framework in social influence theory, illustrating how agents in a network update their opinions based on the opinions of their neighbors. At its core, the model posits that each agent revises their opinion by taking the weighted average of their neighbors' opinions. This iterative process continues until the opinions converge to a consensus or a stable state. + +In practical terms, consider a group of individuals—such as a committee or a team—each holding a subjective probability distribution for an unknown parameter. The DeGroot Model describes how these individuals might converge on a unified subjective probability distribution through iterative opinion updates, effectively pooling their individual insights. + +Additionally, the model is versatile and can be applied to scenarios where opinions are represented as point estimates rather than probability distributions. In such cases, the DeGroot Model helps illustrate how a group can achieve consensus on a specific parameter estimate, reflecting the collective judgment of the group. + +Key features +------------ +Opinion Averaging: + Agents update their opinions based on the average opinions of their neighbors, fostering convergence and consensus. + +Iterative Process: + The model operates through a series of iterations, with opinions being refined each step until stability is achieved. + +Consensus Formation: + Applicable to both probability distributions and point estimates, showing how diverse opinions can be aggregated into a common view. + +The DeGroot Model provides a clear and elegant approach to understanding how social influence and information sharing lead to collective agreement within a network of agents. + +Example: +--------- +>>> from pyseldonlib import DeGroot_Model +>>> # Create the DeGroot Model +>>> model = DeGroot_Model(max_iterations=1000, convergence_tol=1e-6) +>>> # Run the simulation +>>> model.run("output_dir") +>>> # Access the network +>>> network = model.get_Network() +>>> # Access the opinions of the agents +>>> opinions = model.agents_opinions() + +Reference: +---------- +.. bibliography:: + :style: plain + + DeGroot_1974 + +************* +""" + +from bindings import seldoncore +from typing import Optional +from ._basemodel import Base_Model + +from ._othersettings import Other_Settings + + +class DeGroot_Model(Base_Model): + """ + DeGroot Model base class for Simulation. + + Parameters + ----------- + max_iterations : int, default=None + The maximum number of iterations to run the simulation. If None, the simulation runs infinitely. + + convergence_tol : float, default=1e-6 + The tolerance for convergence of the simulation. + + rng_seed : int, default=None + The seed for the random number generator. If not provided, a random seed is picked. + + agent_file : str, default=None + The file to read the agents from. If None, the agents are generated randomly. + + network_file : str, default=None + The file to read the network from. If None, the network is generated randomly + + other_settings : Other_Settings, default=None + The other settings for the simulation. If None, the default settings are used. + + Attributes + ----------- + Network : Network (Object) + The network generated by the simulation. + + Opinion : Float + The opinions of the agents or nodes of the network. + + see also: seldoncore.Network + """ + + def __init__( + self, + max_iterations: int = None, + convergence_tol: float = 1e-6, + rng_seed: Optional[int] = None, + agent_file: Optional[str] = None, + network_file: Optional[str] = None, + other_settings: Other_Settings = None, + ): + super().__init__() + if other_settings is not None: + self.other_settings = other_settings + + self._options.model_string = "DeGroot" + self._options.model_settings = seldoncore.DeGrootSettings() + self._options.output_settings = self.other_settings.output_settings + self._options.network_settings = self.other_settings.network_settings + self._options.model = seldoncore.Model.DeGroot + + self._options.model_settings.max_iterations = max_iterations + self._options.model_settings.convergence_tol = convergence_tol + + if rng_seed is not None: + self._options.rng_seed = rng_seed + + self._simulation = seldoncore.SimulationSimpleAgent( + options=self._options, + cli_agent_file=agent_file, + cli_network_file=network_file, + ) + + self._network = self._simulation.network diff --git a/pyseldonlib/DeffuantModel.py b/pyseldonlib/DeffuantModel.py new file mode 100644 index 0000000..7b8fee8 --- /dev/null +++ b/pyseldonlib/DeffuantModel.py @@ -0,0 +1,121 @@ +""" +The Deffuant Model, also known as the "Mixing of Beliefs among Interacting Agents," describes how agents update their continuous opinions through random binary encounters. In this model, agents only adjust their opinions if the difference between their opinions is below a specified threshold, known as the Homophily Threshold. + +Model Dynamics +-------------- +Homophily Threshold: + If the difference in opinions between two interacting agents is less than this threshold, they will update their opinions towards each other. This process leads to opinion convergence or clustering depending on the value of the threshold. + +High Thresholds: + When the Homophily Threshold is high, opinions tend to converge towards an average opinion, as agents are more selective about whom they interact with. + +Low Thresholds: + When the Homophily Threshold is low, the model results in the formation of several distinct opinion clusters. Agents within the same cluster share similar opinions and are less influenced by agents outside their cluster. + +Example: +--------- +>>> from pyseldonlib import Deffuant_Model +>>> # Create the Deffuant Model +>>> deffuant = Deffuant_Model(max_iterations=1000, homophily_threshold=0.2, mu=0.5) +>>> # Run the simulation +>>> deffuant.run("output_dir") +>>> # Access the network +>>> network = deffuant.get_Network() +>>> # Access the opinions of the agents +>>> opinions = deffuant.agents_opinions() + +Reference: +---------- +.. bibliography:: + :style: plain + + Deffuant_2000 + +************* +""" + +from bindings import seldoncore +import pathlib +from typing import Optional +from ._basemodel import Base_Model +from ._othersettings import Other_Settings + + +class Deffuant_Model(Base_Model): + """ + Deffuant Model base class for Simulation. + + Parameters + ----------- + max_iterations : int, default=None + The maximum number of iterations to run the simulation. If None, the simulation runs infinitely. + + homophily_threshold : float, default=0.2 + The threshold for homophily. If the difference in opinions between two agents is less than this value, they interact. + + mu : float, default=0.5 + The convergence rate of the agents. + + use_network : bool, default=False + For using a square lattice network. Will throw error if sqrt(n_agents) is not an integer. + + rng_seed : int, default=None + The seed for the random number generator. If not provided, a random seed is picked. + + agent_file : str, default=None + The file to read the agents from. If None, the agents are generated randomly. + + network_file : str, default=None + The file to read the network from. If None, the network is generated randomly + + other_settings : Other_Settings, default=None + The other settings for the simulation. If None, the default settings are used. + + Attributes + ----------- + Network : Network (Object) + The network generated by the simulation. + + Opinion : Float + The opinions of the agents or nodes of the network. + """ + + def __init__( + self, + max_iterations: int = None, + homophily_threshold: float = 0.2, + mu: float = 0.5, + use_network: bool = False, + rng_seed: Optional[int] = None, + agent_file: Optional[str] = None, + network_file: Optional[str] = None, + other_settings: Other_Settings = None, + ): + # Other settings and Simulation Options are already intialised in super + super().__init__() + self.other_settings = Other_Settings() + if other_settings is not None: + self.other_settings = other_settings + + self._options.model_string = "Deffuant" + self._options.model_settings = seldoncore.DeffuantSettings() + self._options.output_settings = self.other_settings.output_settings + self._options.network_settings = self.other_settings.network_settings + self._options.model = seldoncore.Model.DeffuantModel + + if rng_seed is not None: + self._options.rng_seed = rng_seed + + self._options.model_settings.max_iterations = max_iterations + self._options.model_settings.homophily_threshold = homophily_threshold + self._options.model_settings.mu = mu + self._options.model_settings.use_network = use_network + self._options.model_settings.use_binary_vector = False + + self._simulation = seldoncore.SimulationSimpleAgent( + options=self._options, + cli_agent_file=agent_file, + cli_network_file=network_file, + ) + + self._network = self._simulation.network diff --git a/pyseldonlib/DeffuantVectorModel.py b/pyseldonlib/DeffuantVectorModel.py new file mode 100644 index 0000000..509addf --- /dev/null +++ b/pyseldonlib/DeffuantVectorModel.py @@ -0,0 +1,124 @@ +""" +The Deffuant Vector Model extends the traditional Deffuant Model to multi-dimensional binary vectors. In this model, each agent’s opinion is represented as a binary vector, where each dimension of the vector can have a value of either 0 or 1. The model describes how agents adjust their binary opinions through random binary encounters, similar to the classical Deffuant approach. + +Model Dynamics +-------------- +Binary Opinions: + Each opinion is represented as a binary vector, where the values are restricted to 0 or 1. The interaction and adjustment process involves comparing these vectors and updating them based on the Homophily Threshold. + +Homophily Threshold: + Agents will only adjust their opinions if the difference between their opinion vectors is below a specified threshold. This difference is computed in a way that considers the multi-dimensional nature of the opinion vectors. + +The Deffuant Model provides insight into how personal interactions and opinion thresholds influence the dynamics of opinion formation and clustering within a group of agents. + +Example: +--------- +>>> from pyseldonlib import Deffuant_Vector_Model +>>> # Create the Deffuant Vector Model +>>> deffuant = Deffuant_Vector_Model(max_iterations=1000, homophily_threshold=0.2, mu=0.5) +>>> # Run the simulation +>>> deffuant.run("output_dir") +>>> # Access the network +>>> network = deffuant.get_Network() +>>> # Access the opinions of the agents +>>> opinions = deffuant.agents_opinions() + +Reference: +---------- +.. bibliography:: + :style: plain + + Deffuant_2000 + +************* +""" + +from bindings import seldoncore +import pathlib +from typing import Optional +from ._basemodel import Base_Model +from ._othersettings import Other_Settings + + +class Deffuant_Vector_Model(Base_Model): + """ + Deffuant Vector Model base class for Simulation. + + Parameters + ----------- + max_iterations : int, default=None + The maximum number of iterations to run the simulation. If None, the simulation runs infinitely. + + homophily_threshold : float, default=0.2 + The threshold for homophily. If the difference in opinions between two agents is less than this value, they interact. + + mu : float, default=0.5 + The convergence rate of the agents. + + use_network : bool, default=False + For using a square lattice network. Will throw if sqrt(n_agents) is not an integer. + + dim : int, default=1 + For the multi-dimensional binary vector Deffuant model, define the number of dimensions in each opinion vector + + rng_seed : int, default=None + The seed for the random number generator. If not provided, a random seed is picked. + + agent_file : str, default=None + The file to read the agents from. If None, the agents are generated randomly. + + network_file : str, default=None + The file to read the network from. If None, the network is generated randomly + + other_settings : Other_Settings, default=None + The other settings for the simulation. If None, the default settings are used. + + Attributes + ----------- + Network : Network (Object) + The network generated by the simulation. + + Opinion : Float + The opinions of the agents or nodes of the network. + """ + + def __init__( + self, + max_iterations: int = None, + homophily_threshold: float = 0.2, + mu: float = 0.5, + use_network: bool = False, + dim: int = 1, + rng_seed: Optional[int] = None, + agent_file: Optional[str] = None, + network_file: Optional[str] = None, + other_settings: Other_Settings = None, + ): + # Other settings and Simulation Options are already intialised in super + super().__init__() + if other_settings is not None: + self.other_settings = other_settings + + self._options.model_string = "Deffuant" + self._options.model_settings = seldoncore.DeffuantSettings() + self._options.output_settings = self.other_settings.output_settings + self._options.network_settings = self.other_settings.network_settings + self._options.model = seldoncore.Model.DeffuantModel + + if rng_seed is not None: + self._options.rng_seed = rng_seed + + self._options.model_settings.max_iterations = max_iterations + self._options.model_settings.homophily_threshold = homophily_threshold + self._options.model_settings.mu = mu + self._options.model_settings.use_network = use_network + self._options.model_settings.use_binary_vector = True + self._options.model_settings.dim = dim + + self._simulation = seldoncore.SimulationDiscreteVectorAgent( + options=self._options, + cli_agent_file=agent_file, + cli_network_file=network_file, + ) + + self._network = self._simulation.network diff --git a/pyseldonlib/InertialModel.py b/pyseldonlib/InertialModel.py new file mode 100644 index 0000000..f9a45e9 --- /dev/null +++ b/pyseldonlib/InertialModel.py @@ -0,0 +1,324 @@ +""" +In the study of Social Opinion Dynamics, it is essential to consider various factors that influence the formation and dissemination of opinions within networks of agents. The Activity Driven Inertial Model (or just Inertial Model) expands upon the traditional Activity Driven Model by introducing an additional parameter: the friction coefficient. This model simulates the evolution of opinions over time, capturing the complex interactions that lead to consensus formation, polarization, and the influence of external entities such as bots. + +The friction coefficient in this model represents the resistance to change in an agent's opinion, introducing an inertial effect. This concept is akin to physical inertia, where an agent's opinion resists sudden changes, leading to smoother transitions in the opinion landscape. This feature adds realism by modeling how strongly held beliefs or social inertia can slow the pace of opinion change, even in the face of active social interactions. + +Like the traditional Activity Driven Model, the Inertial Model is highly configurable, allowing users to explore a wide range of scenarios and behaviors by adjusting parameters related to agent activity, social influence, homophily, and now, opinion inertia. The model also considers the role of bots—agents with fixed opinions—who interact with humans on social media platforms, influencing human-machine interactions in opinion dynamics. + +Key features as discussed in the Activity Driven Model: + +Temporal Dynamics +----------------- +max_iterations: + Limits the total number of simulation steps. + If set to None, the model runs indefinitely, allowing for long-term analysis of opinion evolution. + +dt: + Defines the time step for each iteration, controlling the pace at which the simulation progresses. + Smaller values lead to more granular updates, while larger values speed up the simulation but might miss finer details. + +Agent Behavior and Interaction +------------------------------ +m: + Determines how many agents an active agent interacts with during each time step. + Influences the rate of opinion spreading; higher values mean more interactions and potentially faster consensus or polarization. + +eps: + Sets the minimum activity level for agents, ensuring no agent is completely inactive. + Helps prevent stagnation in the model by keeping all agents engaged at some level. + +gamma: + Controls the distribution of agent activity, typically following a power-law where few agents are very active, and many are less active. + Affects how central or peripheral agents influence the overall opinion dynamics. + +homophily: + Measures the tendency of agents to interact with others who share similar opinions. + High homophily can lead to echo chambers, while low homophily promotes diverse interactions. + +reciprocity: + Determines whether agents are likely to reciprocate interactions, creating more mutual or one-sided connections. + High reciprocity strengthens bidirectional relationships, potentially stabilizing opinion clusters. + +K: + Represents the strength of social influence between agents. + A higher K means opinions are more strongly influenced by interactions, which can accelerate consensus or deepen polarization. + +Social Context and Controversialness +------------------------------------ +alpha: + Controls the degree of controversialness of the issue being simulated. + A higher alpha can lead to more polarized opinions, as agents might have stronger reactions to the issue. + +Bots and External Influence +--------------------------- +n_bots: + Specifies the number of bots in the simulation, which are fixed in their opinions. + Bots influence the network without being influenced, potentially driving opinion shifts or reinforcing certain views. + +bot_m, bot_activity, bot_opinion, bot_homophily: + Define the specific behaviors and characteristics of bots, such as how often they interact or how similar they are to other agents. + These parameters allow bots to mimic or diverge from regular agents, providing a controlled way to study external influence. + +Reluctance and Activity Correlation +----------------------------------- +use_reluctances: + Activates the feature where agents have a reluctance to change their opinions. + Adds complexity by simulating resistance to change, affecting how quickly or slowly opinions evolve. + +reluctance_mean, reluctance_sigma, reluctance_eps: + Define the distribution of reluctance across agents, determining the average resistance and its variability. + These parameters help model heterogeneous populations where some agents are more resistant to change than others. + +covariance_factor: + Introduces a correlation between an agent's activity level and its reluctance, meaning that activity might influence or be influenced by reluctance. + Allows for more realistic scenarios where active agents may be more or less open to changing their opinions, depending on the sign of the covariance. + +Friction Coefficient +-------------------- +The friction coefficient in the Activity Driven Inertial Model represents the resistance to change in an agent's opinion, akin to physical inertia. It introduces an inertial effect that causes opinions to change more gradually, reflecting the persistence of strongly held beliefs and social inertia in opinion dynamics. + +Example: +--------- +>>> from pyseldonlib import Inertial_Model +>>> # Create the Inertial Model +>>> model = Inertial_Model(max_iterations=1000, convergence_tol=1e-6) +>>> # Run the simulation +>>> model.run("output_dir") +>>> # Access the network +>>> network = model.get_Network() +>>> # Access the opinions of the agents +>>> opinions = model.agents_opinions() +>>> activity = model.agents_activity() +>>> reluctance = model.agents_reluctance() +>>> velocity = model.agent_velocity() + +Reference: +---------- +.. bibliography:: + :style: plain + + Baumann_2020 + Baumann_2021 + +************* +""" + +from bindings import seldoncore +import pathlib +from typing import Optional +from .ActivityDrivenModel import Activity_Driven_Model +from ._othersettings import Other_Settings + + +class Inertial_Model(Activity_Driven_Model): + """ + Inertial Model base class for Simulation. + + Parameters + ----------- + max_iterations : int, default=None + The maximum number of iterations to run the simulation. If None, the simulation runs infinitely. + + dt : float, default=0.01 + The time step for the simulation. + + m : int, default=10 + Number of agents contacted, when the agent is active. + + eps : float, default=0.01 + The minimum activity epsilon. + + gamma : float, default=2.1 + Exponent of activity power law distribution of activities. + + alpha : float default=3.0 + Controversialness of the issue, must be greater than 0. + + homophily : float, default=0.5 + The extent to which similar agents interact with similar other. + Example: If 0.0, agents pick their interaction partners at random. + If 1.0, agents interact only with agents of the same opinion. + + reciprocity : float, default=0.5 + The extent to which agents reciprocate interactions. + Example: If 0.0, agents do not reciprocate interactions. + If 1.0, agents reciprocate all interactions. + + K : float, default=3.0 + Social interaction strength. + + mean_activities : bool, default=False + Whether use the mean value of the powerlaw distribution for the activities of all agents. + + mean_weights : bool, default=False + Whether use the meanfield approximation of the network edges, by default is False. + + n_bots : int, default=0 + Number of bots in the simulation. + + .. note:: + Bots are agents that are not influenced by the opinions of other agents, but they can influence the opinions of other agents. So they have fixed opinions and different parameters, the parameters are specified in the following lists. + + bot_m : list[int], default=[] + Value of m for the bots, If not specified, defaults to `m`. + + bot_activity : list[float], default=[], + The list of bot activities, If not specified, defaults to 0. + + bot_opinion : list[float], default=[] + The fixed opinions of the bots. + + bot_homophily : list[float], default=[] + The list of bot homophily, If not specified, defaults to `homophily`. + + use_reluctances : int, default=False + Whether use reluctances, by default is False and every agent has a reluctance of 1. + + reluctance_mean : float, default=1.0 + Mean of distribution before drawing from a truncated normal distribution. + + reluctance_sigma : float, default=0.25 + Width of normal distribution (before truncating). + + reluctance_eps : float, default=0.01 + Minimum such that the normal distribution is truncated at this value. + + covariance_factor : float, default=0.0 + Covariance Factor, defines the correlation between reluctances and activities. + + rng_seed : int, default=None + The seed for the random number generator. If not provided, a random seed is picked. + + agent_file : str, default=None + The file to read the agents from. If None, the agents are generated randomly. + + network_file : str, default=None + The file to read the network from. If None, the network is generated randomly + + other_settings : Other_Settings, default=None + The other settings for the simulation. If None, the default settings are used. + + friction_coefficient : float, default=1.0 + The friction coefficient for the inertial model. + + Attributes + ----------- + Network : Network (Object) + The network generated by the simulation. + + Opinion : Float + The opinions of the agents or nodes of the network. + + Activity : Float + The activity of the agents or nodes of the network. + + Reluctance : Float + The reluctance of the agents or nodes of the network. + """ + + def __init__( + self, + max_iterations: int = None, + dt: float = 0.01, + m: int = 10, + eps: float = 0.01, + gamma: float = 2.1, + alpha: float = 3.0, + homophily: float = 0.5, + reciprocity: float = 0.5, + K: float = 3.0, + mean_activities: bool = False, + mean_weights: bool = False, + n_bots: int = 0, + bot_m: list[int] = [], + bot_activity: list[float] = [], + bot_opinion: list[float] = [], + bot_homophily: list[float] = [], + use_reluctances: int = False, + reluctance_mean: float = 1.0, + reluctance_sigma: float = 0.25, + reluctance_eps: float = 0.01, + covariance_factor: float = 0.0, + friction_coefficient: float = 1.0, + rng_seed: Optional[int] = None, + agent_file: Optional[str] = None, + network_file: Optional[str] = None, + other_settings: Other_Settings = None, + ): + # Other settings and Simulation Options are already intialised in super + super().__init__() + if other_settings is not None: + self.other_settings = other_settings + + self._options.model_string = "ActivityDrivenInertial" + self._options.model_settings = seldoncore.ActivityDrivenInertialSettings() + self._options.output_settings = self.other_settings.output_settings + self._options.network_settings = self.other_settings.network_settings + self._options.model = seldoncore.Model.ActivityDrivenInertial + + if rng_seed is not None: + self._options.rng_seed = rng_seed + + self._options.model_settings.max_iterations = max_iterations + self._options.model_settings.dt = dt + self._options.model_settings.m = m + self._options.model_settings.eps = eps + self._options.model_settings.gamma = gamma + self._options.model_settings.alpha = alpha + self._options.model_settings.homophily = homophily + self._options.model_settings.reciprocity = reciprocity + self._options.model_settings.K = K + self._options.model_settings.mean_activities = mean_activities + self._options.model_settings.mean_weights = mean_weights + self._options.model_settings.n_bots = n_bots + self._options.model_settings.bot_m = bot_m + self._options.model_settings.bot_activity = bot_activity + self._options.model_settings.bot_opinion = bot_opinion + self._options.model_settings.bot_homophily = bot_homophily + self._options.model_settings.use_reluctances = use_reluctances + self._options.model_settings.reluctance_mean = reluctance_mean + self._options.model_settings.reluctance_sigma = reluctance_sigma + self._options.model_settings.reluctance_eps = reluctance_eps + self._options.model_settings.covariance_factor = covariance_factor + self._options.model_settings.friction_coefficient = friction_coefficient + + self._simulation = seldoncore.SimulationInertialAgent( + options=self._options, + cli_agent_file=agent_file, + cli_network_file=network_file, + ) + + self._network = self._simulation.network + + def agent_velocity(self, index: int = None): + """ + Access the agents reluctance data from the simulated network. + + Parameters + ----------- + index : int + The index of the agent to access. The index is 0-based. If not provided, all agents are returned. + """ + if index is None: + result = [agent.data.velocity for agent in self._simulation.network.agent] + return result + else: + if index < 0 or index >= self.Network.n_agents(): + raise IndexError("Agent index is out of range.") + return self._simulation.network.agent[index].data.velocity + + def set_agent_velocity(self, index: int, velocity: float): + """ + Set the velocity of a specific agent. + + Parameters + ---------- + index : int + The index of the agent whose opinion is to be set. + velocity : float + The new velocity value for the agent. + """ + if index < 0 or index >= self.Network.n_agents(): + raise IndexError("Agent index is out of range.") + + self._simulation.network.agent[index].data.velocity = velocity diff --git a/pyseldonlib/__init__.py b/pyseldonlib/__init__.py new file mode 100644 index 0000000..cf22de2 --- /dev/null +++ b/pyseldonlib/__init__.py @@ -0,0 +1,43 @@ +"""This module provides the main interface to the pyseldonlib package. + +It includes the following classes and functions: +- DeGrootModel +- Deffuant_Model +- Deffuant_Vector_Model +- Inertial_Model +- Activity_Driven_Model +- Other_Settings +- Network +- run_simulation_from_config_file +- run_simulation_from_options +- parse_config_file +""" + +from typing import Optional, Union +from .DeGrootModel import DeGroot_Model +from .DeffuantModel import Deffuant_Model +from .DeffuantVectorModel import Deffuant_Vector_Model +from .InertialModel import Inertial_Model +from .ActivityDrivenModel import Activity_Driven_Model +from ._othersettings import Other_Settings +from .utils import * +from .network import Network +from ._run_simulation import ( + run_simulation_from_config_file, + run_simulation_from_options, + parse_config_file, +) +from bindings import seldoncore + +__all__ = [ + "run_simulation_from_config_file", + "run_simulation_from_options", + "DeGroot_Model", + "Deffuant_Model", + "Deffuant_Vector_Model", + "Inertial_Model", + "Activity_Driven_Model", + "Other_Settings", + "Network", + "parse_config_file", +] diff --git a/pyseldonlib/_basemodel.py b/pyseldonlib/_basemodel.py new file mode 100644 index 0000000..b7a289c --- /dev/null +++ b/pyseldonlib/_basemodel.py @@ -0,0 +1,128 @@ +from bindings import seldoncore +import pathlib +import logging +from typing import Optional + +from ._othersettings import Other_Settings + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +class Base_Model: + def __init__(self): + self.other_settings = Other_Settings() + self._options = seldoncore.SimulationOptions() + self._simulation = None + self.Network = None + + def run(self, output_dir: str = None): + """ + Run the simulation. + + Parameters + ----------- + output_dir : str, default="./output" + The directory to output the files to. + """ + seldoncore.validate_settings(self._options) + seldoncore.print_settings(self._options) + cwd = pathlib.Path.cwd() + if output_dir is None: + output_dir = "./output" + output_path = cwd / pathlib.Path(output_dir) + if output_path.exists(): + raise Exception( + "Output Directory already Exists!! Either delete it or change the path!!" + ) + logger.info(f"Output directory path set to: {output_path}\n") + output_path.mkdir(parents=True, exist_ok=True) + self._simulation.run(output_dir) + self._network = self._simulation.network + + def print_settings(self): + """ + Print the settings of the simulation. + """ + seldoncore.print_settings(self._options) + + @property + def Network(self): + """ + Access the network generated by the simulation. + + Returns + ------- + seldoncore.Network + The network generated by the simulation. + """ + return self._network + + @Network.setter + def Network(self, value): + """ + Set the network for the simulation. + + Parameters + ---------- + value : seldoncore.Network + The network to set for the simulation. + """ + self._network = value + self._simulation.network = value + + def agent_opinion(self, index: int = None): + """ + Access the agents data from the simulation. + + Parameters + ----------- + index : int + The index of the agent to access. The index is 0-based. If not provided, all agents are returned. + """ + if index is None: + result = [agent.data.opinion for agent in self._simulation.network.agent] + return result + else: + if index < 0 or index >= self.Network.n_agents(): + raise IndexError("Agent index is out of range.") + return self._simulation.network.agent[index].data.opinion + + def set_agent_opinion(self, index: int, opinion: float): + """ + Set the opinion of a specific agent. + + Parameters + ---------- + index : int + The index of the agent whose opinion is to be set. + opinion : float + The new opinion value for the agent. + """ + if index < 0 or index >= self.Network.n_agents(): + raise IndexError("Agent index is out of range.") + + self._simulation.network.agent[index].data.opinion = opinion + + def __getattr__(self, name): + if "_options" in self.__dict__ and hasattr( + self.__dict__["_options"].model_settings, name + ): + return getattr(self.__dict__["_options"].model_settings, name) + elif name == "rng_seed": + return self.__dict__["_options"].rng_seed + elif name == "other_settings": + return self.__dict__["other_settings"] + else: + return self.__dict__[name] + + def __setattr__(self, name, value): + if "_options" in self.__dict__ and hasattr( + self.__dict__["_options"].model_settings, name + ): + setattr(self.__dict__["_options"].model_settings, name, value) + elif name == "rng_seed": + self.__dict__["_options"].rng_seed = value + elif name == "other_settings": + self.__dict__["other_settings"] = value + else: + self.__dict__[name] = value diff --git a/pyseldonlib/_othersettings.py b/pyseldonlib/_othersettings.py new file mode 100644 index 0000000..b4ce783 --- /dev/null +++ b/pyseldonlib/_othersettings.py @@ -0,0 +1,57 @@ +from bindings import seldoncore +from typing import Optional + + +class Other_Settings: + """ + All other settings for the simulation. + + Parameters + ----------- + + n_output_agents : int, default=None + Write out the agents every n iterations. + + n_output_network : int, default=None + Write out the network every n iterations. + + print_progress : bool, default=False + Print the progress of the simulation. + + output_initial : bool, default=True + Output initial opinions and network. + + start_output : int, default=1 + Start printing opinion and/or network files from this iteration number. + + start_numbering_from : int, default=0 + The initial step number, before the simulation runs, is this value. The first step would be (1+start_numbering_from). + + number_of_agents : int, default=200 + The number of agents in the network. + + connections_per_agent : int, default=10 + The number of connections per agent. + """ + + def __init__( + self, + n_output_agents: Optional[int] = None, + n_output_network: Optional[int] = None, + print_progress: bool = False, + output_initial: bool = True, + start_output: int = 1, + start_numbering_from: int = 0, + number_of_agents: int = 200, + connections_per_agent: int = 10, + ): + self.output_settings = seldoncore.OutputSettings() + self.output_settings.n_output_agents = n_output_agents + self.output_settings.n_output_network = n_output_network + self.output_settings.print_progress = print_progress + self.output_settings.output_initial = output_initial + self.output_settings.start_output = start_output + self.output_settings.start_numbering_from = start_numbering_from + self.network_settings = seldoncore.InitialNetworkSettings() + self.network_settings.number_of_agents = number_of_agents + self.network_settings.connections_per_agent = connections_per_agent diff --git a/pyseldonlib/_run_simulation.py b/pyseldonlib/_run_simulation.py new file mode 100644 index 0000000..8692a01 --- /dev/null +++ b/pyseldonlib/_run_simulation.py @@ -0,0 +1,80 @@ +from bindings import seldoncore +from typing import Optional + + +def run_simulation_from_config_file( + config_file_path: str, + agent_file_path: Optional[str] = None, + network_file_path: Optional[str] = None, + output_dir_path: Optional[str] = None, +): + """Run the simulation using the configuration(toml) file. + + Parameters + ----------- + config_file_path : str + The path to the configuration(toml) file. + + agent_file_path : str, optional + The path to the agent file. + + network_file_path : str, optional + The path to the network file. + + output_dir_path : str, deafult="./output" + """ + seldoncore.run_simulation( + config_file_path=config_file_path, + options=None, + agent_file_path=agent_file_path, + network_file_path=network_file_path, + output_dir_path=output_dir_path, + ) + + +def run_simulation_from_options( + options: object, + agent_file_path: Optional[str] = None, + network_file_path: Optional[str] = None, + output_dir_path: Optional[str] = None, +): + """ + Run the simulation using the simulation options object. + + Note + ---- + The options object must be created using the SimulationOptions class. + + Parameters + ----------- + options : object + The simulation options object. + agent_file_path : str, optional + The path to the agent file. + network_file_path : str, optional + The path to the network file. + output_dir_path : str, optional + The path to the output directory. + """ + seldoncore.run_simulation( + options=options.options, + config_file_path=None, + agent_file_path=agent_file_path, + network_file_path=network_file_path, + output_dir_path=output_dir_path, + ) + + +def parse_config_file(file_path: str): + """Parse the toml file and create the simulation options object. + + Parameters + ----------- + file_path : str + The path to the toml file. + + Returns + ------- + Simulation: The simulation object. + """ + return seldoncore.parse_config_file(file_path) diff --git a/pyseldonlib/network.py b/pyseldonlib/network.py new file mode 100644 index 0000000..af048fe --- /dev/null +++ b/pyseldonlib/network.py @@ -0,0 +1,298 @@ +"""The network module contains the Network class""" + +from bindings import seldoncore +import logging +import bindings + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +class Network: + def __init__( + self, + model_string: str = None, + n_agents: int = None, + agents: int = None, + neighbour_list: list[int] = [], + weight_list: list[float] = [], + direction: str = "Incoming", + ): + """ + The Network class supports various network models: + + 1. DeGroot: The DeGroot network. (opinions only) + 2. Deffuant: The Deffuant network. (opinions only) + 3. DeffuantVector: The DeffuantVector network. (binary vector opinions) + 4. ActivityDriven: The ActivityDriven network. (opinions, activity, and reluctance) + 5. Inertial: The Inertial network. (opinions, activity, reluctance, and velocity) + 6. Float: The float network. (just nodes and edges) + + The Network class can be instantiated in different ways: + - By providing the model string (compulsory). + 1. By providing the number of agents. + 2. By providing the list of agents. + 3. By providing the list of neighbours, weights, and direction. + 4. Default constructor. + + Parameters + ---------- + model_string : str, optional + The model string. Default is None. + n_agents : int, optional + The number of agents. Default is None. + agents : list[int], optional + The list of agents. Default is None. + neighbour_list : list[int], optional + The list of neighbours. Default is an empty list. + weight_list : list[float], optional + The list of weights. Default is an empty list. + direction : str, optional + The direction of the network. Default is "Incoming". + """ + + if model_string == "DeGroot" or model_string == "Deffuant": + if n_agents: + self.network = seldoncore.SimpleAgentNetwork(n_agents) + elif agents: + self.network = seldoncore.SimpleAgentNetwork(agents) + elif neighbour_list and weight_list and direction: + if direction == "Incoming" or direction == "Outgoing": + self.network = seldoncore.SimpleAgentNetwork( + neighbour_list, weight_list, direction + ) + else: + TypeError("Direction allowed values are 'Incoming' or 'Outgoing'") + else: + self.network = seldoncore.SimpleAgentNetwork() + + elif model_string == "DeffuantVector": + if n_agents: + self.network = seldoncore.DiscreteVectorAgentNetwork(n_agents) + elif agents: + self.network = seldoncore.DiscreteVectorAgentNetwork(agents) + elif neighbour_list and weight_list and direction: + if direction == "Incoming" or direction == "Outgoing": + self.network = seldoncore.DiscreteVectorAgentNetwork( + neighbour_list, weight_list, direction + ) + else: + TypeError("Direction allowed values are 'Incoming' or 'Outgoing'") + else: + self.network = seldoncore.DiscreteVectorAgentNetwork() + + elif model_string == "ActivityDriven": + if n_agents: + self.network = seldoncore.ActivityDrivenAgentNetwork(n_agents) + elif agents: + self.network = seldoncore.ActivityDrivenAgentNetwork(agents) + elif neighbour_list and weight_list and direction: + if direction == "Incoming" or direction == "Outgoing": + self.network = seldoncore.ActivityDrivenAgentNetwork( + neighbour_list, weight_list, direction + ) + else: + TypeError("Direction allowed values are 'Incoming' or 'Outgoing'") + else: + self.network = seldoncore.ActivityDrivenAgentNetwork() + + elif model_string == "ActivityDrivenInertial" or model_string == "Inertial": + if n_agents: + self.network = seldoncore.InertialAgentNetwork(n_agents) + elif agents: + self.network = seldoncore.InertialAgentNetwork(agents) + elif neighbour_list and weight_list and direction: + if direction == "Incoming" or direction == "Outgoing": + self.network = seldoncore.InertialAgentNetwork( + neighbour_list, weight_list, direction + ) + else: + TypeError("Direction allowed values are 'Incoming' or 'Outgoing'") + else: + self.network = seldoncore.InertialAgentNetwork() + + else: + logger.warning( + "This is a float type network that can't be used for the simulation as it doesn't contain any agents and their data like opinions, etc." + ) + if n_agents: + self.network = seldoncore.Network(n_agents) + elif agents: + self.network = seldoncore.Network(agents) + elif neighbour_list and weight_list and direction: + if direction == "Incoming" or direction == "Outgoing": + self.network = seldoncore.Network( + neighbour_list, weight_list, direction + ) + else: + TypeError("Direction allowed values are 'Incoming' or 'Outgoing'") + else: + self.network = seldoncore.Network() + + @property + def n_agents(self): + """The number of nodes/agents in the network.""" + return self.network.n_agents() + + def n_edges(self, agent_idx: int): + """The number of edges going out/coming in at `agent_idx` in the network. + + Parameters + ----------- + agent_idx : int + The index of the agent. If not provided, the total number of edges in the network is returned. + + """ + return self.network.n_edges(agent_idx) + + @property + def get_direction(self): + """The direction of the network.""" + return self.network.direction() + + @property + def strongly_connected_components(self): + """The strongly connected components of the network. + + Returns + list: The strongly connected components of the network. + """ + return self.network.strongly_connected_components() + + def get_neighbours(self, index: int): + """The neighbours of the node/agent in the network. + + Parameters + ----------- + index : int + The index of the agent. + + """ + return self.network.get_neighbours(index) + + def get_weights(self, index: int): + """The weights of the agent. + + Parameters + ----------- + index : int + The index of the agent. + + """ + + return self.network.get_weights(index) + + def set_weights(self, agent_idx: int, weights: list): + """Set the weights of the agent. + + Parameters + ----------- + index : int + The index of the agent. + weights : list[float] + The weights of the agent. + + """ + + return self.network.set_weights(agent_idx, weights) + + def set_neighbours_and_weights( + self, agent_idx: int, buffer_neighbours: list[int], buffer_weights: list[float] + ): + """Sets the neighbour indices and weights at agent_idx + + Parameters + ----------- + agent_idx : int + The index of the agent. + buffer_neighbours : list[int] + The list of neighbours. + buffer_weights : list[float] + The list of weights. + + """ + return self.network.set_neighbours_and_weights( + agent_idx, buffer_neighbours, buffer_weights + ) + + def set_neighbours_and_weights( + self, agent_idx: int, buffer_neighbours: list[int], weight: float + ): + """Sets the neighbour indices and sets the weight to a constant value at agent_idx in the network. + + Parameters + ----------- + agent_idx : int + The index of the agent. + buffer_neighbours : list[int] + The list of neighbours. + weight : float + The weight of the agent. + + """ + return self.network.set_neighbours_and_weights( + agent_idx, buffer_neighbours, weight + ) + + def push_back_neighbour_and_weight( + self, agent_idx_i: int, agent_idx_j: int, weight: float + ): + """Adds an edge between agent_idx_i and agent_idx_j with weight w + + Parameters + ------------ + agent_idx_i : int + The index of the agent. + agent_idx_j : int + The index of the agent. + weight : float + The weight of the agent. + + """ + return self.network.push_back_neighbour_and_weight( + agent_idx_i, agent_idx_j, weight + ) + + @property + def transpose(self): + """Transposes the network, without switching the direction flag (expensive). + + Example: + -------- + N(inc) -> N(inc)^T + + """ + return self.network.transpose() + + @property + def toggle_incoming_outgoing(self): + """Switches the direction flag *without* transposing the network (expensive) + + Example: + -------- + N(inc) -> N(out) + + """ + + return self.network.toggle_incoming_outgoing() + + @property + def switch_direction_flag(self): + """Only switches the direction flag. This effectively transposes the network and, simultaneously, changes its representation. + + Example: + -------- + N(inc) -> N^T(out) + + """ + return self.network.switch_direction_flag() + + @property + def remove_double_counting(self): + """Sorts the neighbours by index and removes doubly counted edges by summing the weights of the corresponding edges.""" + return self.network.remove_double_counting() + + @property + def clear(self): + """Clears the network.""" + + return self.network.clear() \ No newline at end of file diff --git a/pyseldonlib/utils.py b/pyseldonlib/utils.py new file mode 100644 index 0000000..e554fa0 --- /dev/null +++ b/pyseldonlib/utils.py @@ -0,0 +1,243 @@ +"""This module contains functions to generate networks and save them to files.""" + +from bindings import seldoncore +import logging +import bindings + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +def generate_n_connections(model_string, n_agents:int, n_connections:int, self_interaction:bool= False, rng_seed:int=None): + """ + Generate n_connections Network for n_agents. + + Parameters + ----------- + model_string : str + The model string. Allowed values are "DeGroot", "Deffuant", "DeffuantVector", "ActivityDriven", "Inertial", "Float". + n_agents : int + The number of agents. + n_connections : int + The number of connections. + self_interaction : bool, default=False + If True, self iteraction is allowed. + rng_seed : int, default=None + The seed for the random number generator. If not provided, a random seed is picked. + + Returns + ----------- + Network: The network. + """ + if model_string == "DeGroot" or model_string == "Deffuant": + if rng_seed is not None: + return seldoncore.generate_n_connections_simple_agent(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction,seed = rng_seed) + else: + return seldoncore.generate_n_connections_simple_agent(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction) + elif model_string == "DeffuantVector": + if rng_seed is not None: + return seldoncore.generate_n_connections_discrete_vector_agent(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction,seed = rng_seed) + else: + return seldoncore.generate_n_connections_discrete_vector_agent(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction) + elif model_string == "ActivityDriven": + if rng_seed is not None: + return seldoncore.generate_n_connections_activity_agent(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction,seed = rng_seed) + else: + return seldoncore.generate_n_connections_activity_agent(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction) + elif model_string == "Inertial": + if rng_seed is not None: + return seldoncore.generate_n_connections_inertial_agent(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction,seed = rng_seed) + else: + return seldoncore.generate_n_connections_inertial_agent(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction) + elif model_string == "Float": + if rng_seed is not None: + return seldoncore.generate_n_connections_(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction,seed = rng_seed) + else: + return seldoncore.generate_n_connections_(n_agents= n_agents,n_connections = n_connections,self_interaction = self_interaction) + +def generate_fully_connected(model_string, n_agents:int, weight:float= None, rng_seed:int=None): + """ + Generate a fully connected Network for n_agents. + + Parameters + ----------- + model_string : str + The model string. Allowed values are "DeGroot", "Deffuant", "DeffuantVector", "ActivityDriven", "Inertial", "Float". + n_agents : int + The number of agents. + weight : float + The weight of the agent. + rng_seed : int, default=None + The seed for the random number generator. If not provided, a random seed is picked. + + Returns + ----------- + Network: The fully connected network. + """ + if model_string == "DeGroot" or model_string == "Deffuant": + return seldoncore.generate_fully_connected_simple_agent(n_agents= n_agents,weight = weight,seed = rng_seed) + elif model_string == "DeffuantVector": + return seldoncore.generate_fully_connected_discrete_vector_agent(n_agents= n_agents,weight = weight,seed = rng_seed) + elif model_string == "ActivityDriven": + return seldoncore.generate_fully_connected_activity_agent(n_agents= n_agents,weight = weight,seed = rng_seed) + elif model_string == "Inertial": + return seldoncore.generate_fully_connected_inertial_agent(n_agents= n_agents,weight = weight,seed = rng_seed) + elif model_string == "Float": + return seldoncore.generate_fully_connected_(n_agents= n_agents,weight = weight,seed = rng_seed) + +def generate_square_lattice(model_string, n_edge: int, weight: float): + """ + Generate a square lattice Network. + + Parameters + ----------- + model_string : str + The model string. Allowed values are "DeGroot", "Deffuant", "DeffuantVector", "ActivityDriven", "Inertial", "Float". + n_edge : int + The number of edges. + weight : float + The weight of the agent. + + Returns + ----------- + Network: The square lattice network. + """ + if model_string == "DeGroot" or model_string == "Deffuant": + return seldoncore.generate_square_lattice_simple_agent(n_edge= n_edge,weight = weight) + elif model_string == "DeffuantVector": + return seldoncore.generate_square_lattice_discrete_vector_agent(n_edge= n_edge,weight = weight) + elif model_string == "ActivityDriven": + return seldoncore.generate_square_lattice_activity_agent(n_edge= n_edge,weight = weight) + elif model_string == "Inertial": + return seldoncore.generate_square_lattice_inertial_agent(n_edge= n_edge,weight = weight) + elif model_string == "Float": + return seldoncore.generate_square_lattice_(n_edge= n_edge,weight = weight) + +def generate_network_from_file(model_string,file_path): + """ + Generate a Network from a file. + + Parameters + ----------- + model_string : str + The model string. Allowed values are "DeGroot", "Deffuant", "DeffuantVector", "ActivityDriven", "Inertial", "Float". + file_path : str + The file path. + + Returns + ----------- + Network: The network. + """ + if model_string == "DeGroot" or model_string == "Deffuant": + return seldoncore.generate_network_from_file_simple_agent(file_path) + elif model_string == "DeffuantVector": + return seldoncore.generate_network_from_file_discrete_vector_agent(file_path) + elif model_string == "ActivityDriven": + return seldoncore.generate_network_from_file_activity_agent(file_path) + elif model_string == "Inertial": + return seldoncore.generate_network_from_file_inertial_agent(file_path) + elif model_string == "Float": + return seldoncore.generate_network_from_file_(file_path) + +def network_to_file(network, file_path): + """ + Save the network to a file along with weights. + + Parameters + ----------- + network : Network + The network. + + file_path : str + The file path. + """ + if isinstance(network, bindings.seldoncore.SimpleAgentNetwork): + return network.network_to_file_simple_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.DiscreteVectorAgentNetwork): + return network.network_to_file_discrete_vector_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.ActivityDrivenAgentNetwork): + return network.network_to_file_activity_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.InertialAgentNetwork): + return network.network_to_file_inertial_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.Network): + return network.network_to_file(network, file_path) + else: + raise TypeError("Invalid network type") + +def network_to_dot_file(network, file_path): + """ + Save the network to a dot file. This is a graph description language file. + + Parameters + ----------- + network : Network + The network. + + file_path : str + The file path. + """ + if isinstance(network, bindings.seldoncore.SimpleAgentNetwork): + return network.network_to_dot_file_simple_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.DiscreteVectorAgentNetwork): + return network.network_to_dot_file_discrete_vector_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.ActivityDrivenAgentNetwork): + return network.network_to_dot_file_activity_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.InertialAgentNetwork): + return network.network_to_dot_file_inertial_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.Network): + return network.network_to_dot_file(network, file_path) + else: + raise TypeError("Invalid network type") + +def agents_from_file(model_string, file_path): + """ + Load agents from a file. + + Parameters + ----------- + model_string : str + The model string. Allowed values are "DeGroot", "Deffuant", "DeffuantVector", "ActivityDriven", "Inertial", "Float". + file_path : str + The file path. + + Returns + ----------- + list: The list of agents. + """ + + if model_string == "DeGroot" or model_string == "Deffuant": + return seldoncore.agents_from_file_simple_agent(file_path) + elif model_string == "DeffuantVector": + return seldoncore.agents_from_file_discrete_vector_agent(file_path) + elif model_string == "ActivityDriven": + return seldoncore.agents_from_file_activity_agent(file_path) + elif model_string == "Inertial": + return seldoncore.agents_from_file_inertial_agent(file_path) + elif model_string == "Float": + return seldoncore.agents_from_file(file_path) + else: + raise TypeError("Invalid model string") + +def agents_to_file(network, file_path): + """ + Save agents to a file. + + Parameters + ----------- + network : Network + The network. + + file_path : str + The file path. + """ + if isinstance(network, bindings.seldoncore.SimpleAgentNetwork): + return network.agents_to_file_simple_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.DiscreteVectorAgentNetwork): + return network.agents_to_file_discrete_vector_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.ActivityDrivenAgentNetwork): + return network.agents_to_file_activity_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.InertialAgentNetwork): + return network.agents_to_file_inertial_agent(network, file_path) + elif isinstance(network, bindings.seldoncore.Network): + return network.agents_to_file(network, file_path) + else: + raise TypeError("Invalid network type") \ No newline at end of file diff --git a/python_bindings/bindings.cpp b/python_bindings/bindings.cpp new file mode 100644 index 0000000..68e84d9 --- /dev/null +++ b/python_bindings/bindings.cpp @@ -0,0 +1,523 @@ +#include "agent.hpp" +#include "agent_io.hpp" +#include "agents/activity_agent.hpp" +#include "agents/discrete_vector_agent.hpp" +#include "agents/inertial_agent.hpp" +#include "agents/simple_agent.hpp" +#include "config_parser.hpp" +#include "model.hpp" +#include "model_factory.hpp" +#include "models/ActivityDrivenModel.hpp" +#include "models/DeGroot.hpp" +#include "models/DeffuantModel.hpp" +#include "models/InertialModel.hpp" +#include "network.hpp" +#include "network_generation.hpp" +#include "network_io.hpp" +#include "simulation.hpp" +#include "util/erfinv.hpp" +#include "util/math.hpp" +#include "util/misc.hpp" +#include "util/tomlplusplus.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// pybind11 headers +#include +#include +#include +#include + +// adding it here because the linker is not able to dynamically identify this template class iteration method +extern template class Seldon::ActivityDrivenModelAbstract; + +using namespace std::string_literals; +using namespace pybind11::literals; +namespace py = pybind11; + +namespace fs = std::filesystem; + +void run_simulation(const std::optional &config_file_path, + const std::optional &options, + const std::optional agent_file_path, + const std::optional network_file_path, + const std::optional output_dir_path) { + std::string _output_dir_path = output_dir_path.value_or((fs::current_path() / fs::path("output")).string()); + fs::remove_all(_output_dir_path); + fs::create_directories(_output_dir_path); + Seldon::Config::SimulationOptions simulation_options; + + if (config_file_path) { + simulation_options = Seldon::Config::parse_config_file(config_file_path.value()); + } else if (options && !options->is_none()) { + simulation_options = py::cast(*options); + } else { + throw std::runtime_error("Either config_file_path or simulation_options must be provided"); + } + + Seldon::Config::validate_settings(simulation_options); + Seldon::Config::print_settings(simulation_options); + + if (simulation_options.model == Seldon::Config::Model::DeGroot) { + auto simulation = Seldon::Simulation(simulation_options, network_file_path, agent_file_path); + simulation.run(_output_dir_path); + } else if (simulation_options.model == Seldon::Config::Model::ActivityDrivenModel) { + auto simulation = Seldon::Simulation(simulation_options, network_file_path, agent_file_path); + + simulation.run(_output_dir_path); + + } else if (simulation_options.model == Seldon::Config::Model::ActivityDrivenInertial) { + auto simulation = Seldon::Simulation(simulation_options, network_file_path, agent_file_path); + + simulation.run(_output_dir_path); + + } else if (simulation_options.model == Seldon::Config::Model::DeffuantModel) { + auto model_settings = std::get(simulation_options.model_settings); + if (model_settings.use_binary_vector) { + auto simulation = Seldon::Simulation(simulation_options, network_file_path, agent_file_path); + + simulation.run(_output_dir_path); + + } else { + auto simulation = Seldon::Simulation(simulation_options, network_file_path, agent_file_path); + + simulation.run(_output_dir_path); + } + } else { + throw std::runtime_error("Model has not been created"); + } +} + +template +void bind_Network(py::module &m, const std::string &name) { + std::string Network_name = name + "Network"; + py::class_>(m, Network_name.c_str()) + .def(py::init<>()) + .def(py::init()) + .def(py::init &>()) + .def(py::init<>( + [](std::vector> &&neighbour_list, std::vector> &&weight_list, const std::string &direction) { + typename Seldon::Network::EdgeDirection edge_direction; + if (direction == "Incoming") { + edge_direction = Seldon::Network::EdgeDirection::Incoming; + } else { + edge_direction = Seldon::Network::EdgeDirection::Outgoing; + } + return Seldon::Network(std::move(neighbour_list), std::move(weight_list), edge_direction); + }), + "neighbour_list"_a, + "weight_list"_a, + "direction"_a = "Incoming") + .def("n_agents", &Seldon::Network::n_agents) + .def("n_edges", &Seldon::Network::n_edges, "agent_idx"_a = std::nullopt) + .def("direction", + [](Seldon::Network &self) { + auto edge_direction = self.direction(); + if (edge_direction == Seldon::Network::EdgeDirection::Incoming) { + return "Incoming"; + } else { + return "Outgoing"; + } + }) + .def("strongly_connected_components", + &Seldon::Network:: + strongly_connected_components) // https://stackoverflow.com/questions/64632424/interpreting-static-cast-static-castvoid-petint-syntax + // // https://pybind11.readthedocs.io/en/stable/classes.html#overloaded-methods + .def( + "get_neighbours", + [](Seldon::Network &self, std::size_t index) { + auto span = self.get_neighbours(index); + return std::vector(span.begin(), span.end()); + }, + "index"_a) + .def("get_weights", + [](Seldon::Network &self, std::size_t index) { + auto span = self.get_weights(index); + return std::vector(span.begin(), span.end()); + }) + .def( + "set_weights", + [](Seldon::Network &self, std::size_t agent_idx, const std::vector &weights) { + self.set_weights(agent_idx, std::span(weights)); + }, + "agent_idx"_a, + "weights"_a) + .def( + "set_neighbours_and_weights", + [](Seldon::Network &self, + std::size_t agent_idx, + const std::vector &buffer_neighbours, + const std::vector &buffer_weights) { + self.set_neighbours_and_weights(agent_idx, std::span(buffer_neighbours), std::span(buffer_weights)); + }, + "agent_idx"_a, + "buffer_neighbours"_a, + "buffer_weights"_a) + .def( + "set_neighbours_and_weights", + [](Seldon::Network &self, std::size_t agent_idx, const std::vector &buffer_neighbours, const double &weight) { + self.set_neighbours_and_weights(agent_idx, std::span(buffer_neighbours), weight); + }, + "agent_idx"_a, + "buffer_neighbours"_a, + "weight"_a) + .def("push_back_neighbour_and_weight", &Seldon::Network::push_back_neighbour_and_weight, "agent_idx_i"_a, "agent_idx_j"_a, "w"_a) + .def("transpose", &Seldon::Network::transpose) + .def("toggle_incoming_outgoing", &Seldon::Network::toggle_incoming_outgoing) + .def("switch_direction_flag", &Seldon::Network::switch_direction_flag) + .def("remove_double_counting", &Seldon::Network::remove_double_counting) + .def("clear", &Seldon::Network::clear) + .def_readwrite("agent", &Seldon::Network::agents); +} + +// generate bindings for generate_n_connections +template +void generate_bindings_for_gnc(std::string name, py::module &m) { + m.def(("generate_n_connections_" + name).c_str(), + [](std::size_t n_agents, std::size_t n_connections, bool self_interaction, std::size_t seed) { + std::mt19937 gen(seed); + return Seldon::NetworkGeneration::generate_n_connections(n_agents, n_connections, self_interaction, gen); + }, + "n_agents"_a, + "n_connections"_a, + "self_interaction"_a, + "seed"_a = std::random_device()()); +} + +// generate bindings for generate_from_file +template +void generate_bindings_for_gff(std::string name, py::module &m) { + m.def(("generate_from_file_" + name).c_str(), &Seldon::NetworkGeneration::generate_from_file, "file"_a); +} + +template +void generate_bindings_for_gsl(std::string name, py::module &m) { + m.def(("generate_square_lattice_" + name).c_str(), + [](std::size_t n_edge, double weight) { return Seldon::NetworkGeneration::generate_square_lattice(n_edge, weight); }, + "n_edge"_a, + "weight"_a = 0.0); +} + +// generate bindings for generate_fully_connected +template +void generate_bindings_for_gfc(std::string name, py::module &m) { + m.def(("generate_fully_connected_" + name).c_str(), + [](std::size_t n_agents, std::optional::WeightT> weight, std::optional seed) { + if (seed.has_value()) { + std::mt19937 gen(seed.value()); + return Seldon::NetworkGeneration::generate_fully_connected(n_agents, gen); + } else if (weight.has_value()) { + return Seldon::NetworkGeneration::generate_fully_connected(n_agents, weight.value()); + } else { + return Seldon::NetworkGeneration::generate_fully_connected(n_agents, 0.0); + } + }, + "n_agents"_a, + "weight"_a, + "seed"_a); +} + +PYBIND11_MODULE(seldoncore, m) { + m.doc() = "Python bindings for Seldon Cpp Engine"; + + m.def("run_simulation", + &run_simulation, + "config_file_path"_a = std::optional{}, + "options"_a = std::optional{}, + "agent_file_path"_a = std::optional{}, + "network_file_path"_a = std::optional{}, + "output_dir_path"_a = std::optional{}); + + py::class_(m, "SimpleAgentData").def(py::init<>()).def_readwrite("opinion", &Seldon::SimpleAgentData::opinion); + + py::class_>(m, "SimpleAgent") + .def(py::init<>()) + .def(py::init()) + .def_readwrite("data", &Seldon::Agent::data); + + py::class_(m, "DiscreteVectorAgentData") + .def(py::init<>()) + .def_readwrite("opinion", &Seldon::DiscreteVectorAgentData::opinion); + + py::class_>(m, "DiscreteVectorAgent") + .def(py::init<>()) + .def(py::init()) + .def_readwrite("data", &Seldon::Agent::data); + + py::class_(m, "ActivityAgentData") + .def(py::init<>()) + .def_readwrite("opinion", &Seldon::ActivityAgentData::opinion) + .def_readwrite("activity", &Seldon::ActivityAgentData::activity) + .def_readwrite("reluctance", &Seldon::ActivityAgentData::reluctance); + + py::class_>(m, "ActivityAgent") + .def(py::init<>()) + .def(py::init()) + .def_readwrite("data", &Seldon::Agent::data); + + py::class_(m, "InertialAgentData") + .def(py::init<>()) + .def_readwrite("opinion", &Seldon::InertialAgentData::opinion) + .def_readwrite("activity", &Seldon::InertialAgentData::activity) + .def_readwrite("reluctance", &Seldon::InertialAgentData::reluctance) + .def_readwrite("velocity", &Seldon::InertialAgentData::velocity); + + py::class_>(m, "InertialAgent") + .def(py::init<>()) + .def(py::init()) + .def_readwrite("data", &Seldon::Agent::data); + + bind_Network(m, ""); + bind_Network(m, "SimpleAgent"); + bind_Network(m, "DiscreteVectorAgent"); + bind_Network(m, "ActivityAgent"); + bind_Network(m, "InertialAgent"); + + py::class_>(m, "SimulationSimpleAgent") + .def(py::init &, const std::optional &>(), + "options"_a, + "cli_network_file"_a = std::nullopt, + "cli_agent_file"_a = std::nullopt) + .def("run", &Seldon::Simulation::run, "output_dir_path"_a) + .def_readwrite("network", &Seldon::Simulation::network); + + py::class_>(m, "SimulationDiscreteVectorAgent") + .def(py::init &, const std::optional &>(), + "options"_a, + "cli_network_file"_a = std::nullopt, + "cli_agent_file"_a = std::nullopt) + .def("run", &Seldon::Simulation::run, "output_dir_path"_a) + .def_readwrite("network", &Seldon::Simulation::network); + + py::class_>(m, "SimulationActivityAgent") + .def(py::init &, const std::optional &>(), + "options"_a, + "cli_network_file"_a = std::nullopt, + "cli_agent_file"_a = std::nullopt) + .def("run", &Seldon::Simulation::run, "output_dir_path"_a) + .def_readwrite("network", &Seldon::Simulation::network); + + py::class_>(m, "SimulationInertialAgent") + .def(py::init &, const std::optional &>(), + "options"_a, + "cli_network_file"_a = std::nullopt, + "cli_agent_file"_a = std::nullopt) + .def("run", &Seldon::Simulation::run, "output_dir_path"_a) + .def_readwrite("network", &Seldon::Simulation::network); + + py::class_(m, "OutputSettings") + .def(py::init<>()) + .def_readwrite("n_output_agents", &Seldon::Config::OutputSettings::n_output_agents) + .def_readwrite("n_output_network", &Seldon::Config::OutputSettings::n_output_network) + .def_readwrite("print_progress", &Seldon::Config::OutputSettings::print_progress) + .def_readwrite("output_initial", &Seldon::Config::OutputSettings::output_initial) + .def_readwrite("start_output", &Seldon::Config::OutputSettings::start_output) + .def_readwrite("start_numbering_from", &Seldon::Config::OutputSettings::start_numbering_from); + + py::class_(m, "DeGrootSettings") + .def(py::init<>()) + .def_readwrite("max_iterations", &Seldon::Config::DeGrootSettings::max_iterations) + .def_readwrite("convergence_tol", &Seldon::Config::DeGrootSettings::convergence_tol); + + py::class_(m, "DeffuantSettings") + .def(py::init<>()) + .def_readwrite("max_iterations", &Seldon::Config::DeffuantSettings::max_iterations) + .def_readwrite("homophily_threshold", &Seldon::Config::DeffuantSettings::homophily_threshold) + .def_readwrite("mu", &Seldon::Config::DeffuantSettings::mu) + .def_readwrite("use_network", &Seldon::Config::DeffuantSettings::use_network) + .def_readwrite("use_binary_vector", &Seldon::Config::DeffuantSettings::use_binary_vector) + .def_readwrite("dim", &Seldon::Config::DeffuantSettings::dim); + + py::class_(m, "ActivityDrivenSettings") + .def(py::init<>()) + .def_readwrite("max_iterations", &Seldon::Config::ActivityDrivenSettings::max_iterations) + .def_readwrite("dt", &Seldon::Config::ActivityDrivenSettings::dt) + .def_readwrite("m", &Seldon::Config::ActivityDrivenSettings::m) + .def_readwrite("eps", &Seldon::Config::ActivityDrivenSettings::eps) + .def_readwrite("gamma", &Seldon::Config::ActivityDrivenSettings::gamma) + .def_readwrite("alpha", &Seldon::Config::ActivityDrivenSettings::alpha) + .def_readwrite("homophily", &Seldon::Config::ActivityDrivenSettings::homophily) + .def_readwrite("reciprocity", &Seldon::Config::ActivityDrivenSettings::reciprocity) + .def_readwrite("K", &Seldon::Config::ActivityDrivenSettings::K) + .def_readwrite("mean_activities", &Seldon::Config::ActivityDrivenSettings::mean_activities) + .def_readwrite("mean_weights", &Seldon::Config::ActivityDrivenSettings::mean_weights) + .def_readwrite("n_bots", &Seldon::Config::ActivityDrivenSettings::n_bots) + .def_readwrite("bot_m", &Seldon::Config::ActivityDrivenSettings::bot_m) + .def_readwrite("bot_activity", &Seldon::Config::ActivityDrivenSettings::bot_activity) + .def_readwrite("bot_opinion", &Seldon::Config::ActivityDrivenSettings::bot_opinion) + .def_readwrite("bot_homophily", &Seldon::Config::ActivityDrivenSettings::bot_homophily) + .def_readwrite("use_reluctances", &Seldon::Config::ActivityDrivenSettings::use_reluctances) + .def_readwrite("reluctance_mean", &Seldon::Config::ActivityDrivenSettings::reluctance_mean) + .def_readwrite("reluctance_sigma", &Seldon::Config::ActivityDrivenSettings::reluctance_sigma) + .def_readwrite("reluctance_eps", &Seldon::Config::ActivityDrivenSettings::reluctance_eps) + .def_readwrite("covariance_factor", &Seldon::Config::ActivityDrivenSettings::covariance_factor); + + py::class_(m, "ActivityDrivenInertialSettings") + .def(py::init<>()) + .def_readwrite("max_iterations", &Seldon::Config::ActivityDrivenInertialSettings::max_iterations) + .def_readwrite("dt", &Seldon::Config::ActivityDrivenInertialSettings::dt) + .def_readwrite("m", &Seldon::Config::ActivityDrivenInertialSettings::m) + .def_readwrite("eps", &Seldon::Config::ActivityDrivenInertialSettings::eps) + .def_readwrite("gamma", &Seldon::Config::ActivityDrivenInertialSettings::gamma) + .def_readwrite("alpha", &Seldon::Config::ActivityDrivenInertialSettings::alpha) + .def_readwrite("homophily", &Seldon::Config::ActivityDrivenInertialSettings::homophily) + .def_readwrite("reciprocity", &Seldon::Config::ActivityDrivenInertialSettings::reciprocity) + .def_readwrite("K", &Seldon::Config::ActivityDrivenInertialSettings::K) + .def_readwrite("mean_activities", &Seldon::Config::ActivityDrivenInertialSettings::mean_activities) + .def_readwrite("mean_weights", &Seldon::Config::ActivityDrivenInertialSettings::mean_weights) + .def_readwrite("n_bots", &Seldon::Config::ActivityDrivenInertialSettings::n_bots) + .def_readwrite("bot_m", &Seldon::Config::ActivityDrivenInertialSettings::bot_m) + .def_readwrite("bot_activity", &Seldon::Config::ActivityDrivenInertialSettings::bot_activity) + .def_readwrite("bot_opinion", &Seldon::Config::ActivityDrivenInertialSettings::bot_opinion) + .def_readwrite("bot_homophily", &Seldon::Config::ActivityDrivenInertialSettings::bot_homophily) + .def_readwrite("use_reluctances", &Seldon::Config::ActivityDrivenInertialSettings::use_reluctances) + .def_readwrite("reluctance_mean", &Seldon::Config::ActivityDrivenInertialSettings::reluctance_mean) + .def_readwrite("reluctance_sigma", &Seldon::Config::ActivityDrivenInertialSettings::reluctance_sigma) + .def_readwrite("reluctance_eps", &Seldon::Config::ActivityDrivenInertialSettings::reluctance_eps) + .def_readwrite("covariance_factor", &Seldon::Config::ActivityDrivenInertialSettings::covariance_factor) + .def_readwrite("friction_coefficient", &Seldon::Config::ActivityDrivenInertialSettings::friction_coefficient); + + py::class_(m, "InitialNetworkSettings") + .def(py::init<>()) + .def_readwrite("file", &Seldon::Config::InitialNetworkSettings::file) + .def_readwrite("number_of_agents", &Seldon::Config::InitialNetworkSettings::n_agents) + .def_readwrite("connections_per_agent", &Seldon::Config::InitialNetworkSettings::n_connections); + + py::enum_(m, "Model") + .value("DeGroot", Seldon::Config::Model::DeGroot) + .value("ActivityDrivenModel", Seldon::Config::Model::ActivityDrivenModel) + .value("DeffuantModel", Seldon::Config::Model::DeffuantModel) + .value("ActivityDrivenInertial", Seldon::Config::Model::ActivityDrivenInertial); + + py::class_(m, "SimulationOptions") + .def(py::init<>()) + .def_readwrite("model_string", &Seldon::Config::SimulationOptions::model_string) + .def_readwrite("model", &Seldon::Config::SimulationOptions::model) + .def_readwrite("rng_seed", &Seldon::Config::SimulationOptions::rng_seed) + .def_readwrite("output_settings", &Seldon::Config::SimulationOptions::output_settings) + .def_readwrite("model_settings", &Seldon::Config::SimulationOptions::model_settings) + .def_readwrite("network_settings", &Seldon::Config::SimulationOptions::network_settings); + + // gnc = generate_n_connections + generate_bindings_for_gnc("", m); + generate_bindings_for_gnc("simple_agent", m); + generate_bindings_for_gnc("discrete_vector_agent", m); + generate_bindings_for_gnc("activity_agent", m); + generate_bindings_for_gnc("inertial_agent", m); + + // gfc = generate_fully_connected + generate_bindings_for_gfc("", m); + generate_bindings_for_gfc("simple_agent", m); + generate_bindings_for_gfc("discrete_vector_agent", m); + generate_bindings_for_gfc("activity_agent", m); + generate_bindings_for_gfc("inertial_agent", m); + + // gff = generate_from_file + generate_bindings_for_gff("", m); + generate_bindings_for_gff("simple_agent", m); + generate_bindings_for_gff("discrete_vector_agent", m); + generate_bindings_for_gff("activity_agent", m); + generate_bindings_for_gff("inertial_agent", m); + + // gsl = generate_square_lattice + generate_bindings_for_gsl("", m); + generate_bindings_for_gsl("simple_agent", m); + generate_bindings_for_gsl("discrete_vector_agent", m); + generate_bindings_for_gsl("activity_agent", m); + generate_bindings_for_gsl("inertial_agent", m); + + m.def("parse_config_file", &Seldon::Config::parse_config_file, "file"_a); + + // network + m.def("network_to_dot_file", &Seldon::network_to_dot_file, "network"_a, "file_path"_a); + m.def("network_to_dot_file_simple_agent", &Seldon::network_to_dot_file, "network"_a, "file_path"_a); + m.def("network_to_dot_file_discrete_vector_agent", &Seldon::network_to_dot_file, "network"_a, "file_path"_a); + m.def("network_to_dot_file_activity_agent", &Seldon::network_to_dot_file, "network"_a, "file_path"_a); + m.def("network_to_dot_file_inertial_agent", &Seldon::network_to_dot_file, "network"_a, "file_path"_a); + + m.def("network_to_file", &Seldon::network_to_file, "network"_a, "file_path"_a); + m.def("network_to_file_simple_agent", &Seldon::network_to_file, "network"_a, "file_path"_a); + m.def("network_to_file_discrete_vector_agent", &Seldon::network_to_file, "network"_a, "file_path"_a); + m.def("network_to_file_activity_agent", &Seldon::network_to_file, "network"_a, "file_path"_a); + m.def("network_to_file_inertial_agent", &Seldon::network_to_file, "network"_a, "file_path"_a); + + m.def("agents_from_file", &Seldon::agents_from_file, "file"_a); + m.def("agents_from_file_simple_agent", &Seldon::agents_from_file, "file"_a); + m.def("agents_from_file_discrete_vector_agent", &Seldon::agents_from_file, "file"_a); + m.def("agents_from_file_activity_agent", &Seldon::agents_from_file, "file"_a); + m.def("agents_from_file_inertial_agent", &Seldon::agents_from_file, "file"_a); + + m.def("agents_to_file", &Seldon::agents_to_file, "network"_a, "file_path"_a); + m.def("agents_to_file_simple_agent", &Seldon::agents_to_file, "network"_a, "file_path"_a); + m.def("agents_to_file_discrete_vector_agent", &Seldon::agents_to_file, "network"_a, "file_path"_a); + m.def("agents_to_file_activity_agent", &Seldon::agents_to_file, "network"_a, "file_path"_a); + m.def("agents_to_file_inertial_agent", &Seldon::agents_to_file, "network"_a, "file_path"_a); + + // Function for getting a vector of k agents (corresponding to connections) + // drawing from n agents (without duplication) + // ignore_idx ignores the index of the agent itself, since we will later add the agent itself ourselves to prevent duplication + // std::optional ignore_idx, std::size_t k, std::size_t n, std::vector & buffer,std::mt19937 & gen + m.def("draw_unique_k_from_n", &Seldon::draw_unique_k_from_n, "ignore_idx"_a, "k"_a, "n"_a, "buffer"_a, "gen"_a = std::random_device()()); + + py::class_>(m, "Power_Law_Distribution") + .def(py::init(), "eps"_a, "gamma"_a) + .def("__call__", &Seldon::power_law_distribution::template operator(), "gen"_a) + .def("pdf", &Seldon::power_law_distribution::pdf, "x"_a) + .def("inverse_cdf", &Seldon::power_law_distribution::inverse_cdf, "x"_a) + .def("mean", &Seldon::power_law_distribution::mean); + + py::class_>(m, "Truncated_Normal_Distribution") + .def(py::init(), "mean"_a, "sigma"_a, "eps"_a) + .def("__call__", &Seldon::truncated_normal_distribution::template operator(), "gen"_a) + .def("pdf", &Seldon::truncated_normal_distribution::pdf, "x"_a) + .def("inverse_cdf", &Seldon::truncated_normal_distribution::inverse_cdf, "y"_a); + + py::class_>(m, "Bivariate_Normal_Distribution") + .def(py::init(), "covariance"_a) + .def("__call__", &Seldon::bivariate_normal_distribution::template operator(), "gen"_a); + + py::class_, Seldon::truncated_normal_distribution>>( + m, "Bivariate_Gaussian_Copula") + .def(py::init, Seldon::truncated_normal_distribution>(), + "covariance"_a, + "dist1"_a, + "dist2"_a) + .def("__call__", + &Seldon::bivariate_gaussian_copula, Seldon::truncated_normal_distribution>:: + template operator(), + "gen"_a); + + m.def( + "hamming_distance", + [](const std::vector &v1, const std::vector &v2) { + return Seldon::hamming_distance(std::span(v1), std::span(v2)); + }, + "v1"_a, + "v2"_a); + + // m.def("reservoir_sampling_A_ExpJ", &Seldon::reservoir_sampling_A_ExpJ>, "k"_a, "n"_a,"weight"_a,"buffer"_a, + // "gen"_a= std::random_device()()); + + //--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + // Connectivity(Tarjan Implementation) + py::class_(m, "TarjanConnectivityAlgo") + .def(py::init> &>(), "adjacency_list_arg"_a) + .def_readwrite("scc_list", &Seldon::TarjanConnectivityAlgo::scc_list); + + m.def("print_settings", &Seldon::Config::print_settings, "options"_a); + m.def("validate_settings", &Seldon::Config::validate_settings, "options"_a); + + py::class_(m, "RandomGenerator").def(py::init()); +} diff --git a/res/PySeldon.svg b/res/PySeldon.svg new file mode 100644 index 0000000..a18e13f --- /dev/null +++ b/res/PySeldon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/PySeldon_1.svg b/res/PySeldon_1.svg new file mode 100644 index 0000000..dcd6c41 --- /dev/null +++ b/res/PySeldon_1.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/_logotext.png b/res/_logotext.png new file mode 100644 index 0000000..67bfc49 Binary files /dev/null and b/res/_logotext.png differ diff --git a/res/favicon.ico b/res/favicon.ico new file mode 100644 index 0000000..489084a Binary files /dev/null and b/res/favicon.ico differ diff --git a/res/logo.png b/res/logo.png new file mode 100644 index 0000000..4577f2a Binary files /dev/null and b/res/logo.png differ diff --git a/res/logotext.png b/res/logotext.png new file mode 100644 index 0000000..b678543 Binary files /dev/null and b/res/logotext.png differ diff --git a/subprojects/seldon.wrap b/subprojects/seldon.wrap new file mode 100644 index 0000000..000c1d6 --- /dev/null +++ b/subprojects/seldon.wrap @@ -0,0 +1,5 @@ +[wrap-git] +directory = seldon +url = https://github.com/seldon-code/seldon.git +revision = b4c479a295431f41ecfe121a752d0bcc3465333f +depth = 1 diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..885fe1f --- /dev/null +++ b/tests/README.md @@ -0,0 +1,9 @@ +# Tests + +This is the main directory for the pyseldon bindings and package tests. + +These are just the same as the tests in the main seldon repository, but are run in the context of the package in Python. + +the seldoncore directory tests the bindings to the seldon code repository, and allother tests the package itself. + +Currently only the activity_driven test for 10 agent meanfield is failing for the time being I have disabled it in the tests. Because it seems to be a upstream issue with the seldon codebase to me \ No newline at end of file diff --git a/tests/output_meanfield_test/network_0.txt b/tests/output_meanfield_test/network_0.txt new file mode 100644 index 0000000..8329eca --- /dev/null +++ b/tests/output_meanfield_test/network_0.txt @@ -0,0 +1,51 @@ +# idx_agent, n_neighbours_in, indices_neighbours_in[...], weights_in[...] + 0, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 1, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 2, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 3, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 4, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 5, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 6, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 7, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 8, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 9, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 10, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 11, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 12, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 13, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 14, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 15, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 16, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 17, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 18, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 19, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 20, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 21, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 22, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 23, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 24, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 25, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 26, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 27, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 28, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 29, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 30, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 31, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 32, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 33, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 34, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 35, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 36, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 37, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 38, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 39, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 40, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 41, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 42, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 43, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 44, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 45, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 46, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 47, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 48, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 49, 50, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ No newline at end of file diff --git a/tests/output_meanfield_test/opinions_0.txt b/tests/output_meanfield_test/opinions_0.txt new file mode 100644 index 0000000..6e0ef9f --- /dev/null +++ b/tests/output_meanfield_test/opinions_0.txt @@ -0,0 +1,51 @@ +# idx_agent, opinion, activity, reluctance + 0, -0.1, 0.11069846006400016, 1 + 1, -0.09591836734693879, 0.11069846006400016, 1 + 2, -0.09183673469387756, 0.11069846006400016, 1 + 3, -0.08775510204081632, 0.11069846006400016, 1 + 4, -0.0836734693877551, 0.11069846006400016, 1 + 5, -0.07959183673469389, 0.11069846006400016, 1 + 6, -0.07551020408163266, 0.11069846006400016, 1 + 7, -0.07142857142857144, 0.11069846006400016, 1 + 8, -0.0673469387755102, 0.11069846006400016, 1 + 9, -0.06326530612244899, 0.11069846006400016, 1 + 10, -0.05918367346938776, 0.11069846006400016, 1 + 11, -0.05510204081632653, 0.11069846006400016, 1 + 12, -0.05102040816326531, 0.11069846006400016, 1 + 13, -0.04693877551020408, 0.11069846006400016, 1 + 14, -0.042857142857142864, 0.11069846006400016, 1 + 15, -0.03877551020408163, 0.11069846006400016, 1 + 16, -0.03469387755102042, 0.11069846006400016, 1 + 17, -0.03061224489795919, 0.11069846006400016, 1 + 18, -0.026530612244897958, 0.11069846006400016, 1 + 19, -0.02244897959183674, 0.11069846006400016, 1 + 20, -0.01836734693877551, 0.11069846006400016, 1 + 21, -0.01428571428571429, 0.11069846006400016, 1 + 22, -0.010204081632653059, 0.11069846006400016, 1 + 23, -0.006122448979591827, 0.11069846006400016, 1 + 24, -0.002040816326530609, 0.11069846006400016, 1 + 25, 0.002040816326530609, 0.11069846006400016, 1 + 26, 0.006122448979591841, 0.11069846006400016, 1 + 27, 0.010204081632653045, 0.11069846006400016, 1 + 28, 0.014285714285714277, 0.11069846006400016, 1 + 29, 0.01836734693877551, 0.11069846006400016, 1 + 30, 0.02244897959183674, 0.11069846006400016, 1 + 31, 0.026530612244897972, 0.11069846006400016, 1 + 32, 0.030612244897959162, 0.11069846006400016, 1 + 33, 0.03469387755102041, 0.11069846006400016, 1 + 34, 0.038775510204081626, 0.11069846006400016, 1 + 35, 0.04285714285714287, 0.11069846006400016, 1 + 36, 0.04693877551020409, 0.11069846006400016, 1 + 37, 0.05102040816326531, 0.11069846006400016, 1 + 38, 0.055102040816326525, 0.11069846006400016, 1 + 39, 0.05918367346938777, 0.11069846006400016, 1 + 40, 0.06326530612244899, 0.11069846006400016, 1 + 41, 0.0673469387755102, 0.11069846006400016, 1 + 42, 0.07142857142857142, 0.11069846006400016, 1 + 43, 0.07551020408163264, 0.11069846006400016, 1 + 44, 0.07959183673469389, 0.11069846006400016, 1 + 45, 0.0836734693877551, 0.11069846006400016, 1 + 46, 0.08775510204081635, 0.11069846006400016, 1 + 47, 0.09183673469387754, 0.11069846006400016, 1 + 48, 0.09591836734693879, 0.11069846006400016, 1 + 49, 0.1, 0.11069846006400016, 1 diff --git a/tests/res/10_agents_meanfield_activity.toml b/tests/res/10_agents_meanfield_activity.toml new file mode 100644 index 0000000..e74e8eb --- /dev/null +++ b/tests/res/10_agents_meanfield_activity.toml @@ -0,0 +1,27 @@ +[simulation] +model = "ActivityDriven" +# rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 1 # Write the network every 20 iterations +# n_output_agents = 1 +print_progress = false # Print the iteration time ; if not set, then does not print + +[model] +max_iterations = 1000 # If not set, max iterations is infinite + +[ActivityDriven] +dt = 0.01 # Timestep for the integration of the coupled ODEs +m = 10 # Number of agents contacted, when the agent is active +eps = 0.01 # Minimum activity epsilon; a_i belongs to [epsilon,1] +gamma = 2.1 # Exponent of activity power law distribution of activities +reciprocity = 1.0 # probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections +homophily = 0.0 # aka beta. if zero, agents pick their interaction partners at random +alpha = 1.01 # Controversialness of the issue, must be greater than 0. +K = 2.0 # Social interaction strength +mean_activities = true # Use the mean value of the powerlaw distribution for the activities of all agents +mean_weights = true # Use the meanfield approximation of the network edges + +[network] +number_of_agents = 50 +connections_per_agent = 1 diff --git a/tests/res/1bot_1agent_activity_prob.toml b/tests/res/1bot_1agent_activity_prob.toml new file mode 100644 index 0000000..21c733c --- /dev/null +++ b/tests/res/1bot_1agent_activity_prob.toml @@ -0,0 +1,40 @@ +[simulation] +model = "ActivityDriven" +rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 1 # Write the network every 20 iterations +# n_output_agents = 1 +print_progress = false # Print the iteration time ; if not set, then does not print + +[model] +max_iterations = 1000 # If not set, max iterations is infinite + +[ActivityDriven] +dt = 0.001 # Timestep for the integration of the coupled ODEs +m = 1 # Number of agents contacted, when the agent is active +eps = 1 # Minimum activity epsilon; a_i belongs to [epsilon,1] +gamma = 2.1 # Exponent of activity power law distribution of activities +reciprocity = 1 # probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections +homophily = 0.5 # aka beta. if zero, agents pick their interaction partners at random +alpha = 1.5 # Controversialness of the issue, must be greater than 0. +K = 2.0 # Social interaction strength +mean_activities = false # Use the mean value of the powerlaw distribution for the activities of all agents +mean_weights = false # Use the meanfield approximation of the network edges + +reluctances = true # Assigns a "reluctance" (m_i) to each agent. By default; false and every agent has a reluctance of 1 +reluctance_mean = 1.5 # Mean of distribution before drawing from a truncated normal distribution (default set to 1.0) +reluctance_sigma = 0.1 # Width of normal distribution (before truncating) +reluctance_eps = 0.01 # Minimum such that the normal distribution is truncated at this value + +n_bots = 1 # The number of bots to be used; if not specified defaults to 0 (which means bots are deactivated) +# Bots are agents with fixed opinions and different parameters, the parameters are specified in the following lists +# If n_bots is smaller than the length of any of the lists, the first n_bots entries are used. If n_bots is greater the code will throw an exception. +bot_m = [1] # If not specified, defaults to `m` +bot_homophily = [0.7] # If not specified, defaults to `homophily` +bot_activity = [1.0] # If not specified, defaults to 0 +bot_opinion = [2] # The fixed opinions of the bots + +[network] +number_of_agents = 2 +connections_per_agent = 1 diff --git a/tests/res/1bot_1agent_inertial.toml b/tests/res/1bot_1agent_inertial.toml new file mode 100644 index 0000000..6520309 --- /dev/null +++ b/tests/res/1bot_1agent_inertial.toml @@ -0,0 +1,42 @@ +[simulation] +model = "ActivityDrivenInertial" +rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 1 # Write the network every 20 iterations +# n_output_agents = 1 +print_progress = false # Print the iteration time ; if not set, then does not print + +[model] +max_iterations = 1000 # If not set, max iterations is infinite + +[ActivityDrivenInertial] +dt = 0.001 # Timestep for the integration of the coupled ODEs +m = 1 # Number of agents contacted, when the agent is active +eps = 1 # Minimum activity epsilon; a_i belongs to [epsilon,1] +gamma = 2.1 # Exponent of activity power law distribution of activities +reciprocity = 1 # probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections +homophily = 0.5 # aka beta. if zero, agents pick their interaction partners at random +alpha = 1.5 # Controversialness of the issue, must be greater than 0. +K = 2.0 # Social interaction strength +mean_activities = false # Use the mean value of the powerlaw distribution for the activities of all agents +mean_weights = false # Use the meanfield approximation of the network edges + +reluctances = true # Assigns a "reluctance" (m_i) to each agent. By default; false and every agent has a reluctance of 1 +reluctance_mean = 1.5 # Mean of distribution before drawing from a truncated normal distribution (default set to 1.0) +reluctance_sigma = 0.1 # Width of normal distribution (before truncating) +reluctance_eps = 0.01 # Minimum such that the normal distribution is truncated at this value + +n_bots = 1 # The number of bots to be used; if not specified defaults to 0 (which means bots are deactivated) +# Bots are agents with fixed opinions and different parameters, the parameters are specified in the following lists +# If n_bots is smaller than the length of any of the lists, the first n_bots entries are used. If n_bots is greater the code will throw an exception. +bot_m = [1] # If not specified, defaults to `m` +bot_homophily = [0.7] # If not specified, defaults to `homophily` +bot_activity = [1.0] # If not specified, defaults to 0 +bot_opinion = [2] # The fixed opinions of the bots + +friction_coefficient = 0.5 # Friction coefficient + +[network] +number_of_agents = 2 +connections_per_agent = 1 diff --git a/tests/res/2_agents_activity_prob.toml b/tests/res/2_agents_activity_prob.toml new file mode 100644 index 0000000..34b51d3 --- /dev/null +++ b/tests/res/2_agents_activity_prob.toml @@ -0,0 +1,27 @@ +[simulation] +model = "ActivityDriven" +rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 1 # Write the network every 20 iterations +# n_output_agents = 1 +print_progress = false # Print the iteration time ; if not set, then does not print + +[model] +max_iterations = 10000 # If not set, max iterations is infinite + +[ActivityDriven] +dt = 0.005 # Timestep for the integration of the coupled ODEs +m = 1 # Number of agents contacted, when the agent is active +eps = 1 # Minimum activity epsilon; a_i belongs to [epsilon,1] +gamma = 2.1 # Exponent of activity power law distribution of activities +reciprocity = 1 # probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections +homophily = 0.5 # aka beta. if zero, agents pick their interaction partners at random +alpha = 1.01 # Controversialness of the issue, must be greater than 0. +K = 2.0 # Social interaction strength +mean_activities = false # Use the mean value of the powerlaw distribution for the activities of all agents +mean_weights = false # Use the meanfield approximation of the network edges + +[network] +number_of_agents = 2 +connections_per_agent = 1 diff --git a/tests/res/activity_probabilistic_conf.toml b/tests/res/activity_probabilistic_conf.toml new file mode 100644 index 0000000..aad817b --- /dev/null +++ b/tests/res/activity_probabilistic_conf.toml @@ -0,0 +1,27 @@ +[simulation] +model = "ActivityDriven" +rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 1 # Write the network every 20 iterations +n_output_agents = 1 +print_progress = false # Print the iteration time ; if not set, then does not print + +[model] +max_iterations = 10 # If not set, max iterations is infinite + +[ActivityDriven] +dt = 0.01 # Timestep for the integration of the coupled ODEs +m = 10 # Number of agents contacted, when the agent is active +eps = 0.01 # Minimum activity epsilon; a_i belongs to [epsilon,1] +gamma = 2.1 # Exponent of activity power law distribution of activities +reciprocity = 0.5 # probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections +homophily = 0.5 # aka beta. if zero, agents pick their interaction partners at random +alpha = 3.0 # Controversialness of the issue, must be greater than 0. +K = 3.0 # Social interaction strength +mean_activities = false # Use the mean value of the powerlaw distribution for the activities of all agents +mean_weights = false # Use the meanfield approximation of the network edges + +[network] +number_of_agents = 1000 +connections_per_agent = 10 \ No newline at end of file diff --git a/tests/res/config/conf.toml b/tests/res/config/conf.toml new file mode 100644 index 0000000..c4fe817 --- /dev/null +++ b/tests/res/config/conf.toml @@ -0,0 +1,21 @@ +[simulation] +model = "DeGroot" +# rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +n_output_network = 20 # Write the network every 20 iterations +n_output_agents = 1 # Write the opinions of agents after every iteration +print_progress = false # Print the iteration time ; if not set, then does not prints +output_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1 + start_numbering_from. +start_numbering_from = 0 # The initial step number, before the simulation runs, is this value. The first step would be (1+start_numbering_from). By default, 0 + +[model] +max_iterations = 20 # If not set, max iterations is infinite + +[DeGroot] +convergence = 1e-3 # If not set, the default 1e-6 is used + +[network] +number_of_agents = 300 +connections_per_agent = 10 diff --git a/tests/res/config/inconf.toml b/tests/res/config/inconf.toml new file mode 100644 index 0000000..60e49b8 --- /dev/null +++ b/tests/res/config/inconf.toml @@ -0,0 +1,21 @@ +[simulation] +model = "DeGroo" +# rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +n_output_network = 20 # Write the network every 20 iterations +n_output_agents = 1 # Write the opinions of agents after every iteration +print_progress = false # Print the iteration time ; if not set, then does not prints +output_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1 + start_numbering_from. +start_numbering_from = 0 # The initial step number, before the simulation runs, is this value. The first step would be (1+start_numbering_from). By default, 0 + +[model] +max_iterations = 20 # If not set, max iterations is infinite + +[DeGroot] +convergence = 1e-3 # If not set, the default 1e-6 is used + +[network] +number_of_agents = 300 +connections_per_agent = 10 diff --git a/tests/res/deffuant_16x16_agents.toml b/tests/res/deffuant_16x16_agents.toml new file mode 100644 index 0000000..fcb0d8e --- /dev/null +++ b/tests/res/deffuant_16x16_agents.toml @@ -0,0 +1,22 @@ +[simulation] +model = "Deffuant" +# rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 20 # Write the network every 20 iterations +# n_output_agents = 1 # Write the opinions of agents after every iteration +# print_progress = false # Print the iteration time ; if not set, then does not prints +# output_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +# start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1. + +[model] +max_iterations = 10000 # If not set, max iterations is infinite + +[Deffuant] +homophily_threshold = 1.0 # d in the paper; agents interact if difference in opinion is less than this value +mu = 0.5 # convergence parameter; similar to social interaction strength K (0,0.5] +use_network = true + +[network] +number_of_agents = 256 +connections_per_agent = 0 diff --git a/tests/res/deffuant_2agents.toml b/tests/res/deffuant_2agents.toml new file mode 100644 index 0000000..20bc605 --- /dev/null +++ b/tests/res/deffuant_2agents.toml @@ -0,0 +1,21 @@ +[simulation] +model = "Deffuant" +# rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 20 # Write the network every 20 iterations +# n_output_agents = 1 # Write the opinions of agents after every iteration +# print_progress = false # Print the iteration time ; if not set, then does not prints +# output_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +# start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1. + +[model] +max_iterations = 10 # If not set, max iterations is infinite + +[Deffuant] +homophily_threshold = 0.2 # d in the paper; agents interact if difference in opinion is less than this value +mu = 0.5 # convergence parameter; similar to social interaction strength K (0,0.5] + +[network] +number_of_agents = 2 +connections_per_agent = 0 diff --git a/tests/res/deffuant_vector_2agents.toml b/tests/res/deffuant_vector_2agents.toml new file mode 100644 index 0000000..fe203f1 --- /dev/null +++ b/tests/res/deffuant_vector_2agents.toml @@ -0,0 +1,24 @@ +[simulation] +model = "Deffuant" +# rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 20 # Write the network every 20 iterations +# n_output_agents = 1 # Write the opinions of agents after every iteration +# print_progress = false # Print the iteration time ; if not set, then does not prints +# output_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +# start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1. + +[model] +max_iterations = 10 # If not set, max iterations is infinite + +[Deffuant] +homophily_threshold = 2 # d in the paper; agents interact if difference in opinion is less than this value +mu = 0.5 # convergence parameter; similar to social interaction strength K (0,0.5] +use_network = false # If true, will use a square lattice Will throw if sqrt(n_agents) is not an integer +binary_vector = true # If true, this will be the multi-dimensional binary vector Deffuant model +dim = 3 # For the multi-dimensional binary vector Deffuant model, define the number of dimensions in each opinion vector + +[network] +number_of_agents = 2 +connections_per_agent = 0 diff --git a/tests/res/network.txt b/tests/res/network.txt new file mode 100644 index 0000000..79bf7f5 --- /dev/null +++ b/tests/res/network.txt @@ -0,0 +1,6 @@ +# idx_agent,n_neighbors_in,indices_neighbors_in[...],weights_in[...] +# comment + 0, 2, 2, 1, 0.1, -0.2 + 1, 0 +2, 1, 1, 1.2 + diff --git a/tests/res/network/net.txt b/tests/res/network/net.txt new file mode 100644 index 0000000..debe27f --- /dev/null +++ b/tests/res/network/net.txt @@ -0,0 +1,4 @@ +digraph G { +0 <- {1, 0} +1 <- {0, 1} +} diff --git a/tests/res/network/network.txt b/tests/res/network/network.txt new file mode 100644 index 0000000..a67aab4 --- /dev/null +++ b/tests/res/network/network.txt @@ -0,0 +1 @@ +# idx_agent, n_neighbours_in, indices_neighbours_in[...], weights_in[...] \ No newline at end of file diff --git a/tests/res/opinions.txt b/tests/res/opinions.txt new file mode 100644 index 0000000..4cede8c --- /dev/null +++ b/tests/res/opinions.txt @@ -0,0 +1,6 @@ +# idx_agent,opinion[...] +# comment + 0, 2.1127107987061544, 0.044554683389757696 + 1,0.8088982488089491, 0.015813166022685163 +2,-0.8802809369462433 , 0.015863953902810535, 2.3 + diff --git a/tests/seldoncore/test.py b/tests/seldoncore/test.py new file mode 100644 index 0000000..ce2a388 --- /dev/null +++ b/tests/seldoncore/test.py @@ -0,0 +1,114 @@ +import pathlib as ptlb +import pytest +import pyseldonlib +import shutil + + +def test_run_simulation(capsys): + # Set up the paths + base_dir = ptlb.Path(__file__).parent.parent.resolve() + config_file = str(base_dir / "res/config/conf.toml") + network_file = str(base_dir / "res/network/network.txt") + invalid_config_file = str(base_dir / "res/config/inconf.txt") + output_dir1 = str("outputs/outputfile") + output_dir2 = str("outputs/opwithnetwork") + output_dir = str("outputs/output") + + if ptlb.Path(output_dir1).exists(): + shutil.rmtree(output_dir1) + if ptlb.Path(output_dir2).exists(): + shutil.rmtree(output_dir2) + + # Test with output directory and config + with capsys.disabled(): + pyseldonlib.seldoncore.run_simulation( + config_file_path=config_file, output_dir_path=output_dir1 + ) + assert ptlb.Path(output_dir1).exists() + assert ptlb.Path(output_dir1).is_dir() + + # Test with network file + with capsys.disabled(): + pyseldonlib.seldoncore.run_simulation( + config_file_path=config_file, + network_file_path=network_file, + output_dir_path=output_dir2, + ) + assert ptlb.Path(output_dir2).exists() + assert ptlb.Path(output_dir2).is_dir() + + # Test with non-existent network file + invalid_network_file = str(ptlb.Path(base_dir, "tests/res/network/net.txt")) + with pytest.raises(RuntimeError): + with capsys.disabled(): + pyseldonlib.seldoncore.run_simulation( + config_file_path=config_file, network_file_path=invalid_network_file + ) + + # Test with invalid config file + with pytest.raises(RuntimeError): + with capsys.disabled(): + pyseldonlib.seldoncore.run_simulation(config_file_path=invalid_config_file) + + if ptlb.Path(output_dir).exists(): + shutil.rmtree(output_dir) + if ptlb.Path(output_dir1).exists(): + shutil.rmtree(output_dir1) + if ptlb.Path(output_dir2).exists(): + shutil.rmtree(output_dir2) + + +def test_settings(): + degroot_settings = pyseldonlib.seldoncore.DeGrootSettings() + output_settings = pyseldonlib.seldoncore.OutputSettings() + deffuant_settings = pyseldonlib.seldoncore.DeffuantSettings() + activitydriven_settings = pyseldonlib.seldoncore.ActivityDrivenSettings() + activitydriveninertial_settings = ( + pyseldonlib.seldoncore.ActivityDrivenInertialSettings() + ) + initial_network_settings = pyseldonlib.seldoncore.InitialNetworkSettings() + + assert degroot_settings is not None + assert output_settings is not None + assert deffuant_settings is not None + assert activitydriven_settings is not None + assert activitydriveninertial_settings is not None + assert initial_network_settings is not None + assert activitydriveninertial_settings.covariance_factor == 0.0 + + +def test_network(): + degroot_network = pyseldonlib.seldoncore.SimpleAgentNetwork() + deffuant_network = pyseldonlib.seldoncore.SimpleAgentNetwork() + activitydriven_network = pyseldonlib.seldoncore.ActivityAgentNetwork() + activitydriveninertial_network = pyseldonlib.seldoncore.InertialAgentNetwork() + + assert degroot_network is not None + assert deffuant_network is not None + assert activitydriven_network is not None + assert activitydriveninertial_network is not None + + +def test_simulation_with_simulationOptions(): + degroot_settings = pyseldonlib.seldoncore.DeGrootSettings() + degroot_settings.max_iterations = 100 + output_settings = pyseldonlib.seldoncore.OutputSettings() + initial_network_settings = pyseldonlib.seldoncore.InitialNetworkSettings() + simulation_options = pyseldonlib.seldoncore.SimulationOptions() + simulation_options.output_settings = output_settings + simulation_options.model_settings = degroot_settings + simulation_options.network_settings = initial_network_settings + simulation_options.model_string = "DeGroot" + + base_dir = ptlb.Path(__file__).parent.resolve() + output_dir = str(base_dir / "outputs/output") + + pyseldonlib.seldoncore.run_simulation( + options=simulation_options, output_dir_path=output_dir + ) + assert ptlb.Path(output_dir).exists() + shutil.rmtree(output_dir) + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/seldoncore/test_activity_driven_settings.py b/tests/seldoncore/test_activity_driven_settings.py new file mode 100644 index 0000000..220332d --- /dev/null +++ b/tests/seldoncore/test_activity_driven_settings.py @@ -0,0 +1,77 @@ +import pyseldonlib +import pytest + + +def test_activity_driven_settings(): + settings = pyseldonlib.seldoncore.ActivityDrivenSettings() + assert settings.max_iterations == None + assert settings.dt == 0.01 + assert settings.m == 10 + assert settings.eps == 0.01 + assert settings.gamma == 2.1 + assert settings.alpha == 3.0 + assert settings.homophily == 0.5 + assert settings.reciprocity == 0.5 + assert settings.K == 3.0 + assert settings.mean_activities is False + assert settings.mean_weights is False + assert settings.n_bots == 0 + assert settings.bot_m == [] + assert settings.bot_activity == [] + assert settings.bot_opinion == [] + assert settings.bot_homophily == [] + assert settings.use_reluctances is False + assert settings.reluctance_mean == 1.0 + assert settings.reluctance_sigma == 0.25 + assert settings.reluctance_eps == 0.01 + assert settings.covariance_factor == 0.0 + + # set values + settings.max_iterations = 100 + settings.dt = 0.02 + settings.m = 20 + settings.eps = 0.02 + settings.gamma = 2.2 + settings.alpha = 3.1 + settings.homophily = 0.6 + settings.reciprocity = 0.6 + settings.K = 4.0 + settings.mean_activities = True + settings.mean_weights = True + settings.n_bots = 1 + settings.bot_m = [10] + settings.bot_activity = [0.5] + settings.bot_opinion = [0.5] + settings.bot_homophily = [0.5] + settings.use_reluctances = True + settings.reluctance_mean = 1.1 + settings.reluctance_sigma = 0.26 + settings.reluctance_eps = 0.02 + settings.covariance_factor = 0.1 + + # check values + assert settings.max_iterations == 100 + assert settings.dt == 0.02 + assert settings.m == 20 + assert settings.eps == 0.02 + assert settings.gamma == 2.2 + assert settings.alpha == 3.1 + assert settings.homophily == 0.6 + assert settings.reciprocity == 0.6 + assert settings.K == 4.0 + assert settings.mean_activities is True + assert settings.mean_weights is True + assert settings.n_bots == 1 + assert settings.bot_m == [10] + assert settings.bot_activity == [0.5] + assert settings.bot_opinion == [0.5] + assert settings.bot_homophily == [0.5] + assert settings.use_reluctances is True + assert settings.reluctance_mean == 1.1 + assert settings.reluctance_sigma == 0.26 + assert settings.reluctance_eps == 0.02 + assert settings.covariance_factor == 0.1 + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/seldoncore/test_deffuant_settings.py b/tests/seldoncore/test_deffuant_settings.py new file mode 100644 index 0000000..8202c9f --- /dev/null +++ b/tests/seldoncore/test_deffuant_settings.py @@ -0,0 +1,32 @@ +import pyseldonlib +import pytest + + +def test_deffuant_settings(): + settings = pyseldonlib.seldoncore.DeffuantSettings() + assert settings.max_iterations == None + assert settings.homophily_threshold == 0.2 + assert settings.mu == 0.5 + assert settings.use_network == False + assert settings.use_binary_vector == False + assert settings.dim == 1 + + # set values + settings.max_iterations = 100 + settings.homophily_threshold = 0.3 + settings.mu = 0.6 + settings.use_network = True + settings.use_binary_vector = True + settings.dim = 2 + + # check values + assert settings.max_iterations == 100 + assert settings.homophily_threshold == 0.3 + assert settings.mu == 0.6 + assert settings.use_network == True + assert settings.use_binary_vector == True + assert settings.dim == 2 + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/seldoncore/test_degroot_settings.py b/tests/seldoncore/test_degroot_settings.py new file mode 100644 index 0000000..d863101 --- /dev/null +++ b/tests/seldoncore/test_degroot_settings.py @@ -0,0 +1,16 @@ +import pyseldonlib +import pytest + + +def test_degroot_settings(): + settings = pyseldonlib.seldoncore.DeGrootSettings() + assert settings.max_iterations == None + assert settings.convergence_tol == 0.0 + + # set values + settings.max_iterations = 100 + settings.convergence_tol = 1e-5 + + # check values + assert settings.max_iterations == 100 + assert settings.convergence_tol == 1e-5 diff --git a/tests/seldoncore/test_initial_network_settings.py b/tests/seldoncore/test_initial_network_settings.py new file mode 100644 index 0000000..f9b4985 --- /dev/null +++ b/tests/seldoncore/test_initial_network_settings.py @@ -0,0 +1,28 @@ +import pyseldonlib +import pathlib as ptlb +import pytest + + +def test_initial_network_settings_readwrite(): + network_settings = pyseldonlib.seldoncore.InitialNetworkSettings() + + # default values + assert network_settings.file is None + assert network_settings.number_of_agents == 200 + assert network_settings.connections_per_agent == 10 + + # set values + base_dir = ptlb.Path(__file__).parent.resolve() + file = str(base_dir / "res/network.txt") + network_settings.file = file + network_settings.number_of_agents = 100 + network_settings.connections_per_agent = 5 + + # check values + assert network_settings.file == file + assert network_settings.number_of_agents == 100 + assert network_settings.connections_per_agent == 5 + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/seldoncore/test_io.py b/tests/seldoncore/test_io.py new file mode 100644 index 0000000..a6c53d6 --- /dev/null +++ b/tests/seldoncore/test_io.py @@ -0,0 +1,48 @@ +import pyseldonlib +import pytest +import pathlib + + +# Test reading in the network from a file +def test_io_network(): + proj_root_path = pathlib.Path.cwd() + network_file = str(proj_root_path / "tests" / "res" / "network.txt") + network = pyseldonlib.seldoncore.generate_from_file_activity_agent(network_file) + + assert network.n_agents() == 3 + + neighbours_expected = [[2, 1], [], [1]] + weights_expected = [[0.1, -0.2], [], [1.2]] + + for i in range(0, network.n_agents()): + assert neighbours_expected[i] == network.get_neighbours(i) + assert weights_expected[i] == network.get_weights(i) + + +def test_io_agents(): + proj_root_path = pathlib.Path.cwd() + agent_file = str(proj_root_path / "tests" / "res" / "opinions.txt") + + agents = pyseldonlib.seldoncore.agents_from_file_activity_agent(agent_file) + opinions_expected = [2.1127107987061544, 0.8088982488089491, -0.8802809369462433] + activities_expected = [ + 0.044554683389757696, + 0.015813166022685163, + 0.015863953902810535, + ] + reluctances_expected = [1.0, 1.0, 2.3] + + assert len(agents) == 3 + + for i in range(0, len(agents)): + assert agents[i].data.opinion == pytest.approx(opinions_expected[i], abs=1e-16) + assert agents[i].data.activity == pytest.approx( + activities_expected[i], abs=1e-16 + ) + assert agents[i].data.reluctance == pytest.approx( + reluctances_expected[i], abs=1e-16 + ) + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/seldoncore/test_network.py b/tests/seldoncore/test_network.py new file mode 100644 index 0000000..29f445f --- /dev/null +++ b/tests/seldoncore/test_network.py @@ -0,0 +1,129 @@ +import pytest +import pyseldonlib + + +def test_network_class_tests(): + # Generate some network + n_agents = 20 + n_connections = 10 + gen_pseudorandom = 0 + + network = pyseldonlib.seldoncore.generate_n_connections_( + n_agents=n_agents, + n_connections=n_connections, + self_interaction = False, + ) + + assert network is not None + assert network.n_agents() == n_agents + assert network.n_edges() == n_agents * n_connections + + # Check that the function for setting neighbours and a single weight work + # Agent 3 + agent_index = 3 + neighbour_list = [0, 10] + weights = [0.5, 0.5] + network.set_neighbours_and_weights( + agent_idx=agent_index, buffer_neighbours=neighbour_list, buffer_weights=weights + ) + + retrieved_weights = network.get_weights(agent_index) + assert retrieved_weights == weights + + # Checking that set_weight, get_neighbour work + weights = [0.25, 0.55] + network.set_weights(agent_idx=agent_index, weights=weights) + buffer_w_get = network.get_weights(agent_index) + + assert buffer_w_get == weights + assert network.n_edges(3) == 2 + assert neighbour_list[0] == network.get_neighbours(3)[0] + assert network.get_weights(3)[1] == 0.55 + + # Checking that set_neighbours_and_weights works with a vector of weights, push_back and transpose + neighbour_list = [0, 10, 15] + weights = [0.1, 0.2, 0.3] + network.set_neighbours_and_weights( + agent_idx=agent_index, + buffer_neighbours=neighbour_list, + buffer_weights=weights, + ) + + retrieved_weights = network.get_weights(agent_index) + retrieved_neighbours = network.get_neighbours(agent_index) + assert retrieved_weights == weights + assert retrieved_neighbours == neighbour_list + + # Now we test the toggle_incoming_outgoing() function + # First record all the old edges as tuples (i,j,w) where this edge goes from j -> i with weight w + old_edges = set() + + for i in range(network.n_agents()): + buffer_n = network.get_neighbours(i) + buffer_w = network.get_weights(i) + for j in range(len(buffer_n)): + neigh = buffer_n[j] + weight = buffer_w[j] + edge = (i, neigh, weight) + old_edges.add(edge) + + old_direction = network.direction() + network.toggle_incoming_outgoing() + new_direction = network.direction() + # Check that the direction has changed + assert old_direction != new_direction + + # Now we go over the toggled network and try to re-identify all edges + for i in range(network.n_agents()): + buffer_n = network.get_neighbours(i) + buffer_w = network.get_weights(i) + for j in range(len(buffer_n)): + neigh = buffer_n[j] + weight = buffer_w[j] + edge = (neigh, i, weight) + assert edge in old_edges + old_edges.remove(edge) + + assert len(old_edges) == 0 + + # Test remove double counting + neighbour_list = [[2, 1, 1, 0], [2, 0, 1, 2], [1, 1, 0, 2, 1], [], [3, 1]] + weights = [[-1, 1, 2, 0], [-1, 1, 2, -1], [-1, 1, 2, 3, 1], [], [1, 1]] + + neighbour_no_double_counting = [[0, 1, 2], [0, 1, 2], [0, 1, 2], [], [1, 3]] + weights_no_double_counting = [[0, 3, -1], [1, 2, -2], [2, 1, 3], [], [1, 1]] + + network = pyseldonlib.seldoncore.Network(neighbour_list, weights, "Incoming") + network.remove_double_counting() + + for i_agent in range(network.n_agents()): + assert ( + network.get_neighbours(i_agent) == neighbour_no_double_counting[i_agent] + ), f"Neighbours mismatch for agent {i_agent}" + assert ( + network.get_weights(i_agent) == weights_no_double_counting[i_agent] + ), f"Weights mismatch for agent {i_agent}" + + # Test the generation of a square lattice neighbour list for three agents + desired_neighbour_list = [ + [2, 1, 3, 6], + [2, 0, 4, 7], + [0, 1, 5, 8], + [4, 5, 6, 0], + [5, 3, 1, 7], + [3, 4, 2, 8], + [7, 8, 3, 0], + [8, 6, 4, 1], + [7, 6, 5, 2], + ] + network = pyseldonlib.seldoncore.generate_square_lattice_(n_edge=3) + + for i_agent in range(network.n_agents()): + assert ( + network.get_neighbours(i_agent).sort() + == desired_neighbour_list[i_agent].sort() + ), f"Neighbours mismatch for agent {i_agent}" + + +# if __name__ == "__main__": +# pytest.main([__file__]) diff --git a/tests/seldoncore/test_network_generation.py b/tests/seldoncore/test_network_generation.py new file mode 100644 index 0000000..a0bc0c9 --- /dev/null +++ b/tests/seldoncore/test_network_generation.py @@ -0,0 +1,25 @@ +import pyseldonlib +import pytest + + +# Testing the network generation functions +def test_network_generation(): + buffer_n_get = [] + buffer_w_get = [] + + n_agents = 3 + neigh = [0, 1, 2] + weight = 0.25 + weights = [weight, weight, weight] + + network = pyseldonlib.utils.generate_fully_connected(model_string = "DeGroot", + n_agents=n_agents, weight=weight, rng_seed=None + ) + + assert network.n_agents() == n_agents + + for i in range(0, n_agents): + buffer_n_get = network.get_neighbours(i) + buffer_w_get = network.get_weights(i) + assert buffer_n_get == neigh + assert buffer_w_get == weights diff --git a/tests/seldoncore/test_output_settings.py b/tests/seldoncore/test_output_settings.py new file mode 100644 index 0000000..f2752a0 --- /dev/null +++ b/tests/seldoncore/test_output_settings.py @@ -0,0 +1,28 @@ +import pyseldonlib +import pytest + + +def test_output_settings(): + settings = pyseldonlib.seldoncore.OutputSettings() + assert settings.n_output_agents is None + assert settings.n_output_network is None + assert settings.print_progress is False + assert settings.output_initial is True + assert settings.start_output == 1 + assert settings.start_numbering_from == 0 + + # set values + settings.n_output_agents = 10 + settings.n_output_network = 5 + settings.print_progress = True + settings.output_initial = False + settings.start_output = 5 + settings.start_numbering_from = 1 + + # check values + assert settings.n_output_agents == 10 + assert settings.n_output_network == 5 + assert settings.print_progress == True + assert settings.output_initial == False + assert settings.start_output == 5 + assert settings.start_numbering_from == 1 diff --git a/tests/seldoncore/test_probability_distributions.py b/tests/seldoncore/test_probability_distributions.py new file mode 100644 index 0000000..5c79f48 --- /dev/null +++ b/tests/seldoncore/test_probability_distributions.py @@ -0,0 +1,42 @@ +import os +from pathlib import Path +import pyseldonlib +import pytest +import random + + +def write_results_to_file(n_samples, dist, filename): + proj_root_path = Path.cwd() + file_path = proj_root_path / f"tests/output_probability_distributions/{filename}" + print(f"file = {file_path}") + file_path.parent.mkdir(parents=True, exist_ok=True) + + gen = pyseldonlib.seldoncore.RandomGenerator(random.randint(0, 2**32 - 1)) + results = [dist(gen) for _ in range(n_samples)] + + with open(file_path, "w") as file: + for value in results: + file.write(f"{value}\n") + + +def test_probability_distributions(): + write_results_to_file( + 10000, + pyseldonlib.seldoncore.Truncated_Normal_Distribution(1.0, 0.5, 0.75), + "truncated_normal.txt", + ) + write_results_to_file( + 10000, pyseldonlib.seldoncore.Power_Law_Distribution(0.01, 2.1), "power_law.txt" + ) + write_results_to_file( + 10000, + pyseldonlib.seldoncore.Bivariate_Normal_Distribution(0.5), + "bivariate_normal.txt", + ) + + +def test_bivariate_gaussian_copula(): + dist1 = pyseldonlib.seldoncore.Power_Law_Distribution(0.02, 2.5) + dist2 = pyseldonlib.seldoncore.Truncated_Normal_Distribution(1.0, 0.75, 0.2) + copula = pyseldonlib.seldoncore.Bivariate_Gaussian_Copula(0.5, dist1, dist2) + write_results_to_file(10000, copula, "gaussian_copula.txt") diff --git a/tests/seldoncore/test_sampling.py b/tests/seldoncore/test_sampling.py new file mode 100644 index 0000000..ee20c10 --- /dev/null +++ b/tests/seldoncore/test_sampling.py @@ -0,0 +1,82 @@ +import math +import pytest +import random +import pyseldonlib + + +def compute_p(k, n): + if k == 0: + return 0.0 + else: + p = 1.0 / (float(n) - 1.0) + return p + (1.0 - p) * compute_p(k - 1, n - 1) + + +def test_draw_unique_k_from_n(): + N_RUNS = 10000 + k = 6 + n = 100 + ignore_idx = 11 + + histogram = [0] * n + buffer = [] + gen = pyseldonlib.seldoncore.RandomGenerator(random.randint(0, 2**32 - 1)) + for _i in range(0, N_RUNS): + pyseldonlib.seldoncore.draw_unique_k_from_n( + ignore_idx=ignore_idx, k=k, n=n, buffer=buffer, gen=gen + ) + for num in buffer: + histogram[num] += 1 + + # In each run there is a probability of p for each element to be selected + # That means for each histogram bin we have a binomial distribution with p + p = compute_p(k, n) + + mean = N_RUNS * p + # The variance of a binomial distribution is var = n*p*(1-p) + sigma = math.sqrt(N_RUNS * p * (1.0 - p)) + + assert histogram[ignore_idx] == 0 # The ignore_idx should never be selected + + number_outside_three_sigma = 0 + for n in histogram: + if n == 0: + continue + + if abs(float(n) - float(mean)) > 3.0 * sigma: + number_outside_three_sigma += 1 + + assert n == pytest.approx(mean, abs=5 * sigma) + + if number_outside_three_sigma > 0.01 * N_RUNS: + pytest.warns( + UserWarning, + f"Many deviations beyond the 3 sigma range. {number_outside_three_sigma} out of {N_RUNS}", + ) + + +# to-do +# def test_weighted_reservoir_sampling(): +# N_RUNS = 10000 +# k = 6 +# n = 100 +# ignore_idx = 11 +# ignore_idx2 = 29 + +# histogram = [0] * n # Count how often each element occurs amongst all samples + +# def weight_callback(idx): +# if idx == ignore_idx or idx == ignore_idx2: +# return 0.0 +# else: +# return abs(n / 2.0 - idx) +# buffer = [] +# gen = pyseldon.seldoncore.RandomGenerator(random.randint(0, 2**32 - 1)) + +# for _ in range(N_RUNS): +# pyseldon.seldoncore.reservoir_sampling_A_ExpJ(k=k, n=n, weight_callback=weight_callback,buffer = buffer,gen = gen) +# for num in buffer: +# histogram[num] += 1 + +# assert histogram[ignore_idx] == 0 # The ignore_idx should never be selected +# assert histogram[ignore_idx2] == 0 # The ignore_idx2 should never be selected diff --git a/tests/seldoncore/test_tarjan_scc.py b/tests/seldoncore/test_tarjan_scc.py new file mode 100644 index 0000000..52265d4 --- /dev/null +++ b/tests/seldoncore/test_tarjan_scc.py @@ -0,0 +1,23 @@ +import pytest +import pyseldonlib + +# Test data similar to the C++ version +neighbour_list = [[1], [2, 3], [0], [4], [5], [4], [4, 7], [5, 8], [9], [6, 7]] + +# Expected SCCs +expected_scc = [{5, 4}, {3}, {2, 1, 0}, {9, 8, 7, 6}] + + +def test_tarjan_scc(): + # Run Tarjan's algorithm + tarjan_scc = pyseldonlib.seldoncore.TarjanConnectivityAlgo(neighbour_list) + + # Convert each SCC list to a set for comparison + scc_sets = [set(scc) for scc in tarjan_scc.scc_list] + + # Check if all expected SCCs are found + for expected_set in expected_scc: + assert expected_set in scc_sets + + # Check the total number of SCCs + assert len(tarjan_scc.scc_list) == 4 diff --git a/tests/seldoncore/test_util.py b/tests/seldoncore/test_util.py new file mode 100644 index 0000000..cf9474f --- /dev/null +++ b/tests/seldoncore/test_util.py @@ -0,0 +1,11 @@ +import pytest +import pyseldonlib + + +def test_hamming_distance(): + v1 = [1, 1, 1, 0, 1] + v2 = [0, 1, 1, 0, 0] + + dist = pyseldonlib.seldoncore.hamming_distance(v1, v2) + + assert dist == 2 diff --git a/tests/test_activity_driven.py b/tests/test_activity_driven.py new file mode 100644 index 0000000..b053f71 --- /dev/null +++ b/tests/test_activity_driven.py @@ -0,0 +1,282 @@ +import pyseldonlib +import pathlib +import pytest +import shutil +import math + + +def test_activity_driven(): + # using ./tests/res/activity_probabilistic_conf.toml + + other_settings = pyseldonlib.Other_Settings( + number_of_agents=1000, + connections_per_agent=10, + n_output_agents=1, + print_progress=False, + ) + + model = pyseldonlib.Activity_Driven_Model( + max_iterations=20, + dt=0.01, # Timestep for the integration of the coupled ODEs + m=10, # Number of agents contacted, when the agent is active + eps=0.01, # Minimum activity epsilon; a_i belongs to [epsilon,1] + gamma=2.1, # Exponent of activity power law distribution of activities + reciprocity=0.5, # Probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections + homophily=0.5, # aka beta. if zero, agents pick their interaction partners at random + alpha=3.0, # Controversialness of the issue, must be greater than 0. + K=3.0, # Social interaction strength + mean_activities=False, # Use the mean value of the powerlaw distribution for the activities of all agents + mean_weights=False, # Use the meanfield approximation of the network edges + other_settings=other_settings, + rng_seed=120, + ) + + base_dir = pathlib.Path(__file__).parent.resolve() + print(base_dir) + output_dir = str(base_dir / "outputs") + + if pathlib.Path(output_dir).exists(): + shutil.rmtree(output_dir) + + # By using the above settings + model.run(output_dir=output_dir) + print("Simulation completed!") + assert pathlib.Path(output_dir).exists() + shutil.rmtree(output_dir) + + # By using a config file + config_file_path = str(base_dir / "res/activity_probabilistic_conf.toml") + pyseldonlib.run_simulation_from_config_file( + config_file_path=config_file_path, output_dir_path=output_dir + ) + assert pathlib.Path(output_dir).exists() + shutil.rmtree(output_dir) + + +# Test that you can produce output for the probabilistic acitivity driven model, from a conf file", +def test_activityProb(): + proj_root = pathlib.Path.cwd() + input_file = str(proj_root / "tests" / "res" / "activity_probabilistic_conf.toml") + options = pyseldonlib.parse_config_file(input_file) + + output_dir_path = proj_root / "tests" / "output" + + if output_dir_path.exists(): + shutil.rmtree(output_dir_path) + output_dir_path.mkdir(parents=True, exist_ok=True) + + simulation = pyseldonlib.seldoncore.SimulationActivityAgent(options=options) + simulation.run(str(output_dir_path)) + + assert any(output_dir_path.iterdir()), "Output directory is empty after simulation." + + shutil.rmtree(output_dir_path) + + +# Test the probabilistic activity driven model for two agents +def test_activityProbTwoAgents(): + proj_root = pathlib.Path.cwd() + + output_dir_path = str(proj_root / "tests" / "output") + + other_settings = pyseldonlib.Other_Settings( + number_of_agents=2, connections_per_agent=1, print_progress=False + ) + + model = pyseldonlib.Activity_Driven_Model( + max_iterations=10000, + dt=0.005, # Timestep for the integration of the coupled ODEs + m=1, # Number of agents contacted, when the agent is active + eps=1, # Minimum activity epsilon; a_i belongs to [epsilon,1] + gamma=2.1, # Exponent of activity power law distribution of activities + reciprocity=1, # Probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections + homophily=0.5, # aka beta. if zero, agents pick their interaction partners at random + alpha=1.01, # Controversialness of the issue, must be greater than 0. + K=2.0, # Social interaction strength + mean_activities=False, # Use the mean value of the powerlaw distribution for the activities of all agents + mean_weights=False, # Use the meanfield approximation of the network edges + other_settings=other_settings, + rng_seed=120, + ) + model.run(output_dir_path) + + K = model.K + alpha = model.alpha + + assert K == pytest.approx(2.0, 1e-16) + assert alpha == pytest.approx(1.01, 1e-16) + + # This is the solution of x = K tanh(alpha x) + analytical_x = 1.9187384098662013 + assert analytical_x == 1.9187384098662013 + + for idx in range(0, model.Network.n_agents()): + assert model.agent_opinion(idx) == pytest.approx(analytical_x, abs=1e-4) + + shutil.rmtree(output_dir_path) + + +# Test the probabilistic activity driven model with one bot and one (reluctant) agent +def test_activity1Bot1AgentReluctance(): + proj_root = pathlib.Path.cwd() + + other_settings = pyseldonlib.Other_Settings( + number_of_agents=2, connections_per_agent=1, print_progress=False + ) + + model = pyseldonlib.Activity_Driven_Model( + max_iterations=1000, + dt=0.001, # Timestep for the integration of the coupled ODEs + m=1, # Number of agents contacted, when the agent is active + eps=1, # Minimum activity epsilon; a_i belongs to [epsilon,1] + gamma=2.1, # Exponent of activity power law distribution of activities + reciprocity=1, # Probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections + homophily=0.5, # aka beta. if zero, agents pick their interaction partners at random + alpha=1.5, # Controversialness of the issue, must be greater than 0. + K=2.0, # Social interaction strength + mean_activities=False, # Use the mean value of the powerlaw distribution for the activities of all agents + mean_weights=False, # Use the meanfield approximation of the network edges + use_reluctances=True, # Assigns a "reluctance" (m_i) to each agent. By default; false and every agent has a reluctance of 1 + reluctance_mean=1.5, # Mean of distribution before drawing from a truncated normal distribution (default set to 1.0) + reluctance_sigma=0.1, # Width of normal distribution (before truncating) + reluctance_eps=0.01, # Minimum such that the normal distribution is truncated at this value + n_bots=1, # The number of bots to be used; if not specified defaults to 0 (which means bots are deactivated) + # Bots are agents with fixed opinions and different parameters, the parameters are specified in the following lists + # If n_bots is smaller than the length of any of the lists, the first n_bots entries are used. If n_bots is greater the code will throw an exception. + bot_m=[1], # If not specified, defaults to `m` + bot_homophily=[0.7], # If not specified, defaults to `homophily` + bot_activity=[1.0], # If not specified, defaults to 0 + bot_opinion=[2], # The fixed opinions of the bots + other_settings=other_settings, + rng_seed=120, + ) + + output_dir_path = str(proj_root / "tests" / "output1bot") + + # Get the bot opinion (which won't change) + x_bot = model.agent_opinion(0) + + # Get the initial agent opinion + x_0 = model.agent_opinion(1) + + model.run(output_dir_path) + + K = model.K + alpha = model.alpha + iterations = model.max_iterations + dt = model.dt + time_elapsed = iterations * dt + + # final agent and bot opinion + x_t = model.agent_opinion(1) + x_t_bot = model.agent_opinion(0) + reluctance = model.agent_reluctance(1) + + # The bot opinion should not change during the simulation + assert x_t_bot == pytest.approx(x_bot, abs=1e-16) + + # Test that the agent opinion matches the analytical solution for an agent with a bot + # Analytical solution is: + # x_t = [x(0) - Ktanh(alpha*x_bot)]e^(-t) + Ktanh(alpha*x_bot) + x_t_analytical = (x_0 - K / reluctance * math.tanh(alpha * x_bot)) * math.exp( + -time_elapsed + ) + K / reluctance * math.tanh(alpha * x_bot) + + assert x_t == pytest.approx(x_t_analytical, abs=1e-5) + + shutil.rmtree(output_dir_path) + + +# Test the meanfield activity driven model with 10 agents +@pytest.mark.xfail(reason="Test is not passing, future work needed") +def test_activityMeanfield10Agents(): + proj_root = pathlib.Path.cwd() + + other_settings = pyseldonlib.Other_Settings( + number_of_agents=50, connections_per_agent=1, print_progress=False + ) + + model = pyseldonlib.Activity_Driven_Model( + max_iterations=1000, + dt=0.01, # Timestep for the integration of the coupled ODEs + m=10, # Number of agents contacted, when the agent is active + eps=0.01, # Minimum activity epsilon; a_i belongs to [epsilon,1] + gamma=2.1, # Exponent of activity power law distribution of activities + reciprocity=1.0, # Probability that when agent i contacts j via weighted reservoir sampling, j also sends feedback to i. So every agent can have more than m incoming connections + homophily=0.0, # aka beta. if zero, agents pick their interaction partners at random + alpha=1.01, # Controversialness of the issue, must be greater than 0. + K=2.0, # Social interaction strength + mean_activities=True, # Use the mean value of the powerlaw distribution for the activities of all agents + mean_weights=True, # Use the meanfield approximation of the network edges + other_settings=other_settings, + ) + + output_dir_path = str(proj_root / "tests" / "output") + + assert model.mean_weights == True + assert model.mean_activities == True + # We require zero homophily, since we only know the critical controversialness in that case + assert model.homophily == pytest.approx(0, abs=1e-16) + + K = model.K + n_agents = model.Network.n_agents() + reciprocity = model.reciprocity + m = model.m + eps = model.eps + gamma = model.gamma + + dist = pyseldonlib.seldoncore.Power_Law_Distribution(eps, gamma) + mean_activity = dist.mean() + print(mean_activity) + print(type(mean_activity)) + + def set_opinions_and_run(above_critical_controversialness): + initial_opinion_delta = 0.1 # Set the initial opinion in the interval [-delta, delta] + + for i in range(0, n_agents): + _model = model + assert mean_activity == pytest.approx(_model.agent_activity(i), abs=1e-16) + opinion = -initial_opinion_delta + 2.0 * i / (n_agents - 1) * ( + initial_opinion_delta + ) + _model.set_agent_opinion(i, opinion) + + output_dir_p = str(proj_root / "tests" / "output_meanfield_test") + if pathlib.Path(output_dir_p).exists(): + shutil.rmtree(output_dir_p) + + _model.run(output_dir_p) + + # Check the opinions after the run, if alpha is above the critical controversialness, + # the opinions need to deviate from zero + avg_deviation = 0.0 + for i in range(0, n_agents): + if above_critical_controversialness is False: + assert abs(_model.agent_opinion(i)) > abs(initial_opinion_delta) + else: + assert abs(_model.agent_opinion(i)) < abs(initial_opinion_delta) + + avg_deviation += abs(_model.agent_opinion(i)) + print(f"Average deviation of agents = { avg_deviation / n_agents}\n") + shutil.rmtree(output_dir_p) + + alpha_critical = ( + float(n_agents) + / (float(n_agents) - 1.0) + * 1.0 + / ((1.0 + reciprocity) * K * m * mean_activity) + ) + print(f"Critical controversialness = { alpha_critical}\n") + delta_alpha = 0.1 + + # Set the critical controversialness to a little above the critical alpha + model.alpha = alpha_critical + delta_alpha + set_opinions_and_run(True) + + # Set the critical controversialness to a little above the critical alpha + model.alpha = alpha_critical - delta_alpha + set_opinions_and_run(False) + shutil.rmtree(output_dir_path) + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/test_activity_driven_inertial_settings.py b/tests/test_activity_driven_inertial_settings.py new file mode 100644 index 0000000..ea67ae0 --- /dev/null +++ b/tests/test_activity_driven_inertial_settings.py @@ -0,0 +1,80 @@ +import pyseldonlib +import pytest + + +def test_activity_driven_inertial_settings(): + settings = pyseldonlib.seldoncore.ActivityDrivenInertialSettings() + assert settings.max_iterations == None + assert settings.dt == 0.01 + assert settings.m == 10 + assert settings.eps == 0.01 + assert settings.gamma == 2.1 + assert settings.alpha == 3.0 + assert settings.homophily == 0.5 + assert settings.reciprocity == 0.5 + assert settings.K == 3.0 + assert settings.mean_activities is False + assert settings.mean_weights is False + assert settings.n_bots == 0 + assert settings.bot_m == [] + assert settings.bot_activity == [] + assert settings.bot_opinion == [] + assert settings.bot_homophily == [] + assert settings.use_reluctances is False + assert settings.reluctance_mean == 1.0 + assert settings.reluctance_sigma == 0.25 + assert settings.reluctance_eps == 0.01 + assert settings.covariance_factor == 0.0 + assert settings.friction_coefficient == 1.0 + + # set values + settings.max_iterations = 100 + settings.dt = 0.02 + settings.m = 20 + settings.eps = 0.02 + settings.gamma = 2.2 + settings.alpha = 3.1 + settings.homophily = 0.6 + settings.reciprocity = 0.6 + settings.K = 4.0 + settings.mean_activities = True + settings.mean_weights = True + settings.n_bots = 1 + settings.bot_m = [10] + settings.bot_activity = [0.5] + settings.bot_opinion = [0.5] + settings.bot_homophily = [0.5] + settings.use_reluctances = True + settings.reluctance_mean = 1.1 + settings.reluctance_sigma = 0.26 + settings.reluctance_eps = 0.02 + settings.covariance_factor = 0.1 + settings.friction_coefficient = 0.5 + + # check values + assert settings.max_iterations == 100 + assert settings.dt == 0.02 + assert settings.m == 20 + assert settings.eps == 0.02 + assert settings.gamma == 2.2 + assert settings.alpha == 3.1 + assert settings.homophily == 0.6 + assert settings.reciprocity == 0.6 + assert settings.K == 4.0 + assert settings.mean_activities is True + assert settings.mean_weights is True + assert settings.n_bots == 1 + assert settings.bot_m == [10] + assert settings.bot_activity == [0.5] + assert settings.bot_opinion == [0.5] + assert settings.bot_homophily == [0.5] + assert settings.use_reluctances is True + assert settings.reluctance_mean == 1.1 + assert settings.reluctance_sigma == 0.26 + assert settings.reluctance_eps == 0.02 + assert settings.covariance_factor == 0.1 + assert settings.friction_coefficient == 0.5 + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/test_activity_inertial.py b/tests/test_activity_inertial.py new file mode 100644 index 0000000..2a323a5 --- /dev/null +++ b/tests/test_activity_inertial.py @@ -0,0 +1,84 @@ +import pyseldonlib +import pathlib +import pytest +import cmath +import shutil + + +# Test the probabilistic inertial activity driven model with one bot and one agent +def test_inertial1Bot1Agent(): + proj_root_path = pathlib.Path.cwd() + other_settings = pyseldonlib.Other_Settings( + print_progress=False, number_of_agents=2, connections_per_agent=1 + ) + model = pyseldonlib.Inertial_Model( + max_iterations=1000, + dt=0.001, + m=1, + eps=1, + gamma=2.1, + reciprocity=1, + homophily=0.5, + alpha=1.5, + K=2.0, + mean_activities=False, + mean_weights=False, + use_reluctances=True, + reluctance_mean=1.5, + reluctance_sigma=0.1, + reluctance_eps=0.01, + n_bots=1, + bot_m=[1], + bot_homophily=[0.7], + bot_activity=[1.0], + bot_opinion=[2], + friction_coefficient=0.5, + other_settings=other_settings, + rng_seed=120, + ) + output_dir_path = str(proj_root_path / "tests" / "output_inertial") + + # Get the bot opinion (which won't change) + x_bot = model.agent_opinion(0) # bot opinion + + # Get the initial agent opinion + x_0 = model.agent_opinion(1) # agent opinion + + model.run(output_dir_path) + + K = model.K + alpha = model.alpha + iterations = model.max_iterations + dt = model.dt + mu = model.friction_coefficient + time_elapsed = iterations * dt + + # Final agent and bot opinions after the simulation run + x_t = model.agent_opinion(1) + x_t_bot = model.agent_opinion(0) + reluctance = model.agent_reluctance(1) + + # The bot opinion should not change during the simulation + assert x_t_bot == pytest.approx(x_bot, abs=1e-16) + + # C = K/m tanh (alpha*x_bot) + C = K / reluctance * cmath.tanh(alpha * x_bot) + + a1 = 0.5 * (-cmath.sqrt(mu * mu - 4.0) - mu) + a2 = 0.5 * (cmath.sqrt(mu * mu - 4.0) - mu) + c1 = (x_0 - C) / (1.0 - a1 / a2) + c2 = -c1 * a1 / a2 + + # Test that the agent opinion matches the analytical solution for an agent with a bot + # Analytical solution is + # x_t = c1 * exp(a1*t) + c2 *exp(a2*t) + C + x_t_analytical = ( + c1 * cmath.exp(a1 * time_elapsed) + c2 * cmath.exp(a2 * time_elapsed) + C + ) + + assert x_t == pytest.approx(x_t_analytical, 1e-5) + shutil.rmtree(output_dir_path) + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/test_deGroot.py b/tests/test_deGroot.py new file mode 100644 index 0000000..0ed147c --- /dev/null +++ b/tests/test_deGroot.py @@ -0,0 +1,35 @@ +import pyseldonlib +import math +import pytest +import pathlib +import shutil + + +def test_deGroot(): + n_agents = 2 + neighbour_list = [[1, 0], [0, 1]] + weight_list = [[0.2, 0.8], [0.2, 0.8]] + + network = pyseldonlib.seldoncore.SimpleAgentNetwork( + neighbour_list=neighbour_list, weight_list=weight_list, direction="Incoming" + ) + + output_dir = str(pathlib.Path.cwd() / "tests/output") + + model = pyseldonlib.DeGroot_Model(max_iterations=100, convergence_tol=1e-6) + model.Network = network + model.set_agent_opinion(0, 0.0) + model.set_agent_opinion(1, 1.0) + if pathlib.Path(output_dir).exists(): + shutil.rmtree(output_dir) + + model.run(output_dir) + + for i in range(n_agents): + print(f"Opinion {i} = {model.agent_opinion(i)}") + assert math.isclose(model.agent_opinion(i), 0.5, rel_tol=0.1) + shutil.rmtree(output_dir) + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/test_deffuant.py b/tests/test_deffuant.py new file mode 100644 index 0000000..9d80a91 --- /dev/null +++ b/tests/test_deffuant.py @@ -0,0 +1,143 @@ +import pyseldonlib +import pytest +from pathlib import Path +import shutil + + +def test_basic_deffuant_model_two_agents(): + proj_root = Path.cwd() + other_settings = pyseldonlib.Other_Settings( + number_of_agents=2, connections_per_agent=0 + ) + model = pyseldonlib.Deffuant_Model( + max_iterations=10, + homophily_threshold=0.2, + mu=0.5, + other_settings=other_settings, + ) + output_dir_path = str(proj_root / "tests" / "output_deffuant") + mu = model.mu + homophily_threshold = model.homophily_threshold + + # agents are too far apart, we dont expect any change with the iterations + agent1_init = homophily_threshold * 1.1 + agent2_init = 0 + + model.set_agent_opinion(0, agent1_init) + model.set_agent_opinion(1, agent2_init) + + model.run(output_dir_path) + + assert model.agent_opinion(0) == pytest.approx(agent1_init) + assert model.agent_opinion(1) == pytest.approx(agent2_init) + + # agents are too close, we expect them to converge to the same opinion + agent1_init = homophily_threshold * 0.9 + agent2_init = 0 + + model.set_agent_opinion(0, agent1_init) + model.set_agent_opinion(1, agent2_init) + + shutil.rmtree(output_dir_path) + model.run(output_dir_path) + + n_iterations = model.max_iterations + expected_diff = (1.0 - 2.0 * mu) ** (2 * n_iterations) * (agent1_init - agent2_init) + assert model.agent_opinion(0) - model.agent_opinion(1) == pytest.approx( + expected_diff + ) + shutil.rmtree(output_dir_path) + + +def test_lattice_deffuant_model_16X16_agents(): + proj_root = Path.cwd() + other_settings = pyseldonlib.Other_Settings( + number_of_agents=256, connections_per_agent=0 + ) + model = pyseldonlib.Deffuant_Model( + max_iterations=10000, + homophily_threshold=1.0, + mu=0.5, + use_network=True, + other_settings=other_settings, + ) + + output_dir_path = str(proj_root / "tests" / "output_deffuant") + homophily_threshold = model.homophily_threshold + + n_agents = model.Network.n_agents() + n_agents_half = int(n_agents / 2) + avg_opinion = 0 + + # first half with low opinions + for i in range(0, n_agents_half): + op = -homophily_threshold - 0.5 * i / n_agents * homophily_threshold + avg_opinion += op / float(n_agents_half) + model.set_agent_opinion(i, op) + + # second half with low opinions + for i in range(n_agents_half, n_agents): + op = ( + homophily_threshold + + 0.5 * (i - n_agents_half) / n_agents * homophily_threshold + ) + model.set_agent_opinion(i, op) + + # The two halves are so far apart that they should not interact an therefore form two stable clusters. + model.run(output_dir_path) + + for i in range(0, n_agents_half): + assert model.agent_opinion(i) == pytest.approx(avg_opinion) + + for i in range(n_agents_half, n_agents): + assert model.agent_opinion(i) == pytest.approx(-avg_opinion) + + shutil.rmtree(output_dir_path) + + +# Test the multi-dimensional Deffuant vector model, with 3-dimensional binary opinions, for two agents +def test_deffuant_vector_model(): + proj_root = Path.cwd() + other_settings = pyseldonlib.Other_Settings( + number_of_agents=2, connections_per_agent=0 + ) + model = pyseldonlib.Deffuant_Vector_Model( + max_iterations=10, + homophily_threshold=2, + mu=0.5, + use_network=False, + dim=3, + other_settings=other_settings, + ) + + output_dir_path = str(proj_root / "tests" / "output_deffuant_vector") + + # agents are too far apart, we dont expect any change with the iterations + agent1_init = [0, 1, 0] + agent2_init = [1, 0, 1] + + model.set_agent_opinion(0, agent1_init) + model.set_agent_opinion(1, agent2_init) + + model.run(output_dir_path) + + assert model.agent_opinion(0) == agent1_init + assert model.agent_opinion(1) == agent2_init + + # agents are close enough, they should converge + # dim-1 or 2 opinions should be the same + agent1_init = [0, 1, 1] + agent2_init = [1, 1, 1] + + model.set_agent_opinion(0, agent1_init) + model.set_agent_opinion(1, agent2_init) + + shutil.rmtree(output_dir_path) + model.run(output_dir_path) + + assert model.agent_opinion(0) == model.agent_opinion(1) + shutil.rmtree(output_dir_path) + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/visualisations/cytoscapenormalisation.py b/visualisations/cytoscapenormalisation.py new file mode 100644 index 0000000..88a23ac --- /dev/null +++ b/visualisations/cytoscapenormalisation.py @@ -0,0 +1,23 @@ +import csv + +input_file = '/home/parrot_user/Desktop/pyseldonlib/examples/ouput_20_agents/network_0.txt' # Your CSV file +output_file ='/home/parrot_user/Desktop/pyseldonlib/examples/ouput_20_agents/network.csv' # Output file for Cytoscape + +with open(input_file, 'r') as csvfile, open(output_file, 'w', newline='') as out_csv: + reader = csv.reader(csvfile) + writer = csv.writer(out_csv) + writer.writerow(['Source', 'Target', 'Weight']) # Column names for Cytoscape + + # Skip the header row + next(reader) + + for row in reader: + idx_agent = row[0].strip() + n_neighbours = int(row[1].strip()) + indices_neighbours = row[2:2 + n_neighbours] + weights = row[2 + n_neighbours:2 + 2 * n_neighbours] + + for target, weight in zip(indices_neighbours, weights): + writer.writerow([idx_agent, target.strip(), weight.strip()]) + +print("Edge list saved to edges.csv") \ No newline at end of file diff --git a/visualisations/degrootmodel.ipynb b/visualisations/degrootmodel.ipynb new file mode 100644 index 0000000..ca24c45 --- /dev/null +++ b/visualisations/degrootmodel.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Import the package" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "import pyseldonlib" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialize some other settings" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "other_settings = pyseldonlib.Other_Settings(n_output_agents=10,\n", + " n_output_network= None, \n", + " print_progress= True, \n", + " output_initial=True, \n", + " start_output=1, \n", + " number_of_agents = 20, \n", + " connections_per_agent = 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialize the model with parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Output directory path set to: /home/parrot_user/Desktop/pyseldon/examples/ouput_20_agents\n", + "\n", + "Random seed: 120\n", + "[Model]\n", + " type DeGroot\n", + " max_iterations optional(1000)\n", + " convergence_tol 1e-06\n", + "[Network]\n", + " n_agents 20\n", + " n_connections 10\n", + "[Output]\n", + " n_output_agents optional(10)\n", + " n_output_network none\n", + " print_progress true\n", + " output_initial true\n", + " start_output 1\n", + " start_numbering_from 0\n", + "-----------------------------------------------------------------\n", + "Starting simulation\n", + "-----------------------------------------------------------------\n", + "Iteration 1 iter_time = 00h 00m 00.000s \n", + "Iteration 2 iter_time = 00h 00m 00.000s \n", + "Iteration 3 iter_time = 00h 00m 00.000s \n", + "Iteration 4 iter_time = 00h 00m 00.000s \n", + "Iteration 5 iter_time = 00h 00m 00.000s \n", + "Iteration 6 iter_time = 00h 00m 00.000s \n", + "Iteration 7 iter_time = 00h 00m 00.000s \n", + "Iteration 8 iter_time = 00h 00m 00.000s \n", + "Iteration 9 iter_time = 00h 00m 00.000s \n", + "Iteration 10 iter_time = 00h 00m 00.000s \n", + "Iteration 11 iter_time = 00h 00m 00.000s \n", + "Iteration 12 iter_time = 00h 00m 00.000s \n", + "-----------------------------------------------------------------\n", + "Finished after 12 iterations, total time = 00h 00m 00.000s\n", + "=================================================================\n" + ] + } + ], + "source": [ + "model = pyseldonlib.DeGroot_Model(max_iterations=1000,\n", + " convergence_tol=1e-6,\n", + " rng_seed=120, \n", + " other_settings=other_settings)\n", + "\n", + "ouput = model.run('./ouput_20_agents')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/visualisations/ouput_20_agents/network_0.txt b/visualisations/ouput_20_agents/network_0.txt new file mode 100644 index 0000000..e95519b --- /dev/null +++ b/visualisations/ouput_20_agents/network_0.txt @@ -0,0 +1,21 @@ +# idx_agent, n_neighbours_in, indices_neighbours_in[...], weights_in[...] + 0, 11, 3, 4, 5, 9, 11, 12, 13, 14, 17, 18, 0, 0.14446034937992014, 0.1495818604630703, 0.12980832944239354, 0.08980152843166306, 0.030870652561222612, 0.03985912236518223, 0.010090150952197853, 0.09828574057104042, 0.1091936588123126, 0.05333323353945794, 0.14471537348153932 + 1, 11, 0, 4, 5, 9, 11, 14, 15, 16, 17, 18, 1, 0.015841838952164376, 0.08819872898398709, 0.026329567428217498, 0.10159761604493783, 0.13965710368920628, 0.09886378750801796, 0.09988364695394308, 0.06486133329660909, 0.11636844066876044, 0.14878065437150279, 0.09961728210265354 + 2, 11, 1, 3, 4, 9, 12, 13, 16, 17, 18, 19, 2, 0.019066476577961844, 0.16021303740419734, 0.0835066624134882, 0.03417102126235183, 0.13809764686965612, 0.09734435887319065, 0.13049024946754717, 0.04797183997342561, 0.014601611108271993, 0.15774187567607342, 0.11679522037383556 + 3, 11, 0, 6, 7, 10, 12, 13, 14, 16, 17, 18, 3, 0.05669716081301938, 0.16549888872125246, 0.0620271993298332, 0.13408054750961962, 0.06098496268106427, 0.15594180161078586, 0.004403033068789022, 0.03994268023091359, 0.12339114842498705, 0.13714721167341137, 0.05988536593632413 + 4, 11, 1, 2, 3, 5, 9, 11, 12, 13, 15, 16, 4, 0.07890002261247607, 0.24940814297284525, 0.1867137876581978, 0.004088414735373788, 0.02846398646079712, 0.0020054164702569526, 0.05267135812398615, 0.10883993688432445, 0.02595994896812456, 0.08525534217728745, 0.17769364293633047 + 5, 11, 4, 6, 9, 10, 12, 13, 14, 16, 17, 18, 5, 0.14815307980384254, 0.11157685211563942, 0.04409842723365831, 0.08911688302175544, 0.029455656889106386, 0.09310115798220649, 0.03775240801311088, 0.09913567593784103, 0.18262434178298162, 0.09718905164735743, 0.06779646557250049 + 6, 11, 1, 3, 5, 8, 9, 10, 11, 17, 18, 19, 6, 0.018311553335762537, 0.10338979611643634, 0.029204706038931244, 0.10600036403028636, 0.007910850832930824, 0.13881984481038007, 0.1441864516269701, 0.11689196220228848, 0.14196709616571854, 0.05210885890787683, 0.1412085159324189 + 7, 11, 0, 1, 4, 5, 6, 8, 9, 11, 12, 13, 7, 0.15026214641742255, 0.11287030541134854, 0.04116790231057979, 0.061640379360852614, 0.028947702420815232, 0.13315366752877497, 0.0645474444582939, 0.08574817789583591, 0.137494231002007, 0.12452810789963792, 0.05963993529443171 + 8, 11, 1, 10, 11, 12, 13, 15, 16, 17, 18, 19, 8, 0.04198410486944965, 0.03145696159134063, 0.09824068795571866, 0.17716906008586455, 0.004770435801130384, 0.09994906702214228, 0.20200725725973734, 0.12084202729119799, 0.12273243529546737, 0.056285359435037534, 0.04456260339291386 + 9, 11, 0, 1, 2, 3, 4, 7, 11, 12, 16, 17, 9, 0.10986437989353265, 0.026339939030869064, 0.013211025534905574, 0.01713650945056324, 0.20138949318312757, 0.07112248022531857, 0.004565786486809113, 0.19433316712237375, 0.09586124098011793, 0.05997833174107093, 0.2061976463513117 + 10, 11, 2, 3, 5, 7, 9, 11, 12, 14, 16, 17, 10, 0.17045120945316167, 0.06639171186284686, 0.0744544506668569, 0.11713875371668457, 0.11275393635862876, 0.15294552631850702, 0.09514990410712647, 0.015912262107559128, 0.0720113665838594, 0.008746959837106077, 0.11404391898766343 + 11, 11, 3, 5, 6, 7, 8, 9, 13, 16, 17, 18, 11, 0.17249566821089793, 0.11261178751757055, 0.09470368929667376, 0.16230127006918904, 0.14304821520531036, 0.010181190473082984, 0.08098224176775386, 0.0982396642886596, 0.006883798891408641, 0.11528588101649849, 0.003266593262954728 + 12, 11, 0, 3, 6, 10, 11, 13, 14, 16, 17, 19, 12, 0.09846151097469068, 0.137033159740162, 0.11994058243845807, 0.09152628960814566, 0.035340492848528415, 0.11226503207097413, 0.04992673497955701, 0.14470293450808364, 0.04041416247438572, 0.12546295595074927, 0.04492614440626538 + 13, 11, 0, 1, 2, 6, 9, 10, 11, 12, 16, 17, 13, 0.10908716752040748, 0.13017009811478114, 0.04438374328024749, 0.021709447117782776, 0.04653786517888591, 0.17570050338321075, 0.019830916272548932, 0.10714758859201455, 0.16644657892450984, 0.10819325012141227, 0.07079284149419883 + 14, 11, 3, 4, 7, 9, 10, 12, 15, 17, 18, 19, 14, 0.09351767299955005, 0.11218444458515739, 0.10555321513892782, 0.044120549373718926, 0.0442304280661078, 0.06225489072368324, 0.14000299054275595, 0.14080865308357154, 0.12707181176545942, 0.05467688741896947, 0.07557845630209825 + 15, 11, 2, 3, 5, 9, 11, 12, 13, 16, 18, 19, 15, 0.0878613334951077, 0.05661226552698981, 0.0840854082539587, 0.12964228713103407, 0.02431020329600412, 0.17696520707043803, 0.093937595048088, 0.08416406108601913, 0.17086305031242424, 0.07012745476481837, 0.021431134015117786 + 16, 11, 1, 2, 4, 5, 6, 8, 12, 15, 17, 18, 16, 0.12173620775606375, 0.06023394851116395, 0.13220478665950183, 0.12621763224353758, 0.046816907246084914, 0.08727430951169961, 0.09334670039987607, 0.07050940684196422, 0.09498068403737615, 0.14083811517460568, 0.0258413016181261 + 17, 11, 3, 4, 5, 6, 7, 8, 11, 14, 16, 19, 17, 0.07666429277195881, 0.1698038896012394, 0.08315859847767687, 0.057922737943923476, 0.052803483946002615, 0.1174734101072552, 0.01904582081986433, 0.12173184459552339, 0.13648612242806277, 0.09322590279477104, 0.07168389651372219 + 18, 11, 1, 2, 3, 6, 7, 8, 9, 13, 14, 17, 18, 0.0844522221544435, 0.11471123720523235, 0.014479364520945674, 0.10803167663942559, 0.07376129642362801, 0.08689861509215936, 0.025535055343767946, 0.16668421170571796, 0.06944382854973007, 0.17037412720018058, 0.08562836516476886 + 19, 11, 2, 5, 6, 8, 11, 13, 14, 15, 16, 18, 19, 0.04741162132024682, 0.07894934508176622, 0.11551727983802626, 0.11193678539277013, 0.0784120706363725, 0.09908599185226225, 0.05424245339017181, 0.0824194556671629, 0.12156502276223334, 0.1239593976984942, 0.0865005763604935 \ No newline at end of file diff --git a/visualisations/ouput_20_agents/opinions_0.txt b/visualisations/ouput_20_agents/opinions_0.txt new file mode 100644 index 0000000..b51cf16 --- /dev/null +++ b/visualisations/ouput_20_agents/opinions_0.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0 + 1, 0.05 + 2, 0.1 + 3, 0.15 + 4, 0.2 + 5, 0.25 + 6, 0.3 + 7, 0.35 + 8, 0.4 + 9, 0.45 + 10, 0.5 + 11, 0.55 + 12, 0.6 + 13, 0.65 + 14, 0.7 + 15, 0.75 + 16, 0.8 + 17, 0.85 + 18, 0.9 + 19, 0.95 diff --git a/visualisations/ouput_20_agents/opinions_10.txt b/visualisations/ouput_20_agents/opinions_10.txt new file mode 100644 index 0000000..929ae23 --- /dev/null +++ b/visualisations/ouput_20_agents/opinions_10.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.49542924206851224 + 1, 0.4954308573963771 + 2, 0.4954336372502829 + 3, 0.4954314710083162 + 4, 0.495431404294156 + 5, 0.4954326341025732 + 6, 0.49543466087996046 + 7, 0.49542943585083526 + 8, 0.4954349475485392 + 9, 0.4954279453934126 + 10, 0.49543013673449865 + 11, 0.4954324848054787 + 12, 0.49543254864430775 + 13, 0.4954302205603191 + 14, 0.4954309746148645 + 15, 0.4954316392753547 + 16, 0.49543386456484834 + 17, 0.4954343710370527 + 18, 0.49543275043176566 + 19, 0.4954352142957563 diff --git a/visualisations/ouput_20_agents_10_connections_each/degrootmodel.ipynb b/visualisations/ouput_20_agents_10_connections_each/degrootmodel.ipynb new file mode 100644 index 0000000..cc65413 --- /dev/null +++ b/visualisations/ouput_20_agents_10_connections_each/degrootmodel.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Import the package" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pyseldonlib" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialize some other settings" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "other_settings = pyseldonlib.Other_Settings(n_output_agents=10,\n", + " n_output_network= None, \n", + " print_progress= True, \n", + " output_initial=True, \n", + " start_output=1, \n", + " number_of_agents = 20, \n", + " connections_per_agent = 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialize the model with parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random seed: 120\n", + "[Model]\n", + " type DeGroot\n", + " max_iterations optional(1000)\n", + " convergence_tol 1e-06\n", + "[Network]\n", + " n_agents 20\n", + " n_connections 10\n", + "[Output]\n", + " n_output_agents optional(10)\n", + " n_output_network none\n", + " print_progress true\n", + " output_initial true\n", + " start_output 1\n", + " start_numbering_from 0\n", + "Output directory path set to: /home/parrot_user/Desktop/pyseldon/examples/ouput_20_agents\n", + "\n", + "-----------------------------------------------------------------\n", + "Starting simulation\n", + "-----------------------------------------------------------------\n", + "Iteration 1 iter_time = 00h 00m 00.000s \n", + "Iteration 2 iter_time = 00h 00m 00.000s \n", + "Iteration 3 iter_time = 00h 00m 00.000s \n", + "Iteration 4 iter_time = 00h 00m 00.000s \n", + "Iteration 5 iter_time = 00h 00m 00.000s \n", + "Iteration 6 iter_time = 00h 00m 00.000s \n", + "Iteration 7 iter_time = 00h 00m 00.000s \n", + "Iteration 8 iter_time = 00h 00m 00.000s \n", + "Iteration 9 iter_time = 00h 00m 00.000s \n", + "Iteration 10 iter_time = 00h 00m 00.000s \n", + "Iteration 11 iter_time = 00h 00m 00.000s \n", + "Iteration 12 iter_time = 00h 00m 00.000s \n", + "-----------------------------------------------------------------\n", + "Finished after 12 iterations, total time = 00h 00m 00.000s\n", + "=================================================================\n" + ] + } + ], + "source": [ + "model = pyseldonlib.DeGroot_Model(max_iterations=1000,\n", + " convergence_tol=1e-6,\n", + " rng_seed=120, \n", + " other_settings=other_settings)\n", + "\n", + "model.run('./ouput_20_agents')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/visualisations/ouput_20_agents_10_connections_each/final.png b/visualisations/ouput_20_agents_10_connections_each/final.png new file mode 100644 index 0000000..3e82d9e Binary files /dev/null and b/visualisations/ouput_20_agents_10_connections_each/final.png differ diff --git a/visualisations/ouput_20_agents_10_connections_each/initial.png b/visualisations/ouput_20_agents_10_connections_each/initial.png new file mode 100644 index 0000000..2830ae9 Binary files /dev/null and b/visualisations/ouput_20_agents_10_connections_each/initial.png differ diff --git a/visualisations/ouput_20_agents_10_connections_each/network.csv b/visualisations/ouput_20_agents_10_connections_each/network.csv new file mode 100644 index 0000000..cb34ba6 --- /dev/null +++ b/visualisations/ouput_20_agents_10_connections_each/network.csv @@ -0,0 +1,221 @@ +Source,Target,Weight +0,3,0.14446034937992014 +0,4,0.1495818604630703 +0,5,0.12980832944239354 +0,9,0.08980152843166306 +0,11,0.030870652561222612 +0,12,0.03985912236518223 +0,13,0.010090150952197853 +0,14,0.09828574057104042 +0,17,0.1091936588123126 +0,18,0.05333323353945794 +0,0,0.14471537348153932 +1,0,0.015841838952164376 +1,4,0.08819872898398709 +1,5,0.026329567428217498 +1,9,0.10159761604493783 +1,11,0.13965710368920628 +1,14,0.09886378750801796 +1,15,0.09988364695394308 +1,16,0.06486133329660909 +1,17,0.11636844066876044 +1,18,0.14878065437150279 +1,1,0.09961728210265354 +2,1,0.019066476577961844 +2,3,0.16021303740419734 +2,4,0.0835066624134882 +2,9,0.03417102126235183 +2,12,0.13809764686965612 +2,13,0.09734435887319065 +2,16,0.13049024946754717 +2,17,0.04797183997342561 +2,18,0.014601611108271993 +2,19,0.15774187567607342 +2,2,0.11679522037383556 +3,0,0.05669716081301938 +3,6,0.16549888872125246 +3,7,0.0620271993298332 +3,10,0.13408054750961962 +3,12,0.06098496268106427 +3,13,0.15594180161078586 +3,14,0.004403033068789022 +3,16,0.03994268023091359 +3,17,0.12339114842498705 +3,18,0.13714721167341137 +3,3,0.05988536593632413 +4,1,0.07890002261247607 +4,2,0.24940814297284525 +4,3,0.1867137876581978 +4,5,0.004088414735373788 +4,9,0.02846398646079712 +4,11,0.0020054164702569526 +4,12,0.05267135812398615 +4,13,0.10883993688432445 +4,15,0.02595994896812456 +4,16,0.08525534217728745 +4,4,0.17769364293633047 +5,4,0.14815307980384254 +5,6,0.11157685211563942 +5,9,0.04409842723365831 +5,10,0.08911688302175544 +5,12,0.029455656889106386 +5,13,0.09310115798220649 +5,14,0.03775240801311088 +5,16,0.09913567593784103 +5,17,0.18262434178298162 +5,18,0.09718905164735743 +5,5,0.06779646557250049 +6,1,0.018311553335762537 +6,3,0.10338979611643634 +6,5,0.029204706038931244 +6,8,0.10600036403028636 +6,9,0.007910850832930824 +6,10,0.13881984481038007 +6,11,0.1441864516269701 +6,17,0.11689196220228848 +6,18,0.14196709616571854 +6,19,0.05210885890787683 +6,6,0.1412085159324189 +7,0,0.15026214641742255 +7,1,0.11287030541134854 +7,4,0.04116790231057979 +7,5,0.061640379360852614 +7,6,0.028947702420815232 +7,8,0.13315366752877497 +7,9,0.0645474444582939 +7,11,0.08574817789583591 +7,12,0.137494231002007 +7,13,0.12452810789963792 +7,7,0.05963993529443171 +8,1,0.04198410486944965 +8,10,0.03145696159134063 +8,11,0.09824068795571866 +8,12,0.17716906008586455 +8,13,0.004770435801130384 +8,15,0.09994906702214228 +8,16,0.20200725725973734 +8,17,0.12084202729119799 +8,18,0.12273243529546737 +8,19,0.056285359435037534 +8,8,0.04456260339291386 +9,0,0.10986437989353265 +9,1,0.026339939030869064 +9,2,0.013211025534905574 +9,3,0.01713650945056324 +9,4,0.20138949318312757 +9,7,0.07112248022531857 +9,11,0.004565786486809113 +9,12,0.19433316712237375 +9,16,0.09586124098011793 +9,17,0.05997833174107093 +9,9,0.2061976463513117 +10,2,0.17045120945316167 +10,3,0.06639171186284686 +10,5,0.0744544506668569 +10,7,0.11713875371668457 +10,9,0.11275393635862876 +10,11,0.15294552631850702 +10,12,0.09514990410712647 +10,14,0.015912262107559128 +10,16,0.0720113665838594 +10,17,0.008746959837106077 +10,10,0.11404391898766343 +11,3,0.17249566821089793 +11,5,0.11261178751757055 +11,6,0.09470368929667376 +11,7,0.16230127006918904 +11,8,0.14304821520531036 +11,9,0.010181190473082984 +11,13,0.08098224176775386 +11,16,0.0982396642886596 +11,17,0.006883798891408641 +11,18,0.11528588101649849 +11,11,0.003266593262954728 +12,0,0.09846151097469068 +12,3,0.137033159740162 +12,6,0.11994058243845807 +12,10,0.09152628960814566 +12,11,0.035340492848528415 +12,13,0.11226503207097413 +12,14,0.04992673497955701 +12,16,0.14470293450808364 +12,17,0.04041416247438572 +12,19,0.12546295595074927 +12,12,0.04492614440626538 +13,0,0.10908716752040748 +13,1,0.13017009811478114 +13,2,0.04438374328024749 +13,6,0.021709447117782776 +13,9,0.04653786517888591 +13,10,0.17570050338321075 +13,11,0.019830916272548932 +13,12,0.10714758859201455 +13,16,0.16644657892450984 +13,17,0.10819325012141227 +13,13,0.07079284149419883 +14,3,0.09351767299955005 +14,4,0.11218444458515739 +14,7,0.10555321513892782 +14,9,0.044120549373718926 +14,10,0.0442304280661078 +14,12,0.06225489072368324 +14,15,0.14000299054275595 +14,17,0.14080865308357154 +14,18,0.12707181176545942 +14,19,0.05467688741896947 +14,14,0.07557845630209825 +15,2,0.0878613334951077 +15,3,0.05661226552698981 +15,5,0.0840854082539587 +15,9,0.12964228713103407 +15,11,0.02431020329600412 +15,12,0.17696520707043803 +15,13,0.093937595048088 +15,16,0.08416406108601913 +15,18,0.17086305031242424 +15,19,0.07012745476481837 +15,15,0.021431134015117786 +16,1,0.12173620775606375 +16,2,0.06023394851116395 +16,4,0.13220478665950183 +16,5,0.12621763224353758 +16,6,0.046816907246084914 +16,8,0.08727430951169961 +16,12,0.09334670039987607 +16,15,0.07050940684196422 +16,17,0.09498068403737615 +16,18,0.14083811517460568 +16,16,0.0258413016181261 +17,3,0.07666429277195881 +17,4,0.1698038896012394 +17,5,0.08315859847767687 +17,6,0.057922737943923476 +17,7,0.052803483946002615 +17,8,0.1174734101072552 +17,11,0.01904582081986433 +17,14,0.12173184459552339 +17,16,0.13648612242806277 +17,19,0.09322590279477104 +17,17,0.07168389651372219 +18,1,0.0844522221544435 +18,2,0.11471123720523235 +18,3,0.014479364520945674 +18,6,0.10803167663942559 +18,7,0.07376129642362801 +18,8,0.08689861509215936 +18,9,0.025535055343767946 +18,13,0.16668421170571796 +18,14,0.06944382854973007 +18,17,0.17037412720018058 +18,18,0.08562836516476886 +19,2,0.04741162132024682 +19,5,0.07894934508176622 +19,6,0.11551727983802626 +19,8,0.11193678539277013 +19,11,0.0784120706363725 +19,13,0.09908599185226225 +19,14,0.05424245339017181 +19,15,0.0824194556671629 +19,16,0.12156502276223334 +19,18,0.1239593976984942 +19,19,0.0865005763604935 diff --git a/visualisations/ouput_20_agents_10_connections_each/network_0.txt b/visualisations/ouput_20_agents_10_connections_each/network_0.txt new file mode 100644 index 0000000..531461b --- /dev/null +++ b/visualisations/ouput_20_agents_10_connections_each/network_0.txt @@ -0,0 +1,21 @@ +idx_agent, n_neighbours_in, indices_neighbours_in[...], weights_in[...] + 0, 11, 3, 4, 5, 9, 11, 12, 13, 14, 17, 18, 0, 0.14446034937992014, 0.1495818604630703, 0.12980832944239354, 0.08980152843166306, 0.030870652561222612, 0.03985912236518223, 0.010090150952197853, 0.09828574057104042, 0.1091936588123126, 0.05333323353945794, 0.14471537348153932 + 1, 11, 0, 4, 5, 9, 11, 14, 15, 16, 17, 18, 1, 0.015841838952164376, 0.08819872898398709, 0.026329567428217498, 0.10159761604493783, 0.13965710368920628, 0.09886378750801796, 0.09988364695394308, 0.06486133329660909, 0.11636844066876044, 0.14878065437150279, 0.09961728210265354 + 2, 11, 1, 3, 4, 9, 12, 13, 16, 17, 18, 19, 2, 0.019066476577961844, 0.16021303740419734, 0.0835066624134882, 0.03417102126235183, 0.13809764686965612, 0.09734435887319065, 0.13049024946754717, 0.04797183997342561, 0.014601611108271993, 0.15774187567607342, 0.11679522037383556 + 3, 11, 0, 6, 7, 10, 12, 13, 14, 16, 17, 18, 3, 0.05669716081301938, 0.16549888872125246, 0.0620271993298332, 0.13408054750961962, 0.06098496268106427, 0.15594180161078586, 0.004403033068789022, 0.03994268023091359, 0.12339114842498705, 0.13714721167341137, 0.05988536593632413 + 4, 11, 1, 2, 3, 5, 9, 11, 12, 13, 15, 16, 4, 0.07890002261247607, 0.24940814297284525, 0.1867137876581978, 0.004088414735373788, 0.02846398646079712, 0.0020054164702569526, 0.05267135812398615, 0.10883993688432445, 0.02595994896812456, 0.08525534217728745, 0.17769364293633047 + 5, 11, 4, 6, 9, 10, 12, 13, 14, 16, 17, 18, 5, 0.14815307980384254, 0.11157685211563942, 0.04409842723365831, 0.08911688302175544, 0.029455656889106386, 0.09310115798220649, 0.03775240801311088, 0.09913567593784103, 0.18262434178298162, 0.09718905164735743, 0.06779646557250049 + 6, 11, 1, 3, 5, 8, 9, 10, 11, 17, 18, 19, 6, 0.018311553335762537, 0.10338979611643634, 0.029204706038931244, 0.10600036403028636, 0.007910850832930824, 0.13881984481038007, 0.1441864516269701, 0.11689196220228848, 0.14196709616571854, 0.05210885890787683, 0.1412085159324189 + 7, 11, 0, 1, 4, 5, 6, 8, 9, 11, 12, 13, 7, 0.15026214641742255, 0.11287030541134854, 0.04116790231057979, 0.061640379360852614, 0.028947702420815232, 0.13315366752877497, 0.0645474444582939, 0.08574817789583591, 0.137494231002007, 0.12452810789963792, 0.05963993529443171 + 8, 11, 1, 10, 11, 12, 13, 15, 16, 17, 18, 19, 8, 0.04198410486944965, 0.03145696159134063, 0.09824068795571866, 0.17716906008586455, 0.004770435801130384, 0.09994906702214228, 0.20200725725973734, 0.12084202729119799, 0.12273243529546737, 0.056285359435037534, 0.04456260339291386 + 9, 11, 0, 1, 2, 3, 4, 7, 11, 12, 16, 17, 9, 0.10986437989353265, 0.026339939030869064, 0.013211025534905574, 0.01713650945056324, 0.20138949318312757, 0.07112248022531857, 0.004565786486809113, 0.19433316712237375, 0.09586124098011793, 0.05997833174107093, 0.2061976463513117 + 10, 11, 2, 3, 5, 7, 9, 11, 12, 14, 16, 17, 10, 0.17045120945316167, 0.06639171186284686, 0.0744544506668569, 0.11713875371668457, 0.11275393635862876, 0.15294552631850702, 0.09514990410712647, 0.015912262107559128, 0.0720113665838594, 0.008746959837106077, 0.11404391898766343 + 11, 11, 3, 5, 6, 7, 8, 9, 13, 16, 17, 18, 11, 0.17249566821089793, 0.11261178751757055, 0.09470368929667376, 0.16230127006918904, 0.14304821520531036, 0.010181190473082984, 0.08098224176775386, 0.0982396642886596, 0.006883798891408641, 0.11528588101649849, 0.003266593262954728 + 12, 11, 0, 3, 6, 10, 11, 13, 14, 16, 17, 19, 12, 0.09846151097469068, 0.137033159740162, 0.11994058243845807, 0.09152628960814566, 0.035340492848528415, 0.11226503207097413, 0.04992673497955701, 0.14470293450808364, 0.04041416247438572, 0.12546295595074927, 0.04492614440626538 + 13, 11, 0, 1, 2, 6, 9, 10, 11, 12, 16, 17, 13, 0.10908716752040748, 0.13017009811478114, 0.04438374328024749, 0.021709447117782776, 0.04653786517888591, 0.17570050338321075, 0.019830916272548932, 0.10714758859201455, 0.16644657892450984, 0.10819325012141227, 0.07079284149419883 + 14, 11, 3, 4, 7, 9, 10, 12, 15, 17, 18, 19, 14, 0.09351767299955005, 0.11218444458515739, 0.10555321513892782, 0.044120549373718926, 0.0442304280661078, 0.06225489072368324, 0.14000299054275595, 0.14080865308357154, 0.12707181176545942, 0.05467688741896947, 0.07557845630209825 + 15, 11, 2, 3, 5, 9, 11, 12, 13, 16, 18, 19, 15, 0.0878613334951077, 0.05661226552698981, 0.0840854082539587, 0.12964228713103407, 0.02431020329600412, 0.17696520707043803, 0.093937595048088, 0.08416406108601913, 0.17086305031242424, 0.07012745476481837, 0.021431134015117786 + 16, 11, 1, 2, 4, 5, 6, 8, 12, 15, 17, 18, 16, 0.12173620775606375, 0.06023394851116395, 0.13220478665950183, 0.12621763224353758, 0.046816907246084914, 0.08727430951169961, 0.09334670039987607, 0.07050940684196422, 0.09498068403737615, 0.14083811517460568, 0.0258413016181261 + 17, 11, 3, 4, 5, 6, 7, 8, 11, 14, 16, 19, 17, 0.07666429277195881, 0.1698038896012394, 0.08315859847767687, 0.057922737943923476, 0.052803483946002615, 0.1174734101072552, 0.01904582081986433, 0.12173184459552339, 0.13648612242806277, 0.09322590279477104, 0.07168389651372219 + 18, 11, 1, 2, 3, 6, 7, 8, 9, 13, 14, 17, 18, 0.0844522221544435, 0.11471123720523235, 0.014479364520945674, 0.10803167663942559, 0.07376129642362801, 0.08689861509215936, 0.025535055343767946, 0.16668421170571796, 0.06944382854973007, 0.17037412720018058, 0.08562836516476886 + 19, 11, 2, 5, 6, 8, 11, 13, 14, 15, 16, 18, 19, 0.04741162132024682, 0.07894934508176622, 0.11551727983802626, 0.11193678539277013, 0.0784120706363725, 0.09908599185226225, 0.05424245339017181, 0.0824194556671629, 0.12156502276223334, 0.1239593976984942, 0.0865005763604935 \ No newline at end of file diff --git a/visualisations/ouput_20_agents_10_connections_each/opinions_0.csv b/visualisations/ouput_20_agents_10_connections_each/opinions_0.csv new file mode 100644 index 0000000..8716bcb --- /dev/null +++ b/visualisations/ouput_20_agents_10_connections_each/opinions_0.csv @@ -0,0 +1,21 @@ +idx_agent, opinion + 0, 0 + 1, 0.05 + 2, 0.1 + 3, 0.15 + 4, 0.2 + 5, 0.25 + 6, 0.3 + 7, 0.35 + 8, 0.4 + 9, 0.45 + 10, 0.5 + 11, 0.55 + 12, 0.6 + 13, 0.65 + 14, 0.7 + 15, 0.75 + 16, 0.8 + 17, 0.85 + 18, 0.9 + 19, 0.95 diff --git a/visualisations/ouput_20_agents_10_connections_each/opinions_10.csv b/visualisations/ouput_20_agents_10_connections_each/opinions_10.csv new file mode 100644 index 0000000..f591c29 --- /dev/null +++ b/visualisations/ouput_20_agents_10_connections_each/opinions_10.csv @@ -0,0 +1,21 @@ +idx_agent, opinion + 0, 0.49542924206851224 + 1, 0.4954308573963771 + 2, 0.4954336372502829 + 3, 0.4954314710083162 + 4, 0.495431404294156 + 5, 0.4954326341025732 + 6, 0.49543466087996046 + 7, 0.49542943585083526 + 8, 0.4954349475485392 + 9, 0.4954279453934126 + 10, 0.49543013673449865 + 11, 0.4954324848054787 + 12, 0.49543254864430775 + 13, 0.4954302205603191 + 14, 0.4954309746148645 + 15, 0.4954316392753547 + 16, 0.49543386456484834 + 17, 0.4954343710370527 + 18, 0.49543275043176566 + 19, 0.4954352142957563 diff --git a/visualisations/output_20_agents_4_connections_each/degrootmodel.ipynb b/visualisations/output_20_agents_4_connections_each/degrootmodel.ipynb new file mode 100644 index 0000000..78bbcf5 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/degrootmodel.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Import the package" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import pyseldonlib" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialize some other settings" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "other_settings = pyseldonlib.Other_Settings(n_output_agents=1,\n", + " n_output_network= None, \n", + " print_progress= True, \n", + " output_initial=True, \n", + " start_output=1, \n", + " number_of_agents = 20, \n", + " connections_per_agent = 4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialize the model with parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Output directory path set to: /home/parrot_user/Desktop/pyseldon/examples/output_20_agents_4_connections_each/ouput_20_agents\n", + "\n", + "WARNING: You have 3 strongly connected components in your network!\n", + "Random seed: 120\n", + "[Model]\n", + " type DeGroot\n", + " max_iterations optional(1000)\n", + " convergence_tol 1e-06\n", + "[Network]\n", + " n_agents 20\n", + " n_connections 4\n", + "[Output]\n", + " n_output_agents optional(1)\n", + " n_output_network none\n", + " print_progress true\n", + " output_initial true\n", + " start_output 1\n", + " start_numbering_from 0\n", + "-----------------------------------------------------------------\n", + "Starting simulation\n", + "-----------------------------------------------------------------\n", + "Iteration 1 iter_time = 00h 00m 00.000s \n", + "Iteration 2 iter_time = 00h 00m 00.000s \n", + "Iteration 3 iter_time = 00h 00m 00.000s \n", + "Iteration 4 iter_time = 00h 00m 00.000s \n", + "Iteration 5 iter_time = 00h 00m 00.000s \n", + "Iteration 6 iter_time = 00h 00m 00.000s \n", + "Iteration 7 iter_time = 00h 00m 00.000s \n", + "Iteration 8 iter_time = 00h 00m 00.000s \n", + "Iteration 9 iter_time = 00h 00m 00.000s \n", + "Iteration 10 iter_time = 00h 00m 00.000s \n", + "Iteration 11 iter_time = 00h 00m 00.000s \n", + "Iteration 12 iter_time = 00h 00m 00.000s \n", + "Iteration 13 iter_time = 00h 00m 00.000s \n", + "Iteration 14 iter_time = 00h 00m 00.000s \n", + "Iteration 15 iter_time = 00h 00m 00.000s \n", + "Iteration 16 iter_time = 00h 00m 00.000s \n", + "Iteration 17 iter_time = 00h 00m 00.000s \n", + "Iteration 18 iter_time = 00h 00m 00.000s \n", + "Iteration 19 iter_time = 00h 00m 00.000s \n", + "Iteration 20 iter_time = 00h 00m 00.000s \n", + "-----------------------------------------------------------------\n", + "Finished after 20 iterations, total time = 00h 00m 00.001s\n", + "=================================================================\n" + ] + } + ], + "source": [ + "model = pyseldonlib.DeGroot_Model(max_iterations=1000,\n", + " convergence_tol=1e-6,\n", + " rng_seed=120, \n", + " other_settings=other_settings)\n", + "\n", + "model.run('./ouput_20_agents')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/network_0.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/network_0.txt new file mode 100644 index 0000000..0dc1fb0 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/network_0.txt @@ -0,0 +1,21 @@ +# idx_agent, n_neighbours_in, indices_neighbours_in[...], weights_in[...] + 0, 5, 4, 12, 17, 18, 0, 0.26529719330369905, 0.27470269814752424, 0.23838918856509247, 0.16491787227128268, 0.05669304771240153 + 1, 5, 7, 10, 14, 17, 1, 0.1723125799006238, 0.15237787946061931, 0.1743929985945665, 0.13225509827452211, 0.3686614437696684 + 2, 5, 0, 3, 6, 15, 2, 0.2012377970716371, 0.2766234975374838, 0.1958228114277024, 0.19784288115187676, 0.12847301281130002 + 3, 5, 0, 9, 15, 16, 3, 0.24565177946925887, 0.21302879656325716, 0.2852274374490626, 0.2227758971707417, 0.03331608934767977 + 4, 5, 11, 13, 16, 18, 4, 0.16573998806372883, 0.16720019252946725, 0.11402494291160763, 0.037019262478262864, 0.5160156140169334 + 5, 5, 9, 10, 12, 15, 5, 0.17643082298220675, 0.11162708652136173, 0.32583922202739724, 0.12212102770195683, 0.2639818407670774 + 6, 5, 1, 5, 11, 14, 6, 0.1757027243251029, 0.25929518338866764, 0.2079925784253286, 0.24738151897797167, 0.10962799488292896 + 7, 5, 6, 8, 13, 18, 7, 0.6815790605011212, 0.014924328348885378, 0.10390479135682051, 0.00732056207982558, 0.1922712577133474 + 8, 5, 1, 3, 6, 9, 8, 0.25974093740962795, 0.3004925649923733, 0.0074481393486750605, 0.18948662614512737, 0.24283173210419629 + 9, 5, 7, 8, 13, 16, 9, 0.2556702646810396, 0.08450627241252644, 0.26710087804138216, 0.10830908602023646, 0.28441349884481537 + 10, 5, 1, 4, 12, 19, 10, 0.01821396668148236, 0.4094836616391018, 0.26280006476388584, 0.046568653725991, 0.26293365318953904 + 11, 5, 1, 3, 8, 10, 11, 0.23371358824240332, 0.24274867207717227, 0.19679635833269182, 0.2390122212178374, 0.08772916012989508 + 12, 5, 5, 8, 9, 10, 12, 0.015446870694283599, 0.35952523120461466, 0.08203409405627729, 0.03740776076200192, 0.5055860432828225 + 13, 5, 1, 4, 17, 19, 13, 0.15588508928835165, 0.11836247850551028, 0.2466514566577405, 0.2175375762987044, 0.26156339924969313 + 14, 5, 1, 9, 11, 17, 14, 0.1687588505180858, 0.3043428089686921, 0.008194703019913296, 0.17169352141156288, 0.347010116081746 + 15, 5, 3, 5, 7, 14, 15, 0.1634507034645481, 0.25565359955295164, 0.015956655014272795, 0.43164542626894037, 0.13329361569928716 + 16, 5, 4, 8, 11, 12, 16, 0.3550138796584278, 0.12537629067252432, 0.0080486700113009, 0.3425748310697055, 0.16898632858804147 + 17, 5, 4, 9, 10, 19, 17, 0.29425815520383314, 0.31524094289977467, 0.015065030766633335, 0.289437982973859, 0.0859978881558998 + 18, 5, 10, 15, 16, 17, 18, 0.16348853097150426, 0.019858359618407057, 0.2589156915917409, 0.335069013810346, 0.2226684040080019 + 19, 5, 5, 7, 11, 14, 19, 0.20950594323925983, 0.33642409967283765, 0.24107364694854722, 0.020756996192013977, 0.19223931394734134 \ No newline at end of file diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_0.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_0.txt new file mode 100644 index 0000000..b51cf16 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_0.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0 + 1, 0.05 + 2, 0.1 + 3, 0.15 + 4, 0.2 + 5, 0.25 + 6, 0.3 + 7, 0.35 + 8, 0.4 + 9, 0.45 + 10, 0.5 + 11, 0.55 + 12, 0.6 + 13, 0.65 + 14, 0.7 + 15, 0.75 + 16, 0.8 + 17, 0.85 + 18, 0.9 + 19, 0.95 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_1.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_1.txt new file mode 100644 index 0000000..42d22bd --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_1.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.5689379528737374 + 1, 0.3894233474335517 + 2, 0.26146983020397085 + 3, 0.493001667679008 + 4, 0.42757753194231396 + 5, 0.48829717778734927 + 6, 0.3940603119468116 + 7, 0.3518650099433384 + 8, 0.24269704803092576 + 9, 0.5115360156266289 + 10, 0.4161945171546869 + 11, 0.29457367223713377 + 12, 0.506642658831436 + 13, 0.6177973953206688 + 14, 0.5387458676798187 + 15, 0.4961378448256392 + 16, 0.4663140222191673 + 17, 0.5563068594866628 + 18, 0.7889808138189459 + 19, 0.4998726721013932 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_10.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_10.txt new file mode 100644 index 0000000..5040297 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_10.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4520967127772832 + 1, 0.4520008478026326 + 2, 0.45201787288108547 + 3, 0.4521062843896498 + 4, 0.45185081497623714 + 5, 0.4523396204399665 + 6, 0.45213583213294223 + 7, 0.45202165006760986 + 8, 0.4520220512083631 + 9, 0.4520443623979478 + 10, 0.4520196840452112 + 11, 0.45199243909634745 + 12, 0.45230756790072113 + 13, 0.4520248850206953 + 14, 0.4520835931182291 + 15, 0.452181014828135 + 16, 0.45210952166085877 + 17, 0.4519769437207235 + 18, 0.45208210369202684 + 19, 0.4520579421472229 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_11.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_11.txt new file mode 100644 index 0000000..aa4581b --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_11.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.45205843830468795 + 1, 0.4520185712882843 + 2, 0.4521135707340128 + 3, 0.45211277829613306 + 4, 0.45194145345378367 + 5, 0.4522220011982345 + 6, 0.45212220869058856 + 7, 0.4521002588408174 + 8, 0.45204693038880583 + 9, 0.4520385250106194 + 10, 0.4520276293619927 + 11, 0.4520343795505893 + 12, 0.45217305164195265 + 13, 0.4519959009906278 + 14, 0.4520386315355225 + 15, 0.4521647536169631 + 16, 0.4520736137744193 + 17, 0.4519851703419234 + 18, 0.45204572610306154 + 19, 0.4520894872711718 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_12.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_12.txt new file mode 100644 index 0000000..1fe98c1 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_12.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.452039324385639 + 1, 0.45203310825249576 + 2, 0.45211407447642066 + 3, 0.4520897113685933 + 4, 0.4519848883321907 + 5, 0.4521449923628787 + 6, 0.452090931780371 + 7, 0.45210318101653185 + 8, 0.452058319152552 + 9, 0.45204743434127603 + 10, 0.45203327437624585 + 11, 0.45205057271794025 + 12, 0.4521119882945356 + 13, 0.45201070220910333 + 14, 0.4520259999868276 + 15, 0.4521154246239298 + 16, 0.4520570986896941 + 17, 0.4520199587951713 + 18, 0.4520320613811758 + 19, 0.45210653291058644 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_13.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_13.txt new file mode 100644 index 0000000..aa5ce17 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_13.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.45203902927863643 + 1, 0.452042229270297 + 2, 0.4520880277739916 + 3, 0.45206839629944406 + 4, 0.45201007104006474 + 5, 0.452100944504859 + 6, 0.45207033236647404 + 7, 0.4520840330335726 + 8, 0.4520593843544891 + 9, 0.4520538424953091 + 10, 0.45203755563012404 + 11, 0.4520533818455773 + 12, 0.4520849625569341 + 13, 0.4520342695116999 + 14, 0.4520328870939771 + 15, 0.4520799857705293 + 16, 0.4520503672993442 + 17, 0.4520435588794753 + 18, 0.45203634249970504 + 19, 0.45209830058715894 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_14.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_14.txt new file mode 100644 index 0000000..0dd161b --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_14.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4520446014447073 + 1, 0.4520472670626684 + 2, 0.4520676806799907 + 3, 0.4520573710676114 + 4, 0.4520268626902937 + 5, 0.4520777912954914 + 6, 0.4520605433050143 + 7, 0.4520688072909122 + 8, 0.45205666793341504 + 9, 0.45205642528367274 + 10, 0.45204167361938 + 11, 0.4520518186889142 + 12, 0.45207168712583035 + 13, 0.4520488665293571 + 14, 0.4520428415194534 + 15, 0.45206318429166414 + 16, 0.452049067820541 + 17, 0.45205270051670077 + 18, 0.45204345674233304 + 19, 0.4520818680422824 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_15.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_15.txt new file mode 100644 index 0000000..6d05dc7 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_15.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.45204907786195503 + 1, 0.45205007321618706 + 2, 0.4520578971452342 + 3, 0.45205384103549195 + 4, 0.45203782418161176 + 5, 0.4520662171612736 + 6, 0.45205648920391994 + 7, 0.4520607360384772 + 8, 0.45205442031445975 + 9, 0.45205679567014434 + 10, 0.4520454700162959 + 11, 0.45205063226262215 + 12, 0.4520640069080602 + 13, 0.4520541374951204 + 14, 0.45204948878119083 + 15, 0.45205727730888723 + 16, 0.4520499085105846 + 17, 0.45205454779384535 + 18, 0.45204810707750603 + 19, 0.4520685658074892 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_16.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_16.txt new file mode 100644 index 0000000..1348e53 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_16.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4520513372143105 + 1, 0.45205169899310327 + 2, 0.45205460201959685 + 3, 0.4520534044233916 + 4, 0.4520444331621084 + 5, 0.45206042704534394 + 6, 0.4520549343343515 + 7, 0.45205696915566707 + 8, 0.4520535826335681 + 9, 0.4520561464298643 + 10, 0.4520483700507014 + 11, 0.4520507921660728 + 12, 0.45205930943629324 + 13, 0.45205481295129746 + 14, 0.4520526891790662 + 15, 0.4520556944604229 + 16, 0.45205101966068184 + 17, 0.4520542559458014 + 18, 0.45205048255775876 + 19, 0.4520607203423319 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_17.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_17.txt new file mode 100644 index 0000000..ecde276 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_17.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4520522504254194 + 1, 0.4520526107027568 + 2, 0.4520538949405723 + 3, 0.45205360264988326 + 4, 0.4520481975753896 + 5, 0.45205738381664995 + 6, 0.45205437315939867 + 7, 0.4520552601969909 + 8, 0.4520535356969611 + 9, 0.45205522867053943 + 10, 0.4520502686000873 + 11, 0.45205160846453335 + 12, 0.45205659907749596 + 13, 0.4520542466473824 + 14, 0.4520538277240804 + 15, 0.45205525317854944 + 16, 0.45205184073575416 + 17, 0.45205374384027364 + 18, 0.4520516440967056 + 19, 0.4520568367807897 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_18.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_18.txt new file mode 100644 index 0000000..e53a762 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_18.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4520526258216543 + 1, 0.45205307246251714 + 2, 0.4520538455113628 + 3, 0.45205369512682203 + 4, 0.45205031730094725 + 5, 0.45205569343704316 + 6, 0.45205413417325807 + 7, 0.4520544980891903 + 8, 0.45205364259034314 + 9, 0.45205446442066166 + 10, 0.45205143272949644 + 11, 0.45205238581541646 + 12, 0.45205516060758755 + 13, 0.45205371507809256 + 14, 0.4520540161204623 + 15, 0.4520549129248666 + 16, 0.4520523880898518 + 17, 0.45205342274575766 + 18, 0.4520522453611711 + 19, 0.4520550981190226 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_19.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_19.txt new file mode 100644 index 0000000..22110f0 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_19.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4520528369234821 + 1, 0.4520532791509698 + 2, 0.45205382617054196 + 3, 0.45205365250492757 + 4, 0.45205153574245766 + 5, 0.45205473205661717 + 6, 0.45205395908784207 + 7, 0.4520541394341254 + 8, 0.45205366967886085 + 9, 0.4520539785433361 + 10, 0.45205215622463185 + 11, 0.45205288365732527 + 12, 0.45205442650999894 + 13, 0.4520534414937575 + 14, 0.4520538780683159 + 15, 0.4520545196947579 + 16, 0.4520527599921559 + 17, 0.4520532922588779 + 18, 0.45205259693843897 + 19, 0.45205434465322797 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_2.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_2.txt new file mode 100644 index 0000000..8b24412 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_2.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.5476006289334227 + 1, 0.43514290482704765 + 2, 0.45978282158378975 + 3, 0.5105530607119741 + 4, 0.4551340806468483 + 5, 0.4912838027233292 + 6, 0.43278079989864415 + 7, 0.40982676812766466 + 8, 0.40809131539586696 + 9, 0.4714788633551408 + 10, 0.44803462304965724 + 11, 0.3837692001311244 + 12, 0.4084821314558032 + 13, 0.5188626032752623 + 14, 0.5062794378965284 + 15, 0.509710186046819 + 16, 0.436959058236584 + 17, 0.4858686078943995 + 18, 0.5607138209027913 + 19, 0.3989689049208901 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_20.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_20.txt new file mode 100644 index 0000000..a7f0fc6 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_20.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.45205299735672627 + 1, 0.45205336246002525 + 2, 0.4520537422937217 + 3, 0.45205357012746367 + 4, 0.45205225666753046 + 5, 0.4520541860881081 + 6, 0.4520537963238619 + 7, 0.4520539256918096 + 8, 0.45205362376338687 + 9, 0.45205371815076273 + 10, 0.45205262114357714 + 11, 0.45205314354717047 + 12, 0.45205403745501693 + 13, 0.4520533502796121 + 14, 0.4520536988459749 + 15, 0.452054149220237 + 16, 0.45205301132207426 + 17, 0.45205327922306326 + 18, 0.45205283826707215 + 19, 0.45205399488337694 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_3.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_3.txt new file mode 100644 index 0000000..56770c6 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_3.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4722996398934791 + 1, 0.45185947484045036 + 2, 0.4960894866300126 + 3, 0.49469455162230663 + 4, 0.4557975656792661 + 5, 0.4582320377699919 + 6, 0.4563535307182528 + 7, 0.43787977880874845 + 8, 0.45810169658021344 + 9, 0.4592770404503442 + 10, 0.4380276038262394 + 11, 0.4466992719840444 + 12, 0.41626809990378855 + 13, 0.46404948314252825 + 14, 0.47917487144284604 + 15, 0.5020625112912425 + 16, 0.4296085269407242 + 17, 0.4465664410734399 + 18, 0.4841588541772793 + 19, 0.42052546757186415 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_4.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_4.txt new file mode 100644 index 0000000..d12c425 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_4.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.44835095070483033 + 1, 0.4514065093971484 + 2, 0.48431671228617257 + 3, 0.46925019863671635 + 4, 0.453733047115472 + 5, 0.44784017196049947 + 6, 0.45968856445438155 + 7, 0.4538308463327281 + 8, 0.4676859082875683 + 9, 0.4517684270253989 + 10, 0.43902259614805406 + 11, 0.4597273928764626 + 12, 0.43629871873413084 + 13, 0.4473921985553799 + 14, 0.46264461060335216 + 15, 0.47874930799569093 + 16, 0.4380458303352459 + 17, 0.4456237914119915 + 18, 0.4502524725984998 + 19, 0.4417908265978274 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_5.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_5.txt new file mode 100644 index 0000000..ac793f6 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_5.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4461314947894861 + 1, 0.4511322709411836 + 2, 0.4669870655000862 + 3, 0.456149965901029 + 4, 0.4517487765108746 + 5, 0.4475629542540108 + 6, 0.45590050092337914 + 7, 0.45733491977968155 + 8, 0.46085182442632805 + 9, 0.4509856769510626 + 10, 0.44468492326232156 + 11, 0.456711845188635 + 12, 0.4491324223104394 + 13, 0.4471137992333065 + 14, 0.45449172318362335 + 15, 0.4619455057351207 + 16, 0.4469071643349551 + 17, 0.4487381939455507 + 18, 0.44427099813591975 + 19, 0.45186564856508793 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_6.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_6.txt new file mode 100644 index 0000000..4c11fd3 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_6.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4487606864982912 + 1, 0.45148786825917264 + 2, 0.45662390104547435 + 3, 0.45218274194905955 + 4, 0.45096749996873087 + 5, 0.4501133680401241 + 6, 0.4527210722412112 + 7, 0.455252078715753 + 8, 0.45500800381884654 + 9, 0.4519668189799326 + 10, 0.4491980872796358 + 11, 0.45321157893299346 + 12, 0.4533072587807256 + 13, 0.4497231915612158 + 14, 0.4518880954710972 + 15, 0.4540303089391186 + 16, 0.4512155656300707 + 17, 0.45117672258849334 + 18, 0.4468690205637554 + 19, 0.4540270028939369 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_7.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_7.txt new file mode 100644 index 0000000..0db0681 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_7.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.45085909095356663 + 1, 0.4518162233264972 + 2, 0.45253560946590093 + 3, 0.45160762335377747 + 4, 0.45100794797219734 + 5, 0.4518572395123404 + 6, 0.4517241884957686 + 7, 0.4528875084829614 + 8, 0.4526514130830598 + 9, 0.4523831189833597 + 10, 0.45126910549671306 + 11, 0.45195323183533503 + 12, 0.45360570724735655 + 13, 0.45144031400432283 + 14, 0.4517332199593487 + 15, 0.45182176148810094 + 16, 0.45233560739009054 + 17, 0.45215939903850794 + 18, 0.44996077389065586 + 19, 0.45337824364147544 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_8.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_8.txt new file mode 100644 index 0000000..c50b422 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_8.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4518149160601065 + 1, 0.45194836211232614 + 2, 0.451641403333472 + 3, 0.45181220330651406 + 4, 0.4513495316693515 + 5, 0.45244975592728987 + 6, 0.451824732285949 + 7, 0.4519192945911288 + 8, 0.45206308481801005 + 9, 0.45227777893953625 + 10, 0.45188440975855254 + 11, 0.45181120005637876 + 12, 0.4530479051023597 + 13, 0.45204667263483644 + 14, 0.4520199947255287 + 15, 0.4517746177552177 + 16, 0.45233589104502087 + 17, 0.45223046848213144 + 18, 0.45156320008185996 + 19, 0.4525168105594926 diff --git a/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_9.txt b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_9.txt new file mode 100644 index 0000000..2caf436 --- /dev/null +++ b/visualisations/output_20_agents_4_connections_each/ouput_20_agents/opinions_9.txt @@ -0,0 +1,21 @@ +# idx_agent, opinion + 0, 0.4520877070452688 + 1, 0.4519834107205789 + 2, 0.45178582345416535 + 3, 0.45201799529137504 + 4, 0.45166299010450306 + 5, 0.45246875783456114 + 6, 0.4520540097185373 + 7, 0.45186761725365837 + 8, 0.4519968049878632 + 9, 0.4521125475235014 + 10, 0.45200176745171683 + 11, 0.45191056824762277 + 12, 0.45257789747142557 + 13, 0.45209643833583313 + 14, 0.4521207868299939 + 15, 0.45206158704920874 + 16, 0.45219121137429863 + 17, 0.4520638247219471 + 18, 0.45204355536816276 + 19, 0.4521213269421429 diff --git a/visualisations/test.toml b/visualisations/test.toml new file mode 100644 index 0000000..c379366 --- /dev/null +++ b/visualisations/test.toml @@ -0,0 +1,21 @@ +[simulation] +model = "DeGroot" +rng_seed = 120 # Leaving this empty will pick a random seed + +[io] +# n_output_network = 20 # Write the network every 20 iterations +# n_output_agents = 1 # Write the opinions of agents after every iteration +print_progress = false # Print the iteration time ; if not set, then does not prints +output_initial = true # Print the initial opinions and network file from step 0. If not set, this is true by default. +start_output = 1 # Start writing out opinions and/or network files from this iteration. If not set, this is 1 + start_numbering_from. +start_numbering_from = 0 # The initial step number, before the simulation runs, is this value. The first step would be (1+start_numbering_from). By default, 0 + +[model] +max_iterations = 100 # If not set, max iterations is infinite + +[DeGroot] +convergence = 1e-6 # If not set, the default 1e-6 is used + +# [network] +# number_of_agents = 300 +# connections_per_agent = 10