diff --git a/CMakeLists.txt b/CMakeLists.txt index 91b12b7..b8758d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,16 +11,17 @@ get_filename_component(PLUGIN_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) set(PLUGIN_NAME plugin_${PLUGIN_NAME}) project(${PLUGIN_NAME}) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() -find_package(EvoplexCore 0.1.0 REQUIRED) -find_package(Qt5Core REQUIRED) -find_package(Qt5Concurrent REQUIRED) +find_package(EvoplexCore 0.2.0 REQUIRED) +find_package(Qt5Core 5.8.0 REQUIRED) +find_package(Qt5Concurrent 5.8.0 REQUIRED) +find_package(Qt5Network 5.8.0 REQUIRED) # set compilation and installation directories if(APPLE) @@ -30,23 +31,8 @@ else() endif() set(PLUGIN_OUTPUT_LIBRARY "${CMAKE_BINARY_DIR}/plugin") -# https://cmake.org/Wiki/CMake_RPATH_handling -# use, i.e. don't skip the full RPATH for the build tree -set(CMAKE_SKIP_BUILD_RPATH FALSE) -# when building, don't use the install RPATH already (but later on when installing) -set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib/evoplex") -# add the automatically determined parts of the RPATH -# which point to directories outside the build tree to the install RPATH -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -# the RPATH to be used when installing, but only if it's not a system directory -list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib/evoplex" isSystemDir) -if("${isSystemDir}" STREQUAL "-1") - set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib/evoplex") -endif("${isSystemDir}" STREQUAL "-1") - add_library(${PLUGIN_NAME} SHARED plugin.cpp) -target_link_libraries(${PLUGIN_NAME} PRIVATE Evoplex::EvoplexCore Qt5::Core) +target_link_libraries(${PLUGIN_NAME} PUBLIC Evoplex::EvoplexCore) set_target_properties(${PLUGIN_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PLUGIN_OUTPUT_LIBRARY} ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PLUGIN_OUTPUT_LIBRARY} diff --git a/README.md b/README.md index 5aa38e4..fef1499 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,77 @@ -# Evoplex: Minimal Model - -This is a minimum example of a model plugin for [Evoplex](https://evoplex.github.io). It has the essential files for you to create a new model plugin: -``` bash -├── CMakeLists.txt -├── metadata.json -├── plugin.cpp -└── plugin.h -``` -You do not need to touch the `CMakeLists.txt` file. Also, you must NOT rename any of those files. +# Cellular Automata 1D Model -:point_right: **Note:** you *DO NOT* need to compile Evoplex from source to be able to create plugins. +## What is it? -### Installing dependencies -* If you compiled Evoplex from source, you already have all dependencies to compile a plugin and *DO NOT* have to install anything else. -* If you installed Evoplex from the binary packages, you will have to install the dependencies as follows: - * [Instructions for Linux](https://github.com/evoplex/evoplex/wiki/Building-on-Linux#installing-dependencies) - * [Instructions for MacOS](https://github.com/evoplex/evoplex/wiki/Building-on-MacOS#installing-dependencies) - * [Instructions for Windows](https://github.com/evoplex/evoplex/wiki/Building-on-Windows#installing-dependencies) +This is a model plugin for [Evoplex](https://evoplex.org) and is included by default in the software. -### Compiling this plugin +It implements the [256 elementary cellular automaton rules](http://mathworld.wolfram.com/ElementaryCellularAutomaton.html). -:point_right: if you compiled Evoplex from source in Debug mode, you should also compile your plugin in Debug mode. +## How it works -:point_right: the plugin must be compiled with the same architecture (32/64 bits) of Evoplex. +The model runs in a lattice grid, i.e., it uses the `squareGrid` graph generator. -#### from QtCreator -* Open the `CMakeLists.txt` -* In the projects page, make sure the `EvoplexCore_DIR` is set. If it shows `EvoplexCore_DIR-NOTFOUND` and you compiled Evoplex from source (eg., at `~/evoplex/build`), set it to `~/evoplex/build/src/core/EvoplexCore/` as shown [here](https://i.imgur.com/hyKuFR3.png). -* Build +Each cell (node in the graph) can be in one of two possible states: on or off. -#### from command line -Assuming you placed this repository at `~/evoplex/minimal-model` and you are at `~/evoplex`, just run the commands below: -``` bash -mkdir build-plugin -cd build-plugin -cmake ../minimal-model -cmake --build . -``` -When you run the `cmake` command, you might get an **error** like `FindEvoplexCore.cmake` not found. If you compiled Evoplex from source (eg., at `~/evoplex/build`), just run the command below: -``` bash -export EvoplexCore_DIR=~/evoplex/build/src/core/EvoplexCore/ -``` +Starting from the first row in the graph, at each time step: + +- based on the selected rule, compute the next state for each cell in the current row; +- assign the new states to the row below. + +## Parameters + +``rule`` : + An integer between 0 and 255. Its 8-bit binary representation will indicate the transition rule. + +For example, for rule 110: + +(110)10 = (01101110)2 + +| pattern |111|110|101|100|011|010|001|000| +|:-------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +|**state**| 0 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | +## Examples -## Running this plugin -After compiling the plugin, open Evoplex, go to the `Plugins` page, click on `import` and select the `plugin_minimal-model` file at `~/evoplex/minimal-model/build/plugin/`. The plugin will now be available in the `Projects` page. +The figures below were produced using this model in Evoplex. -## Support -- Ask a question in the [mailing list](https://groups.google.com/group/evoplex) (or send it directly to evoplex@googlegroups.com) -- Follow us on [Twitter](https://twitter.com/EvoplexMAS) -- Join us on our [Gitter chat channel](https://gitter.im/EvoplexMAS/evoplex) +

+Example
+Rule 30: initial population with all cells off (blue) and one on-cell in the first row. +

+ +

+Example
+Rule 110: random initial population. +

+ +

+Example
+Rule 110: initial population with all cells off (blue) and one on-cell in the first row. +

+ +## References +- [1] Weisstein, Eric W. "Elementary Cellular Automaton." From MathWorld--A Wolfram Web Resource. [http://mathworld.wolfram.com/ElementaryCellularAutomaton.html](http://mathworld.wolfram.com/ElementaryCellularAutomaton.html) +- [2] Weisstein, Eric W. "Rule 30." From MathWorld--A Wolfram Web Resource. [http://mathworld.wolfram.com/Rule30.html](http://mathworld.wolfram.com/Rule30.html) +- [3] Weisstein, Eric W. "Rule 110." From MathWorld--A Wolfram Web Resource. [http://mathworld.wolfram.com/Rule110.html](http://mathworld.wolfram.com/Rule110.html) + +## How to cite +If you mention this model or the Evoplex software in a publication, please cite it as: + +> Cardinot, M., O’Riordan, C., Griffith, J., & Perc, M. (2019). Evoplex: A platform for agent-based modeling on networks. SoftwareX, 9, 199-204. https://doi.org/10.1016/j.softx.2019.02.009 + +``` +@article{Evoplex, +author = "Marcos Cardinot and Colm O’Riordan and Josephine Griffith and Matjaž Perc", +title = "Evoplex: A platform for agent-based modeling on networks", +journal = "SoftwareX", +volume = "9", +pages = "199 - 204", +year = "2019", +issn = "2352-7110", +doi = "10.1016/j.softx.2019.02.009", +url = "http://www.sciencedirect.com/science/article/pii/S2352711018302437" +} +``` -## Licensing -This plugin is available freely under the [MIT license](https://opensource.org/licenses/MIT). +## License +This plugin is licensed under the [MIT License](https://opensource.org/licenses/MIT) terms. diff --git a/example1.gif b/example1.gif new file mode 100644 index 0000000..111709b Binary files /dev/null and b/example1.gif differ diff --git a/example2.gif b/example2.gif new file mode 100644 index 0000000..e0b2e36 Binary files /dev/null and b/example2.gif differ diff --git a/example3.gif b/example3.gif new file mode 100644 index 0000000..f805da2 Binary files /dev/null and b/example3.gif differ diff --git a/metadata.json b/metadata.json index c8ec5ab..b7aec82 100644 --- a/metadata.json +++ b/metadata.json @@ -1,15 +1,13 @@ { "type": "model", - "uid": "minimalModel", + "uid": "cellularAutomata1D", "version": 1, - "title": "Mininal Model", - "author": "Evoplex", - "description": "This is a mininal example of a model plugin for Evoplex.", + "title": "Cellular Automata 1D", + "author": "Ethan Padden, Marcos Cardinot and Eleftheria Chatziargyriou", + "description": "This model implements elementary cellular automata.", - "pluginAttributesScope": [], - "nodeAttributesScope": [], - "edgeAttributesScope": [], + "pluginAttributesScope": [ {"rule": "int[0,255]"} ], + "nodeAttributesScope": [ {"state": "bool"} ], - "supportedGraphs": [], - "customOutputs": [] + "supportedGraphs": [ "squareGrid" ] } diff --git a/plugin.cpp b/plugin.cpp index fa00b0b..5b83627 100644 --- a/plugin.cpp +++ b/plugin.cpp @@ -1,22 +1,93 @@ /** - * Evoplex - * Copyright (C) 2016-present + * Copyright (c) 2018 - Marcos Cardinot + * Copyright (c) 2018 - Ethan Padden + * + * This source code is licensed under the MIT license found in + * the LICENSE file in the root directory of this source tree. */ #include "plugin.h" namespace evoplex { -bool MinimalModel::init() +bool CellularAutomata1D::init() { + // this model is only valid for `squareGrid` graph + if (graphId() != "squareGrid" || !graph()->attrExists("boundary") || + !graph()->attrExists("width") || !graph()->attrExists("height")) { + return false; + } + + // starts reading from row=0 + m_currRow = 0; + + // checks if the `squareGrid` was set with periodic bondary conditions (i.e., a toroid) + m_toroidal = graph()->attr("boundary") == "periodic"; + // gets the `squareGrid` width + m_width = graph()->attr("width").toInt(); + // gets the `squareGrid` height + m_height = graph()->attr("height").toInt(); + + if (m_height < 2) { + qWarning() << "the 'squareGrid' height must be >= 2"; + return false; + } + + // gets the id of the `state` node's attribute, which is the same for all nodes + m_stateAttrId = node(0).attrs().indexOf("state"); + + // determines which rule to use + m_binrule = std::bitset<8>(attr("rule").toInt()); + + return m_stateAttrId >= 0; +} + +bool CellularAutomata1D::algorithmStep() +{ + // 1. gets first node in the current row + Node first = node(linearIdx(m_currRow, 0)); + + // 2. for each node (starting from the second), + int lastColumn = m_width - 1; + for (int col = 1; col < lastColumn; ++col) { + Node central = node(first.id() + col); + // a. compute the next state based on its neighbours on the left and right + Value state = nextState(node(central.id()-1), central, node(central.id()+1)); + // b. assign the next state to the node below the current node + node(central.id()+m_width).setAttr(m_stateAttrId, state); + } + + // 3. edge case: if the graph is a toroid, we compute the state for the last column + if (m_toroidal) { + Node last = node(linearIdx(m_currRow, lastColumn)); + // a. compute the next state based on its left neighbour and the first node in the row + Value state = nextState(node(last.id()-1), last, first); + // b. assign the next state to the first node of the next row + node(first.id()+m_width).setAttr(m_stateAttrId, state); + } + + ++m_currRow; + if (m_currRow == m_height-1) { + // all rows have been filled; return false to stop the simulation + return false; + } return true; } -bool MinimalModel::algorithmStep() +Value CellularAutomata1D::nextState(const Node& leftNode, const Node& node, const Node& rightNode) const +{ + bool left = leftNode.attr(m_stateAttrId).toBool(); + bool center = node.attr(m_stateAttrId).toBool(); + bool right = rightNode.attr(m_stateAttrId).toBool(); + + return Value(m_binrule[left*4 + center*2 + right]); +} + +int CellularAutomata1D::linearIdx(int row, int col) const { - return false; + return row * m_width + col; } } // evoplex -REGISTER_PLUGIN(MinimalModel) +REGISTER_PLUGIN(CellularAutomata1D) #include "plugin.moc" diff --git a/plugin.h b/plugin.h index fbed7ed..c4bb493 100644 --- a/plugin.h +++ b/plugin.h @@ -1,44 +1,40 @@ /** - * Evoplex - * Copyright (C) 2016-present + * Copyright (c) 2018 - Marcos Cardinot + * Copyright (c) 2018 - Ethan Padden + * + * This source code is licensed under the MIT license found in + * the LICENSE file in the root directory of this source tree. */ -#ifndef MINIMAL_MODEL_H -#define MINIMAL_MODEL_H +#ifndef CELLULARAUTOMATA1D_H +#define CELLULARAUTOMATA1D_H +#include #include namespace evoplex { -class MinimalModel: public AbstractModel +class CellularAutomata1D: public AbstractModel { public: - // Initializes the model. - // This method is called when the model is created and - // is mainly used to validate inputs and set the environment. - // Return true if successful bool init() override; + bool algorithmStep() override; - // [OPTIONAL] - // It is executed before the algorithmStep() loop - // The default implementation of this method does nothing. - // void beforeLoop() override; +private: + int m_currRow; - // It is executed in a loop and must contain all the logic to perform ONE step. - // Return true if algorithm is good for another step or false to stop asap. - bool algorithmStep() override; + int m_stateAttrId; // the id of the `state` node attribute + std::bitset<8> m_binrule; // binary representation of the automaton rule + + bool m_toroidal; // true if the graph is a toroid + int m_width; // the number of columns in the `squareGrid` graph + int m_height; // the number of rows in the `squareGrid` graph + + // returns the next state of a node based on the state + // of itself and its neighbours on the left and right + Value nextState(const Node& leftNode, const Node& node, const Node& rightNode) const; - // [OPTIONAL] - // It is executed after the algorithmStep() loop ends. - // The default implementation of this method does nothing. - // void afterLoop() override; - - // [OPTIONAL] - // It allows implementing custom outputs which can be plotted or stored - // in a file through Evoplex. The "inputs" must be defined in the - // metadata.json file. If an experiment requests some custom output, - // this method will be called once at each time step, receiving the - // requested inputs. - // Values customOutputs(const Values& inputs) const override; + // return the linear index of an element in a matrix. + int linearIdx(int row, int col) const; }; } // evoplex -#endif // MINIMAL_MODEL_H +#endif // CELLULARAUTOMATA1D_H