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 = [