diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6a3e7fcb7..be17bc6338 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,23 @@
# Changelog
+### 35.3.8 [#1014](https://github.com/openfisca/openfisca-core/pull/1014)
+
+#### Bug fix
+
+- Drop latest NumPy supported version to 1.18.x
+ - OpenFisca relies on MyPy for optional duck & static type checking
+ - When libraries do not implement their own types, MyPy provides stubs, or type sheds
+ - Thanks to `__future__.annotations`, those stubs or type sheds are casted to `typing.Any`
+ - Since 1.20.x, NumPy now provides their own type definitions
+ - The introduction of NumPy 1.20.x in #990 caused one major problem:
+ - It is general practice to do not import at runtime modules only used for typing purposes, thanks to the `typing.TYPE_CHEKING` variable
+ - The new `numpy.typing` module was being imported at runtime, rendering OpenFisca unusable to all users depending on previous versions of NumPy (1.20.x-)
+ - These changes revert #990 and solve #1009 and #1012
+
### 35.3.7 [#990](https://github.com/openfisca/openfisca-core/pull/990)
+_Note: this version has been unpublished due to an issue introduced by NumPy upgrade. Please use 34.3.8 or a more recent version._
+
#### Technical changes
- Update dependencies.
diff --git a/openfisca_core/indexed_enums/enum.py b/openfisca_core/indexed_enums/enum.py
index 3fbb98f6d5..7ba1f78b41 100644
--- a/openfisca_core/indexed_enums/enum.py
+++ b/openfisca_core/indexed_enums/enum.py
@@ -7,28 +7,23 @@
from openfisca_core.indexed_enums import config, EnumArray
-if typing.TYPE_CHECKING:
- IndexedEnumArray = numpy.object_
-
class Enum(enum.Enum):
"""
- Enum based on `enum34 `_, whose items
- have an index.
+ Enum based on `enum34 `_, whose items have an
+ index.
"""
# Tweak enums to add an index attribute to each enum item
def __init__(self, name: str) -> None:
- # When the enum item is initialized, self._member_names_ contains the
- # names of the previously initialized items, so its length is the index
- # of this item.
+ # When the enum item is initialized, self._member_names_ contains the names of
+ # the previously initialized items, so its length is the index of this item.
self.index = len(self._member_names_)
# Bypass the slow Enum.__eq__
__eq__ = object.__eq__
- # In Python 3, __hash__ must be defined if __eq__ is defined to stay
- # hashable.
+ # In Python 3, __hash__ must be defined if __eq__ is defined to stay hashable.
__hash__ = object.__hash__
@classmethod
@@ -36,18 +31,16 @@ def encode(
cls,
array: typing.Union[
EnumArray,
- numpy.int_,
- numpy.float_,
- IndexedEnumArray,
+ numpy.ndarray[int],
+ numpy.ndarray[str],
+ numpy.ndarray[Enum],
],
) -> EnumArray:
"""
- Encode a string numpy array, an enum item numpy array, or an int numpy
- array into an :any:`EnumArray`. See :any:`EnumArray.decode` for
- decoding.
+ Encode a string numpy array, an enum item numpy array, or an int numpy array
+ into an :any:`EnumArray`. See :any:`EnumArray.decode` for decoding.
- :param ndarray array: Array of string identifiers, or of enum items, to
- encode.
+ :param ndarray array: Array of string identifiers, or of enum items, to encode.
:returns: An :any:`EnumArray` encoding the input array values.
:rtype: :any:`EnumArray`
@@ -66,31 +59,24 @@ def encode(
>>> encoded_array[0]
2 # Encoded value
"""
- if isinstance(array, EnumArray):
+ if type(array) is EnumArray:
return array
- # String array
- if isinstance(array, numpy.ndarray) and \
- array.dtype.kind in {'U', 'S'}:
+ if array.dtype.kind in {'U', 'S'}: # String array
array = numpy.select(
[array == item.name for item in cls],
[item.index for item in cls],
).astype(config.ENUM_ARRAY_DTYPE)
- # Enum items arrays
- elif isinstance(array, numpy.ndarray) and \
- array.dtype.kind == 'O':
+ elif array.dtype.kind == 'O': # Enum items arrays
# Ensure we are comparing the comparable. The problem this fixes:
# On entering this method "cls" will generally come from
- # variable.possible_values, while the array values may come from
- # directly importing a module containing an Enum class. However,
- # variables (and hence their possible_values) are loaded by a call
- # to load_module, which gives them a different identity from the
- # ones imported in the usual way.
- #
- # So, instead of relying on the "cls" passed in, we use only its
- # name to check that the values in the array, if non-empty, are of
- # the right type.
+ # variable.possible_values, while the array values may come from directly
+ # importing a module containing an Enum class. However, variables (and
+ # hence their possible_values) are loaded by a call to load_module, which
+ # gives them a different identity from the ones imported in the usual way.
+ # So, instead of relying on the "cls" passed in, we use only its name to
+ # check that the values in the array, if non-empty, are of the right type.
if len(array) > 0 and cls.__name__ is array[0].__class__.__name__:
cls = array[0].__class__
diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py
index 90ab35922a..278c0bd289 100644
--- a/openfisca_core/indexed_enums/enum_array.py
+++ b/openfisca_core/indexed_enums/enum_array.py
@@ -7,8 +7,6 @@
if typing.TYPE_CHECKING:
from openfisca_core.indexed_enums import Enum
- IndexedEnumArray = numpy.object_
-
class EnumArray(numpy.ndarray):
"""
@@ -22,7 +20,7 @@ class EnumArray(numpy.ndarray):
# https://docs.scipy.org/doc/numpy-1.13.0/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array.
def __new__(
cls,
- input_array: numpy.int_,
+ input_array: numpy.ndarray[int],
possible_values: typing.Optional[typing.Type[Enum]] = None,
) -> EnumArray:
obj = numpy.asarray(input_array).view(cls)
@@ -30,15 +28,15 @@ def __new__(
return obj
# See previous comment
- def __array_finalize__(self, obj: typing.Optional[numpy.int_]) -> None:
+ def __array_finalize__(self, obj: typing.Optional[numpy.ndarray[int]]) -> None:
if obj is None:
return
self.possible_values = getattr(obj, "possible_values", None)
def __eq__(self, other: typing.Any) -> bool:
- # When comparing to an item of self.possible_values, use the item index
- # to speed up the comparison.
+ # When comparing to an item of self.possible_values, use the item index to
+ # speed up the comparison.
if other.__class__.__name__ is self.possible_values.__name__:
# Use view(ndarray) so that the result is a classic ndarray, not an
# EnumArray.
@@ -51,8 +49,8 @@ def __ne__(self, other: typing.Any) -> bool:
def _forbidden_operation(self, other: typing.Any) -> typing.NoReturn:
raise TypeError(
- "Forbidden operation. The only operations allowed on EnumArrays "
- "are '==' and '!='.",
+ "Forbidden operation. The only operations allowed on EnumArrays are "
+ "'==' and '!='.",
)
__add__ = _forbidden_operation
@@ -64,7 +62,7 @@ def _forbidden_operation(self, other: typing.Any) -> typing.NoReturn:
__and__ = _forbidden_operation
__or__ = _forbidden_operation
- def decode(self) -> IndexedEnumArray:
+ def decode(self) -> numpy.ndarray[Enum]:
"""
Return the array of enum items corresponding to self.
@@ -74,16 +72,14 @@ def decode(self) -> IndexedEnumArray:
>>> enum_array[0]
>>> 2 # Encoded value
>>> enum_array.decode()[0]
-
-
- Decoded value: enum item
+ # Decoded value : enum item
"""
return numpy.select(
[self == item.index for item in self.possible_values],
list(self.possible_values),
)
- def decode_to_str(self) -> numpy.str_:
+ def decode_to_str(self) -> numpy.ndarray[str]:
"""
Return the array of string identifiers corresponding to self.
diff --git a/openfisca_core/taxscales/abstract_rate_tax_scale.py b/openfisca_core/taxscales/abstract_rate_tax_scale.py
index b9316273d1..bc972824f1 100644
--- a/openfisca_core/taxscales/abstract_rate_tax_scale.py
+++ b/openfisca_core/taxscales/abstract_rate_tax_scale.py
@@ -3,36 +3,28 @@
import typing
import warnings
-from openfisca_core.taxscales import RateTaxScaleLike
-
-if typing.TYPE_CHECKING:
- import numpy
+import numpy
- NumericalArray = typing.Union[numpy.int_, numpy.float_]
+from openfisca_core.taxscales import RateTaxScaleLike
class AbstractRateTaxScale(RateTaxScaleLike):
"""
- Base class for various types of rate-based tax scales: marginal rate,
- linear average rate...
+ Base class for various types of rate-based tax scales: marginal rate, linear
+ average rate...
"""
- def __init__(
- self, name: typing.Optional[str] = None,
- option: typing.Any = None,
- unit: typing.Any = None,
- ) -> None:
+ def __init__(self, name: typing.Optional[str] = None, option = None, unit = None) -> None:
message = [
- "The 'AbstractRateTaxScale' class has been deprecated since",
- "version 34.7.0, and will be removed in the future.",
+ "The 'AbstractRateTaxScale' class has been deprecated since version",
+ "34.7.0, and will be removed in the future.",
]
-
warnings.warn(" ".join(message), DeprecationWarning)
- super().__init__(name, option, unit)
+ super(AbstractRateTaxScale, self).__init__(name, option, unit)
def calc(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
right: bool,
) -> typing.NoReturn:
raise NotImplementedError(
diff --git a/openfisca_core/taxscales/abstract_tax_scale.py b/openfisca_core/taxscales/abstract_tax_scale.py
index 9cbeeb7565..9d5f23538b 100644
--- a/openfisca_core/taxscales/abstract_tax_scale.py
+++ b/openfisca_core/taxscales/abstract_tax_scale.py
@@ -3,34 +3,24 @@
import typing
import warnings
-from openfisca_core.taxscales import TaxScaleLike
-
-if typing.TYPE_CHECKING:
- import numpy
+import numpy
- NumericalArray = typing.Union[numpy.int_, numpy.float_]
+from openfisca_core.taxscales import TaxScaleLike
class AbstractTaxScale(TaxScaleLike):
"""
- Base class for various types of tax scales: amount-based tax scales,
- rate-based tax scales...
+ Base class for various types of tax scales: amount-based tax scales, rate-based
+ tax scales...
"""
- def __init__(
- self,
- name: typing.Optional[str] = None,
- option: typing.Any = None,
- unit: numpy.int_ = None,
- ) -> None:
-
+ def __init__(self, name: typing.Optional[str] = None, option = None, unit = None) -> None:
message = [
- "The 'AbstractTaxScale' class has been deprecated since",
- "version 34.7.0, and will be removed in the future.",
+ "The 'AbstractTaxScale' class has been deprecated since version 34.7.0,",
+ "and will be removed in the future.",
]
-
warnings.warn(" ".join(message), DeprecationWarning)
- super().__init__(name, option, unit)
+ super(AbstractTaxScale, self).__init__(name, option, unit)
def __repr__(self) -> typing.NoReturn:
raise NotImplementedError(
@@ -40,7 +30,7 @@ def __repr__(self) -> typing.NoReturn:
def calc(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
right: bool,
) -> typing.NoReturn:
raise NotImplementedError(
diff --git a/openfisca_core/taxscales/amount_tax_scale_like.py b/openfisca_core/taxscales/amount_tax_scale_like.py
index cfc0a6973f..465635bf0b 100644
--- a/openfisca_core/taxscales/amount_tax_scale_like.py
+++ b/openfisca_core/taxscales/amount_tax_scale_like.py
@@ -9,19 +9,14 @@
class AmountTaxScaleLike(TaxScaleLike, abc.ABC):
"""
- Base class for various types of amount-based tax scales: single amount,
- marginal amount...
+ Base class for various types of amount-based tax scales: single amount, marginal
+ amount...
"""
amounts: typing.List
- def __init__(
- self,
- name: typing.Optional[str] = None,
- option: typing.Any = None,
- unit: typing.Any = None,
- ) -> None:
- super().__init__(name, option, unit)
+ def __init__(self, name: typing.Optional[str] = None, option = None, unit = None) -> None:
+ super(AmountTaxScaleLike, self).__init__(name, option, unit)
self.amounts = []
def __repr__(self) -> str:
@@ -29,8 +24,7 @@ def __repr__(self) -> str:
os.linesep.join(
[
f"- threshold: {threshold}{os.linesep} amount: {amount}"
- for (threshold, amount)
- in zip(self.thresholds, self.amounts)
+ for (threshold, amount) in zip(self.thresholds, self.amounts)
]
)
)
@@ -43,7 +37,6 @@ def add_bracket(
if threshold in self.thresholds:
i = self.thresholds.index(threshold)
self.amounts[i] += amount
-
else:
i = bisect.bisect_left(self.thresholds, threshold)
self.thresholds.insert(i, threshold)
@@ -52,6 +45,5 @@ def add_bracket(
def to_dict(self) -> dict:
return {
str(threshold): self.amounts[index]
- for index, threshold
- in enumerate(self.thresholds)
+ for index, threshold in enumerate(self.thresholds)
}
diff --git a/openfisca_core/taxscales/helpers.py b/openfisca_core/taxscales/helpers.py
index 181fbfed36..a1b49e197e 100644
--- a/openfisca_core/taxscales/helpers.py
+++ b/openfisca_core/taxscales/helpers.py
@@ -10,13 +10,11 @@
if typing.TYPE_CHECKING:
from openfisca_core.parameters import ParameterNodeAtInstant
- TaxScales = typing.Optional[taxscales.MarginalRateTaxScale]
-
def combine_tax_scales(
node: ParameterNodeAtInstant,
- combined_tax_scales: TaxScales = None,
- ) -> TaxScales:
+ combined_tax_scales: typing.Optional[taxscales.MarginalRateTaxScale] = None,
+ ) -> typing.Optional[taxscales.MarginalRateTaxScale]:
"""
Combine all the MarginalRateTaxScales in the node into a single
MarginalRateTaxScale.
diff --git a/openfisca_core/taxscales/linear_average_rate_tax_scale.py b/openfisca_core/taxscales/linear_average_rate_tax_scale.py
index d1fe9c8094..885d29d8cb 100644
--- a/openfisca_core/taxscales/linear_average_rate_tax_scale.py
+++ b/openfisca_core/taxscales/linear_average_rate_tax_scale.py
@@ -10,17 +10,14 @@
log = logging.getLogger(__name__)
-if typing.TYPE_CHECKING:
- NumericalArray = typing.Union[numpy.int_, numpy.float_]
-
class LinearAverageRateTaxScale(RateTaxScaleLike):
def calc(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
right: bool = False,
- ) -> numpy.float_:
+ ) -> numpy.ndarray[float]:
if len(self.rates) == 1:
return tax_base * self.rates[0]
diff --git a/openfisca_core/taxscales/marginal_amount_tax_scale.py b/openfisca_core/taxscales/marginal_amount_tax_scale.py
index 348f2445c0..9208813d47 100644
--- a/openfisca_core/taxscales/marginal_amount_tax_scale.py
+++ b/openfisca_core/taxscales/marginal_amount_tax_scale.py
@@ -6,29 +6,19 @@
from openfisca_core.taxscales import AmountTaxScaleLike
-if typing.TYPE_CHECKING:
- NumericalArray = typing.Union[numpy.int_, numpy.float_]
-
class MarginalAmountTaxScale(AmountTaxScaleLike):
def calc(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
right: bool = False,
- ) -> numpy.float_:
+ ) -> numpy.ndarray[float]:
"""
- Matches the input amount to a set of brackets and returns the sum of
- cell values from the lowest bracket to the one containing the input.
+ Matches the input amount to a set of brackets and returns the sum of cell
+ values from the lowest bracket to the one containing the input.
"""
base1 = numpy.tile(tax_base, (len(self.thresholds), 1)).T
-
- thresholds1 = numpy.tile(
- numpy.hstack((self.thresholds, numpy.inf)), (len(tax_base), 1)
- )
-
- a = numpy.maximum(
- numpy.minimum(base1, thresholds1[:, 1:]) - thresholds1[:, :-1], 0
- )
-
+ thresholds1 = numpy.tile(numpy.hstack((self.thresholds, numpy.inf)), (len(tax_base), 1))
+ a = numpy.maximum(numpy.minimum(base1, thresholds1[:, 1:]) - thresholds1[:, :-1], 0)
return numpy.dot(self.amounts, a.T > 0)
diff --git a/openfisca_core/taxscales/marginal_rate_tax_scale.py b/openfisca_core/taxscales/marginal_rate_tax_scale.py
index 38331e0bb8..e3749209ed 100644
--- a/openfisca_core/taxscales/marginal_rate_tax_scale.py
+++ b/openfisca_core/taxscales/marginal_rate_tax_scale.py
@@ -9,14 +9,11 @@
from openfisca_core import taxscales
from openfisca_core.taxscales import RateTaxScaleLike
-if typing.TYPE_CHECKING:
- NumericalArray = typing.Union[numpy.int_, numpy.float_]
-
class MarginalRateTaxScale(RateTaxScaleLike):
def add_tax_scale(self, tax_scale: RateTaxScaleLike) -> None:
- # So as not to have problems with empty scales
+ # Pour ne pas avoir de problèmes avec les barèmes vides
if (len(tax_scale.thresholds) > 0):
for threshold_low, threshold_high, rate in zip(
tax_scale.thresholds[:-1],
@@ -25,7 +22,7 @@ def add_tax_scale(self, tax_scale: RateTaxScaleLike) -> None:
):
self.combine_bracket(rate, threshold_low, threshold_high)
- # To process the last threshold
+ # Pour traiter le dernier threshold
self.combine_bracket(
tax_scale.rates[-1],
tax_scale.thresholds[-1],
@@ -33,17 +30,16 @@ def add_tax_scale(self, tax_scale: RateTaxScaleLike) -> None:
def calc(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
factor: float = 1.0,
round_base_decimals: typing.Optional[int] = None,
- ) -> numpy.float_:
+ ) -> numpy.ndarray[float]:
"""
- Compute the tax amount for the given tax bases by applying a taxscale.
+ Compute the tax amount for the given tax bases by applying the taxscale.
:param ndarray tax_base: Array of the tax bases.
- :param float factor: Factor to apply to the thresholds of the taxscale.
- :param int round_base_decimals: Decimals to keep when rounding
- thresholds.
+ :param float factor: Factor to apply to the thresholds of the tax scale.
+ :param int round_base_decimals: Decimals to keep when rounding thresholds.
:returns: Float array with tax amount for the given tax bases.
@@ -56,31 +52,20 @@ def calc(
>>> tax_scale.calc(tax_base)
[0.0, 5.0]
"""
+
base1 = numpy.tile(tax_base, (len(self.thresholds), 1)).T
factor = numpy.ones(len(tax_base)) * factor
- # To avoid the creation of:
- #
- # numpy.nan = 0 * numpy.inf
- #
- # We use:
- #
- # numpy.finfo(float_).eps
- thresholds1 = numpy.outer(
- factor + numpy.finfo(numpy.float_).eps,
- numpy.array(self.thresholds + [numpy.inf]),
- )
+ # finfo(float_).eps is used to avoid nan = 0 * inf creation
+ thresholds1 = numpy.outer(factor + numpy.finfo(numpy.float_).eps, numpy.array(self.thresholds + [numpy.inf]))
if round_base_decimals is not None:
thresholds1 = numpy.round_(thresholds1, round_base_decimals)
- a = numpy.maximum(
- numpy.minimum(base1, thresholds1[:, 1:]) - thresholds1[:, :-1], 0
- )
+ a = numpy.maximum(numpy.minimum(base1, thresholds1[:, 1:]) - thresholds1[:, :-1], 0)
if round_base_decimals is None:
return numpy.dot(self.rates, a.T)
-
else:
r = numpy.tile(self.rates, (len(tax_base), 1))
b = numpy.round_(a, round_base_decimals)
@@ -106,30 +91,26 @@ def combine_bracket(
if threshold_high:
j = self.thresholds.index(threshold_high) - 1
-
else:
j = len(self.thresholds) - 1
-
while i <= j:
self.add_bracket(self.thresholds[i], rate)
i += 1
def marginal_rates(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
factor: float = 1.0,
round_base_decimals: typing.Optional[int] = None,
- ) -> numpy.float_:
+ ) -> numpy.ndarray[float]:
"""
Compute the marginal tax rates relevant for the given tax bases.
:param ndarray tax_base: Array of the tax bases.
- :param float factor: Factor to apply to the thresholds of a tax scale.
- :param int round_base_decimals: Decimals to keep when rounding
- thresholds.
+ :param float factor: Factor to apply to the thresholds of the tax scale.
+ :param int round_base_decimals: Decimals to keep when rounding thresholds.
- :returns: Float array with relevant marginal tax rate for the given tax
- bases.
+ :returns: Float array with relevant marginal tax rate for the given tax bases.
For instance:
@@ -154,29 +135,29 @@ def inverse(self) -> MarginalRateTaxScale:
Invert a taxscale:
- Assume tax_scale composed of bracket whose thresholds are expressed
- in terms of gross revenue.
+ Assume tax_scale composed of bracket which thresholds are expressed in term
+ of brut revenue.
- The inverse is another MarginalRateTaxScale whose thresholds are
- expressed in terms of net revenue.
+ The inverse is another MarginalTaxSclae which thresholds are expressed in
+ terms of net revenue.
- If net = gross_revenue - tax_scale.calc(gross_revenue)
- Then gross = tax_scale.inverse().calc(net)
+ IF net = revbrut - tax_scale.calc(revbrut)
+ THEN brut = tax_scale.inverse().calc(net)
"""
# Threshold of net revenue.
net_threshold: int = 0
- # Threshold of gross revenue.
+ # Threshold of brut revenue.
threshold: int
- # The intercept of the segments of the different thresholds in a
- # representation of taxable revenue as a piecewise linear function
- # of gross revenue.
+ # Ordonnée à l'origine des segments des différents seuils dans une
+ # représentation du revenu imposable comme fonction linéaire par morceaux du
+ # revenu brut.
theta: int
- # Actually 1 / (1 - global_rate)
+ # Actually 1 / (1- global_rate)
inverse = self.__class__(
- name = str(self.name) + "'",
+ name = self.name + "'",
option = self.option,
unit = self.unit,
)
@@ -186,8 +167,7 @@ def inverse(self) -> MarginalRateTaxScale:
previous_rate = 0
theta = 0
- # We calculate the taxable revenue threshold of the considered
- # bracket.
+ # On calcule le seuil de revenu imposable de la tranche considérée.
net_threshold = (1 - previous_rate) * threshold + theta
inverse.add_bracket(net_threshold, 1 / (1 - rate))
theta = (rate - previous_rate) * threshold + theta
diff --git a/openfisca_core/taxscales/rate_tax_scale_like.py b/openfisca_core/taxscales/rate_tax_scale_like.py
index 824a94debe..ba21e3007a 100644
--- a/openfisca_core/taxscales/rate_tax_scale_like.py
+++ b/openfisca_core/taxscales/rate_tax_scale_like.py
@@ -11,25 +11,17 @@
from openfisca_core.errors import EmptyArgumentError
from openfisca_core.taxscales import TaxScaleLike
-if typing.TYPE_CHECKING:
- NumericalArray = typing.Union[numpy.int_, numpy.float_]
-
class RateTaxScaleLike(TaxScaleLike, abc.ABC):
"""
- Base class for various types of rate-based tax scales: marginal rate,
- linear average rate...
+ Base class for various types of rate-based tax scales: marginal rate, linear
+ average rate...
"""
rates: typing.List
- def __init__(
- self,
- name: typing.Optional[str] = None,
- option: typing.Any = None,
- unit: typing.Any = None,
- ) -> None:
- super().__init__(name, option, unit)
+ def __init__(self, name: typing.Optional[str] = None, option = None, unit = None) -> None:
+ super(RateTaxScaleLike, self).__init__(name, option, unit)
self.rates = []
def __repr__(self) -> str:
@@ -37,8 +29,7 @@ def __repr__(self) -> str:
os.linesep.join(
[
f"- threshold: {threshold}{os.linesep} rate: {rate}"
- for (threshold, rate)
- in zip(self.thresholds, self.rates)
+ for (threshold, rate) in zip(self.thresholds, self.rates)
]
)
)
@@ -51,7 +42,6 @@ def add_bracket(
if threshold in self.thresholds:
i = self.thresholds.index(threshold)
self.rates[i] += rate
-
else:
i = bisect.bisect_left(self.thresholds, threshold)
self.thresholds.insert(i, threshold)
@@ -95,11 +85,7 @@ def multiply_thresholds(
for i, threshold in enumerate(self.thresholds):
if decimals is not None:
- self.thresholds[i] = numpy.around(
- threshold * factor,
- decimals = decimals,
- )
-
+ self.thresholds[i] = numpy.around(threshold * factor, decimals = decimals)
else:
self.thresholds[i] = threshold * factor
@@ -125,19 +111,18 @@ def multiply_thresholds(
def bracket_indices(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
factor: float = 1.0,
round_decimals: typing.Optional[int] = None,
- ) -> numpy.int_:
+ ) -> numpy.ndarray[int]:
"""
Compute the relevant bracket indices for the given tax bases.
:param ndarray tax_base: Array of the tax bases.
- :param float factor: Factor to apply to the thresholds.
+ :param float factor: Factor to apply to the thresholds of the tax scales.
:param int round_decimals: Decimals to keep when rounding thresholds.
- :returns: Integer array with relevant bracket indices for the given tax
- bases.
+ :returns: Int array with relevant bracket indices for the given tax bases.
For instance:
@@ -168,17 +153,8 @@ def bracket_indices(
base1 = numpy.tile(tax_base, (len(self.thresholds), 1)).T
factor = numpy.ones(len(tax_base)) * factor
- # To avoid the creation of:
- #
- # numpy.nan = 0 * numpy.inf
- #
- # We use:
- #
- # numpy.finfo(float_).eps
- thresholds1 = numpy.outer(
- + factor
- + numpy.finfo(numpy.float_).eps, numpy.array(self.thresholds)
- )
+ # finfo(float_).eps is used to avoid nan = 0 * inf creation
+ thresholds1 = numpy.outer(factor + numpy.finfo(numpy.float_).eps, numpy.array(self.thresholds))
if round_decimals is not None:
thresholds1 = numpy.round_(thresholds1, round_decimals)
@@ -188,6 +164,5 @@ def bracket_indices(
def to_dict(self) -> dict:
return {
str(threshold): self.rates[index]
- for index, threshold
- in enumerate(self.thresholds)
+ for index, threshold in enumerate(self.thresholds)
}
diff --git a/openfisca_core/taxscales/single_amount_tax_scale.py b/openfisca_core/taxscales/single_amount_tax_scale.py
index bdfee48010..891614adee 100644
--- a/openfisca_core/taxscales/single_amount_tax_scale.py
+++ b/openfisca_core/taxscales/single_amount_tax_scale.py
@@ -6,37 +6,19 @@
from openfisca_core.taxscales import AmountTaxScaleLike
-if typing.TYPE_CHECKING:
- NumericalArray = typing.Union[numpy.int_, numpy.float_]
-
class SingleAmountTaxScale(AmountTaxScaleLike):
def calc(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
right: bool = False,
- ) -> numpy.float_:
+ ) -> numpy.ndarray[float]:
"""
- Matches the input amount to a set of brackets and returns the single
- cell value that fits within that bracket.
+ Matches the input amount to a set of brackets and returns the single cell value
+ that fits within that bracket.
"""
- guarded_thresholds = numpy.array(
- [-numpy.inf]
- + self.thresholds
- + [numpy.inf]
- )
-
- bracket_indices = numpy.digitize(
- tax_base,
- guarded_thresholds,
- right = right,
- )
-
- guarded_amounts = numpy.array(
- [0]
- + self.amounts
- + [0]
- )
-
+ guarded_thresholds = numpy.array([-numpy.inf] + self.thresholds + [numpy.inf])
+ bracket_indices = numpy.digitize(tax_base, guarded_thresholds, right = right)
+ guarded_amounts = numpy.array([0] + self.amounts + [0])
return guarded_amounts[bracket_indices - 1]
diff --git a/openfisca_core/taxscales/tax_scale_like.py b/openfisca_core/taxscales/tax_scale_like.py
index 8177ee0505..513e0bcdfd 100644
--- a/openfisca_core/taxscales/tax_scale_like.py
+++ b/openfisca_core/taxscales/tax_scale_like.py
@@ -8,28 +8,20 @@
from openfisca_core import commons
-if typing.TYPE_CHECKING:
- NumericalArray = typing.Union[numpy.int_, numpy.float_]
-
class TaxScaleLike(abc.ABC):
"""
- Base class for various types of tax scales: amount-based tax scales,
- rate-based tax scales...
+ Base class for various types of tax scales: amount-based tax scales, rate-based
+ tax scales...
"""
- name: typing.Optional[str]
- option: typing.Any
- unit: typing.Any
+ name: str
+ option: None
+ unit: None
thresholds: typing.List
@abc.abstractmethod
- def __init__(
- self,
- name: typing.Optional[str] = None,
- option: typing.Any = None,
- unit: typing.Any = None,
- ) -> None:
+ def __init__(self, name: typing.Optional[str] = None, option = None, unit = None) -> None:
self.name = name or "Untitled TaxScale"
self.option = option
self.unit = unit
@@ -54,9 +46,9 @@ def __repr__(self) -> str:
@abc.abstractmethod
def calc(
self,
- tax_base: NumericalArray,
+ tax_base: typing.Union[numpy.ndarray[int], numpy.ndarray[float]],
right: bool,
- ) -> numpy.float_:
+ ) -> numpy.ndarray[float]:
...
@abc.abstractmethod
diff --git a/openfisca_core/tracers/computation_log.py b/openfisca_core/tracers/computation_log.py
index 51baa4f439..7fe4d23e81 100644
--- a/openfisca_core/tracers/computation_log.py
+++ b/openfisca_core/tracers/computation_log.py
@@ -1,101 +1,61 @@
-from __future__ import annotations
-
import typing
import numpy
-import numpy.typing
from openfisca_core.indexed_enums import EnumArray
-if typing.TYPE_CHECKING:
- from openfisca_core.tracers import FullTracer, TraceNode
-
- Array = typing.Union[EnumArray, numpy.typing.ArrayLike]
-
class ComputationLog:
- _full_tracer: FullTracer
-
- def __init__(self, full_tracer: FullTracer) -> None:
+ def __init__(self, full_tracer):
self._full_tracer = full_tracer
- def display(
- self,
- value: typing.Optional[Array],
- ) -> str:
+ def display(self, value):
if isinstance(value, EnumArray):
value = value.decode_to_str()
return numpy.array2string(value, max_line_width = float("inf"))
- def _get_node_log(
- self,
- node: TraceNode,
- depth: int,
- aggregate: bool,
- ) -> typing.List[str]:
+ def _get_node_log(self, node, depth, aggregate) -> typing.List[str]:
- def print_line(depth: int, node: TraceNode) -> str:
- indent = ' ' * depth
+ def print_line(depth, node) -> str:
value = node.value
-
- if value is None:
- formatted_value = "{'avg': '?', 'max': '?', 'min': '?'}"
-
- elif aggregate:
+ if aggregate:
try:
- formatted_value = str({
- 'avg': numpy.mean(value),
- 'max': numpy.max(value),
- 'min': numpy.min(value),
- })
-
+ formatted_value = str({'avg': numpy.mean(value), 'max': numpy.max(value), 'min': numpy.min(value)})
except TypeError:
formatted_value = "{'avg': '?', 'max': '?', 'min': '?'}"
-
else:
formatted_value = self.display(value)
- return f"{indent}{node.name}<{node.period}> >> {formatted_value}"
+ return "{}{}<{}> >> {}".format(' ' * depth, node.name, node.period, formatted_value)
- node_log = [print_line(depth, node)]
+ # if not self.trace.get(node):
+ # return print_line(depth, node, "Calculation aborted due to a circular dependency")
- children_logs = [
+ node_log = [print_line(depth, node)]
+ children_logs = self._flatten(
self._get_node_log(child, depth + 1, aggregate)
- for child
- in node.children
- ]
+ for child in node.children
+ )
- return node_log + self._flatten(children_logs)
+ return node_log + children_logs
- def _flatten(
- self,
- list_of_lists: typing.List[typing.List[str]],
- ) -> typing.List[str]:
+ def _flatten(self, list_of_lists):
return [item for _list in list_of_lists for item in _list]
- def lines(self, aggregate: bool = False) -> typing.List[str]:
+ def lines(self, aggregate = False) -> typing.List[str]:
depth = 1
-
- lines_by_tree = [
- self._get_node_log(node, depth, aggregate)
- for node
- in self._full_tracer.trees
- ]
-
+ lines_by_tree = [self._get_node_log(node, depth, aggregate) for node in self._full_tracer.trees]
return self._flatten(lines_by_tree)
- def print_log(self, aggregate = False) -> None:
+ def print_log(self, aggregate = False):
"""
Print the computation log of a simulation.
- If ``aggregate`` is ``False`` (default), print the value of each
- computed vector.
-
- If ``aggregate`` is ``True``, only print the minimum, maximum, and
- average value of each computed vector.
+ If ``aggregate`` is ``False`` (default), print the value of each computed vector.
+ If ``aggregate`` is ``True``, only print the minimum, maximum, and average value of each computed vector.
This mode is more suited for simulations on a large population.
"""
for line in self.lines(aggregate):
diff --git a/openfisca_core/tracers/flat_trace.py b/openfisca_core/tracers/flat_trace.py
index 362c386137..bbd9b1fa7d 100644
--- a/openfisca_core/tracers/flat_trace.py
+++ b/openfisca_core/tracers/flat_trace.py
@@ -3,22 +3,16 @@
import typing
import numpy
-import numpy.typing
from openfisca_core.indexed_enums import EnumArray
if typing.TYPE_CHECKING:
- from openfisca_core.tracers import TraceNode, FullTracer
-
- Array = typing.Union[EnumArray, numpy.typing.ArrayLike]
- Trace = typing.Dict[str, dict]
+ from openfisca_core.tracers import TraceNode
class FlatTrace:
- _full_tracer: FullTracer
-
- def __init__(self, full_tracer: FullTracer) -> None:
+ def __init__(self, full_tracer):
self._full_tracer = full_tracer
def key(self, node: TraceNode) -> str:
@@ -26,23 +20,17 @@ def key(self, node: TraceNode) -> str:
period = node.period
return f"{name}<{period}>"
- def get_trace(self) -> dict:
+ def get_trace(self):
trace = {}
-
for node in self._full_tracer.browse_trace():
- # We don't want cache read to overwrite data about the initial
- # calculation.
- #
- # We therefore use a non-overwriting update.
- trace.update({
+ trace.update({ # We don't want cache read to overwrite data about the initial calculation. We therefore use a non-overwriting update.
key: node_trace
for key, node_trace in self._get_flat_trace(node).items()
if key not in trace
})
-
return trace
- def get_serialized_trace(self) -> dict:
+ def get_serialized_trace(self):
return {
key: {
**flat_trace,
@@ -51,26 +39,16 @@ def get_serialized_trace(self) -> dict:
for key, flat_trace in self.get_trace().items()
}
- def serialize(
- self,
- value: typing.Optional[Array],
- ) -> typing.Union[typing.Optional[Array], list]:
+ def serialize(self, value: numpy.ndarray) -> typing.List:
if isinstance(value, EnumArray):
value = value.decode_to_str()
-
- if isinstance(value, numpy.ndarray) and \
- numpy.issubdtype(value.dtype, numpy.dtype(bytes)):
+ if isinstance(value, numpy.ndarray) and numpy.issubdtype(value.dtype, numpy.dtype(bytes)):
value = value.astype(numpy.dtype(str))
-
if isinstance(value, numpy.ndarray):
value = value.tolist()
-
return value
- def _get_flat_trace(
- self,
- node: TraceNode,
- ) -> Trace:
+ def _get_flat_trace(self, node: TraceNode) -> typing.Dict[str, typing.Dict]:
key = self.key(node)
node_trace = {
@@ -79,15 +57,11 @@ def _get_flat_trace(
self.key(child) for child in node.children
],
'parameters': {
- self.key(parameter):
- self.serialize(parameter.value)
- for parameter
- in node.parameters
+ self.key(parameter): self.serialize(parameter.value) for parameter in node.parameters
},
'value': node.value,
'calculation_time': node.calculation_time(),
'formula_time': node.formula_time(),
},
}
-
return node_trace
diff --git a/openfisca_core/tracers/full_tracer.py b/openfisca_core/tracers/full_tracer.py
index 56e695b4e0..ab109fb9c9 100644
--- a/openfisca_core/tracers/full_tracer.py
+++ b/openfisca_core/tracers/full_tracer.py
@@ -1,8 +1,9 @@
-from __future__ import annotations
-
import time
import typing
+import numpy
+
+# from openfisca_core import tracers
from openfisca_core.tracers import (
ComputationLog,
FlatTrace,
@@ -11,117 +12,71 @@
TraceNode,
)
-if typing.TYPE_CHECKING:
- import numpy
- import numpy.typing
-
- from openfisca_core.periods import Period
-
- Stack = typing.List[typing.Dict[str, typing.Union[str, Period]]]
-
class FullTracer:
- _simple_tracer: SimpleTracer
- _trees: list
- _current_node: typing.Optional[TraceNode]
-
- def __init__(self) -> None:
+ def __init__(self):
self._simple_tracer = SimpleTracer()
self._trees = []
self._current_node = None
- def record_calculation_start(
- self,
- variable: str,
- period: Period,
- ) -> None:
+ def record_calculation_start(self, variable: str, period):
self._simple_tracer.record_calculation_start(variable, period)
self._enter_calculation(variable, period)
self._record_start_time()
- def _enter_calculation(
- self,
- variable: str,
- period: Period,
- ) -> None:
- new_node = TraceNode(
- name = variable,
- period = period,
- parent = self._current_node,
- )
-
+ def _enter_calculation(self, variable: str, period):
+ new_node = TraceNode(name = variable, period = period, parent = self._current_node)
if self._current_node is None:
self._trees.append(new_node)
-
else:
self._current_node.append_child(new_node)
-
self._current_node = new_node
- def record_parameter_access(
- self,
- parameter: str,
- period: Period,
- value: numpy.typing.ArrayLike,
- ) -> None:
-
- if self._current_node is not None:
- self._current_node.parameters.append(
- TraceNode(name = parameter, period = period, value = value),
- )
-
- def _record_start_time(
- self,
- time_in_s: typing.Optional[float] = None,
- ) -> None:
+ def record_parameter_access(self, parameter: str, period, value):
+ self._current_node.parameters.append(TraceNode(name = parameter, period = period, value = value))
+
+ def _record_start_time(self, time_in_s: typing.Optional[float] = None):
if time_in_s is None:
time_in_s = self._get_time_in_sec()
- if self._current_node is not None:
- self._current_node.start = time_in_s
+ self._current_node.start = time_in_s
- def record_calculation_result(self, value: numpy.typing.ArrayLike) -> None:
- if self._current_node is not None:
- self._current_node.value = value
+ def record_calculation_result(self, value: numpy.ndarray):
+ self._current_node.value = value
- def record_calculation_end(self) -> None:
+ def record_calculation_end(self):
self._simple_tracer.record_calculation_end()
self._record_end_time()
self._exit_calculation()
- def _record_end_time(
- self,
- time_in_s: typing.Optional[float] = None,
- ) -> None:
+ def _record_end_time(self, time_in_s: typing.Optional[float] = None):
if time_in_s is None:
time_in_s = self._get_time_in_sec()
- if self._current_node is not None:
- self._current_node.end = time_in_s
+ self._current_node.end = time_in_s
- def _exit_calculation(self) -> None:
- if self._current_node is not None:
- self._current_node = self._current_node.parent
+ def _exit_calculation(self):
+ self._current_node = self._current_node.parent
@property
- def stack(self) -> Stack:
+ def stack(self):
return self._simple_tracer.stack
@property
- def trees(self) -> typing.List[TraceNode]:
+ def trees(self):
return self._trees
@property
- def computation_log(self) -> ComputationLog:
+ def computation_log(self):
return ComputationLog(self)
@property
- def performance_log(self) -> PerformanceLog:
+ def performance_log(self):
return PerformanceLog(self)
@property
- def flat_trace(self) -> FlatTrace:
+ def flat_trace(self):
return FlatTrace(self)
def _get_time_in_sec(self) -> float:
@@ -136,34 +91,24 @@ def generate_performance_graph(self, dir_path: str) -> None:
def generate_performance_tables(self, dir_path: str) -> None:
self.performance_log.generate_performance_tables(dir_path)
- def _get_nb_requests(self, tree: TraceNode, variable: str) -> int:
+ def _get_nb_requests(self, tree, variable: str):
tree_call = tree.name == variable
- children_calls = sum(
- self._get_nb_requests(child, variable)
- for child
- in tree.children
- )
+ children_calls = sum(self._get_nb_requests(child, variable) for child in tree.children)
return tree_call + children_calls
- def get_nb_requests(self, variable: str) -> int:
- return sum(
- self._get_nb_requests(tree, variable)
- for tree
- in self.trees
- )
+ def get_nb_requests(self, variable: str):
+ return sum(self._get_nb_requests(tree, variable) for tree in self.trees)
- def get_flat_trace(self) -> dict:
+ def get_flat_trace(self):
return self.flat_trace.get_trace()
- def get_serialized_flat_trace(self) -> dict:
+ def get_serialized_flat_trace(self):
return self.flat_trace.get_serialized_trace()
def browse_trace(self) -> typing.Iterator[TraceNode]:
-
def _browse_node(node):
yield node
-
for child in node.children:
yield from _browse_node(child)
diff --git a/openfisca_core/tracers/performance_log.py b/openfisca_core/tracers/performance_log.py
index cdd66a286b..7ab8c09965 100644
--- a/openfisca_core/tracers/performance_log.py
+++ b/openfisca_core/tracers/performance_log.py
@@ -1,5 +1,3 @@
-from __future__ import annotations
-
import csv
import importlib.resources
import itertools
@@ -9,82 +7,41 @@
from openfisca_core.tracers import TraceNode
-if typing.TYPE_CHECKING:
- from openfisca_core.tracers import FullTracer
-
- Trace = typing.Dict[str, dict]
- Calculation = typing.Tuple[str, dict]
- SortedTrace = typing.List[Calculation]
-
class PerformanceLog:
- def __init__(self, full_tracer: FullTracer) -> None:
+ def __init__(self, full_tracer):
self._full_tracer = full_tracer
- def generate_graph(self, dir_path: str) -> None:
+ def generate_graph(self, dir_path):
with open(os.path.join(dir_path, 'performance_graph.html'), 'w') as f:
- template = importlib.resources.read_text(
- 'openfisca_core.scripts.assets',
- 'index.html',
- )
-
- perf_graph_html = template.replace(
- '{{data}}',
- json.dumps(self._json()),
- )
-
+ template = importlib.resources.read_text('openfisca_core.scripts.assets', 'index.html')
+ perf_graph_html = template.replace('{{data}}', json.dumps(self._json()))
f.write(perf_graph_html)
def generate_performance_tables(self, dir_path: str) -> None:
+
flat_trace = self._full_tracer.get_flat_trace()
csv_rows = [
- {
- 'name': key,
- 'calculation_time': trace['calculation_time'],
- 'formula_time': trace['formula_time'],
- }
- for key, trace
- in flat_trace.items()
+ {'name': key, 'calculation_time': trace['calculation_time'], 'formula_time': trace['formula_time']}
+ for key, trace in flat_trace.items()
]
-
- self._write_csv(
- os.path.join(dir_path, 'performance_table.csv'),
- csv_rows,
- )
+ self._write_csv(os.path.join(dir_path, 'performance_table.csv'), csv_rows)
aggregated_csv_rows = [
{'name': key, **aggregated_time}
- for key, aggregated_time
- in self.aggregate_calculation_times(flat_trace).items()
+ for key, aggregated_time in self.aggregate_calculation_times(flat_trace).items()
]
- self._write_csv(
- os.path.join(dir_path, 'aggregated_performance_table.csv'),
- aggregated_csv_rows,
- )
+ self._write_csv(os.path.join(dir_path, 'aggregated_performance_table.csv'), aggregated_csv_rows)
- def aggregate_calculation_times(
- self,
- flat_trace: Trace,
- ) -> typing.Dict[str, dict]:
+ def aggregate_calculation_times(self, flat_trace: typing.Dict) -> typing.Dict[str, typing.Dict]:
- def _aggregate_calculations(calculations: list) -> dict:
+ def _aggregate_calculations(calculations):
calculation_count = len(calculations)
-
- calculation_time = sum(
- calculation[1]['calculation_time']
- for calculation
- in calculations
- )
-
- formula_time = sum(
- calculation[1]['formula_time']
- for calculation
- in calculations
- )
-
+ calculation_time = sum(calculation[1]['calculation_time'] for calculation in calculations)
+ formula_time = sum(calculation[1]['formula_time'] for calculation in calculations)
return {
'calculation_count': calculation_count,
'calculation_time': TraceNode.round(calculation_time),
@@ -93,43 +50,26 @@ def _aggregate_calculations(calculations: list) -> dict:
'avg_formula_time': TraceNode.round(formula_time / calculation_count),
}
- def _groupby(calculation: Calculation) -> str:
- return calculation[0].split('<')[0]
-
- all_calculations: SortedTrace = sorted(flat_trace.items())
-
+ all_calculations = sorted(flat_trace.items())
return {
variable_name: _aggregate_calculations(list(calculations))
- for variable_name, calculations
- in itertools.groupby(all_calculations, _groupby)
+ for variable_name, calculations in itertools.groupby(all_calculations, lambda calculation: calculation[0].split('<')[0])
}
- def _json(self) -> dict:
+ def _json(self):
children = [self._json_tree(tree) for tree in self._full_tracer.trees]
calculations_total_time = sum(child['value'] for child in children)
+ return {'name': 'All calculations', 'value': calculations_total_time, 'children': children}
- return {
- 'name': 'All calculations',
- 'value': calculations_total_time,
- 'children': children,
- }
-
- def _json_tree(self, tree: TraceNode) -> dict:
+ def _json_tree(self, tree: TraceNode):
calculation_total_time = tree.calculation_time()
children = [self._json_tree(child) for child in tree.children]
+ return {'name': f"{tree.name}<{tree.period}>", 'value': calculation_total_time, 'children': children}
- return {
- 'name': f"{tree.name}<{tree.period}>",
- 'value': calculation_total_time,
- 'children': children,
- }
-
- def _write_csv(self, path: str, rows: typing.List[dict]) -> None:
+ def _write_csv(self, path: str, rows: typing.List[typing.Dict[str, typing.Any]]) -> None:
fieldnames = list(rows[0].keys())
-
with open(path, 'w') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames = fieldnames)
writer.writeheader()
-
for row in rows:
writer.writerow(row)
diff --git a/openfisca_core/tracers/simple_tracer.py b/openfisca_core/tracers/simple_tracer.py
index c54b313f60..3e6c602487 100644
--- a/openfisca_core/tracers/simple_tracer.py
+++ b/openfisca_core/tracers/simple_tracer.py
@@ -1,35 +1,23 @@
-from __future__ import annotations
-
-import typing
-
-if typing.TYPE_CHECKING:
- import numpy
- import numpy.typing
-
- from openfisca_core.periods import Period
-
- Stack = typing.List[typing.Dict[str, typing.Union[str, Period]]]
+import numpy
class SimpleTracer:
- _stack: Stack
-
- def __init__(self) -> None:
+ def __init__(self):
self._stack = []
- def record_calculation_start(self, variable: str, period: Period) -> None:
+ def record_calculation_start(self, variable: str, period):
self.stack.append({'name': variable, 'period': period})
- def record_calculation_result(self, value: numpy.typing.ArrayLike) -> None:
+ def record_calculation_result(self, value: numpy.ndarray):
pass # ignore calculation result
def record_parameter_access(self, parameter: str, period, value):
pass
- def record_calculation_end(self) -> None:
+ def record_calculation_end(self):
self.stack.pop()
@property
- def stack(self) -> Stack:
+ def stack(self):
return self._stack
diff --git a/openfisca_core/tracers/trace_node.py b/openfisca_core/tracers/trace_node.py
index 93b630886c..71f15cfb93 100644
--- a/openfisca_core/tracers/trace_node.py
+++ b/openfisca_core/tracers/trace_node.py
@@ -3,14 +3,9 @@
import dataclasses
import typing
-if typing.TYPE_CHECKING:
- import numpy
+import numpy
- from openfisca_core.indexed_enums import EnumArray
- from openfisca_core.periods import Period
-
- Array = typing.Union[EnumArray, numpy.typing.ArrayLike]
- Time = typing.Union[float, int]
+from openfisca_core.periods import Period
@dataclasses.dataclass
@@ -20,35 +15,23 @@ class TraceNode:
parent: typing.Optional[TraceNode] = None
children: typing.List[TraceNode] = dataclasses.field(default_factory = list)
parameters: typing.List[TraceNode] = dataclasses.field(default_factory = list)
- value: typing.Optional[Array] = None
+ value: numpy.ndarray = None
start: float = 0
end: float = 0
- def calculation_time(self, round_: bool = True) -> Time:
+ def calculation_time(self, round_ = True):
result = self.end - self.start
-
if round_:
return self.round(result)
-
return result
- def formula_time(self) -> float:
- children_calculation_time = sum(
- child.calculation_time(round_ = False)
- for child
- in self.children
- )
-
- result = (
- + self.calculation_time(round_ = False)
- - children_calculation_time
- )
-
+ def formula_time(self):
+ result = self.calculation_time(round_ = False) - sum(child.calculation_time(round_ = False) for child in self.children)
return self.round(result)
- def append_child(self, node: TraceNode) -> None:
+ def append_child(self, node: TraceNode):
self.children.append(node)
@staticmethod
- def round(time: Time) -> float:
+ def round(time):
return float(f'{time:.4g}') # Keep only 4 significant figures
diff --git a/openfisca_core/tracers/tracing_parameter_node_at_instant.py b/openfisca_core/tracers/tracing_parameter_node_at_instant.py
index 94abf6b2ca..2595989467 100644
--- a/openfisca_core/tracers/tracing_parameter_node_at_instant.py
+++ b/openfisca_core/tracers/tracing_parameter_node_at_instant.py
@@ -1,7 +1,3 @@
-from __future__ import annotations
-
-import typing
-
import numpy
from openfisca_core.parameters import (
@@ -10,70 +6,30 @@
VectorialParameterNodeAtInstant,
)
-if typing.TYPE_CHECKING:
- import numpy.typing
-
- from openfisca_core.tracers import FullTracer
-
- ParameterNode = typing.Union[
- VectorialParameterNodeAtInstant,
- ParameterNodeAtInstant,
- ]
-
- Child = typing.Union[ParameterNode, numpy.typing.ArrayLike]
-
class TracingParameterNodeAtInstant:
- def __init__(
- self,
- parameter_node_at_instant: ParameterNode,
- tracer: FullTracer,
- ) -> None:
+ def __init__(self, parameter_node_at_instant, tracer):
self.parameter_node_at_instant = parameter_node_at_instant
self.tracer = tracer
- def __getattr__(
- self,
- key: str,
- ) -> typing.Union[TracingParameterNodeAtInstant, Child]:
+ def __getattr__(self, key):
child = getattr(self.parameter_node_at_instant, key)
return self.get_traced_child(child, key)
- def __getitem__(
- self,
- key: typing.Union[str, numpy.typing.ArrayLike],
- ) -> typing.Union[TracingParameterNodeAtInstant, Child]:
+ def __getitem__(self, key):
child = self.parameter_node_at_instant[key]
return self.get_traced_child(child, key)
- def get_traced_child(
- self,
- child: Child,
- key: typing.Union[str, numpy.typing.ArrayLike],
- ) -> typing.Union[TracingParameterNodeAtInstant, Child]:
+ def get_traced_child(self, child, key):
period = self.parameter_node_at_instant._instant_str
-
- if isinstance(
- child,
- (ParameterNodeAtInstant, VectorialParameterNodeAtInstant),
- ):
+ if isinstance(child, (ParameterNodeAtInstant, VectorialParameterNodeAtInstant)):
return TracingParameterNodeAtInstant(child, self.tracer)
-
- if not isinstance(key, str) or \
- isinstance(
- self.parameter_node_at_instant,
- VectorialParameterNodeAtInstant,
- ):
- # In case of vectorization, we keep the parent node name as, for
- # instance, rate[status].zone1 is best described as the value of
- # "rate".
+ if not isinstance(key, str) or isinstance(self.parameter_node_at_instant, VectorialParameterNodeAtInstant):
+ # In case of vectorization, we keep the parent node name as, for instance, rate[status].zone1 is best described as the value of "rate"
name = self.parameter_node_at_instant._name
-
else:
name = '.'.join([self.parameter_node_at_instant._name, key])
-
if isinstance(child, (numpy.ndarray,) + config.ALLOWED_PARAM_TYPES):
self.tracer.record_parameter_access(name, period, child)
-
return child
diff --git a/openfisca_core/variables/config.py b/openfisca_core/variables/config.py
index b260bb3dd9..edda2664b6 100644
--- a/openfisca_core/variables/config.py
+++ b/openfisca_core/variables/config.py
@@ -8,7 +8,7 @@
VALUE_TYPES = {
bool: {
- 'dtype': numpy.bool_,
+ 'dtype': numpy.bool,
'default': False,
'json_type': 'boolean',
'formatted_value_type': 'Boolean',
diff --git a/setup.py b/setup.py
index cb28724032..90b18649dc 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@
general_requirements = [
'dpath >= 1.5.0, < 2.0.0',
'pytest >= 4.4.1, < 6.0.0', # For openfisca test
- 'numpy >= 1.11, < 1.21',
+ 'numpy >= 1.11, < 1.19',
'psutil >= 5.4.7, < 6.0.0',
'PyYAML >= 3.10',
'sortedcontainers == 2.2.2',
@@ -35,7 +35,7 @@
setup(
name = 'OpenFisca-Core',
- version = '35.3.7',
+ version = '35.3.8',
author = 'OpenFisca Team',
author_email = 'contact@openfisca.org',
classifiers = [