Changelog¶
Unreleased¶
laser.core.plottinginput-validation hardening. Bothchoroplethandanimate_choroplethnow reject bad inputs at the API boundary with cleanValueError/TypeErrormessages rather than leaking low-level NumPy, matplotlib, or geopandas errors:Non-numeric
values/column/values_by_frame→TypeError/ValueErrornaming the offending dtype, instead of a downstream matplotlib or NumPyufuncerror.Ragged
values_by_frame(e.g.[[1, 2, 3], [4, 5]]) → cleanValueError("values_by_frame must be a rectangular numeric array ...")instead of NumPy’s"setting an array element with a sequence"internals surfacing up.log=Truewith any value<= -1→ValueError("log=True requires all plotted values to be greater than -1")instead of silently plotting-inf/NaN(log1pis undefined there).
Add
laser.core.plotting— a new dependency-light module for region-keyed spatial plotting, ported intolaser.corefrom an upstream project. Two helpers, both operating on aGeoDataFrame(or a path to a vector file readable bygeopandas.read_file) with per-region values supplied via either an existing column or an aligned array:``choropleth`` — a static single-panel map. Values come from
column=(an existing column) orvalues=(an aligned per-region array, so per-node model output can be mapped without materializing it back onto the geometry file). Supports log scaling, explicitvmin/vmaxfor shared scales across multiple calls, and composition into subplot grids viaax=.``animate_choropleth`` — the same map, animated over a sequence of per-region value frames with a shared colorbar across all frames so absolute colors are directly comparable frame-to-frame. Output format is inferred from the
output_pathextension:.gif→PillowWriter(Pillow is a hard dep of matplotlib — no extra install)..html/.htm→ self-contained JS animation viato_jshtml()(matplotlib only — no extra install)..mp4/.webm/.mov→FFMpegWriter(requires theffmpegbinary onPATH). A missing binary raisesRuntimeErrorwithbrew/apt-get/condainstall hints rather than a bare matplotlib traceback.
Full test coverage lands with the module:
tests/test_plotting.pycovers both helpers (column and values plot paths, log transform, path-loading, subplot composition, non-mutation of the input, and — for the animation — frame-count handling, shared-scale semantics, GIF and HTML output writing, the ffmpeg install-hint error,fps > 0validation, and region-count / empty / mismatched-title / non-string input rejection). Newexamples/choropleth_examples.ipynbruns (a) a single choropleth of Mali admin-2 population fromexamples/MLI_admin2.gpkg, (b) a 3×2 grid of stochastic per-patch values, and (c) a 24-frame animated choropleth in all three output formats.calc_capacity()now returns auint32array (previouslyint32) and clamps per-node estimates that exceed2**32 - 1to theuint32maximum before casting, preventing silent overflow / wraparound for very large populations.Add
test_un_member_states_2020_laserframe_exceeds_uint32_maxexercising aLaserFramewhose total agent count exceedsuint32.max, built from 2020 UN population estimates for all 193 member states (one node per country). The test allocates ~24 GiB of property storage and self-skips on machines without sufficient free RAM. New fixturetests/data/un_member_states_2020.csv.Add
test_calc_capacity_rejects_negative_initial_popcovering theinitial_pop >= 0validation incalc_capacity()(single negative, multiple negatives with offending values surfaced in the message, and a zero-allowed sanity case).calc_capacity()gains optional mortality. New keyword-only parametersdeathrates(a 2D CDR matrix matching thebirthratesshape; defaultNonefor backward compat) andmortality_safety_factor(default1.0) switch the function from a cumulative-births bound to a peak-living bound appropriate for simulations that reclaim dead-agent slots viaLaserFrame.squash. Mortality is intentionally underestimated: only1 / (1 + mortality_safety_factor)of the death rate is credited against births, with the remainder held back as headroom against a lower-mortality realization. Per-node estimates floor atinitial_popwhendeathratesis provided. Math mirrors thecalc_capacity_cdrfunction in razer. New tests cover backward- compatibility (zero deaths == no deaths), monotonicity inmortality_safety_factor, the net-shrinking floor, validation, and the keyword-only API surface.calc_capacity()deaths-aware path now bounds the peak-across-time net growth rather than the end-of-simulation cumulative. Fluctuating CBR / CDR (e.g. high CBR early followed by high CDR later) can produce an intermediate peak in living population well above the end-of-sim value; the previous formula usedexp(sum_b - death_credit * sum_d)(an end-of- sim cumulative) and would have under-allocated against that peak. Now computesmax_t cumsum(lamda_b - death_credit * lamda_d), floored at 0, and uses that exponent. Monotonic-growth scenarios (births dominate deaths every tick) are unchanged because peak == end when the cumulative is monotonic. New tests cover the spike-then-decline scenario (asserts new > end-of-sim) and the monotonic case (asserts new == end-of-sim). Illustrative script + plot undermisc/calc_capacity_peak_vs_end.{py,png}.Implemented
LaserFrame.__contains__so thatname in lfmatches every name the LaserFrame documented access surface advertises, and nothing more:Truefor property names added viaadd_scalar_property/add_vector_property/add_array_property(e.g."age" in lf).Truefor the underscored backing arrays of registered scalar / vector properties (e.g."_age" in lf) — these are a documented “underlying array access” path (see the module docstring andtest_underlying_array_access). The rule is “an underscored name matches iff its public counterpart is a registered property”, which automatically keeps internal state filtered.Truefor non-underscored kwargs-set attributes (e.g.LaserFrame(N, start_year=1944)→"start_year" in lf).Truefor a user-added property whose public name itself begins with_(e.g.lf.add_scalar_property("_private_col")→"_private_col" in lf) — the property registry is always honored, regardless of name.Falsefor internal state (_count,_capacity,_properties) — these live in__dict__but are not advertised columns or backing arrays. Direct access vialf._countetc. is unchanged; only theinadvertisement is filtered.Falsefor methods and class-level@propertydescriptors (count,capacity,sort); usehasattr(lf, name)for that broader question.Falsefor non-string items, including unhashable ones (a short-circuitisinstance(item, str)check makes the test safe for lists / dicts).
Eleven new tests pin the contract.
1.0.2 (2026-05-19)¶
Prune the supported Python test matrix to 3.10 (oldest) and 3.14 (newest released).
Remove the unused
argsparameter fromkmestimatorfor a fasterlaser.coreimport.Update release/publish infrastructure and the GitHub Actions workflow.
Remove deprecated files and tidy the project structure.
Linter clean-up.
1.0.1 (2026-02-26)¶
Add support for Python 3.14 now that Numba supports it.
Fix snapshot capacity restoration and CBR handling for multi-node models (#355).
Improve calibration documentation (#354).
Documentation polish: fix README formatting, fix the LASER documentation link, clarify install/build instructions, and bring CHANGELOG up to date.
Update copyright year in
LICENSE.Update project URLs and the required
uvversion.Remove macOS x86_64 entries from the CI configuration.
Make Sphinx happier.
1.0.0 (2025-12-16)¶
Fix documentation link in README
Major version release with stable API
0.9.1 (2025-12-09)¶
Fix bug in initialize_population() function - wasn’t handling lists correctly
0.9.0 (2025-12-09)¶
Add initialize_population() function to distribute population among states
Grid creates default state columns
Update OS versions in CI workflow
Add tests for new population initialization features
0.8.1 (2025-12-08)¶
Fix PropertySet to correctly handle lists or tuples of key value pairs
Improve PropertySet docstrings and documentation
Add test for PropertySet constructor exception
Fix up docstrings for MkDocs
Replace SortedQueue with Distributions in non-auto API reference
0.8.0 (2025-11-25)¶
Implement new calc_capacity() function using geometric Brownian motion
Update README to reference laser-generic package
Switch MacOS runner to macos-15-intel
Address caching issues with sample functions
CI improvements
0.7.0 (2025-10-30)¶
Add Numba-compatible distributions for model waiting times
Change distribution signatures to take tick and node parameters
Cache in-proc with lru_cache rather than Numba caching
Remove redundant count from samplers
Validate max_year argument to predict_year_of_death()
Default properties to current count of active agents
Remove deprecated calc_distances() in favor of distance()
Add grid generation utility function
Enable Python 3.13 support
Allow NumPy integers in LaserFrame()
Add describe() function to LaserFrame
Update LICENSE copyright
Adapt docs to shared top level domain change
Update docstring formatting for mkdocs build
0.6.0 (2025-07-24)¶
Fix snapshot capacity restoration
Fix cap reload functionality
Various bug fixes for snapshot handling
0.5.1 (2025-06-05)¶
Bug fixes for version 0.5.0
0.5.0 (2025-06-02)¶
Add save_snapshot and load_snapshot functionality
Add additional examples for PropertySet operators
Improve PropertySet functionality: - Update
+=to only accept new keys - Add<<=to update existing keys - Add|=to add or update keysFix overflow in migration matrix calculations
Update row_normalizer() to return float32
Update calibration documentation
Add performance section to documentation
Add spatial examples and migration samples
Build wheels with GitHub Actions
0.4.1 (2025-05-27)¶
0.4.0 (2025-01-31)¶
Remove NumPy and Numba version restrictions
0.3.0 (2025-01-28)¶
Add spatial reference support
Expose functionality directly from laser_core namespace
Enhance distance() function options
Implement add_array_property
Promote methods to laser_core
Add unit tests for utility functions
Cleanup pyproject.toml
0.2.0 (2025-01-15)¶
Add documentation content for architecture and design
Point to PyPI package (not test site)
Documentation enhancements for GPT integration
Additional badges for PyPI
0.1.1 (2024-11-19)¶
Downgrade Numba dependency to 0.59.1
Point doc links to IDM sites
Update badge links
0.1.0 (2024-11-18)¶
Switch to bump-my-version for version management
Update NumPy and Numba versions for Python 3.12 support
Fix argsort() with kind=”stable” for newer NumPy
Update migration tests to use np.int64
Update documentation
0.0.3 (2024-11-05)¶
Add __setitem__ to PropertySet
Add __getitem__ to PropertySet
Modify coverage configuration to ignore if __name__ == “__main__”
0.0.2 (2024-11-04)¶
Add SortedQueue types
Add Kaplan-Meier Estimator for predicting age/year of death
Add AliasedDistribution class for drawing from population pyramids
Add LaserFrame class (renamed from Population)
Add PropertySet class
Add migration models and functions
Add random number seed functions
Enable testing on arm64 (Apple Silicon)
Build fixes and improvements
Extensive documentation cleanup
Update GitHub action versions
Fix author and copyright information
Add many unit tests
0.0.1 (2023-11-18)¶
Initial package structure