Skip to main content

Python bindings for the Simlin system dynamics simulation engine

Project description

pysimlin - Python bindings for Simlin

Python bindings for the Simlin system dynamics simulation engine.

Features

  • Load models from XMILE, Vensim MDL, and Simlin JSON and protobuf formats
  • Run system dynamics simulations with full control
  • Get simulation results as pandas DataFrames
  • Analyze model structure and feedback loops
  • Edit existing models or build new ones programmatically via Python context managers
  • Full type hints for IDE support
  • Loops That Matter (LTM) analysis for feedback loop importance

Installation

pip install pysimlin

Note: Install with pip install pysimlin but import with import simlin.

Requirements

  • Python 3.11 or higher
  • numpy >= 1.22.0
  • pandas >= 1.5.0
  • cffi >= 1.15.0

Quick Start

import simlin
from simlin import Project
from simlin.json_types import Stock, Flow, Auxiliary

# Create a simple population model programmatically
project = Project.new(
    name="population-demo",
    sim_start=0.0,
    sim_stop=100.0,
    dt=0.25,
    time_units="years"
)

model = project.get_model()
with model.edit() as (_, patch):
    # Stock: population level
    patch.upsert_stock(Stock(
        name="population",
        initial_equation="1000",
        inflows=["births"],
        outflows=["deaths"]
    ))

    # Flows: births and deaths
    patch.upsert_flow(Flow(name="births", equation="population * birth_rate"))
    patch.upsert_flow(Flow(name="deaths", equation="population * death_rate"))

    # Parameters
    patch.upsert_aux(Auxiliary(name="birth_rate", equation="0.03"))
    patch.upsert_aux(Auxiliary(name="death_rate", equation="0.02"))

# Run simulation and get results
run = model.run(analyze_loops=False)
print(run.results.head())

# Access individual variables
population_series = run.results["population"]
print(f"Population grows from {population_series.iloc[0]:.0f} to {population_series.iloc[-1]:.0f}")

Examples

Editing a flow in an existing model

"""Example showing how to edit an existing model's flow equation with pysimlin."""

from __future__ import annotations

import simlin


EXAMPLE_XMILE = b"""<?xml version='1.0' encoding='utf-8'?>
<xmile version=\"1.0\" xmlns=\"http://docs.oasis-open.org/xmile/ns/XMILE/v1.0\" xmlns:isee=\"http://iseesystems.com/XMILE\" xmlns:simlin=\"https://simlin.com/XMILE/v1.0\">
  <header>
    <name>pysimlin-edit-example</name>
    <vendor>Simlin</vendor>
    <product version=\"0.1.0\" lang=\"en\">Simlin</product>
  </header>
  <sim_specs method=\"Euler\" time_units=\"Year\">
    <start>0</start>
    <stop>80</stop>
    <dt>0.25</dt>
  </sim_specs>
  <model name=\"main\">
    <variables>
      <stock name=\"population\">
        <eqn>25</eqn>
        <inflow>net_birth_rate</inflow>
      </stock>
      <flow name=\"net_birth_rate\">
        <eqn>fractional_growth_rate * population</eqn>
      </flow>
      <aux name=\"fractional_growth_rate\">
        <eqn>maximum_growth_rate * (1 - population / carrying_capacity)</eqn>
      </aux>
      <aux name=\"maximum_growth_rate\">
        <eqn>0.10</eqn>
      </aux>
      <aux name=\"carrying_capacity\">
        <eqn>1000</eqn>
      </aux>
    </variables>
  </model>
</xmile>
"""


def run_simulation(model: simlin.Model) -> float:
    """Run the model to the configured stop time and return the ending population."""

    with model.simulate() as sim:
        sim.run_to_end()
        return float(sim.get_value("population"))


def main() -> None:
    """Demonstrate editing a flow equation and verify the change takes effect."""

    # Load model from XMILE bytes by writing to temp file first
    import tempfile
    import os

    with tempfile.NamedTemporaryFile(suffix=".stmx", delete=False) as f:
        f.write(EXAMPLE_XMILE)
        temp_path = f.name

    try:
        model = simlin.load(temp_path)
        baseline_final = run_simulation(model)

        with model.edit() as (current, patch):
            flow = current["net_birth_rate"]
            flow.equation = "fractional_growth_rate * population * 1.5"
            patch.upsert_flow(flow)

        accelerated_final = run_simulation(model)

        if not accelerated_final > baseline_final + 10:
            raise RuntimeError(
                "Edited model did not accelerate growth as expected: "
                f"baseline={baseline_final:.2f} accelerated={accelerated_final:.2f}"
            )

        print(
            "Updated growth equation increased the final population from "
            f"{baseline_final:.1f} to {accelerated_final:.1f}."
        )
    finally:
        os.unlink(temp_path)


if __name__ == "__main__":
    main()

Building a logistic population model programmatically

"""Create a new Simlin project and build a simple population model using pysimlin's edit API."""

from __future__ import annotations

import simlin
from simlin.json_types import Stock, Flow, Auxiliary


def build_population_project() -> simlin.Project:
    """Return a project containing a logistic population model created via model.edit()."""

    project = simlin.Project.new(
        name="pysimlin-population-example",
        sim_start=0.0,
        sim_stop=80.0,
        dt=0.25,
        time_units="years",
    )

    model = project.get_model()
    with model.edit() as (_, patch):
        population = Stock(
            name="population",
            initial_equation="50",
            inflows=["births"],
            outflows=["deaths"],
        )
        patch.upsert_stock(population)

        births = Flow(
            name="births",
            equation="population * birth_rate",
        )
        patch.upsert_flow(births)

        deaths = Flow(
            name="deaths",
            equation="population * birth_rate * (population / 1000)",
        )
        patch.upsert_flow(deaths)

        birth_rate = Auxiliary(
            name="birth_rate",
            equation="0.08",
        )
        patch.upsert_aux(birth_rate)

    return project


def validate_population_curve(values: list[float]) -> None:
    """Ensure the generated population series shows logistic (S-shaped) growth."""

    if len(values) < 3:
        raise RuntimeError("Population series is unexpectedly short")

    if any(b < a for a, b in zip(values, values[1:])):
        raise RuntimeError("Population should not decline in this model")

    initial = values[0]
    mid = values[len(values) // 2]
    last = values[-1]
    growth_first_half = mid - initial
    growth_second_half = last - mid

    if not growth_first_half > 0:
        raise RuntimeError("Population failed to grow early in the simulation")

    if not growth_second_half > 0:
        raise RuntimeError("Population failed to grow late in the simulation")

    if not growth_second_half < growth_first_half:
        raise RuntimeError("Logistic growth should slow over time")

    if not 950 <= last <= 1025:
        raise RuntimeError(
            "Population should approach the carrying capacity (~1000), "
            f"but ended at {last:.2f}"
        )


def main() -> None:
    """Build, simulate, and validate the population model."""

    project = build_population_project()
    errors = project.get_errors()
    if errors:
        raise RuntimeError(f"Generated project contains validation errors: {errors}")

    model = project.get_model()
    with model.simulate() as sim:
        sim.run_to_end()
        population_series = [float(value) for value in sim.get_series("population")]

    validate_population_curve(population_series)

    print(
        "Population grows from "
        f"{population_series[0]:.1f} to {population_series[-1]:.1f}, forming an S-shaped trajectory."
    )


if __name__ == "__main__":
    main()

Both examples live under src/pysimlin/examples/ and are executed by scripts/pysimlin-tests.sh.

API Reference

Loading Models

import simlin
from simlin import Project
from simlin.json_types import Stock, Flow, Auxiliary

# Create a model programmatically (used by all API examples below)
project = Project.new(
    name="api-demo",
    sim_start=0.0,
    sim_stop=100.0,
    dt=0.25,
    time_units="years"
)

model = project.get_model()
with model.edit() as (_, patch):
    patch.upsert_stock(Stock(
        name="population",
        initial_equation="1000",
        inflows=["births"],
        outflows=["deaths"]
    ))
    patch.upsert_flow(Flow(name="births", equation="population * birth_rate"))
    patch.upsert_flow(Flow(name="deaths", equation="population * death_rate"))
    patch.upsert_aux(Auxiliary(name="birth_rate", equation="0.03"))
    patch.upsert_aux(Auxiliary(name="death_rate", equation="0.02"))

print(f"Created model with {len(model.get_var_names())} variables")

You can also load models from files:

# Load from file (auto-detects format from extension)
model = simlin.load("model.stmx")  # .stmx, .mdl, .json, etc.
project = model.project

Working with Models

from simlin import VARTYPE_STOCK, VARTYPE_FLOW, VARTYPE_AUX

# Get variable names, optionally filtered by type
all_names = model.get_var_names()                          # All variable names
stock_names = model.get_var_names(type_mask=VARTYPE_STOCK) # Stock names only
flow_names = model.get_var_names(type_mask=VARTYPE_FLOW)   # Flow names only
aux_names = model.get_var_names(type_mask=VARTYPE_AUX)     # Aux names only

# Get detailed variable information
for name in model.get_var_names():
    var = model.get_variable(name)
    if var is not None:
        print(f"{var.name} ({type(var).__name__})")

# Get time configuration
time_spec = model.time_spec
print(f"Simulation: t={time_spec.start} to {time_spec.stop}, dt={time_spec.dt}")

# Analyze variable dependencies
incoming_deps = model.get_incoming_links("population")

# Get causal links
links = model.get_links()
for link in links:
    print(f"{link.from_var} --{link.polarity}--> {link.to_var}")

# Check for model issues
issues = model.check()
for issue in issues:
    print(f"{issue.severity}: {issue.message}")

# Get explanation for a variable
explanation = model.explain("population")
print(explanation)

Model Editing

from dataclasses import replace
from simlin.json_types import Stock, Flow, Auxiliary

# Edit existing model variables using context manager
with model.edit() as (current, patch):
    # Access current variables by name (returns Stock, Flow, Auxiliary, or Module)
    stock_var = current["population"]

    # Modify the variable using dataclasses.replace()
    updated_stock = replace(stock_var, initial_equation="100")

    # Apply the change
    patch.upsert_stock(updated_stock)

# Create new variables programmatically
with model.edit() as (current, patch):
    # Create a new auxiliary variable
    new_aux = Auxiliary(
        name="growth_rate",
        equation="0.05",
    )
    patch.upsert_aux(new_aux)

    # Create a new flow variable
    new_flow = Flow(
        name="births",
        equation="population * growth_rate",
    )
    patch.upsert_flow(new_flow)

Running Simulations

# High-level API: run and get results immediately
run = model.run(analyze_loops=False)
print(run.results.head())

# Run with variable overrides
run = model.run(overrides={"birth_rate": 0.05}, analyze_loops=False)

# Use the cached base case
base_case = model.base_case  # Automatically cached
print(base_case.results["population"].tail())

# Low-level API: create simulation for step-by-step control
with model.simulate() as sim:
    sim.run_to(50.0)        # Run to specific time
    sim.set_value("growth_rate", 0.10)  # Intervention
    sim.run_to_end()        # Continue to end
    run = sim.get_run()     # Get results as Run object

# Enable Loops That Matter analysis
with model.simulate(enable_ltm=True) as sim:
    sim.run_to_end()
    run = sim.get_run()
    print(run.dominant_periods)

Accessing Results

# Results are pandas DataFrames
run = model.run(analyze_loops=False)
df = run.results  # Time series for all variables

# Access specific variables
population = df["population"]
births = df["births"]

# Standard pandas operations
print(df.describe())
print(df.tail())

# Get metadata
time_spec = run.time_spec
overrides = run.overrides  # Dict of variable overrides used

Model Interventions

# Run with different parameter values
scenarios = {}
for rate in [0.02, 0.03, 0.04]:
    run = model.run(
        overrides={"birth_rate": rate},
        analyze_loops=False
    )
    scenarios[f"rate_{rate}"] = run.results["population"]

# Compare scenarios
import pandas as pd
comparison = pd.DataFrame(scenarios)
print(comparison.tail())

Feedback Loop Analysis

run = model.run()

# Access feedback loops with polarity and behavioral importance
for loop in run.loops:
    print(f"Loop {loop.id} ({loop.polarity}): {' -> '.join(loop.variables)}")
    if loop.behavior_time_series is not None:
        avg_importance = loop.average_importance()
        print(f"  avg importance = {avg_importance:.3f}")

# Analyze dominant periods
for period in run.dominant_periods:
    print(f"t=[{period.start_time}, {period.end_time}]: {period.dominant_loops}")

Loop Polarity

Loops are classified by polarity, which indicates how they affect the system:

  • R (Reinforcing): Loop amplifies changes (positive loop scores)
  • B (Balancing): Loop counteracts changes (negative loop scores)
  • U (Undetermined): Loop polarity cannot be reliably determined

Loop IDs use the polarity as a prefix (e.g., "R1", "B2", "U3").

When you run a simulation, pysimlin computes actual loop scores at each timestep. The polarity is classified based on these runtime values:

  • If loop scores are consistently positive throughout: Reinforcing
  • If loop scores are consistently negative throughout: Balancing
  • If loop scores change sign during simulation: Undetermined (occurs in nonlinear models where link effects depend on variable values)
from simlin import LoopPolarity

run = model.run()

# Filter by polarity
reinforcing = [l for l in run.loops if l.polarity == LoopPolarity.REINFORCING]
balancing = [l for l in run.loops if l.polarity == LoopPolarity.BALANCING]
undetermined = [l for l in run.loops if l.polarity == LoopPolarity.UNDETERMINED]

Loops That Matter (LTM)

# Run simulation with LTM enabled
sim = model.simulate(enable_ltm=True)
sim.run_to_end()

# Get links with importance scores over time
links = sim.get_links()
for link in links:
    if link.has_score():
        print(f"{link.from_var} -> {link.to_var}")
        print(f"  Average score: {link.average_score():.4f}")
        print(f"  Max score: {link.max_score():.4f}")

# Get relative loop scores
loops = model.get_loops()
if loops:
    loop_scores = sim.get_relative_loop_score(loops[0].id)

Model Export

from pathlib import Path

# Export to different formats
xmile_bytes = project.to_xmile()           # Export as XMILE XML
json_bytes = project.serialize_json()      # Export as JSON

print(f"XMILE export: {len(xmile_bytes)} bytes")
print(f"JSON export: {len(json_bytes)} bytes")

# Save to file (example - commented out to avoid creating files)
# Path("exported.stmx").write_bytes(xmile_bytes)
# Path("model.json").write_bytes(json_bytes)

Error Handling

from simlin import (
    SimlinError,
    SimlinImportError,
    SimlinRuntimeError,
    SimlinCompilationError,
    ErrorCode
)

# Check for compilation errors on the existing project
errors = project.get_errors()
if errors:
    for error in errors:
        print(f"{error.code.name} in {error.model_name}/{error.variable_name}")
        print(f"  {error.message}")
else:
    print("No errors in project")

When loading models from files, you can catch import errors:

try:
    model = simlin.load("model.stmx")
except SimlinImportError as e:
    print(f"Import failed: {e}")
    if e.code == ErrorCode.XML_DESERIALIZATION:
        print("Invalid XML format")

Complete Example

This example demonstrates loading a model from file and comparing scenarios with matplotlib:

import simlin
import pandas as pd
import matplotlib.pyplot as plt

# Load and run a population model
model = simlin.load("population_model.stmx")

# Run baseline simulation
with model.simulate() as sim:
    sim.run_to_end()
    baseline = sim.get_run().results

# Run intervention scenario
with model.simulate() as sim:
    sim.set_value("birth_rate", 0.03)
    sim.run_to_end()
    intervention = sim.get_run().results

# Compare results
fig, ax = plt.subplots()
ax.plot(baseline.index, baseline["population"], label="Baseline")
ax.plot(intervention.index, intervention["population"], label="Intervention")
ax.set_xlabel("Time")
ax.set_ylabel("Population")
ax.legend()
plt.show()

Supported Platforms

  • macOS (ARM64)
  • Linux (ARM64, x86_64)

License

Apache License 2.0

Development

For development setup and contribution guidelines, see the main Simlin repository.

Running Tests

cd src/pysimlin
pip install -e ".[dev]"
pytest

Building from Source

cd src/pysimlin
python -m build

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pysimlin-0.6.5-cp314-cp314-manylinux_2_28_x86_64.whl (7.6 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ x86-64

pysimlin-0.6.5-cp314-cp314-manylinux_2_28_aarch64.whl (8.0 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ ARM64

pysimlin-0.6.5-cp314-cp314-macosx_11_0_arm64.whl (5.2 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

pysimlin-0.6.5-cp313-cp313-manylinux_2_28_x86_64.whl (7.6 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ x86-64

pysimlin-0.6.5-cp313-cp313-manylinux_2_28_aarch64.whl (8.0 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ ARM64

pysimlin-0.6.5-cp313-cp313-macosx_11_0_arm64.whl (5.2 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

pysimlin-0.6.5-cp312-cp312-manylinux_2_28_x86_64.whl (7.6 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

pysimlin-0.6.5-cp312-cp312-manylinux_2_28_aarch64.whl (8.0 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ ARM64

pysimlin-0.6.5-cp312-cp312-macosx_11_0_arm64.whl (5.2 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

pysimlin-0.6.5-cp311-cp311-manylinux_2_28_x86_64.whl (7.6 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ x86-64

pysimlin-0.6.5-cp311-cp311-manylinux_2_28_aarch64.whl (8.0 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ ARM64

pysimlin-0.6.5-cp311-cp311-macosx_11_0_arm64.whl (5.2 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

File details

Details for the file pysimlin-0.6.5-cp314-cp314-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp314-cp314-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 698d30052c8cfe3038324c318ac626ff00c607f246dda5e21111aa3261344da2
MD5 6c84ff9df30eeaecdbfe556f015d0ee9
BLAKE2b-256 4a5553e390f3993f76fa632bf518bb8bc5bf3b4b97e417fe9f35635ecce14df2

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp314-cp314-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp314-cp314-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 de763c1624cb22abc28247c31ad3fa1b9eb6b38d442dcb2d681654732e839750
MD5 8f175c31e3eb1d8e1bb4ac26d1e540e6
BLAKE2b-256 655ca130422d2eb941e93f62e38e0e7446c099f64a235e14a39dc02f91372b74

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c8881320d89576e5427fe6deb2f80e3f1120f30e297b756cdf03c7473ca14a59
MD5 3228749b71252204e77ef526bf1d884d
BLAKE2b-256 2d2c145de1da409d169dfab96491c770252acef6cba4334327fd41deb256cd21

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp313-cp313-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp313-cp313-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a75ff96078c4d55c5634732403c429b51ad38565f5937e504427b8a2b024c6a1
MD5 2845c9fe61f3646822262f83a5a295f2
BLAKE2b-256 abbfde62447ee384bb2412c49ff5b15d30ccb67a1e12b6512bb59302e38d59ad

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp313-cp313-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp313-cp313-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 b63985a8cfd37c78a0a0ca26385bfe700c5b8723d685a536e101402e407696bc
MD5 2abd629e9a38c8fef8b1771fd82c0970
BLAKE2b-256 ee52f0bf900889c3e0020282221adee82800d675386c3170255985fd624fb2b9

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ad758c5d5da7587db6d94669b8677c34f6873d250de636d5b844089ee1038394
MD5 b6b4d90c3c836d6f3af08c7af75e9c60
BLAKE2b-256 a8febf7ba92500c50b2dc22ba9b7ccc784f2189af49ad142048b7c2bccabeaa4

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 47e11f3f0150c8270f4c062880de50754881bff95bbae8e79a12805859700fc2
MD5 4de740c67f420d6e8ab236557959a512
BLAKE2b-256 75c7b17ec1c07a15c0fb0a5bdc6e99353d0f98a6ac012ffd3b358954296e1865

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp312-cp312-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp312-cp312-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 93b615c2f4b8c409d636641141a585de89e69fe6e23f35b16403e050754d5e5b
MD5 c85247a6b8ca1a38c12bc7ae6c101b35
BLAKE2b-256 0ae5ca8b07fd76729693331a2d10364fbaa897e39fe70b0d27bac340ccf49c01

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 fa8905fb47779f329e92ce2fedfd1cd8a42445c59f867a7e74bc838b950dc096
MD5 9fc2acdec0fb2797aa6f0ec75e1d4970
BLAKE2b-256 654fedace444780fb9bf20bf657aee9e0e5c68a361e1234bb18055ce2ef3c1e1

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 3520bfde62fae28475ff31a2fc87df5e5bbc2c86747eca7cb4ef4758b0202277
MD5 3de15127ef008e20aba85a1ee0a6c631
BLAKE2b-256 3ccf1c764a348bf60f7ce6f24989fe2769ade066275d7ebb188a2e3496cacb1c

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp311-cp311-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp311-cp311-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 d1142f5d6c9296d13a832ee5c2ff00092805ed2ab3f50ff24df05fe5b60cbbc0
MD5 303e231548f8d2f1ce7d3a3ac5403864
BLAKE2b-256 8acf00044174f9755576c096234da11c555407e415c5fdc4bd88daa85a25fcb4

See more details on using hashes here.

File details

Details for the file pysimlin-0.6.5-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pysimlin-0.6.5-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 580c8860786c0764070ac0d6a456d274157cb3a1af30a5b8d86724200f75732f
MD5 fed35533330203441fe67548af4182df
BLAKE2b-256 51a90249c72c0ad23fd74dc4d4293c8c985eddcf99ad099e2403cca51c6002e4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page