Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "paramz",
"image": "mcr.microsoft.com/devcontainers/python:1-3.13-bullseye",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.debugpy"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false
}
}
},
"postCreateCommand": "python -m pip install --upgrade pip && python -m pip install -e '.[test,docs,notebook]' nose",
"remoteUser": "vscode"
}
40 changes: 26 additions & 14 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
- development
- deploy
pull_request:
workflow_dispatch:
release:
types: [published]

permissions:
contents: read
Expand All @@ -16,21 +19,34 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python: ['3.9', '3.10', '3.11', '3.12']
python: ['3.11', '3.12', '3.13', '3.14']
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}

- name: Install lib
- name: Install and test
run: |
pip install --upgrade pip
pip install -e .
pip install ".[test]"
python -m pytest

- name: Build and validate distribution
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.14'
run: |
pip install build twine
python -m build
twine check dist/*
python -m venv /tmp/paramz-wheel-test
/tmp/paramz-wheel-test/bin/python -m pip install --upgrade pip
/tmp/paramz-wheel-test/bin/python -m pip install dist/*.whl
cd /tmp
/tmp/paramz-wheel-test/bin/python -c "import paramz; print(paramz.__version__)"

deploy:
runs-on: ubuntu-latest
Expand All @@ -41,20 +57,16 @@ jobs:
uses: actions/checkout@v4

- name: Setup python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.9'
python-version: '3.11'

- name: Install twine
- name: Build and validate distribution
run: |
pip install --upgrade pip
pip install twine

- name: Build wheel
run: |
pip install setuptools
pip install wheel
python setup.py sdist bdist_wheel
pip install build twine
python -m build
twine check dist/*

- name: Upload to PyPI using twine
run: |
Expand Down
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Test: Parameterized Tests",
"type": "debugpy",
"request": "launch",
"program": "parameterized_tests.py",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/paramz/tests",
"justMyCode": false
}
]
}
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@ Changelog
=========


0.10.0 (2026-07-29)
-------------------

New
~~~
- Full compatibility with NumPy 2.x and Python 3.11-3.14. [fbordeu]
- Modernized package setup and build configuration. [fbordeu]
- Updated GitHub Actions workflows for modern CI/CD. [fbordeu]
- Added replacement wrapper for numpy's .data access for compatibility. [fbordeu]
- Refactored numerical operations and removed unnecessary float conversions. [fbordeu]
- Improved error handling in HDF5 parameter writing. [fbordeu]
- Updated AssertionsMixin to inherit from unittest.TestCase with improved tests. [fbordeu]

Fix
~~~
- Restored compatibility with NumPy 1.26.x while supporting NumPy 2.x. [fbordeu]
- Updated deprecated assertRegexp calls to modern equivalents. [fbordeu]
- Deactivated catch warnings causing issues with NumPy. [fbordeu]
- Added debugging config to resolve NumPy memory address issues in tests. [fbordeu]

Other
~~~~~
- Builds on preparatory NumPy compatibility work by [MartinBubel]. [fbordeu]
- Dropped support for Python < 3.11. [fbordeu]
- Set minimum dependency versions: numpy>=2, scipy>=1.15. [fbordeu]
- Bumped version: 0.9.6 → 0.10.0. [fbordeu]


(unreleased)
------------

Expand Down
2 changes: 1 addition & 1 deletion paramz/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.9.6"
__version__ = "0.10.0"
19 changes: 10 additions & 9 deletions paramz/core/index_operations.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
#===============================================================================
# Copyright (c) 2015, Max Zwiessele
# All rights reserved.
#
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
#
# * Neither the name of paramax nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
Expand All @@ -29,10 +29,11 @@
#===============================================================================

import numpy
from numpy.lib.function_base import vectorize
from .lists_and_dicts import IntArrayDict
from functools import reduce

vectorize = numpy.vectorize

def extract_properties_to_index(index, props):
prop_index = dict()
for i, cl in enumerate(props):
Expand Down Expand Up @@ -64,11 +65,11 @@ class ParameterIndexOperations(object):
model : 0 1 2 3 4 5 6 7 8 9
key1: 4 5
key2: 7 8

param1: 0 1 2 3 4 5
key1: 2 3
key2: 5

param2: 0 1 2 3 4
key1: 0
key2: 2 3
Expand Down Expand Up @@ -226,7 +227,7 @@ def items(self):
ind2 = self._filter_index(ind)
if ind2.size > 0:
yield i, ind2

def properties(self):
return [i[0] for i in self.items()]

Expand Down
2 changes: 1 addition & 1 deletion paramz/core/indexable.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def _raveled_index_for(self, param):
"""
from ..param import ParamConcatenation
if isinstance(param, ParamConcatenation):
return np.hstack((self._raveled_index_for(p) for p in param.params))
return np.hstack([self._raveled_index_for(p) for p in param.params])
return param._raveled_index() + self._offset_for(param)

def _raveled_index_for_transformed(self, param):
Expand Down
2 changes: 1 addition & 1 deletion paramz/core/lists_and_dicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

def intarray_default_factory():
import numpy as np
return np.int_([])
return np.array([], dtype=np.int64)

class IntArrayDict(defaultdict):
def __init__(self, default_factory=None):
Expand Down
7 changes: 4 additions & 3 deletions paramz/core/observable_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __new__(cls, input_array, *a, **kw):
if not isinstance(input_array, ObsAr):
try:
# try to cast ints to floats
obj = np.atleast_1d(np.require(input_array, dtype=np.float_, requirements=['W', 'C'])).view(cls)
obj = np.atleast_1d(np.require(input_array, dtype=np.float64, requirements=['W', 'C'])).view(cls)
except ValueError:
# do we have other dtypes in the array?
obj = np.atleast_1d(np.require(input_array, requirements=['W', 'C'])).view(cls)
Expand All @@ -72,10 +72,11 @@ def __array_finalize__(self, obj):
self.observers = getattr(obj, 'observers', None)
self._update_on = getattr(obj, '_update_on', None)

def __array_wrap__(self, out_arr, context=None):
def __array_wrap__(self, out_arr, context=None, return_scalar=False):
#np.ndarray.__array_wrap__(self, out_arr, context)
#return out_arr
return out_arr.view(np.ndarray)
result = out_arr.view(np.ndarray)
return result[()] if return_scalar else result

def _setup_observers(self):
# do not setup anything, as observable arrays do not have default observers
Expand Down
52 changes: 42 additions & 10 deletions paramz/core/parameter_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def _trigger_params_changed(self, trigger_parent=True):

If trigger_parent is True, we will tell the parent, otherwise not.
"""
self._sync_params_to_children()
[p._trigger_params_changed(trigger_parent=False) for p in self.parameters if not p.is_fixed]
self.notify_observers(None, None if trigger_parent else -np.inf)

Expand Down Expand Up @@ -280,21 +281,51 @@ def _propagate_param_grad(self, parray, garray):
#if self.gradient.size != self.size:
# self._gradient_array_ = np.empty(self.size, dtype=np.float64)

pi_old_size = 0
for pi in self.parameters:
pislice = slice(pi_old_size, pi_old_size + pi.size)
from ..param import Param

self.param_array[pislice] = pi.param_array.flat # , requirements=['C', 'W']).flat
self.gradient_full[pislice] = pi.gradient_full.flat # , requirements=['C', 'W']).flat
if isinstance(self, Param):
self._gradient_array_ = garray.reshape(self.shape)
np.ndarray.__setitem__(self, Ellipsis, parray.reshape(self.shape))
return

pi.param_array.data = parray[pislice].data
pi.gradient_full.data = garray[pislice].data
self._param_array_ = parray
self._gradient_array_ = garray

pi_old_size = 0
for pi in self.parameters:
pislice = slice(pi_old_size, pi_old_size + pi.size)
pi._propagate_param_grad(parray[pislice], garray[pislice])
pi_old_size += pi.size

self._model_initialized_ = True

def _sync_params_to_children(self):
"""Copy the canonical flat parameter array into ndarray leaf parameters."""
from ..param import Param

offset = 0
for parameter in self.parameters:
pslice = slice(offset, offset + parameter.size)
if isinstance(parameter, Param):
values = self.param_array[pslice].reshape(parameter.shape)
np.ndarray.__setitem__(parameter, Ellipsis, values)
offset += parameter.size

def _sync_from_child(self, child):
"""Copy a changed immediate child's values and gradients into this object."""
child = getattr(child, '_original_', child)
while child not in self.parameters and child.has_parent():
child = child._parent_
try:
index = self.parameters.index(child)
except ValueError:
return

start = sum(parameter.size for parameter in self.parameters[:index])
pslice = slice(start, start + child.size)
self.param_array[pslice] = child.param_array.flat
self.gradient_full[pslice] = child.gradient_full.flat

def _connect_parameters(self):
pass

Expand Down Expand Up @@ -444,7 +475,7 @@ def num_params(self):
def _add_parameter_name(self, param):
try:
pname = adjust_name_for_printing(param.name)

def warn_and_retry(param, match=None):
#===================================================================
# print """
Expand Down Expand Up @@ -507,6 +538,7 @@ def _parameters_changed_notification(self, me, which=None):
self._optimizer_copy_transformed = False # tells the optimizer array to update on next request
self.parameters_changed()
def _pass_through_notify_observers(self, me, which=None):
self._sync_from_child(me)
self.notify_observers(which=which)
def _setup_observers(self):
"""
Expand Down Expand Up @@ -573,6 +605,6 @@ def gather_params(self, plist):
d = f.create_dataset('param_array',self.param_array.shape, dtype=self.param_array.dtype)
d[:] = self.param_array
f.close()
except:
raise 'Fails to write the parameters into a HDF5 file!'
except Exception as error:
raise RuntimeError('Failed to write parameters to an HDF5 file') from error

14 changes: 7 additions & 7 deletions paramz/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@


import numpy as np
from numpy.linalg.linalg import LinAlgError
from numpy.linalg import LinAlgError

from . import optimization
from .parameterized import Parameterized
Expand Down Expand Up @@ -370,9 +370,9 @@ def _checkgrad(self, target_param=None, verbose=False, step=1e-6, tolerance=1e-3
for xind in zip(transformed_index):
xx = x.copy()
xx[xind] += step
f1 = float(self._objective(xx))
f1 = self._objective(xx)
xx[xind] -= 2.*step
f2 = float(self._objective(xx))
f2 = self._objective(xx)
#Avoid divide by zero, if any of the values are above 1e-15, otherwise both values are essentiall
#the same
if f1 > 1e-15 or f1 < -1e-15 or f2 > 1e-15 or f2 < -1e-15:
Expand All @@ -396,11 +396,11 @@ def _checkgrad(self, target_param=None, verbose=False, step=1e-6, tolerance=1e-3
if df_unstable: # pragma: no cover
formatted_name = "\033[94m {0} \033[0m".format(names[xind])

r = '%.6f' % float(ratio)
d = '%.6f' % float(difference)
r = '%.6f' % ratio
d = '%.6f' % difference
g = '%.6f' % gradient[xind]
ng = '%.6f' % float(numerical_gradient)
df = '%1.e' % float(df_ratio)
ng = '%.6f' % numerical_gradient
df = '%1.e' % df_ratio
grad_string = "{0:<{c0}}|{1:^{c1}}|{2:^{c2}}|{3:^{c3}}|{4:^{c4}}|{5:^{c5}}".format(formatted_name, r, d, g, ng, df, c0=cols[0] + 9, c1=cols[1], c2=cols[2], c3=cols[3], c4=cols[4], c5=cols[5])
print(grad_string)

Expand Down
Loading
Loading