Skip to main content

Minimal, file-based run cache for Python-driven simulations and scripts.

Project description

entropic

Entropic is a minimal run cache for Python-driven simulations and scripts. By hashing your input parameters, it automatically identifies duplicate runs and skips unnecessary computation. It is completely agnostic to your simulation engine, lightweight by design, and built to manage locally run research workflows without getting in your way.

Storage is backed by SQLAlchemy: parameters and metadata live in a SQL database (SQLite by default), result files live on disk next to it.

Install

pip install entropic
# or
uv add entropic

Quickstart

from pathlib import Path
import numpy as np

from entropic import Store, Base, Mapped


# 1. Define a SQLAlchemy model for your simulation parameters.
#    The four reserved columns (id, result_file, created_at, custom_data)
#    come from Base — your columns are the parameters you want to query on.
class SimResult(Base):
    __tablename__ = "results"

    n: Mapped[int]
    steps: Mapped[int]
    dt: Mapped[float]


# 2. Define a runner. It receives a single dict; entropic injects
#    `params["result_file"]` (the target path) before calling.
def my_simulation(params: dict) -> None:
    data = np.random.randn(params["n"], params["steps"])
    np.save(params["result_file"], data)


store = Store(
    runner=my_simulation,
    result_cls=SimResult,
    results_dir="./results",
    db_url="sqlite:///./runs.sqlite3",
    file_suffix=".npy",
)

# 3. Run or retrieve from cache
record = store.run_or_retrieve({"n": 100, "steps": 5000, "dt": 0.01})
print(record.result_file)         # ./results/a3f8c1d2e4b6f7a8.npy
print(record.id)                  # a3f8c1d2e4b6f7a8
print(record.n, record.dt)        # 100  0.01
print(record.custom_data)         # {"elapsed_seconds": 0.042}

# Second call with same params → instant cache hit, runner not invoked
same = store.run_or_retrieve({"n": 100, "steps": 5000, "dt": 0.01})

Core API

Store

store = Store(
    runner=my_simulation,
    result_cls=SimResult,
    results_dir="./results",            # where result files live
    file_suffix=".h5",                  # extension for auto-generated filenames
    db_url="sqlite:///./db.sqlite3",    # SQLAlchemy URL
)

runner is called as runner(params); the Store passes a copy of your params with id and result_file injected. The runner writes its output to params["result_file"].

result_cls must be a Base subclass. Its column names must match the keys of the params dicts you pass to the Store — those columns are how list(where=...) filters work.

store.run_or_retrieve(params, **custom_data) → ModelT

The main workhorse. Returns the cached record if params hashes to an existing row, otherwise runs the simulation and persists the new row.

record = store.run_or_retrieve(
    {"n": 50, "method": "rk4"},
    git_sha="abc123",   # stored on record.custom_data
)

store.run(params, **custom_data) → ModelT

Always runs the simulation and overwrites the cache for that hash.

store.retrieve(params) → ModelT | None

Look up a cached run by exact parameter match. Returns None on a miss.

store.register(params, result_file, **custom_data) → ModelT

Index an externally-produced result file. Raises FileNotFoundError if result_file does not exist.

store.register(
    {"n": 50, "method": "euler"},
    result_file="./results/my_external_run.h5",
)

store.sweep(grid, client=None) → list[ModelT]

Run or retrieve results for all combinations in the grid. Reuses cached results where possible.

records = store.sweep({"n": [10], "dt": [0.01, 0.005, 0.001]})

Pass a Dask distributed.Client as client to dispatch runs in parallel.

store.delete(params, remove_file=False) → bool

Delete a record by exact parameter match. Returns True if a row was removed.

Record fields (from Base)

Field Type Description
id str 16-char hex hash of the parameters (primary key).
result_file str Path to the result file on disk.
created_at datetime UTC timestamp set on insert.
custom_data dict[str,Any] JSON column. elapsed_seconds is added automatically on runs.

Any user-defined columns on the model are populated from the matching keys in params.

How it works

Each run is keyed by a deterministic 16-char SHA-256 hash of its normalized params (dict keys sorted, floats rounded to 12 digits, enums replaced by .value, tuples flattened to lists, everything else str()-coerced).

When the runner finishes, entropic writes a sidecar JSON next to the result file with the params + custom_data, then ingests it into the database. The sidecar is unlinked on a successful insert. Sidecars left behind imply the result file was missing or empty when ingestion ran — they will be re-ingested on the next operation that triggers _ingest_to_db.

Reserved keys in params

The four Base columns — id, result_file, created_at, custom_data — are stripped from params before hashing. If you pass an explicit id, it short-circuits hashing and is used verbatim as the row's primary key.

User-defined param keys must match column names on result_cls; extra keys will fail the SQLAlchemy insert.

Runner contract

def runner(params: dict[str, Any]) -> None:
    # params["result_file"] is the path to write to
    # all other keys are your simulation parameters
    ...

The library generates params["result_file"] (<results_dir>/<hash><suffix>) before invoking your runner.

Logging

entropic uses a NullHandler by default (no output). To see what the library is doing:

import logging
logging.getLogger("entropic").addHandler(logging.StreamHandler())
logging.getLogger("entropic").setLevel(logging.INFO)

Development

git clone https://github.com/jpvanegasc/entropic.git
cd entropic
uv sync --group dev
uv run pytest tests/ -v

License

MIT

Project details


Download files

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

Source Distribution

entropic-2.2.0.tar.gz (8.4 kB view details)

Uploaded Source

Built Distribution

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

entropic-2.2.0-py3-none-any.whl (10.5 kB view details)

Uploaded Python 3

File details

Details for the file entropic-2.2.0.tar.gz.

File metadata

  • Download URL: entropic-2.2.0.tar.gz
  • Upload date:
  • Size: 8.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.5 {"installer":{"name":"uv","version":"0.11.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for entropic-2.2.0.tar.gz
Algorithm Hash digest
SHA256 7769845ed6f6a289ef971499b91fa00235e264ef837cc1a29d847dfa856c2d25
MD5 ae28c2edd5e5cee995a5cdd5f23d506e
BLAKE2b-256 4101e82e32b7bdb169fb0b8e4c71d83b5a8f0ddc3a78c5ad211339c8ceab95dd

See more details on using hashes here.

File details

Details for the file entropic-2.2.0-py3-none-any.whl.

File metadata

  • Download URL: entropic-2.2.0-py3-none-any.whl
  • Upload date:
  • Size: 10.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.5 {"installer":{"name":"uv","version":"0.11.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for entropic-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f256eae2faf2a3157b599583a4f2dcd5776bfec32fc8430874cffc0022a0945e
MD5 659b776e9149e53e22012d8a1d5fa825
BLAKE2b-256 1702fe892cbe922ac15de3c5632f7298cc9fa6a0ff5323e3a04d85c3c6b47c6c

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