Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fix-spiral-anchored-recursion.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Evaluate finite anchored period recursions in full regardless of earlier calculations, tracing or cache policy, and discard calculated values from cut recursions without deleting inputs or retaining invalid values in clones.
5 changes: 5 additions & 0 deletions docs/python_api/data_storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

The `policyengine_core.data_storage` module contains the classes that are used to handle the storage of data in simulations.

Both storage classes provide `delete_exact(period, branch_name, derived_only)`
to remove a single stored period, leaving contained periods in place.
With `derived_only=True`, an input at that key remains stored; this is
used to discard provisional results from cut period recursions.

## InMemoryStorage

```{eval-rst}
Expand Down
6 changes: 6 additions & 0 deletions docs/python_api/tracers.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ The `policyengine_core.tracers` module contains classes used to represent tracer

## FlatTrace

Flat traces retain the first completed calculation's dependencies and parameters
when a later request reads its cache. When the period-recursion scheduler retries
a calculation, a completed accepted result replaces an abandoned or provisional
cut result for the same variable, period, and branch. The full computation and
performance logs still contain the retry attempts.

```{eval-rst}
.. autoclass:: policyengine_core.tracers.flat_trace.FlatTrace
:members:
Expand Down
20 changes: 20 additions & 0 deletions policyengine_core/data_storage/in_memory_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,26 @@ def delete(self, period: Period = None, branch_name: str = "default") -> None:
self._stop_sharing_dropped_keys()
self._unmark_dropped_keys()

def delete_exact(
self,
period: Period,
branch_name: str = "default",
derived_only: bool = False,
) -> None:
"""Drop the array stored for exactly ``period``.

Unlike ``delete``, this leaves the periods ``period`` contains. With
``derived_only``, an input stored there stays.
"""
if self.is_eternal:
period = periods.period(periods.ETERNITY)
key = f"{branch_name}:{periods.period(period)}"
if derived_only and key in self._inputs:
return
self._arrays.pop(key, None)
self._stop_sharing(key)
self._unmark_input(key)

def get_known_periods(self) -> list:
# Split on the first colon only: an anchored period's string form
# itself contains colons (e.g. "default:year:2027-11").
Expand Down
16 changes: 16 additions & 0 deletions policyengine_core/data_storage/on_disk_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,22 @@ def delete(self, period: Period = None, branch_name: str = "default") -> None:
}
self._derived.intersection_update(self._files)

def delete_exact(
self,
period: Period,
branch_name: str = "default",
derived_only: bool = False,
) -> None:
"""Drop the file recorded for exactly ``period``. With
``derived_only``, an input stored there stays."""
if self.is_eternal:
period = periods.period(periods.ETERNITY)
key = f"{branch_name}_{periods.period(period)}"
if derived_only and key in self._files and key not in self._derived:
return
self._files.pop(key, None)
self._derived.discard(key)

def get_known_periods(self) -> list:
return list([periods.period(x.split("_")[1]) for x in self._files.keys()])

Expand Down
16 changes: 16 additions & 0 deletions policyengine_core/holders/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ def delete_arrays(
if self._disk_storage:
self._disk_storage.delete(period, branch_name)

def delete_array(
self,
period: Period,
branch_name: str = "default",
derived_only: bool = False,
) -> None:
"""
Remove the value stored for exactly ``period`` under ``branch_name``, leaving the periods it contains (unlike ``delete_arrays``).

With ``derived_only``, remove it only if the simulation calculated it (see ``put_in_cache``): an input stored there stays.
"""
self._memory_storage.delete_exact(period, branch_name, derived_only)
if self._disk_storage:
self._disk_storage.delete_exact(period, branch_name, derived_only)

def _get_array_from_storage(
self, period: Period, branch_name: str = "default"
) -> ArrayLike:
Expand Down Expand Up @@ -423,6 +438,7 @@ def put_in_cache(
return

self._set(period, value, branch_name, derived=derived)
self.simulation._note_cached_value(self.variable.name, period)

def default_array(self) -> ArrayLike:
"""
Expand Down
Loading
Loading