policyengine 3.1.8__tar.gz → 3.1.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. {policyengine-3.1.8 → policyengine-3.1.10}/CHANGELOG.md +14 -0
  2. {policyengine-3.1.8 → policyengine-3.1.10}/PKG-INFO +2 -1
  3. {policyengine-3.1.8 → policyengine-3.1.10}/changelog.yaml +10 -0
  4. {policyengine-3.1.8 → policyengine-3.1.10}/pyproject.toml +2 -1
  5. policyengine-3.1.10/src/policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
  6. policyengine-3.1.10/src/policyengine/core/cache.py +56 -0
  7. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/simulation.py +8 -0
  8. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine.egg-info/PKG-INFO +2 -1
  9. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine.egg-info/SOURCES.txt +2 -0
  10. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine.egg-info/requires.txt +1 -0
  11. policyengine-3.1.10/tests/test_cache.py +145 -0
  12. policyengine-3.1.8/src/policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
  13. {policyengine-3.1.8 → policyengine-3.1.10}/.claude/policyengine-guide.md +0 -0
  14. {policyengine-3.1.8 → policyengine-3.1.10}/.claude/quick-reference.md +0 -0
  15. {policyengine-3.1.8 → policyengine-3.1.10}/.github/CONTRIBUTING.md +0 -0
  16. {policyengine-3.1.8 → policyengine-3.1.10}/.github/changelog_template.md +0 -0
  17. {policyengine-3.1.8 → policyengine-3.1.10}/.github/fetch_version.py +0 -0
  18. {policyengine-3.1.8 → policyengine-3.1.10}/.github/get-changelog-diff.sh +0 -0
  19. {policyengine-3.1.8 → policyengine-3.1.10}/.github/has-functional-changes.sh +0 -0
  20. {policyengine-3.1.8 → policyengine-3.1.10}/.github/is-version-number-acceptable.sh +0 -0
  21. {policyengine-3.1.8 → policyengine-3.1.10}/.github/publish-git-tag.sh +0 -0
  22. {policyengine-3.1.8 → policyengine-3.1.10}/.github/workflows/code_changes.yaml +0 -0
  23. {policyengine-3.1.8 → policyengine-3.1.10}/.github/workflows/docs.yml +0 -0
  24. {policyengine-3.1.8 → policyengine-3.1.10}/.github/workflows/pr_code_changes.yaml +0 -0
  25. {policyengine-3.1.8 → policyengine-3.1.10}/.github/workflows/pr_docs_changes.yaml +0 -0
  26. {policyengine-3.1.8 → policyengine-3.1.10}/.github/workflows/versioning.yaml +0 -0
  27. {policyengine-3.1.8 → policyengine-3.1.10}/.gitignore +0 -0
  28. {policyengine-3.1.8 → policyengine-3.1.10}/CLAUDE.md +0 -0
  29. {policyengine-3.1.8 → policyengine-3.1.10}/LICENSE +0 -0
  30. {policyengine-3.1.8 → policyengine-3.1.10}/Makefile +0 -0
  31. {policyengine-3.1.8 → policyengine-3.1.10}/README.md +0 -0
  32. {policyengine-3.1.8 → policyengine-3.1.10}/changelog_entry.yaml +0 -0
  33. {policyengine-3.1.8 → policyengine-3.1.10}/docs/.gitignore +0 -0
  34. {policyengine-3.1.8 → policyengine-3.1.10}/docs/core-concepts.md +0 -0
  35. {policyengine-3.1.8 → policyengine-3.1.10}/docs/country-models-uk.md +0 -0
  36. {policyengine-3.1.8 → policyengine-3.1.10}/docs/country-models-us.md +0 -0
  37. {policyengine-3.1.8 → policyengine-3.1.10}/docs/dev.md +0 -0
  38. {policyengine-3.1.8 → policyengine-3.1.10}/docs/index.md +0 -0
  39. {policyengine-3.1.8 → policyengine-3.1.10}/docs/myst.yml +0 -0
  40. {policyengine-3.1.8 → policyengine-3.1.10}/docs/visualisation.md +0 -0
  41. {policyengine-3.1.8 → policyengine-3.1.10}/examples/employment_income_variation_uk.py +0 -0
  42. {policyengine-3.1.8 → policyengine-3.1.10}/examples/employment_income_variation_us.py +0 -0
  43. {policyengine-3.1.8 → policyengine-3.1.10}/examples/income_bands_uk.py +0 -0
  44. {policyengine-3.1.8 → policyengine-3.1.10}/examples/income_distribution_us.py +0 -0
  45. {policyengine-3.1.8 → policyengine-3.1.10}/examples/policy_change_uk.py +0 -0
  46. {policyengine-3.1.8 → policyengine-3.1.10}/examples/speedtest_us_simulation.py +0 -0
  47. {policyengine-3.1.8 → policyengine-3.1.10}/setup.cfg +0 -0
  48. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/__init__.py +0 -0
  49. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/__init__.py +0 -0
  50. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/dataset.py +0 -0
  51. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/dataset_version.py +0 -0
  52. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/dynamic.py +0 -0
  53. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/output.py +0 -0
  54. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/parameter.py +0 -0
  55. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/parameter_value.py +0 -0
  56. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/policy.py +0 -0
  57. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/tax_benefit_model.py +0 -0
  58. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/tax_benefit_model_version.py +0 -0
  59. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/core/variable.py +0 -0
  60. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/outputs/__init__.py +0 -0
  61. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/outputs/aggregate.py +0 -0
  62. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/outputs/change_aggregate.py +0 -0
  63. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/outputs/decile_impact.py +0 -0
  64. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/uk/__init__.py +0 -0
  65. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/uk/analysis.py +0 -0
  66. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/uk/datasets.py +0 -0
  67. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/uk/model.py +0 -0
  68. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/uk/outputs.py +0 -0
  69. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/uk.py +0 -0
  70. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/us/__init__.py +0 -0
  71. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/us/analysis.py +0 -0
  72. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/us/datasets.py +0 -0
  73. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/us/model.py +0 -0
  74. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/us/outputs.py +0 -0
  75. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/tax_benefit_models/us.py +0 -0
  76. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/utils/__init__.py +0 -0
  77. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/utils/dates.py +0 -0
  78. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/utils/parametric_reforms.py +0 -0
  79. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine/utils/plotting.py +0 -0
  80. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine.egg-info/dependency_links.txt +0 -0
  81. {policyengine-3.1.8 → policyengine-3.1.10}/src/policyengine.egg-info/top_level.txt +0 -0
  82. {policyengine-3.1.8 → policyengine-3.1.10}/tests/test_aggregate.py +0 -0
  83. {policyengine-3.1.8 → policyengine-3.1.10}/tests/test_change_aggregate.py +0 -0
  84. {policyengine-3.1.8 → policyengine-3.1.10}/tests/test_entity_mapping.py +0 -0
  85. {policyengine-3.1.8 → policyengine-3.1.10}/uv.lock +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.1.10] - 2025-12-02 13:02:37
9
+
10
+ ### Fixed
11
+
12
+ - Caching failure.
13
+
14
+ ## [3.1.9] - 2025-12-02 12:48:37
15
+
16
+ ### Fixed
17
+
18
+ - Added caching of saved simulations
19
+
8
20
  ## [3.1.8] - 2025-12-02 00:20:11
9
21
 
10
22
  ### Fixed
@@ -245,6 +257,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
245
257
 
246
258
 
247
259
 
260
+ [3.1.10]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.9...3.1.10
261
+ [3.1.9]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.8...3.1.9
248
262
  [3.1.8]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.7...3.1.8
249
263
  [3.1.7]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.6...3.1.7
250
264
  [3.1.6]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.5...3.1.6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.1.8
3
+ Version: 3.1.10
4
4
  Summary: A package to conduct policy analysis using PolicyEngine tax-benefit models.
5
5
  Author-email: PolicyEngine <hello@policyengine.org>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -673,6 +673,7 @@ Requires-Dist: pandas>=2.0.0
673
673
  Requires-Dist: microdf_python
674
674
  Requires-Dist: plotly>=5.0.0
675
675
  Requires-Dist: requests>=2.31.0
676
+ Requires-Dist: psutil>=5.9.0
676
677
  Provides-Extra: uk
677
678
  Requires-Dist: policyengine_core>=3.10; extra == "uk"
678
679
  Requires-Dist: policyengine-uk>=2.51.0; extra == "uk"
@@ -200,3 +200,13 @@
200
200
  fixed:
201
201
  - Dataset speedup with better handling of string cols.
202
202
  date: 2025-12-02 00:20:11
203
+ - bump: patch
204
+ changes:
205
+ fixed:
206
+ - Added caching of saved simulations
207
+ date: 2025-12-02 12:48:37
208
+ - bump: patch
209
+ changes:
210
+ fixed:
211
+ - Caching failure.
212
+ date: 2025-12-02 13:02:37
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "policyengine"
7
- version = "3.1.8"
7
+ version = "3.1.10"
8
8
  description = "A package to conduct policy analysis using PolicyEngine tax-benefit models."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -18,6 +18,7 @@ dependencies = [
18
18
  "microdf_python",
19
19
  "plotly>=5.0.0",
20
20
  "requests>=2.31.0",
21
+ "psutil>=5.9.0",
21
22
  ]
22
23
 
23
24
  [project.optional-dependencies]
@@ -0,0 +1,56 @@
1
+ import logging
2
+ from collections import OrderedDict
3
+
4
+ import psutil
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ _MEMORY_THRESHOLDS_GB = [8, 16, 32]
9
+ _warned_thresholds: set[int] = set()
10
+
11
+
12
+ class LRUCache[T]:
13
+ """Least-recently-used cache with configurable size limit and memory monitoring."""
14
+
15
+ def __init__(self, max_size: int = 100):
16
+ self._max_size = max_size
17
+ self._cache: OrderedDict[str, T] = OrderedDict()
18
+
19
+ def get(self, key: str) -> T | None:
20
+ """Get item from cache, marking it as recently used."""
21
+ if key not in self._cache:
22
+ return None
23
+ self._cache.move_to_end(key)
24
+ return self._cache[key]
25
+
26
+ def add(self, key: str, value: T) -> None:
27
+ """Add item to cache with LRU eviction when full."""
28
+ if key in self._cache:
29
+ self._cache.move_to_end(key)
30
+ else:
31
+ self._cache[key] = value
32
+ if len(self._cache) > self._max_size:
33
+ self._cache.popitem(last=False)
34
+
35
+ self._check_memory_usage()
36
+
37
+ def clear(self) -> None:
38
+ """Clear all items from cache."""
39
+ self._cache.clear()
40
+ _warned_thresholds.clear()
41
+
42
+ def __len__(self) -> int:
43
+ return len(self._cache)
44
+
45
+ def _check_memory_usage(self) -> None:
46
+ """Check memory usage and warn at threshold crossings."""
47
+ process = psutil.Process()
48
+ memory_gb = process.memory_info().rss / (1024**3)
49
+
50
+ for threshold in _MEMORY_THRESHOLDS_GB:
51
+ if memory_gb >= threshold and threshold not in _warned_thresholds:
52
+ logger.warning(
53
+ f"Memory usage has reached {memory_gb:.2f}GB (threshold: {threshold}GB). "
54
+ f"Cache contains {len(self._cache)} items."
55
+ )
56
+ _warned_thresholds.add(threshold)
@@ -3,11 +3,14 @@ from uuid import uuid4
3
3
 
4
4
  from pydantic import BaseModel, Field
5
5
 
6
+ from .cache import LRUCache
6
7
  from .dataset import Dataset
7
8
  from .dynamic import Dynamic
8
9
  from .policy import Policy
9
10
  from .tax_benefit_model_version import TaxBenefitModelVersion
10
11
 
12
+ _cache: LRUCache["Simulation"] = LRUCache(max_size=100)
13
+
11
14
 
12
15
  class Simulation(BaseModel):
13
16
  id: str = Field(default_factory=lambda: str(uuid4()))
@@ -25,12 +28,17 @@ class Simulation(BaseModel):
25
28
  self.tax_benefit_model_version.run(self)
26
29
 
27
30
  def ensure(self):
31
+ cached_result = _cache.get(self.id)
32
+ if cached_result:
33
+ self.output_dataset = cached_result.output_dataset
28
34
  try:
29
35
  self.tax_benefit_model_version.load(self)
30
36
  except Exception:
31
37
  self.run()
32
38
  self.save()
33
39
 
40
+ _cache.add(self.id, self)
41
+
34
42
  def save(self):
35
43
  """Save the simulation's output dataset."""
36
44
  self.tax_benefit_model_version.save(self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.1.8
3
+ Version: 3.1.10
4
4
  Summary: A package to conduct policy analysis using PolicyEngine tax-benefit models.
5
5
  Author-email: PolicyEngine <hello@policyengine.org>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -673,6 +673,7 @@ Requires-Dist: pandas>=2.0.0
673
673
  Requires-Dist: microdf_python
674
674
  Requires-Dist: plotly>=5.0.0
675
675
  Requires-Dist: requests>=2.31.0
676
+ Requires-Dist: psutil>=5.9.0
676
677
  Provides-Extra: uk
677
678
  Requires-Dist: policyengine_core>=3.10; extra == "uk"
678
679
  Requires-Dist: policyengine-uk>=2.51.0; extra == "uk"
@@ -44,6 +44,7 @@ src/policyengine.egg-info/requires.txt
44
44
  src/policyengine.egg-info/top_level.txt
45
45
  src/policyengine/__pycache__/__init__.cpython-313.pyc
46
46
  src/policyengine/core/__init__.py
47
+ src/policyengine/core/cache.py
47
48
  src/policyengine/core/dataset.py
48
49
  src/policyengine/core/dataset_version.py
49
50
  src/policyengine/core/dynamic.py
@@ -76,5 +77,6 @@ src/policyengine/utils/dates.py
76
77
  src/policyengine/utils/parametric_reforms.py
77
78
  src/policyengine/utils/plotting.py
78
79
  tests/test_aggregate.py
80
+ tests/test_cache.py
79
81
  tests/test_change_aggregate.py
80
82
  tests/test_entity_mapping.py
@@ -3,6 +3,7 @@ pandas>=2.0.0
3
3
  microdf_python
4
4
  plotly>=5.0.0
5
5
  requests>=2.31.0
6
+ psutil>=5.9.0
6
7
 
7
8
  [dev]
8
9
  black
@@ -0,0 +1,145 @@
1
+ import os
2
+ import tempfile
3
+
4
+ import pandas as pd
5
+ from microdf import MicroDataFrame
6
+
7
+ from policyengine.core import Simulation
8
+ from policyengine.core.cache import LRUCache
9
+ from policyengine.tax_benefit_models.uk import (
10
+ PolicyEngineUKDataset,
11
+ UKYearData,
12
+ uk_latest,
13
+ )
14
+
15
+
16
+ def test_simulation_cache_hit():
17
+ """Test that simulation caching works with UK simulations."""
18
+ person_df = MicroDataFrame(
19
+ pd.DataFrame(
20
+ {
21
+ "person_id": [1, 2, 3],
22
+ "benunit_id": [1, 1, 2],
23
+ "household_id": [1, 1, 2],
24
+ "age": [30, 25, 40],
25
+ "employment_income": [50000, 30000, 60000],
26
+ "person_weight": [1.0, 1.0, 1.0],
27
+ }
28
+ ),
29
+ weights="person_weight",
30
+ )
31
+
32
+ benunit_df = MicroDataFrame(
33
+ pd.DataFrame(
34
+ {
35
+ "benunit_id": [1, 2],
36
+ "benunit_weight": [1.0, 1.0],
37
+ }
38
+ ),
39
+ weights="benunit_weight",
40
+ )
41
+
42
+ household_df = MicroDataFrame(
43
+ pd.DataFrame(
44
+ {
45
+ "household_id": [1, 2],
46
+ "household_weight": [1.0, 1.0],
47
+ }
48
+ ),
49
+ weights="household_weight",
50
+ )
51
+
52
+ with tempfile.TemporaryDirectory() as tmpdir:
53
+ filepath = os.path.join(tmpdir, "test.h5")
54
+
55
+ dataset = PolicyEngineUKDataset(
56
+ name="Test",
57
+ description="Test dataset",
58
+ filepath=filepath,
59
+ year=2024,
60
+ data=UKYearData(
61
+ person=person_df, benunit=benunit_df, household=household_df
62
+ ),
63
+ )
64
+
65
+ simulation = Simulation(
66
+ dataset=dataset,
67
+ tax_benefit_model_version=uk_latest,
68
+ output_dataset=dataset,
69
+ )
70
+
71
+ # Import the cache
72
+ from policyengine.core.simulation import _cache
73
+
74
+ # Manually add to cache (simulating what ensure() does)
75
+ _cache.add(simulation.id, simulation)
76
+
77
+ # Verify simulation is in cache
78
+ assert simulation.id in _cache._cache
79
+ assert len(_cache) >= 1
80
+
81
+ # Verify cache returns same object
82
+ cached_sim = _cache.get(simulation.id)
83
+ assert cached_sim is simulation
84
+
85
+ # Clear cache for other tests
86
+ _cache.clear()
87
+
88
+
89
+ def test_lru_cache_eviction():
90
+ """Test that LRU cache properly evicts old items."""
91
+ cache = LRUCache[str](max_size=3)
92
+
93
+ cache.add("a", "value_a")
94
+ cache.add("b", "value_b")
95
+ cache.add("c", "value_c")
96
+
97
+ assert len(cache) == 3
98
+ assert cache.get("a") == "value_a"
99
+
100
+ # Add fourth item, should evict 'b' (least recently used)
101
+ cache.add("d", "value_d")
102
+
103
+ assert len(cache) == 3
104
+ assert cache.get("b") is None
105
+ assert cache.get("a") == "value_a"
106
+ assert cache.get("c") == "value_c"
107
+ assert cache.get("d") == "value_d"
108
+
109
+
110
+ def test_lru_cache_access_updates_order():
111
+ """Test that accessing items updates their position in LRU order."""
112
+ cache = LRUCache[str](max_size=3)
113
+
114
+ cache.add("a", "value_a")
115
+ cache.add("b", "value_b")
116
+ cache.add("c", "value_c")
117
+
118
+ # Access 'a' to move it to most recently used
119
+ cache.get("a")
120
+
121
+ # Add fourth item, should evict 'b' (now least recently used)
122
+ cache.add("d", "value_d")
123
+
124
+ assert cache.get("a") == "value_a"
125
+ assert cache.get("b") is None
126
+ assert cache.get("c") == "value_c"
127
+ assert cache.get("d") == "value_d"
128
+
129
+
130
+ def test_lru_cache_clear():
131
+ """Test that clearing cache works properly."""
132
+ cache = LRUCache[str](max_size=10)
133
+
134
+ cache.add("a", "value_a")
135
+ cache.add("b", "value_b")
136
+ cache.add("c", "value_c")
137
+
138
+ assert len(cache) == 3
139
+
140
+ cache.clear()
141
+
142
+ assert len(cache) == 0
143
+ assert cache.get("a") is None
144
+ assert cache.get("b") is None
145
+ assert cache.get("c") is None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes