Skip to content

Commit

Permalink
Split tax scale tests by class
Browse files Browse the repository at this point in the history
  • Loading branch information
Mauko Quiroga committed Dec 30, 2019
1 parent 3a06983 commit 6b62771
Show file tree
Hide file tree
Showing 9 changed files with 401 additions and 261 deletions.
85 changes: 48 additions & 37 deletions openfisca_core/taxscales.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,38 +86,16 @@ def multiply_thresholds(
f"{self.__class__.__name__}"
)

def compute_bracket_index(
def bracket_indices(
self,
tax_base: ndarray,
factor: float = 1.0,
round_base_decimals: Optional[int] = None,
) -> ndarray:
"""
Compute the relevant bracket for the given tax bases.
:param tax_base: Array of the tax bases.
:param factor: Factor to apply to the thresholds of the tax scales.
:param round_base_decimals: Decimals to keep when rounding thresholds.
:returns: Int array with relevant bracket indices for the given tax bases.
>>> marginal_tax_scale = MarginalRateTaxScale()
>>> marginal_tax_scale.add_bracket(0, 0)
>>> marginal_tax_scale.add_bracket(100, 0.1)
>>> tax_base = array([0, 150])
>>> marginal_tax_scale.compute_bracket_index(tax_base)
[0, 1]
"""
base1 = tile(tax_base, (len(self.thresholds), 1)).T
factor = ones(len(tax_base)) * factor

# finfo(float_).eps is used to avoid nan = 0 * inf creation
thresholds1 = outer(factor + finfo(float_).eps, array(self.thresholds + [inf]))

if round_base_decimals is not None:
thresholds1 = round_(thresholds1, round_base_decimals)

return (base1 - thresholds1[:, :-1] >= 0).sum(axis = 1) - 1
) -> Any:
raise NotImplementedError(
f'Method "bracket_indices" is not implemented for'
f"{self.__class__.__name__}"
)

def copy(self) -> "AbstractTaxScale":
new = empty_clone(self)
Expand Down Expand Up @@ -226,11 +204,44 @@ def multiply_thresholds(

return new_tax_scale

def bracket_indices(
self,
tax_base: ndarray,
factor: float = 1.0,
round_base_decimals: Optional[int] = None,
) -> ndarray:
"""
Compute the relevant bracket indices for the given tax bases.
:param tax_base: Array of the tax bases.
:param factor: Factor to apply to the thresholds of the tax scales.
:param round_base_decimals: Decimals to keep when rounding thresholds.
:returns: Int array with relevant bracket indices for the given tax bases.
>>> marginal_tax_scale = MarginalRateTaxScale()
>>> marginal_tax_scale.add_bracket(0, 0)
>>> marginal_tax_scale.add_bracket(100, 0.1)
>>> tax_base = array([0, 150])
>>> marginal_tax_scale.compute_bracket_index(tax_base)
[0, 1]
"""
base1 = tile(tax_base, (len(self.thresholds), 1)).T
factor = ones(len(tax_base)) * factor

# finfo(float_).eps is used to avoid nan = 0 * inf creation
thresholds1 = outer(factor + finfo(float_).eps, array(self.thresholds + [inf]))

if round_base_decimals is not None:
thresholds1 = round_(thresholds1, round_base_decimals)

return (base1 - thresholds1[:, :-1] >= 0).sum(axis = 1) - 1

def to_dict(self) -> dict:
raise ValueError({
return {
str(threshold): self.rates[index]
for index, threshold in enumerate(self.thresholds)
})
}


class SingleAmountTaxScale(AbstractTaxScale):
Expand Down Expand Up @@ -278,10 +289,10 @@ def calc(self, tax_base: ndarray, right: bool = False) -> ndarray:
return guarded_amounts[bracket_indices - 1]

def to_dict(self) -> dict:
raise ValueError({
return {
str(threshold): self.amounts[index]
for index, threshold in enumerate(self.thresholds)
})
}


class MarginalAmountTaxScale(SingleAmountTaxScale):
Expand Down Expand Up @@ -442,16 +453,16 @@ def combine_bracket(
self.add_bracket(self.thresholds[i], rate)
i += 1

def compute_marginal_rate(
def marginal_rates(
self,
tax_base: ndarray,
factor: float = 1.0,
round_base_decimals: Optional[int] = None,
) -> ndarray:
"""
Compute the marginal tax rate relevant for the given tax bases.
Compute the marginal tax rates relevant for the given tax bases.
:param base: Array of the tax bases.
:param tax_base: Array of the tax bases.
:param factor: Factor to apply to the thresholds of the tax scale.
:param round_base_decimals: Decimals to keep when rounding thresholds.
Expand All @@ -461,10 +472,10 @@ def compute_marginal_rate(
>>> marginal_tax_scale.add_bracket(0, 0)
>>> marginal_tax_scale.add_bracket(100, 0.1)
>>> tax_base = array([0, 150])
>>> marginal_tax_scale.compute_marginal_rate(tax_base)
>>> marginal_tax_scale.marginal_rates(tax_base)
[0.0, 0.1]
"""
bracket_indices = self.compute_bracket_index(
bracket_indices = self.bracket_indices(
tax_base,
factor,
round_base_decimals,
Expand Down
Empty file.
26 changes: 26 additions & 0 deletions tests/core/tax_scales/test_abstract_rate_tax_scale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from numpy import array

from openfisca_core.taxscales import AbstractRateTaxScale
from openfisca_core.tools import assert_near


def test_bracket_indices():
tax_base = array([0, 10, 50, 125, 250])
tax_scale = AbstractRateTaxScale()
tax_scale.add_bracket(0, 0)
tax_scale.add_bracket(100, 0)
tax_scale.add_bracket(200, 0)

result = tax_scale.bracket_indices(tax_base)

assert_near(result, [0, 0, 0, 1, 2])


def test_to_dict():
tax_scale = AbstractRateTaxScale()
tax_scale.add_bracket(0, 0)
tax_scale.add_bracket(100, 0.1)

result = tax_scale.to_dict()

assert result == {"0": 0.0, "100": 0.1}
33 changes: 33 additions & 0 deletions tests/core/tax_scales/test_commons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from pytest import fixture

from openfisca_core.parameters import ParameterNode
from openfisca_core.taxscales import combine_tax_scales
from openfisca_core.tools import assert_near


@fixture
def node():
return ParameterNode(
"baremes",
data = {
"health": {
"brackets": [
{"rate": {"2015-01-01": 0.05}, "threshold": {"2015-01-01": 0}},
{"rate": {"2015-01-01": 0.10}, "threshold": {"2015-01-01": 2000}},
]
},
"retirement": {
"brackets": [
{"rate": {"2015-01-01": 0.02}, "threshold": {"2015-01-01": 0}},
{"rate": {"2015-01-01": 0.04}, "threshold": {"2015-01-01": 3000}},
]
},
},
)(2015)


def test_combine_tax_scales(node):
result = combine_tax_scales(node)

assert_near(result.thresholds, [0, 2000, 3000])
assert_near(result.rates, [0.07, 0.12, 0.14], 1e-13)
18 changes: 18 additions & 0 deletions tests/core/tax_scales/test_linear_average_rate_tax_scale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from numpy import array

from openfisca_core.taxscales import LinearAverageRateTaxScale
from openfisca_core.tools import assert_near


def test_to_marginal():
tax_base = array([1, 1.5, 2, 2.5])
tax_scale = LinearAverageRateTaxScale()
tax_scale.add_bracket(0, 0)
tax_scale.add_bracket(1, 0.1)
tax_scale.add_bracket(2, 0.2)

result = tax_scale.to_marginal()

assert result.thresholds == [0, 1, 2]
assert_near(result.rates, [0.1, 0.3, 0.2], absolute_error_margin = 0)
assert_near(result.calc(tax_base), [0.1, 0.25, 0.4, 0.5], absolute_error_margin = 0)
43 changes: 43 additions & 0 deletions tests/core/tax_scales/test_marginal_amount_tax_scale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from pytest import fixture

from numpy import array

from openfisca_core.parameters import Scale
from openfisca_core.periods import Instant
from openfisca_core.taxscales import MarginalAmountTaxScale
from openfisca_core.tools import assert_near


@fixture
def data():
return {
"description": "Social security contribution tax scale",
"metadata": {"threshold_unit": "currency-EUR", "rate_unit": "/1"},
"brackets": [
{
"threshold": {"2017-10-01": {"value": 0.23}},
"amount": {"2017-10-01": {"value": 6}, },
}
],
}


def test_calc():
tax_base = array([1, 8, 10])
tax_scale = MarginalAmountTaxScale()
tax_scale.add_bracket(6, 0.23)
tax_scale.add_bracket(9, 0.29)

result = tax_scale.calc(tax_base)

assert_near(result, [0, 0.23, 0.52])


# TODO: move, as we're testing Scale, not MarginalAmountTaxScale
def test_dispatch_scale_type_on_creation(data):
scale = Scale("amount_scale", data, "")
first_jan = Instant((2017, 11, 1))

result = scale.get_at_instant(first_jan)

assert isinstance(result, MarginalAmountTaxScale)
Loading

0 comments on commit 6b62771

Please sign in to comment.