policyengine 3.1.7__py3-none-any.whl → 3.1.9__py3-none-any.whl

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.
@@ -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
+ return cached_result
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)
@@ -40,14 +40,37 @@ class PolicyEngineUKDataset(Dataset):
40
40
  self.load()
41
41
 
42
42
  def save(self) -> None:
43
- """Save dataset to HDF5 file."""
43
+ """Save dataset to HDF5 file.
44
+
45
+ Converts object columns to categorical dtype to avoid slow pickle serialization.
46
+ """
44
47
  filepath = Path(self.filepath)
45
48
  if not filepath.parent.exists():
46
49
  filepath.parent.mkdir(parents=True, exist_ok=True)
50
+
51
+ # Convert DataFrames and optimize object columns to categorical
52
+ person_df = pd.DataFrame(self.data.person)
53
+ benunit_df = pd.DataFrame(self.data.benunit)
54
+ household_df = pd.DataFrame(self.data.household)
55
+
56
+ # Convert object columns to categorical to avoid pickle serialization
57
+ for col in person_df.columns:
58
+ if person_df[col].dtype == "object":
59
+ person_df[col] = person_df[col].astype("category")
60
+
61
+ for col in benunit_df.columns:
62
+ if benunit_df[col].dtype == "object":
63
+ benunit_df[col] = benunit_df[col].astype("category")
64
+
65
+ for col in household_df.columns:
66
+ if household_df[col].dtype == "object":
67
+ household_df[col] = household_df[col].astype("category")
68
+
47
69
  with pd.HDFStore(filepath, mode="w") as store:
48
- store["person"] = pd.DataFrame(self.data.person)
49
- store["benunit"] = pd.DataFrame(self.data.benunit)
50
- store["household"] = pd.DataFrame(self.data.household)
70
+ # Use format='table' to support categorical dtypes
71
+ store.put("person", person_df, format="table")
72
+ store.put("benunit", benunit_df, format="table")
73
+ store.put("household", household_df, format="table")
51
74
 
52
75
  def load(self) -> None:
53
76
  """Load dataset from HDF5 file into this instance."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.1.7
3
+ Version: 3.1.9
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"
@@ -1,6 +1,7 @@
1
1
  policyengine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- policyengine/__pycache__/__init__.cpython-313.pyc,sha256=QdDnJpEU322F2GyH3GCCTbAoQy1XElYycMgEbuzy5Lw,175
2
+ policyengine/__pycache__/__init__.cpython-313.pyc,sha256=W_AvDMmqtW4p3mYpguIKS3z3ko3AZldZ8QOmrZNnYl4,175
3
3
  policyengine/core/__init__.py,sha256=KBVhkqzkvjWLDDwk96vquQKL63ZFuLen5AzBOBnO9pg,912
4
+ policyengine/core/cache.py,sha256=DcVVFaCt7k9PmqwlhXoNDMtJ8sF4neYP1uRqWik5QYg,1812
4
5
  policyengine/core/dataset.py,sha256=iJr9-J6w11uMRYy3EEJO9Gveku1m71AA1yzeo-0SiCs,16094
5
6
  policyengine/core/dataset_version.py,sha256=6KeFCRGQto_Yyl4QY4Vo2JFythjaXrNAOHQiwRGESyM,378
6
7
  policyengine/core/dynamic.py,sha256=ng9BjDzxdwjJ0e7zoqXFmq33E1SRbaaPYfW7pjRSSzI,1641
@@ -8,7 +9,7 @@ policyengine/core/output.py,sha256=cCW4vbzkLdQaT_nJTyDJBl7Hubm7nZeRuR7aVG1dKvg,6
8
9
  policyengine/core/parameter.py,sha256=8RKKuGCDW1OoHPoXI9vF3JY6COc1qhUtMolXTUxPoEs,626
9
10
  policyengine/core/parameter_value.py,sha256=ZRBZWFYtaY9TqdgjrCymzOZNmuKOBZsrWBET24DIJ_Q,434
10
11
  policyengine/core/policy.py,sha256=ExMrUDMvNk_uuOL0cSm0UCzDyGka0t_yk6x4U0Kp6Ww,1635
11
- policyengine/core/simulation.py,sha256=yvvved75XMcGP3Bj9E2tmKRxvI-DQVZv7k4uTETwBm0,1134
12
+ policyengine/core/simulation.py,sha256=_drlY5C41I8_V6RibtdDIdl1lJ757akIeIdViFMizbA,1357
12
13
  policyengine/core/tax_benefit_model.py,sha256=2Yc1RlQrUG7djDMZbJOQH4Ns86_lOnLeISCGR4-9zMo,176
13
14
  policyengine/core/tax_benefit_model_version.py,sha256=V1CGft5Y6YflMASx0wR3V73jr-WqQu2R8N5QVMRm9yw,2752
14
15
  policyengine/core/variable.py,sha256=AjSImORlRkh05xhYxyeT6GFMOfViRzYg0qRQAIj-mxo,350
@@ -20,7 +21,7 @@ policyengine/tax_benefit_models/uk.py,sha256=HzAG_dORmsj1NJ9pd9WrqwgZPe9DUDrZ1wV
20
21
  policyengine/tax_benefit_models/us.py,sha256=G51dAmHo8NJLb2mnbne6iO5eNaatCGUd_2unvawwF84,946
21
22
  policyengine/tax_benefit_models/uk/__init__.py,sha256=AiA74iED5FEryvUCMfVZi6pYDYuTfQcj9B01h8J5xFA,1105
22
23
  policyengine/tax_benefit_models/uk/analysis.py,sha256=O4eYJYF7tsgiuLuiWMU0OXq7ss6U8-vzlg6nC2U8sgU,3175
23
- policyengine/tax_benefit_models/uk/datasets.py,sha256=-lmj4eG2my2GGmMMkxI1iXobGQW5irBgylEwyV0xU6c,8039
24
+ policyengine/tax_benefit_models/uk/datasets.py,sha256=_puLrvNo700HFv17TLGXufXRFcrLS9-pBVjYwp1VFac,8989
24
25
  policyengine/tax_benefit_models/uk/model.py,sha256=djCLLBeik9ZGFqe5WXUNVcu1JXjh0U8MXakvncasUj0,9754
25
26
  policyengine/tax_benefit_models/uk/outputs.py,sha256=2mYLwQW4QNvrOHtHfm_ACqE9gbmuLxvcCyldRU46s0o,3543
26
27
  policyengine/tax_benefit_models/us/__init__.py,sha256=zP-UUQqOc9g0ymyHkweJdi4RVXQDKSR6SUxavUKvV0s,1101
@@ -32,8 +33,8 @@ policyengine/utils/__init__.py,sha256=1X-VYAWLyB9A0YRHwsGWrqQHns1WfeZ7ISC6DMU5my
32
33
  policyengine/utils/dates.py,sha256=HnAqyl8S8EOYp8ibsnMTmECYoDWCSqwL-7A2_qKgxSc,1510
33
34
  policyengine/utils/parametric_reforms.py,sha256=4P3U39-4pYTU4BN6JjgmVLUkCkBhRfZJ6UIWTlsjyQE,1155
34
35
  policyengine/utils/plotting.py,sha256=ZAzTWz38vIaW0c3Nt4Un1kfrNoXLyHCDd1pEJIlsRg4,5335
35
- policyengine-3.1.7.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
36
- policyengine-3.1.7.dist-info/METADATA,sha256=stN3kD7499L_CWLa2RQwzpb_GYtJ8kC5RiDIY-1Tpww,45889
37
- policyengine-3.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- policyengine-3.1.7.dist-info/top_level.txt,sha256=_23UPobfkneHQkpJ0e0OmDJfhCUfoXj_F2sTckCGOH4,13
39
- policyengine-3.1.7.dist-info/RECORD,,
36
+ policyengine-3.1.9.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
37
+ policyengine-3.1.9.dist-info/METADATA,sha256=x-6iT0pQLAvHjgtIeZvyauWML0sZGJJ3oeIKQoc-3aU,45918
38
+ policyengine-3.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
+ policyengine-3.1.9.dist-info/top_level.txt,sha256=_23UPobfkneHQkpJ0e0OmDJfhCUfoXj_F2sTckCGOH4,13
40
+ policyengine-3.1.9.dist-info/RECORD,,