Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4a2d5d1
Add periods to doctests path
bonjourmauko Jul 28, 2022
63931f5
Add periods to tests path
bonjourmauko Jul 28, 2022
384cb8e
Ignore mypy periods' tests errors
bonjourmauko Jul 28, 2022
1226334
Fix instants' docstrings
bonjourmauko Jul 28, 2022
7112262
Fix periods' doctests
bonjourmauko Jul 28, 2022
e8e647b
Fix periods' helpers doctests
bonjourmauko Jul 28, 2022
9fd327c
Add tests to periods.instant
bonjourmauko Jul 30, 2022
9caa64a
Add tests to periods.instant_date
bonjourmauko Jul 30, 2022
42f52b8
Add tests to periods.key_period_size
bonjourmauko Jul 30, 2022
2ab5548
Add doctest to periods.unit_weights
bonjourmauko Jul 30, 2022
db4e06a
Add doctest to periods.unit_weight
bonjourmauko Jul 30, 2022
56ad038
Fix typo in doc
bonjourmauko Jul 30, 2022
8bd4ecf
Extract raise error from period builder
bonjourmauko Jul 30, 2022
f2ff5b3
Refactor period's str year tests
bonjourmauko Jul 30, 2022
213e98a
Refactor period's str month tests
bonjourmauko Jul 30, 2022
2694717
Refactor period's str day tests
bonjourmauko Jul 30, 2022
de8a3ac
Consolidate tests
bonjourmauko Jul 30, 2022
699aff2
Add date units to periods
bonjourmauko Jul 29, 2022
3cf30f4
Add week to variables
bonjourmauko Jul 29, 2022
3886f35
Fix week/day offset
bonjourmauko Jul 31, 2022
b724bce
Fix eternity edge case
bonjourmauko Jul 31, 2022
332d5d7
Skip tests with deprecated syntax
bonjourmauko Jun 26, 2023
b20bf8c
Fix failing parsing test
bonjourmauko Jun 26, 2023
f997309
Fix conda build
bonjourmauko Jun 26, 2023
607f9e8
Bump version
bonjourmauko Jun 26, 2023
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
3 changes: 2 additions & 1 deletion .conda/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ requirements:
{% for req in data.get('install_requires', []) %}
- {{ req }}
{% endfor %}
# - python >=3.9,<4.0
# - PyYAML >=6.0,<7.0
# - dpath >=2.1.4,<3.0.0
# - importlib-metadata >=6.1.0,<7.0
# - numexpr >=2.8.4,<=3.0
# - numpy >=1.24.2,<1.25.0
# - pendulum >=2.1.2,<3.0.0
# - psutil >=5.9.4,<6.0.0
# - pytest >=7.2.2,<8.0.0
# - python >=3.9,<4.0
# - sortedcontainers >=2.4.0
# - typing-extensions >=4.5.0,<5.0

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 40.1.0 [#1174](https://github.com/openfisca/openfisca-core/pull/1174)

#### New Features

* Allows for dispatching and dividing inputs over a broader range.
* For example, divide a monthly variable by week.

### 40.0.1 [#1184](https://github.com/openfisca/openfisca-core/pull/1184)

#### Technical changes
Expand Down
7 changes: 4 additions & 3 deletions openfisca_core/data_storage/in_memory_storage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy

from openfisca_core import periods
from openfisca_core.periods import DateUnit


class InMemoryStorage:
Expand All @@ -14,7 +15,7 @@ def __init__(self, is_eternal=False):

def get(self, period):
if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(DateUnit.ETERNITY)
period = periods.period(period)

values = self._arrays.get(period)
Expand All @@ -24,7 +25,7 @@ def get(self, period):

def put(self, value, period):
if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(DateUnit.ETERNITY)
period = periods.period(period)

self._arrays[period] = value
Expand All @@ -35,7 +36,7 @@ def delete(self, period=None):
return

if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(DateUnit.ETERNITY)
period = periods.period(period)

self._arrays = {
Expand Down
7 changes: 4 additions & 3 deletions openfisca_core/data_storage/on_disk_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import numpy

from openfisca_core import periods
from openfisca_core.periods import DateUnit
from openfisca_core.indexed_enums import EnumArray


Expand All @@ -28,7 +29,7 @@ def _decode_file(self, file):

def get(self, period):
if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(DateUnit.ETERNITY)
period = periods.period(period)

values = self._files.get(period)
Expand All @@ -38,7 +39,7 @@ def get(self, period):

def put(self, value, period):
if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(DateUnit.ETERNITY)
period = periods.period(period)

filename = str(period)
Expand All @@ -55,7 +56,7 @@ def delete(self, period=None):
return

if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(DateUnit.ETERNITY)
period = periods.period(period)

if period is not None:
Expand Down
15 changes: 9 additions & 6 deletions openfisca_core/holders/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import numpy

from openfisca_core import periods
from openfisca_core.periods import Period

log = logging.getLogger(__name__)

Expand All @@ -21,7 +20,9 @@ def set_input_dispatch_by_period(holder, period, array):
period_size = period.size
period_unit = period.unit

if holder.variable.definition_period == periods.ETERNITY:
if holder.variable.definition_period not in (
periods.DateUnit.isoformat + periods.DateUnit.isocalendar
):
raise ValueError(
"set_input_dispatch_by_period can't be used for eternal variables."
)
Expand All @@ -30,7 +31,7 @@ def set_input_dispatch_by_period(holder, period, array):
after_instant = period.start.offset(period_size, period_unit)

# Cache the input data, skipping the existing cached months
sub_period = Period((cached_period_unit, period.start, 1))
sub_period = periods.Period((cached_period_unit, period.start, 1))
while sub_period.start < after_instant:
existing_array = holder.get_array(sub_period)
if existing_array is None:
Expand All @@ -55,7 +56,9 @@ def set_input_divide_by_period(holder, period, array):
period_size = period.size
period_unit = period.unit

if holder.variable.definition_period == periods.ETERNITY:
if holder.variable.definition_period not in (
periods.DateUnit.isoformat + periods.DateUnit.isocalendar
):
raise ValueError(
"set_input_divide_by_period can't be used for eternal variables."
)
Expand All @@ -65,7 +68,7 @@ def set_input_divide_by_period(holder, period, array):

# Count the number of elementary periods to change, and the difference with what is already known.
remaining_array = array.copy()
sub_period = Period((cached_period_unit, period.start, 1))
sub_period = periods.Period((cached_period_unit, period.start, 1))
sub_periods_count = 0
while sub_period.start < after_instant:
existing_array = holder.get_array(sub_period)
Expand All @@ -78,7 +81,7 @@ def set_input_divide_by_period(holder, period, array):
# Cache the input data
if sub_periods_count > 0:
divided_array = remaining_array / sub_periods_count
sub_period = Period((cached_period_unit, period.start, 1))
sub_period = periods.Period((cached_period_unit, period.start, 1))
while sub_period.start < after_instant:
if holder.get_array(sub_period) is None:
holder._set(sub_period, divided_array)
Expand Down
34 changes: 17 additions & 17 deletions openfisca_core/holders/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ def __init__(self, variable, population):
self.population = population
self.variable = variable
self.simulation = population.simulation
self._memory_storage = storage.InMemoryStorage(
is_eternal=(self.variable.definition_period == periods.ETERNITY)
)
self._eternal = self.variable.definition_period == periods.DateUnit.ETERNITY
self._memory_storage = storage.InMemoryStorage(is_eternal=self._eternal)

# By default, do not activate on-disk storage, or variable dropping
self._disk_storage = None
Expand Down Expand Up @@ -71,9 +70,7 @@ def create_disk_storage(self, directory=None, preserve=False):
if not os.path.isdir(storage_dir):
os.mkdir(storage_dir)
return storage.OnDiskStorage(
storage_dir,
is_eternal=(self.variable.definition_period == periods.ETERNITY),
preserve_storage_dir=preserve,
storage_dir, self._eternal, preserve_storage_dir=preserve
)

def delete_arrays(self, period=None):
Expand Down Expand Up @@ -121,7 +118,7 @@ def get_memory_usage(self) -> MemoryUsage:
>>> entity = entities.Entity("", "", "", "")

>>> class MyVariable(variables.Variable):
... definition_period = "year"
... definition_period = periods.DateUnit.YEAR
... entity = entity
... value_type = int

Expand Down Expand Up @@ -197,7 +194,7 @@ def set_input(
>>> entity = entities.Entity("", "", "", "")

>>> class MyVariable(variables.Variable):
... definition_period = "year"
... definition_period = periods.DateUnit.YEAR
... entity = entity
... value_type = int

Expand All @@ -221,16 +218,18 @@ def set_input(
"""

period = periods.period(period)
if (
period.unit == periods.ETERNITY
and self.variable.definition_period != periods.ETERNITY
):

if period.unit == periods.DateUnit.ETERNITY and not self._eternal:
error_message = os.linesep.join(
[
"Unable to set a value for variable {0} for periods.ETERNITY.",
"{0} is only defined for {1}s. Please adapt your input.",
"Unable to set a value for variable {1} for {0}.",
"{1} is only defined for {2}s. Please adapt your input.",
]
).format(self.variable.name, self.variable.definition_period)
).format(
periods.DateUnit.ETERNITY.upper(),
self.variable.name,
self.variable.definition_period,
)
raise errors.PeriodMismatchError(
self.variable.name,
period,
Expand Down Expand Up @@ -279,10 +278,11 @@ def _to_array(self, value):

def _set(self, period, value):
value = self._to_array(value)
if self.variable.definition_period != periods.ETERNITY:
if not self._eternal:
if period is None:
raise ValueError(
"A period must be specified to set values, except for variables with periods.ETERNITY as as period_definition."
f"A period must be specified to set values, except for variables with "
f"{periods.DateUnit.ETERNITY.upper()} as as period_definition.",
)
if self.variable.definition_period != period.unit or period.size > 1:
name = self.variable.name
Expand Down
Empty file.
77 changes: 57 additions & 20 deletions openfisca_core/holders/tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import pytest

from openfisca_core import holders, periods, tools
from openfisca_core import holders, tools
from openfisca_core.entities import Entity
from openfisca_core.holders import Holder
from openfisca_core.periods import Instant, Period
from openfisca_core.periods import DateUnit, Instant, Period
from openfisca_core.populations import Population
from openfisca_core.variables import Variable

Expand Down Expand Up @@ -37,15 +37,36 @@ def population(people):
@pytest.mark.parametrize(
"dispatch_unit, definition_unit, values, expected",
[
[periods.YEAR, periods.YEAR, [1.0], [3.0]],
[periods.YEAR, periods.MONTH, [1.0], [36.0]],
[periods.YEAR, periods.DAY, [1.0], [1096.0]],
[periods.MONTH, periods.YEAR, [1.0], [1.0]],
[periods.MONTH, periods.MONTH, [1.0], [3.0]],
[periods.MONTH, periods.DAY, [1.0], [90.0]],
[periods.DAY, periods.YEAR, [1.0], [1.0]],
[periods.DAY, periods.MONTH, [1.0], [1.0]],
[periods.DAY, periods.DAY, [1.0], [3.0]],
[DateUnit.YEAR, DateUnit.YEAR, [1.0], [3.0]],
[DateUnit.YEAR, DateUnit.MONTH, [1.0], [36.0]],
[DateUnit.YEAR, DateUnit.DAY, [1.0], [1096.0]],
[DateUnit.YEAR, DateUnit.WEEK, [1.0], [157.0]],
[DateUnit.YEAR, DateUnit.WEEKDAY, [1.0], [1096.0]],
[DateUnit.MONTH, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.MONTH, DateUnit.MONTH, [1.0], [3.0]],
[DateUnit.MONTH, DateUnit.DAY, [1.0], [90.0]],
[DateUnit.MONTH, DateUnit.WEEK, [1.0], [13.0]],
[DateUnit.MONTH, DateUnit.WEEKDAY, [1.0], [90.0]],
[DateUnit.DAY, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.DAY, DateUnit.MONTH, [1.0], [1.0]],
[DateUnit.DAY, DateUnit.DAY, [1.0], [3.0]],
[DateUnit.DAY, DateUnit.WEEK, [1.0], [1.0]],
[DateUnit.DAY, DateUnit.WEEKDAY, [1.0], [3.0]],
[DateUnit.WEEK, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.WEEK, DateUnit.MONTH, [1.0], [1.0]],
[DateUnit.WEEK, DateUnit.DAY, [1.0], [21.0]],
[DateUnit.WEEK, DateUnit.WEEK, [1.0], [3.0]],
[DateUnit.WEEK, DateUnit.WEEKDAY, [1.0], [21.0]],
[DateUnit.WEEK, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.WEEK, DateUnit.MONTH, [1.0], [1.0]],
[DateUnit.WEEK, DateUnit.DAY, [1.0], [21.0]],
[DateUnit.WEEK, DateUnit.WEEK, [1.0], [3.0]],
[DateUnit.WEEK, DateUnit.WEEKDAY, [1.0], [21.0]],
[DateUnit.WEEKDAY, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.WEEKDAY, DateUnit.MONTH, [1.0], [1.0]],
[DateUnit.WEEKDAY, DateUnit.DAY, [1.0], [3.0]],
[DateUnit.WEEKDAY, DateUnit.WEEK, [1.0], [1.0]],
[DateUnit.WEEKDAY, DateUnit.WEEKDAY, [1.0], [3.0]],
],
)
def test_set_input_dispatch_by_period(
Expand All @@ -71,15 +92,31 @@ def test_set_input_dispatch_by_period(
@pytest.mark.parametrize(
"divide_unit, definition_unit, values, expected",
[
[periods.YEAR, periods.YEAR, [3.0], [1.0]],
[periods.YEAR, periods.MONTH, [36.0], [1.0]],
[periods.YEAR, periods.DAY, [1095.0], [1.0]],
[periods.MONTH, periods.YEAR, [1.0], [1.0]],
[periods.MONTH, periods.MONTH, [3.0], [1.0]],
[periods.MONTH, periods.DAY, [90.0], [1.0]],
[periods.DAY, periods.YEAR, [1.0], [1.0]],
[periods.DAY, periods.MONTH, [1.0], [1.0]],
[periods.DAY, periods.DAY, [3.0], [1.0]],
[DateUnit.YEAR, DateUnit.YEAR, [3.0], [1.0]],
[DateUnit.YEAR, DateUnit.MONTH, [36.0], [1.0]],
[DateUnit.YEAR, DateUnit.DAY, [1095.0], [1.0]],
[DateUnit.YEAR, DateUnit.WEEK, [157.0], [1.0]],
[DateUnit.YEAR, DateUnit.WEEKDAY, [1095.0], [1.0]],
[DateUnit.MONTH, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.MONTH, DateUnit.MONTH, [3.0], [1.0]],
[DateUnit.MONTH, DateUnit.DAY, [90.0], [1.0]],
[DateUnit.MONTH, DateUnit.WEEK, [13.0], [1.0]],
[DateUnit.MONTH, DateUnit.WEEKDAY, [90.0], [1.0]],
[DateUnit.DAY, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.DAY, DateUnit.MONTH, [1.0], [1.0]],
[DateUnit.DAY, DateUnit.DAY, [3.0], [1.0]],
[DateUnit.DAY, DateUnit.WEEK, [1.0], [1.0]],
[DateUnit.DAY, DateUnit.WEEKDAY, [3.0], [1.0]],
[DateUnit.WEEK, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.WEEK, DateUnit.MONTH, [1.0], [1.0]],
[DateUnit.WEEK, DateUnit.DAY, [21.0], [1.0]],
[DateUnit.WEEK, DateUnit.WEEK, [3.0], [1.0]],
[DateUnit.WEEK, DateUnit.WEEKDAY, [21.0], [1.0]],
[DateUnit.WEEKDAY, DateUnit.YEAR, [1.0], [1.0]],
[DateUnit.WEEKDAY, DateUnit.MONTH, [1.0], [1.0]],
[DateUnit.WEEKDAY, DateUnit.DAY, [3.0], [1.0]],
[DateUnit.WEEKDAY, DateUnit.WEEK, [1.0], [1.0]],
[DateUnit.WEEKDAY, DateUnit.WEEKDAY, [3.0], [1.0]],
],
)
def test_set_input_divide_by_period(
Expand Down
13 changes: 7 additions & 6 deletions openfisca_core/periods/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,24 @@

from .config import ( # noqa: F401
DAY,
MONTH,
YEAR,
ETERNITY,
INSTANT_PATTERN,
MONTH,
WEEK,
WEEKDAY,
YEAR,
date_by_instant_cache,
str_by_instant_cache,
year_or_month_or_day_re,
)

from .date_unit import DateUnit # noqa: F401
from .helpers import ( # noqa: F401
instant,
instant_date,
period,
key_period_size,
unit_weights,
period,
unit_weight,
unit_weights,
)

from .instant_ import Instant # noqa: F401
from .period_ import Period # noqa: F401
Loading