Skip to content

Commit c4e1b1b

Browse files
0xC000005claude
andcommitted
v0.19: address PR review — restore in-place sequential build + plot_2d coverage on Spline/Slider/TT
- _build_fixed_grid: re-introduces the fast in-place np.ndindex loop for the n_workers=None/1 path, avoiding the unnecessary points-list materialisation that caused a ~2s regression for default (single-worker) users; parallel path unchanged. - test_v019_build_diagnostics: adds test_plot_2d_surface_{spline,slider,tt}, test_plot_2d_contour_{spline,slider,tt}, and test_n_workers_bool_rejected (7 new tests; total 990 → 997). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 86833e8 commit c4e1b1b

2 files changed

Lines changed: 85 additions & 9 deletions

File tree

src/pychebyshev/barycentric.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -661,15 +661,25 @@ def _build_fixed_grid(self, verbose: bool | int = True) -> None:
661661
self._cached_error_estimate = None
662662

663663
# Step 1: Evaluate at all node combinations
664-
from pychebyshev._parallel import _evaluate_in_parallel
665-
points = [
666-
[self.nodes[d][idx[d]] for d in range(self.num_dimensions)]
667-
for idx in np.ndindex(*self.n_nodes)
668-
]
669-
flat = _evaluate_in_parallel(
670-
self.function, points, self.additional_data, self.n_workers
671-
)
672-
self.tensor_values = flat.reshape(self.n_nodes)
664+
if self.n_workers is None or self.n_workers == 1:
665+
# Fast path: in-place fill (original behavior, no intermediate list)
666+
self.tensor_values = np.zeros(self.n_nodes)
667+
for idx in np.ndindex(*self.n_nodes):
668+
point = [self.nodes[d][idx[d]] for d in range(self.num_dimensions)]
669+
self.tensor_values[idx] = float(
670+
self.function(point, self.additional_data)
671+
)
672+
else:
673+
# Parallel path: build points list, dispatch to pool, reshape
674+
from pychebyshev._parallel import _evaluate_in_parallel
675+
points = [
676+
[self.nodes[d][idx[d]] for d in range(self.num_dimensions)]
677+
for idx in np.ndindex(*self.n_nodes)
678+
]
679+
flat = _evaluate_in_parallel(
680+
self.function, points, self.additional_data, self.n_workers
681+
)
682+
self.tensor_values = flat.reshape(self.n_nodes)
673683
self.n_evaluations = total
674684

675685
# Step 2: Pre-compute barycentric weights

tests/test_v019_build_diagnostics.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ def test_parallel_with_additional_data(self):
9393
# f(x, ad) = 7 * x; eval at 0.5 should be 3.5
9494
assert cheb.eval([0.5], [0]) == pytest.approx(3.5, abs=1e-10)
9595

96+
def test_n_workers_bool_rejected(self):
97+
with pytest.raises(ValueError, match="n_workers"):
98+
ChebyshevApproximation(
99+
_t3_f_simple, 2, [[-1, 1], [-1, 1]], [4, 4], n_workers=True
100+
)
101+
96102

97103
# ============================================================================
98104
# T4: Parallel build (Spline)
@@ -360,6 +366,36 @@ def test_plot_2d_surface_with_fixed(self, matplotlib_or_skip):
360366
ax = cheb.plot_2d_surface(fixed={2: 0.5}, n_points=10)
361367
assert ax is not None
362368

369+
def test_plot_2d_surface_spline(self, matplotlib_or_skip):
370+
def f(x, _):
371+
return abs(x[0]) + abs(x[1])
372+
spl = ChebyshevSpline(
373+
f, 2, [[-1, 1], [-1, 1]],
374+
knots=[[0.0], [0.0]], n_nodes=[6, 6],
375+
)
376+
spl.build(verbose=False)
377+
ax = spl.plot_2d_surface(n_points=15)
378+
assert ax is not None
379+
380+
def test_plot_2d_surface_slider(self, matplotlib_or_skip):
381+
def f(x, _):
382+
return math.sin(x[0]) + math.cos(x[1])
383+
slider = ChebyshevSlider(
384+
f, 2, [[-1, 1], [-1, 1]], [6, 6],
385+
partition=[[0], [1]], pivot_point=[0.0, 0.0],
386+
)
387+
slider.build(verbose=False)
388+
ax = slider.plot_2d_surface(n_points=15)
389+
assert ax is not None
390+
391+
def test_plot_2d_surface_tt(self, matplotlib_or_skip):
392+
def f(x, _):
393+
return x[0] * x[1]
394+
tt = ChebyshevTT(f, 2, [[-1, 1], [-1, 1]], [6, 6])
395+
tt.build(verbose=False)
396+
ax = tt.plot_2d_surface(n_points=15)
397+
assert ax is not None
398+
363399

364400
class TestPlot2DContour:
365401
@pytest.fixture
@@ -383,6 +419,36 @@ def test_plot_2d_contour_n_levels_kwarg(self, matplotlib_or_skip):
383419
ax = cheb.plot_2d_contour(n_points=15, n_levels=5)
384420
assert ax is not None
385421

422+
def test_plot_2d_contour_spline(self, matplotlib_or_skip):
423+
def f(x, _):
424+
return abs(x[0]) + abs(x[1])
425+
spl = ChebyshevSpline(
426+
f, 2, [[-1, 1], [-1, 1]],
427+
knots=[[0.0], [0.0]], n_nodes=[6, 6],
428+
)
429+
spl.build(verbose=False)
430+
ax = spl.plot_2d_contour(n_points=15, n_levels=10)
431+
assert ax is not None
432+
433+
def test_plot_2d_contour_slider(self, matplotlib_or_skip):
434+
def f(x, _):
435+
return math.sin(x[0]) + math.cos(x[1])
436+
slider = ChebyshevSlider(
437+
f, 2, [[-1, 1], [-1, 1]], [6, 6],
438+
partition=[[0], [1]], pivot_point=[0.0, 0.0],
439+
)
440+
slider.build(verbose=False)
441+
ax = slider.plot_2d_contour(n_points=15, n_levels=10)
442+
assert ax is not None
443+
444+
def test_plot_2d_contour_tt(self, matplotlib_or_skip):
445+
def f(x, _):
446+
return x[0] * x[1]
447+
tt = ChebyshevTT(f, 2, [[-1, 1], [-1, 1]], [6, 6])
448+
tt.build(verbose=False)
449+
ax = tt.plot_2d_contour(n_points=15, n_levels=10)
450+
assert ax is not None
451+
386452

387453
# ============================================================================
388454
# T8: Cross-feature tests

0 commit comments

Comments
 (0)