Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3b66ba9
running on python 3.13.0
noman404 Jan 23, 2025
a29dc5b
upgrade numpy 2.1.0
noman404 Jan 23, 2025
9906443
enable policyengine dependencies for build test
noman404 Jan 23, 2025
446bc11
updated to float64, round and inf
noman404 Jan 26, 2025
ee7a428
updated numpy.select default types
noman404 Jan 26, 2025
5fd2a36
fix concat issue
noman404 Jan 26, 2025
87b06f1
fix enum passing issue
noman404 Jan 26, 2025
14ecf64
fix tracer line issue
noman404 Jan 26, 2025
66d1b3c
fix unknow dtype issue for PE us
noman404 Jan 29, 2025
5b92473
applied format
noman404 Jan 29, 2025
107b120
added change log
noman404 Jan 29, 2025
9350086
updated change log, fix lint issue
noman404 Jan 30, 2025
929def7
added new tests
noman404 Jan 31, 2025
f810378
added GH workflow test for python 3.13
noman404 Jan 31, 2025
bf721bb
point to updated PE-US, added windows in GH workflow
noman404 Feb 7, 2025
3df7162
added long path ignore for windows
noman404 Feb 7, 2025
c46184c
added standard-imghdr
noman404 Feb 7, 2025
646125c
imghdr fix
noman404 Feb 7, 2025
719c611
argsort fix
noman404 Feb 21, 2025
b39d992
python build error fix for workflow
noman404 Feb 22, 2025
c141b10
added code comment for sorting
noman404 Feb 24, 2025
ca1910f
Removed country package installs
SakshiKekre May 6, 2025
4c62dce
Moved smoke test for country package to PR workflow
SakshiKekre May 6, 2025
ed10801
Added changelog
SakshiKekre May 6, 2025
dc4abe6
added pytest config for custom marker
SakshiKekre May 6, 2025
bcf0f76
PR workflow modified to verify if setuptools fixes distutils module u…
SakshiKekre May 6, 2025
39946bf
Merge remote-tracking branch 'upstream/master' into 334-upgrade-pytho…
MaxGhenis Jul 22, 2025
1379b52
Add Python 3.13 to smoke test matrix and merge latest main
MaxGhenis Jul 22, 2025
a0d73d8
Skip policyengine-us smoke tests for Python 3.13
MaxGhenis Jul 22, 2025
db04b45
Complete Python 3.13 upgrade with additional improvements
MaxGhenis Jul 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
Lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Check formatting
uses: "lgeiger/black-action@master"
with:
Expand All @@ -15,13 +15,13 @@ jobs:
name: Check version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build changelog
Expand All @@ -34,21 +34,58 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
python-version: [ "3.12", "3.13" ]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
- name: Enable long paths in Git (Windows Only)
if: runner.os == 'Windows'
run: git config --system core.longpaths true
- name: Enable Win32 long paths via registry (Windows Only)
if: runner.os == 'Windows'
run: reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: ${{ matrix.python-version }}
- name: Install package
run: make install
- name: Run tests
run: make test
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4
- name: Build package
run: make build
- name: Test documentation builds
run: make documentation
SmokeTestForMultipleVersions:
name: Test Core and Country Compatibility on (${{ matrix.os }}, py${{ matrix.python-version }})
runs-on: ${{ matrix.os }}
needs: Lint
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install -core package with dev deps
# run: python -m pip install .
# Installing with dev deps to see if setuptools provides distutils (removed in Python 3.12)
run: python -m pip install ".[dev]"
- name: Install -us package from PyPI
# Skip policyengine-us installation for Python 3.13 until it's updated
if: matrix.python-version != '3.13'
run: python -m pip install policyengine-us
- name: Run smoke tests only
# Only run smoke tests if policyengine-us was installed
if: matrix.python-version != '3.13'
run: pytest -m smoke
env:
RUN_SMOKE_TESTS: "1"
22 changes: 11 additions & 11 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
(github.repository == 'PolicyEngine/policyengine-core')
&& (github.event.head_commit.message == 'Update PolicyEngine Core')
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Check formatting
uses: "lgeiger/black-action@master"
with:
Expand All @@ -22,15 +22,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
token: ${{ secrets.POLICYENGINE_GITHUB }}
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
- name: Build changelog
run: pip install yaml-changelog && make changelog
- name: Preview changelog update
Expand All @@ -49,16 +49,16 @@ jobs:
&& (github.event.head_commit.message == 'Update PolicyEngine Core')
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
- name: Install package
run: make install
- name: Run tests
run: make test
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4
- name: Generate documentation
run: make documentation
- name: Deploy documentation
Expand All @@ -75,11 +75,11 @@ jobs:
&& (github.event.head_commit.message == 'Update PolicyEngine Core')
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"
- name: Publish a git tag
run: ".github/publish-git-tag.sh || true"
- name: Install package
Expand Down
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ format:

install:
pip install -e ".[dev]" --config-settings editable_mode=compat
pip install policyengine-us
pip install policyengine-uk

test-country-template:
policyengine-core test policyengine_core/country_template/tests -c policyengine_core.country_template
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This package, a fork of [OpenFisca-Core](https://github.com/OpenFisca/OpenFisca-

# Prerequisites

Python 3.10 or beyond is required.
Python 3.10, 3.11, 3.12, or 3.13 is required.

# Setting Up

Expand Down
5 changes: 5 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- bump: minor
changes:
changed:
- added support for python 3.13.0
- upgraded dependency to numpy 2.1.0
5 changes: 5 additions & 0 deletions policyengine_core/commons/formulas.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ def concat(this: ArrayLike[str], that: ArrayLike[str]) -> ArrayType[str]:
array(['this1.0', 'that2.5']...)

"""
if isinstance(this, tuple):
raise TypeError("First argument must not be a tuple.")

if isinstance(that, tuple):
raise TypeError("Second argument must not be a tuple.")

if isinstance(this, numpy.ndarray) and not numpy.issubdtype(
this.dtype, numpy.str_
Expand Down
3 changes: 2 additions & 1 deletion policyengine_core/enums/enum_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ def decode_to_str(self) -> numpy.str_:
"""
return numpy.select(
[self == item.index for item in self.possible_values],
[item.name for item in self.possible_values],
[str(item.name) for item in self.possible_values],
default="unknown",
)

def __repr__(self) -> str:
Expand Down
2 changes: 0 additions & 2 deletions policyengine_core/parameters/parameter_node_at_instant.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,4 @@ def __repr__(self) -> str:
for name, value in self._children.items()
]
)
if sys.version_info < (3, 0):
return result
return result
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,15 @@ def __getitem__(self, key: str) -> Any:
enum = type(key[0])
key = numpy.select(
[key == item for item in enum],
[item.name for item in enum],
[str(item.name) for item in enum],
default="unknown",
)
elif isinstance(key, EnumArray):
enum = key.possible_values
key = numpy.select(
[key == item.index for item in enum],
[item.name for item in enum],
default="unknown",
)
else:
key = key.astype("str")
Expand Down
4 changes: 2 additions & 2 deletions policyengine_core/populations/group_population.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def max(self, array: ArrayLike, role: Role = None) -> ArrayLike:
return self.reduce(
array,
reducer=numpy.maximum,
neutral_element=-numpy.infty,
neutral_element=-numpy.inf,
role=role,
)

Expand All @@ -256,7 +256,7 @@ def min(self, array: ArrayLike, role: Role = None) -> ArrayLike:
return self.reduce(
array,
reducer=numpy.minimum,
neutral_element=numpy.infty,
neutral_element=numpy.inf,
role=role,
)

Expand Down
6 changes: 5 additions & 1 deletion policyengine_core/populations/population.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,11 @@ def get_rank(
# We double-argsort all lines of the matrix.
# Double-argsorting gets the rank of each value once sorted
# For instance, if x = [3,1,6,4,0], y = numpy.argsort(x) is [4, 1, 0, 3, 2] (because the value with index 4 is the smallest one, the value with index 1 the second smallest, etc.) and z = numpy.argsort(y) is [2, 1, 4, 3, 0], the rank of each value.
sorted_matrix = numpy.argsort(numpy.argsort(matrix))

# because of the infinities the first sort creates positional indices
# The second argsort converts these positions to ranks, thus fixes the broken sort issue
first_argsort = numpy.argsort(matrix, axis=1, kind="stable")
sorted_matrix = numpy.argsort(first_argsort, axis=1, kind="stable")

# Build the result vector by taking for each person the value in the right line (corresponding to its household id) and the right column (corresponding to its position)
result = sorted_matrix[ids, positions]
Expand Down
8 changes: 4 additions & 4 deletions policyengine_core/taxscales/marginal_rate_tax_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ def calc(
#
# numpy.finfo(float_).eps
thresholds1 = numpy.outer(
factor + numpy.finfo(numpy.float_).eps,
factor + numpy.finfo(numpy.float64).eps,
numpy.array(self.thresholds + [numpy.inf]),
)

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

a = numpy.maximum(
numpy.minimum(base1, thresholds1[:, 1:]) - thresholds1[:, :-1], 0
Expand All @@ -82,8 +82,8 @@ def calc(

else:
r = numpy.tile(self.rates, (len(tax_base), 1))
b = numpy.round_(a, round_base_decimals)
return numpy.round_(r * b, round_base_decimals).sum(axis=1)
b = numpy.round(a, round_base_decimals)
return numpy.round(r * b, round_base_decimals).sum(axis=1)

def combine_bracket(
self,
Expand Down
4 changes: 2 additions & 2 deletions policyengine_core/taxscales/rate_tax_scale_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ def bracket_indices(
#
# numpy.finfo(float_).eps
thresholds1 = numpy.outer(
+factor + numpy.finfo(numpy.float_).eps,
+factor + numpy.finfo(numpy.float64).eps,
numpy.array(self.thresholds),
)

if round_decimals is not None:
thresholds1 = numpy.round_(thresholds1, round_decimals)
thresholds1 = numpy.round(thresholds1, round_decimals)

return (base1 - thresholds1 >= 0).sum(axis=1) - 1

Expand Down
3 changes: 2 additions & 1 deletion policyengine_core/tools/simulation_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ def _dump_entity(population, directory):
else:
encoded_roles = np.select(
[population.members_role == role for role in flattened_roles],
[role.key for role in flattened_roles],
[str(role.key) for role in flattened_roles],
default="unknown",
)
np.save(os.path.join(path, "members_role.npy"), encoded_roles)

Expand Down
5 changes: 5 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

[pytest]
markers =
smoke: mark a test as part of the smoke suite

4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

general_requirements = [
"pytest>=8,<9",
"numpy~=1.26.4",
"numpy~=2.1.0",
"sortedcontainers<3",
"numexpr<3",
"dpath<3",
Expand All @@ -25,6 +25,7 @@
"pyvis>=0.3.2",
"microdf_python>=1.0.0",
"huggingface_hub>=0.25.1",
"standard-imghdr",
]

dev_requirements = [
Expand Down Expand Up @@ -60,6 +61,7 @@
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Information Analysis",
],
description="Core microsimulation engine enabling country-specific policy models.",
Expand Down
10 changes: 10 additions & 0 deletions tests/core/commons/test_formulas.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,13 @@ def test_switch_when_values_are_empty():

with pytest.raises(AssertionError):
assert commons.switch(conditions, value_by_condition)


def test_concat_tuple_inputs():
with pytest.raises(TypeError, match="First argument must not be a tuple."):
commons.concat(("a", "b"), numpy.array(["c", "d"]))

with pytest.raises(
TypeError, match="Second argument must not be a tuple."
):
commons.concat(numpy.array(["a", "b"]), ("c", "d"))
7 changes: 6 additions & 1 deletion tests/core/parameters_fancy_indexing/test_fancy_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,10 @@ class TypesZone(Enum):
z1 = "Zone 1"
z2 = "Zone 2"

zone = np.asarray([TypesZone.z1, TypesZone.z2, TypesZone.z2, TypesZone.z1])
zone = np.asarray(
[
z.name
for z in [TypesZone.z1, TypesZone.z2, TypesZone.z2, TypesZone.z1]
]
)
assert_near(P.single.owner[zone], [100, 200, 200, 100])
3 changes: 2 additions & 1 deletion tests/core/test_tracers.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,8 @@ def test_log_aggregate(tracer):

lines = tracer.computation_log.lines(aggregate=True)
assert (
lines[0] == " A<2017, (default)> = {'avg': 1.0, 'max': 1, 'min': 1}"
lines[0].strip()
== "A<2017, (default)> = {'avg': np.float64(1.0), 'max': np.int64(1), 'min': np.int64(1)}"
)


Expand Down
9 changes: 9 additions & 0 deletions tests/test_us.py → tests/smoke/test_us.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import os
import pytest


@pytest.mark.smoke
@pytest.mark.skipif(
os.getenv("RUN_SMOKE_TESTS") != "1",
reason="Skip smoke tests unless explicitly enabled",
)
def test_policyengine_us_microsimulation_runs():
from policyengine_us import Microsimulation

Expand Down