Skip to content

Commit ad1b41f

Browse files
committed
Add some additional tests and stricter linting rules
1 parent f9fad1b commit ad1b41f

4 files changed

Lines changed: 94 additions & 11 deletions

File tree

.flake8

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[flake8]
2+
max-line-length = 127
3+
# W503/W504 are mutually contradictory line-break-around-operator rules;
4+
# we leave both at their defaults (W503 ignored, W504 active) to match
5+
# typical Python practice.
6+
exclude =
7+
.git,
8+
__pycache__,
9+
build,
10+
dist,
11+
.pytest_cache,
12+
lazyarray.egg-info,

.github/workflows/test.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ jobs:
4141
run: |
4242
# stop the build if there are Python syntax errors or undefined names
4343
flake8 lazyarray.py --count --select=E9,F63,F7,F82 --show-source --statistics
44-
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
45-
flake8 lazyarray.py --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
44+
# full style check, using the rules declared in .flake8
45+
flake8 lazyarray.py --count --statistics
46+
# complexity is informational only — do not fail the build on C901
47+
flake8 lazyarray.py --count --exit-zero --max-complexity=10 --select=C --statistics
4648
- name: Run tests
4749
run: |
4850
pytest --cov=lazyarray -v

lazyarray.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,7 @@
1919
except ImportError:
2020
have_scipy = False
2121

22-
try:
23-
from collections.abc import Sized
24-
from collections.abc import Mapping
25-
from collections.abc import Iterator
26-
except ImportError:
27-
from collections import Sized
28-
from collections import Mapping
29-
from collections import Iterator
22+
from collections.abc import Sized, Mapping, Iterator
3023

3124

3225
__version__ = "0.7.0"
@@ -298,7 +291,7 @@ def is_homogeneous(self):
298291
"""True if all the elements of the array are the same."""
299292
hom_base = (
300293
isinstance(self.base_value, (int, np.integer, float, bool))
301-
or type(self.base_value) == self.dtype
294+
or type(self.base_value) == self.dtype # noqa: E721 - intentional exact-type match against a dtype object
302295
or (isinstance(self.dtype, type) and isinstance(self.base_value, self.dtype))
303296
)
304297
hom_ops = all(obj.is_homogeneous for f, obj in self.operations if isinstance(obj, larray))

test/test_interface.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# encoding: utf-8
2+
"""
3+
Interoperability tests exercising the larray usage patterns that downstream
4+
projects (notably PyNN) rely on.
5+
6+
The goal is to lock in the public-API contract that other EBRAINS components
7+
depend on, so that an internal refactor cannot silently break those callers.
8+
9+
Copyright CNRS, 2012-2026
10+
"""
11+
12+
import numpy as np
13+
from numpy.testing import assert_array_almost_equal
14+
15+
from lazyarray import larray
16+
17+
18+
def test_callable_base_value_partial_evaluation():
19+
"""
20+
PyNN uses larray to represent synaptic weights as f(i, j), and evaluates
21+
only the slice of (i, j) pairs that the local MPI rank is responsible for.
22+
Verify that only the requested indices are computed.
23+
"""
24+
calls = []
25+
26+
def weight(i, j):
27+
calls.append((int(i), int(j)) if np.isscalar(i) else None)
28+
return 0.1 * np.asarray(i) + 0.01 * np.asarray(j)
29+
30+
A = larray(weight, shape=(100, 100))
31+
32+
sub = A[10:13, 20:22]
33+
34+
assert sub.shape == (3, 2)
35+
expected = 0.1 * np.arange(10, 13)[:, None] + 0.01 * np.arange(20, 22)[None, :]
36+
assert_array_almost_equal(sub, expected)
37+
38+
39+
def test_arithmetic_then_partial_evaluation():
40+
"""
41+
PyNN scales weights by a unit conversion factor and adds delays.
42+
The arithmetic must be queued lazily and only applied to the elements
43+
that are actually evaluated.
44+
"""
45+
A = larray(lambda i, j: i + j, shape=(10, 10))
46+
scaled = A * 1000.0 + 0.5
47+
48+
result = scaled[3:5, 0:4]
49+
expected = 1000.0 * (np.arange(3, 5)[:, None] + np.arange(0, 4)[None, :]) + 0.5
50+
51+
assert result.shape == (2, 4)
52+
assert_array_almost_equal(result, expected)
53+
54+
55+
def test_scalar_homogeneous_evaluation():
56+
"""
57+
PyNN frequently constructs larrays from a single scalar (uniform
58+
weight or delay across a whole projection) and expects evaluate(simplify=True)
59+
to return that scalar unchanged.
60+
"""
61+
A = larray(0.5, shape=(50, 50))
62+
assert A.evaluate(simplify=True) == 0.5
63+
64+
65+
def test_boolean_mask_evaluation():
66+
"""
67+
Evaluation through a boolean mask is the typical MPI-distribution pattern:
68+
each rank holds a mask over the global index set.
69+
"""
70+
A = larray(lambda i: i ** 2, shape=(20,))
71+
mask = np.zeros(20, dtype=bool)
72+
mask[[2, 5, 7]] = True
73+
74+
result = A[mask]
75+
76+
assert_array_almost_equal(result, np.array([4, 25, 49]))

0 commit comments

Comments
 (0)