diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..494f3f2 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,22 @@ +name: tests + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.13"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - run: pip install ".[dev]" + - run: pytest -q + env: + MPLBACKEND: Agg diff --git a/.gitignore b/.gitignore index a16f8ad..58279e7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ wheels/ # OS .DS_Store Thumbs.db + +# MkDocs build output +site/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ee7b0f5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +## [0.1.1] — 2026-10-08 + +First citable release. The `v0.1.0` tag was created before the release +branch was merged and points to the previous code (no license, citation +metadata or tests); use 0.1.1. + +- Models compatible with dissmodel 0.5.0 or later (tested with 0.5.0, 0.6.0 and 0.6.6). +- `tests/test_models_smoke.py` runs every exported model for a few steps, headless, + and checks that its state stays numeric; a GitHub Actions workflow runs it. +- `streamlit` is no longer a required dependency: no module of the package uses + it, only the Streamlit examples (`pip install "dissmodel-sysdyn[examples]"`). +- `demo/`: self-contained Streamlit app (Dockerfile, pinned requirements, + Hugging Face Space header) that can be deployed on its own; before, the + Dockerfile copied `src/` and `pyproject.toml` from the repository root. +- MIT license and `CITATION.cff`. diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..f3d9f67 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,26 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +title: "dissmodel-sysdyn: System Dynamics extension for DisSModel" +abstract: >- + System dynamics models for the DisSModel framework: population growth, SIR, predator-prey, Lorenz, Daisyworld and other classic examples, ported from the TerraME model collection. +authors: + - family-names: "Costa" + given-names: "Sérgio Souza" + orcid: "https://orcid.org/0000-0002-0232-4549" + - family-names: "Santos Junior" + given-names: "Nerval de Jesus" + orcid: "https://orcid.org/0009-0000-2339-3191" + - family-names: "Vale" + given-names: "Mateus Dutra" + orcid: "https://orcid.org/0009-0005-1301-879X" +version: 0.1.1 +date-released: "2026-10-08" +license: MIT +url: "https://github.com/DisSModel/dissmodel-sysdyn" +repository-code: "https://github.com/DisSModel/dissmodel-sysdyn" +keywords: + - system dynamics + - DisSModel + - simulation + - TerraME +type: software diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf0b64a --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 Sérgio Souza Costa, Nerval de Jesus Santos Junior, +Mateus Dutra Vale + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 0144cea..e802c52 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # dissmodel-sysdyn -System Dynamics models for [dissmodel](https://github.com/LambdaGeo/dissmodel). +System Dynamics models for [dissmodel](https://github.com/DisSModel/dissmodel). This library provides a collection of System Dynamics models implemented using the `dissmodel` core. ## Installation +The package is not on PyPI. Install it from GitHub: + ```bash -pip install dissmodel-sysdyn +pip install git+https://github.com/DisSModel/dissmodel-sysdyn ``` +or, from a clone, `pip install .` (`pip install ".[examples]"` for the Streamlit apps, `pip install ".[dev]"` for the tests). + ## Models The library includes a wide range of System Dynamics models: diff --git a/demo/Dockerfile b/demo/Dockerfile index e9b4342..f789a7e 100644 --- a/demo/Dockerfile +++ b/demo/Dockerfile @@ -1,33 +1,24 @@ +# Self-contained: build from this folder only (e.g. a Hugging Face Space). FROM python:3.11-slim -# Install system dependencies -RUN apt-get update && apt-get install -y \ - git \ - build-essential \ - libgl1 \ - libglib2.0-0 \ +# git: dissmodel-sysdyn is installed from GitHub (not on PyPI) +RUN apt-get update && apt-get install -y --no-install-recommends git \ && rm -rf /var/lib/apt/lists/* -# Set up a new user to avoid running as root +# Hugging Face runs the container as uid 1000 RUN useradd -m -u 1000 user USER user ENV HOME=/home/user \ - PATH=/home/user/.local/bin:$PATH + PATH=/home/user/.local/bin:$PATH \ + MPLBACKEND=Agg WORKDIR $HOME/app -# Copy requirements and install -COPY --chown=user demo/requirements.txt ./demo/requirements.txt -RUN pip install --no-cache-dir -r demo/requirements.txt +COPY --chown=user requirements.txt . +RUN pip install --no-cache-dir --upgrade -r requirements.txt -COPY --chown=user pyproject.toml README.md ./ -COPY --chown=user src ./src -COPY --chown=user demo/app.py ./demo/app.py +COPY --chown=user app.py . -RUN pip install --no-cache-dir . - -# Expose the port Streamlit will run on (Hugging Face uses 7860) +# Hugging Face Spaces expect port 7860 EXPOSE 7860 - -# Command to run the app -CMD ["streamlit", "run", "demo/app.py", "--server.port", "7860", "--server.address", "0.0.0.0"] +CMD ["streamlit", "run", "app.py", "--server.port", "7860", "--server.address", "0.0.0.0"] diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..2dd3997 --- /dev/null +++ b/demo/README.md @@ -0,0 +1,30 @@ +--- +title: DisSModel System Dynamics Explorer +emoji: 📈 +colorFrom: blue +colorTo: purple +sdk: docker +app_port: 7860 +license: mit +short_description: Run the dissmodel-sysdyn models in the browser +--- + +# System Dynamics Explorer + +Interactive demo of [dissmodel-sysdyn](https://github.com/DisSModel/dissmodel-sysdyn): +pick a model (SIR, predator–prey, Lorenz, Daisyworld…), set its parameters in +the sidebar and watch the stocks evolve. + +This folder is self-contained and is what gets deployed: + +```bash +# locally +pip install -r requirements.txt +streamlit run app.py + +# or with Docker +docker build -t sysdyn-demo . && docker run -p 7860:7860 sysdyn-demo +``` + +To publish on Hugging Face, create a Space with the Docker SDK and push the +contents of this folder to it. The header above is the Space configuration. diff --git a/demo/app.py b/demo/app.py index 3f570a6..17b6e81 100644 --- a/demo/app.py +++ b/demo/app.py @@ -6,6 +6,14 @@ This application dynamically discovers all available models and generates the corresponding UI for parameters and live plotting. + +Standalone: this folder is what gets deployed (e.g. to a Hugging Face +Space), so the app imports only installed packages (see requirements.txt). + +Usage +----- + pip install -r requirements.txt + streamlit run app.py """ from __future__ import annotations diff --git a/demo/requirements.txt b/demo/requirements.txt index 78e98e2..013081b 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -1,2 +1,4 @@ -streamlit -matplotlib +# Pinned so that the deployed demo is reproducible; bump together with releases. +dissmodel==0.6.6 +dissmodel-sysdyn @ git+https://github.com/DisSModel/dissmodel-sysdyn@v0.1.1 +streamlit>=1.30 diff --git a/pyproject.toml b/pyproject.toml index dadca68..808a232 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,19 +4,24 @@ build-backend = "setuptools.build_meta" [project] name = "dissmodel-sysdyn" -version = "0.1.0" -description = "System Dynamics models for dissmodel" +version = "0.1.1" +description = "System dynamics models for DisSModel" readme = "README.md" -requires-python = ">=3.9" -dependencies = [ - "dissmodel>=0.1.0", - "streamlit", -] +license = { text = "MIT" } authors = [ - { name = "Sergio", email = "sergio@example.com" }, + { name = "Sérgio Souza Costa", email = "sergio.costa@ufma.br" }, + { name = "Nerval de Jesus Santos Junior" }, + { name = "Mateus Dutra Vale" }, +] +requires-python = ">=3.10" +dependencies = [ + "dissmodel>=0.5.0", ] [project.optional-dependencies] +examples = [ + "streamlit", +] dev = [ "pytest", ] @@ -24,3 +29,9 @@ dev = [ [tool.setuptools] package-dir = {"" = "src"} packages = ["dissmodel_sysdyn", "dissmodel_sysdyn.models"] + +[project.urls] +Homepage = "https://github.com/DisSModel/dissmodel-sysdyn" + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/site/404.html b/site/404.html deleted file mode 100644 index c637542..0000000 --- a/site/404.html +++ /dev/null @@ -1,383 +0,0 @@ - - - -
- - - - - - - - - - - - - - - - - - - - - -The dissmodel-sysdyn package provides a high-level framework for System Dynamics (SysDyn) modeling in Python. Inspired by the philosophy of TerraME, it allows researchers and students to translate Forrester diagrams directly into executable code using clear abstractions for Stocks, Flows, and Converters.
In dissmodel-sysdyn, the fundamental components of System Dynamics are mapped to Python's object-oriented primitives:
Stocks represent the state of the system at any given time. In our framework, Stocks are defined as instance attributes of a model class. They are initialized in the setup() method and updated in the execute() method.
Example
-In a population model, self.population is a Stock that accumulates individuals over time.
Flows represent the rate at which Stocks change during a time step. Unlike some visual tools where Flows are explicit objects, dissmodel-sysdyn expresses Flows as intermediate variables or logic within the execute() method.
Mathematical Mapping
-A flow is equivalent to the derivative dS/dt or the difference ΔS.
-Converters (or auxiliary variables) and Parameters influence the rates of Flows. These are implemented as model attributes that are typically set during the setup() phase and remain constant or change according to their own internal logic.
System Dynamics models are naturally described by Ordinary Differential Equations (ODEs). To solve these on a computer, we must discretize time.
-The Environment class manages the simulation clock. At each time step (t -> t + dt), it calls the execute() method of all active models.
Most models in this package employ the Forward Euler Method. The new value of a Stock is calculated by adding the net flow (Inflows - Outflows) multiplied by the time step to the current value:
-S(t + dt) = S(t) + (Inflow - Outflow) * dt
-In many discrete models within the package, dt is implicitly assumed to be 1, simplifying the update to: -
self.stock += inflow - outflow
-To illustrate the framework overhead versus the business logic, consider a simple radioactive decay model.
-amount = 100.0
-decay_rate = 0.1
-for t in range(50):
- decay = amount * decay_rate
- amount -= decay
- print(f"Time {t}: {amount}")
-dissmodel-sysdyn FrameworkUsing the framework adds structure, automatic plotting, and environment management.
-from dissmodel.core import Model, Environment
-from dissmodel.visualization import track_plot
-
-@track_plot("Substance", "blue")
-class Decay(Model):
- def setup(self, amount=100.0, rate=0.1):
- self.amount = amount # Stock
- self.rate = rate # Parameter
-
- def execute(self):
- # Flow logic
- decay = self.amount * self.rate
- # Update rule (Euler)
- self.amount -= decay
-
-# Running the simulation
-env = Environment(end_time=50)
-model = Decay()
-env.run()
-The framework allows you to focus on the Differential Equations and Model Logic while providing tools for complex orchestrations and visualizations.
- - - - - - - - - - - - - -0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function K(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function B(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o