From d8b6009d8898a661d92f846988d6ea0e64379022 Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Tue, 16 Apr 2024 14:20:58 +0200 Subject: [PATCH 1/5] Handle copy+dtype arguments for __array__ Support for numpy 2.0 --- brian2/codegen/runtime/numpy_rt/numpy_rt.py | 8 +++++--- brian2/core/variables.py | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/brian2/codegen/runtime/numpy_rt/numpy_rt.py b/brian2/codegen/runtime/numpy_rt/numpy_rt.py index 317dcbc7f..d56e8b855 100644 --- a/brian2/codegen/runtime/numpy_rt/numpy_rt.py +++ b/brian2/codegen/runtime/numpy_rt/numpy_rt.py @@ -127,11 +127,13 @@ def __iter__(self): return iter(self.indices) # Allow conversion to a proper array with np.array(...) - def __array__(self, dtype=None): + def __array__(self, dtype=None, copy=None): + if copy is False: + raise ValueError("LazyArange does not support copy=False") if self.indices is None: - return np.arange(self.start, self.stop) + return np.arange(self.start, self.stop, dtype=dtype) else: - return self.indices + self.start + return (self.indices + self.start).astype(dtype) # Allow basic arithmetics (used when shifting stuff for subgroups) def __add__(self, other): diff --git a/brian2/core/variables.py b/brian2/core/variables.py index eaa52b0a4..8a7e794d5 100644 --- a/brian2/core/variables.py +++ b/brian2/core/variables.py @@ -1337,11 +1337,13 @@ def set_with_index_array(self, item, value, check_units): variable.get_value()[indices] = value # Allow some basic calculations directly on the ArrayView object - def __array__(self, dtype=None): + def __array__(self, dtype=None, copy=None): try: # This will fail for subexpressions that refer to external # parameters - self[:] + values = self[:] + # Never hand over copy = None + return np.array(values, dtype=dtype, copy=copy is not False, subok=True) except ValueError: var = self.variable.name raise ValueError( @@ -1350,7 +1352,6 @@ def __array__(self, dtype=None): f"variables, use 'group.{var}[:]' instead of " f"'group.{var}'" ) - return np.asanyarray(self[:], dtype=dtype) def __array__ufunc__(self, ufunc, method, *inputs, **kwargs): if method == "__call__": From 06ed2a95d9830e8fa45a2cfbdf1fb26f0a64230f Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Wed, 17 Apr 2024 10:17:54 +0200 Subject: [PATCH 2/5] Build against numpy 2.0 and update versions according to NEP29 --- .github/workflows/publish.yml | 1 - README.md | 4 ++-- pyproject.toml | 9 +++++---- versions.md | 1 - 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dcfdfab1d..1a6352fc2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -83,7 +83,6 @@ jobs: - name: Build source tarball run: | python -m pip install --upgrade pip build - python -m pip install "cython>=0.29" oldest-supported-numpy "setuptools>=61" "setuptools_scm[toml]>=6.2" python -m build --sdist --config-setting=--formats=gztar --config-setting=--with-cython --config-setting=--fail-on-error if: ${{ matrix.arch == 'auto64' }} - name: Set up QEMU diff --git a/README.md b/README.md index 6175a9338..89de6d90e 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ Try out Brian on the [mybinder](https://mybinder.org/) service: ## Dependencies The following packages need to be installed to use Brian 2 (cf. [`pyproject.toml`](pyproject.toml)): -* Python >= 3.9 -* NumPy >=1.21 +* Python >= 3.10 +* NumPy >=1.23 * SymPy >= 1.2 * Cython >= 0.29.21 * PyParsing diff --git a/pyproject.toml b/pyproject.toml index 01498e464..56f1029b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,9 +5,9 @@ authors = [ {name = 'Dan Goodman'}, {name ='Romain Brette'} ] -requires-python = '>=3.9' +requires-python = '>=3.10' dependencies = [ - 'numpy>=1.21', + 'numpy>=1.23.5', 'cython>=0.29.21', 'sympy>=1.2', 'pyparsing', @@ -60,10 +60,11 @@ fallback_version = 'unknown' [build-system] requires = [ "setuptools>=61", - "numpy>=1.10", + # By building against numpy 2.0, we make sure that the wheel is compatible with + # both numpy 2.0 and numpy>=1.23 + "numpy>=2.0.0rc1", "wheel", "Cython", - "oldest-supported-numpy", "setuptools_scm[toml]>=6.2" ] build-backend = "setuptools.build_meta" diff --git a/versions.md b/versions.md index 9c1d3234d..20a9b5865 100644 --- a/versions.md +++ b/versions.md @@ -6,7 +6,6 @@ This file contains a list of other files where versions of packages are specifie In the future it would be advantageous to implement an automated way of keep versions synchronised across files e.g. https://github.com/pre-commit/pre-commit/issues/945#issuecomment-527603460 or preferably parsing `.pre-commit-config.yaml` and using it to `pip install` requirements (see discussion here: https://github.com/brian-team/brian2/pull/1449#issuecomment-1372476018). Until then, the files are listed below for manual checking and updating. * [`README.md`](https://github.com/brian-team/brian2/blob/master/README.md) -* [`setup.py`](https://github.com/brian-team/brian2/blob/master/setup.py) * [`rtd-requirements.txt`](https://github.com/brian-team/brian2/blob/master/rtd-requirements.txt) * [`pyproject.toml`](https://github.com/brian-team/brian2/blob/master/pyproject.toml) * [`.pre-commit-config.yaml`](https://github.com/brian-team/brian2/blob/master/.pre-commit-config.yaml) From 0174f02e125f7fb387b6b3267b941dd5f025731d Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Wed, 17 Apr 2024 15:12:19 +0200 Subject: [PATCH 3/5] Fix rendering for numpy scalars In numpy 2.0, `repr(np.float64(0.5))` is `"np.float64(0.5)"`. Before, it was `"0.5"` (as for a Python `float`). --- brian2/parsing/rendering.py | 6 +++++- brian2/tests/test_codegen.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/brian2/parsing/rendering.py b/brian2/parsing/rendering.py index 6c93f11df..b60361a99 100644 --- a/brian2/parsing/rendering.py +++ b/brian2/parsing/rendering.py @@ -1,6 +1,7 @@ import ast import numbers +import numpy as np import sympy from brian2.core.functions import DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS @@ -87,6 +88,9 @@ def render_Name(self, node): return node.id def render_Constant(self, node): + if isinstance(node.value, np.number): + # repr prints the dtype in numpy 2.0 + return repr(node.value.item()) return repr(node.value) def render_Call(self, node): @@ -344,7 +348,7 @@ def render_Constant(self, node): elif node.value is False: return "false" else: - return repr(node.value) + return super().render_Constant(node) def render_Name(self, node): if node.id == "inf": diff --git a/brian2/tests/test_codegen.py b/brian2/tests/test_codegen.py index a1c832d58..1a3f1def8 100644 --- a/brian2/tests/test_codegen.py +++ b/brian2/tests/test_codegen.py @@ -10,6 +10,7 @@ from brian2 import _cache_dirs_and_extensions, clear_cache, prefs from brian2.codegen.codeobject import CodeObject from brian2.codegen.cpp_prefs import compiler_supports_c99, get_compiler_and_args +from brian2.codegen.generators.cython_generator import CythonNodeRenderer from brian2.codegen.optimisation import optimise_statements from brian2.codegen.runtime.cython_rt import CythonCodeObject from brian2.codegen.statements import Statement @@ -22,6 +23,7 @@ from brian2.core.functions import DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS, Function from brian2.core.variables import ArrayVariable, Constant, Subexpression, Variable from brian2.devices.device import auto_target, device +from brian2.parsing.rendering import CPPNodeRenderer, NodeRenderer, NumpyNodeRenderer from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.units import ms, second from brian2.units.fundamentalunits import Unit @@ -618,6 +620,25 @@ def test_msvc_flags(): assert len(previously_stored_flags[hostname]) +@pytest.mark.codegen_independent +@pytest.mark.parametrize( + "renderer", + [ + NodeRenderer(), + NumpyNodeRenderer(), + CythonNodeRenderer(), + CPPNodeRenderer(), + ], +) +def test_number_rendering(renderer): + import ast + + for number in [0.5, np.float32(0.5), np.float64(0.5)]: + # In numpy 2.0, repr(np.float64(0.5)) is 'np.float64(0.5)' + node = ast.Constant(value=number) + assert renderer.render_node(node) == "0.5" + + if __name__ == "__main__": test_auto_target() test_analyse_identifiers() From c776c5f8d6f295cd98150ca7a5c579deb68a1aab Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Fri, 19 Apr 2024 14:14:57 +0200 Subject: [PATCH 4/5] Support matrix multiplication on VariableView --- brian2/core/variables.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/brian2/core/variables.py b/brian2/core/variables.py index 8a7e794d5..d52f2656c 100644 --- a/brian2/core/variables.py +++ b/brian2/core/variables.py @@ -1435,6 +1435,13 @@ def __iadd__(self, other): self[:] = rhs return self + # Support matrix multiplication with @ + def __matmul__(self, other): + return self.get_item(slice(None), level=1) @ np.asanyarray(other) + + def __rmatmul__(self, other): + return np.asanyarray(other) @ self.get_item(slice(None), level=1) + def __isub__(self, other): if isinstance(other, str): raise TypeError( From 66e636b8c51f277b9fbe0ecb372570ba7af0e351 Mon Sep 17 00:00:00 2001 From: Marcel Stimberg Date: Fri, 19 Apr 2024 14:15:13 +0200 Subject: [PATCH 5/5] Use integers instead of floats --- examples/advanced/stochastic_odes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/advanced/stochastic_odes.py b/examples/advanced/stochastic_odes.py index a60ec1c3f..367fb7b9b 100644 --- a/examples/advanced/stochastic_odes.py +++ b/examples/advanced/stochastic_odes.py @@ -55,8 +55,8 @@ def exact_solution(t, dt): methods = ['milstein', 'heun'] dts = [1*ms, 0.5*ms, 0.2*ms, 0.1*ms, 0.05*ms, 0.025*ms, 0.01*ms, 0.005*ms] -rows = floor(sqrt(len(dts))) -cols = ceil(1.0 * len(dts) / rows) +rows = int(sqrt(len(dts))) +cols = int(ceil(1.0 * len(dts) / rows)) errors = dict([(method, zeros(len(dts))) for method in methods]) for dt_idx, dt in enumerate(dts): print('dt: %s' % dt)