Skip to content

Add Parameters.update() to avoid needless recomputation#294

Merged
benluijten merged 13 commits intomainfrom
feature/fast_parameter_updates
Mar 23, 2026
Merged

Add Parameters.update() to avoid needless recomputation#294
benluijten merged 13 commits intomainfrom
feature/fast_parameter_updates

Conversation

@benluijten
Copy link
Collaborator

@benluijten benluijten commented Mar 18, 2026

TLDR

Added a new Parameters.update() helper (inherited by Scan) so callers can update multiple parameter fields at once without forcing recomputation when values haven’t actually changed.

Changes

  • New method: Parameters.update(**kwargs, force=False)
    • By default (force=False) compares new vs current values and only assigns when values differ.
    • Uses np.array_equal for array comparisons and == for scalars.
    • force=True forces assignment for every key (same effect as calling setattr repeatedly --> old behavior).
  • Scan (and other Parameters subclasses) inherit this behavior.
  • Example usage:
# create once
self.scan = Scan.safe_initialize(**params)

# update each loop without triggering recompute for unchanged values
for new_params in params_list:
   self.scan.update(**new_params)

Motivation

In processing loops we often recreate a Scan or reassign many parameters every iteration. Recreating objects or assigning identical values invalidates cached computed properties and triggers expensive recomputation. This change reduces unnecessary work by only assigning when values actually change.

Compatibility

  • Additive change — direct setattr still works exactly as before.
  • Callers who want unconditional invalidation can use force=True.

Summary by CodeRabbit

  • New Features

    • Bulk parameter update with optional force mode: by default skips unchanged values; force mode applies all updates.
  • Behavior

    • Unknown update keys are ignored without side effects.
    • Cache invalidation occurs when values change or when forced.
    • Array-like values are compared by content to avoid unnecessary recomputation; equality edge cases fall back to assignment.
  • Tests

    • Expanded test coverage for update semantics, cache invalidation, array handling, ignored keys, and equality edge cases.

…re equal to current parameters, and skip update if this is the case. Included a 'force' flag to force update all parameters.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds Parameters.update(force=False, **kwargs) to bulk-apply changes for keys in VALID_PARAMS; by default skips assignments when new and old values are equal (ndarray equality via np.array_equal); force=True forces assignment; unknown keys are ignored; assignments go through setattr for validation and cache invalidation.

Changes

Cohort / File(s) Summary
Parameters class update method
zea/internal/parameters.py
Added Parameters.update(self, force=False, **kwargs) that applies only keys in VALID_PARAMS, skips no-op updates using ndarray or general equality checks, supports force=True to always assign, and delegates validation/cache invalidation to __setattr__ via setattr.
Scan tests
tests/test_scan.py
Added probe_geometry to fixture data and new tests test_update_behaviour_and_cache_invalidation and test_update_ignores_unknown_keys to exercise update skipping, forced invalidation, value-change invalidation, and ignoring unknown keys.
Parameters tests (ndarray / caching)
tests/test_parameters.py
Expanded tests covering Parameters.update() semantics: ndarray equality skipping, boolean/array-like equality handling, fallback-on-exceptions during equality checks, None-to-None skipping, forced invalidation behavior, and ensuring unknown keys are ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • OisinNolan
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: adding a Parameters.update() method to avoid needless recomputation, which is the core focus of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/fast_parameter_updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
zea/internal/parameters.py (1)

366-367: Consider adding diagnostics for ignored keys in update().

Silently ignoring unknown keys makes typo-driven no-ops hard to spot. A debug log (similar to standardize_params on Line 594) would improve debuggability without changing behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zea/internal/parameters.py` around lines 366 - 367, The update() method
currently silently skips keys not in VALID_PARAMS; add a debug-level diagnostic
there to log each ignored key (and its source/value) so typos are visible
without altering behavior. Follow the pattern used in standardize_params (same
module) for log formatting and logger usage; locate the check in update() that
references self.VALID_PARAMS and insert a debug log call before the continue,
including the key name and the provided value/source context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zea/internal/parameters.py`:
- Around line 370-373: The update logic conflates “not set” with None because it
uses self._params.get(key); change it to detect presence explicitly (e.g., use a
unique sentinel or check `key in self._params`) so you can distinguish an unset
param from one set to None; then adjust the conditional around old_val/new_val
(references: self._params.get, old_val, new_val, update, setattr) to skip only
when both old and new are present and None or when both are absent, but allow
update(x=None) to run when the key was previously absent so dependency
resolution/state remains correct.
- Around line 374-381: The fast-path equality check in the parameters update
uses "if isinstance(old_val, np.ndarray) or isinstance(new_val, np.ndarray):"
which lets np.array_equal run for array-like inputs and thereby bypass
_validate_parameter; change the condition to require both be np.ndarray (use
AND) so np.array_equal is only used when both old_val and new_val are actual
numpy arrays; this ensures list/tuple new_val values will fall through to the
normal equality/assignment path (the elif old_val == new_val branch and
subsequent setattr) and therefore trigger the existing _validate_parameter
checks for Scan.VALID_PARAMS (e.g., probe_geometry, polar_angles,
azimuth_angles, etc.).

---

Nitpick comments:
In `@zea/internal/parameters.py`:
- Around line 366-367: The update() method currently silently skips keys not in
VALID_PARAMS; add a debug-level diagnostic there to log each ignored key (and
its source/value) so typos are visible without altering behavior. Follow the
pattern used in standardize_params (same module) for log formatting and logger
usage; locate the check in update() that references self.VALID_PARAMS and insert
a debug log call before the continue, including the key name and the provided
value/source context.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6be69d67-f05b-4e8e-a770-48e56892d1fa

📥 Commits

Reviewing files that changed from the base of the PR and between 4b83401 and ef07dd5.

📒 Files selected for processing (1)
  • zea/internal/parameters.py

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/test_scan.py (1)

357-384: Timing test is informational only.

The timing comparison at lines 366-384 uses print() for output without any assertions. This is acceptable for demonstrating the optimization benefit, but be aware that:

  1. The test will pass regardless of timing results
  2. The loop variable i is unused (could be _ for clarity)

If the goal is purely informational, this is fine. If you want to guard against performance regressions, consider adding a soft assertion (e.g., that non-forced updates are measurably faster), though such assertions can be flaky in CI environments.

♻️ Optional: use underscore for unused loop variable
     t0 = time.perf_counter()
-    for i in range(n):
+    for _ in range(n):
         # update with same value repeatedly; should be skipped by checks
         scan.update(n_tx=scan.n_tx)
     t1 = time.perf_counter()
     print(f"{n} updates with checks took {t1 - t0:.6f} seconds")

     t0 = time.perf_counter()
-    for i in range(n):
+    for _ in range(n):
         # force update each time, bypassing the equality checks
         scan.update(force=True, n_tx=scan.n_tx)
     t1 = time.perf_counter()
     print(f"{n} forced updates took {t1 - t0:.6f} seconds")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_scan.py` around lines 357 - 384, The timing loop in
test_update_ignores_unknown_keys_and_timings is purely informational and uses an
unused loop variable `i`; update the test to use `_` for the loop variable
(e.g., "for _ in range(n):") in both timing loops to signal it's unused and
optionally replace the print statements with logging or remove them if you
prefer no output, while leaving the Scan.update calls (including forced updates
via Scan.update(force=True, n_tx=...)) and behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/test_scan.py`:
- Around line 357-384: The timing loop in
test_update_ignores_unknown_keys_and_timings is purely informational and uses an
unused loop variable `i`; update the test to use `_` for the loop variable
(e.g., "for _ in range(n):") in both timing loops to signal it's unused and
optionally replace the print statements with logging or remove them if you
prefer no output, while leaving the Scan.update calls (including forced updates
via Scan.update(force=True, n_tx=...)) and behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9f5457c2-fa5c-4847-9c28-d12f153c9291

📥 Commits

Reviewing files that changed from the base of the PR and between ef07dd5 and 65354d9.

📒 Files selected for processing (1)
  • tests/test_scan.py

@codecov
Copy link

codecov bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 88.37209% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
zea/internal/parameters.py 88.37% 2 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
zea/internal/parameters.py (1)

375-382: ⚠️ Potential issue | 🟠 Major

Require both operands to be np.ndarray in the fast-path check.

Line 375 currently uses or, which allows array-like inputs (e.g., lists) to compare equal and skip setattr, bypassing parameter type validation.

Suggested fix
-                    if isinstance(old_val, np.ndarray) or isinstance(new_val, np.ndarray):
+                    if isinstance(old_val, np.ndarray) and isinstance(new_val, np.ndarray):
                         try:
                             if np.array_equal(old_val, new_val):
                                 continue
Does numpy.array_equal accept array-like inputs (such as Python lists), and can np.array_equal(np.array([1, 2]), [1, 2]) return True?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zea/internal/parameters.py` around lines 375 - 382, The fast-path uses "if
isinstance(old_val, np.ndarray) or isinstance(new_val, np.ndarray):" which lets
array-like values (e.g., lists) cause np.array_equal to be used and skip
setattr; change that condition to require both operands to be numpy arrays (use
"and") so that np.array_equal is only called when both old_val and new_val are
np.ndarray; keep the existing try/except and the fallback equality branch (the
"elif old_val == new_val: continue") so non-ndarray comparisons still use normal
equality and validation via setattr occurs when types differ; update the check
around np.array_equal(old_val, new_val) accordingly.
🧹 Nitpick comments (1)
tests/test_parameters.py (1)

372-423: Add one regression test to lock type-validation behavior for ndarray params.

Consider asserting that update(arr=[...]) raises TypeError when arr is typed as np.ndarray, so array-like equality never masks invalid input types.

Suggested test addition
+def test_update_ndarray_array_like_input_still_validates_type():
+    """List input for ndarray-typed params should not bypass type validation."""
+    params = DummyArrayParameters(arr=np.array([1.0, 2.0, 3.0]))
+    with pytest.raises(TypeError, match="expected type ndarray"):
+        params.update(arr=[1.0, 2.0, 3.0])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_parameters.py` around lines 372 - 423, Add a regression test
ensuring type validation for ndarray-typed params: create a DummyArrayParameters
(or use existing DummyArrayParameters) whose arr is annotated as np.ndarray,
then assert that calling params.update(arr=[1.0, 2.0, 3.0]) raises TypeError;
include this alongside the other update tests (near
test_update_ndarray_equality_skips_recompute) to lock behavior that array-like
inputs are rejected and not silently accepted by params.update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@zea/internal/parameters.py`:
- Around line 375-382: The fast-path uses "if isinstance(old_val, np.ndarray) or
isinstance(new_val, np.ndarray):" which lets array-like values (e.g., lists)
cause np.array_equal to be used and skip setattr; change that condition to
require both operands to be numpy arrays (use "and") so that np.array_equal is
only called when both old_val and new_val are np.ndarray; keep the existing
try/except and the fallback equality branch (the "elif old_val == new_val:
continue") so non-ndarray comparisons still use normal equality and validation
via setattr occurs when types differ; update the check around
np.array_equal(old_val, new_val) accordingly.

---

Nitpick comments:
In `@tests/test_parameters.py`:
- Around line 372-423: Add a regression test ensuring type validation for
ndarray-typed params: create a DummyArrayParameters (or use existing
DummyArrayParameters) whose arr is annotated as np.ndarray, then assert that
calling params.update(arr=[1.0, 2.0, 3.0]) raises TypeError; include this
alongside the other update tests (near
test_update_ndarray_equality_skips_recompute) to lock behavior that array-like
inputs are rejected and not silently accepted by params.update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8d49cc57-5fdb-4e96-b2ca-8782903b01c5

📥 Commits

Reviewing files that changed from the base of the PR and between 65354d9 and 81eeea6.

📒 Files selected for processing (3)
  • tests/test_parameters.py
  • tests/test_scan.py
  • zea/internal/parameters.py

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zea/internal/parameters.py`:
- Around line 374-382: The equality check in update can raise or return
non-scalar results for mixed/custom types (old_val == new_val), aborting the
path before setattr validation; modify the branch handling non-ndarray values in
the update method to safely evaluate equality: wrap the old_val == new_val in a
try/except catching TypeError and ValueError, then inspect the result—if it's a
boolean (or numpy.bool_) and True, continue; if it's a numpy array-like result,
use np.all(...) (or np.array_equal semantics) to decide to continue; otherwise
fall through to setattr so validation still runs. Reference: the update logic
using old_exists, old_val, new_val and numpy checks for np.ndarray.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c298d278-7824-49de-bcea-2ec15900f845

📥 Commits

Reviewing files that changed from the base of the PR and between 81eeea6 and f0e25b3.

📒 Files selected for processing (2)
  • tests/test_parameters.py
  • zea/internal/parameters.py

@benluijten
Copy link
Collaborator Author

Added a "defensive" fix to cache_with_dependencies to more gracefully handle edge cases.

@benluijten benluijten added this to the v0.0.12 milestone Mar 23, 2026
@benluijten benluijten added the efficiency Improvements made regarding code or tests efficiency label Mar 23, 2026
@benluijten benluijten merged commit 5aa1d35 into main Mar 23, 2026
10 checks passed
@benluijten benluijten deleted the feature/fast_parameter_updates branch March 23, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

efficiency Improvements made regarding code or tests efficiency

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants