Add unified root-solving batch workflow#53
Conversation
|
Warning Review limit reached
More reviews will be available in 29 minutes and 20 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (13)
📝 WalkthroughWalkthroughThis PR introduces a complete root-solving feature to the DataLab application. It adds immutable data models for root problems and results, comprehensive input normalization with symbol classification, symbolic/numeric expression evaluation supporting SciPy/mpmath solvers across multiple modes, linear uncertainty propagation, batch row processing, reusable Qt ChangesRoot-Solving Feature Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (3)
root_solving/solver.py (1)
203-212: 💤 Low valueMinor: Unused variable
center_x.The unpacked variable
center_xis never used. Consider prefixing with underscore to indicate it's intentionally unused.♻️ Suggested fix
- for left, center, right in zip(samples, samples[1:], samples[2:], strict=False): - left_x, left_y = left - center_x, center_y = center - right_x, right_y = right + for left, center, right in zip(samples, samples[1:], samples[2:], strict=False): + left_x, left_y = left + _center_x, center_y = center + right_x, right_y = right🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@root_solving/solver.py` around lines 203 - 212, The loop unpacks center_x which is never used; change the unused variable name to _center_x in the for left, center, right in zip(...) unpack (i.e., replace center_x with _center_x) to signal intentional unused value and avoid linter warnings, keeping the rest of the logic (center_y, left_x, left_y, right_x, right_y) unchanged and ensuring no other references to center_x exist in this scope.tests/test_workspace_controller.py (1)
253-267: ⚡ Quick winAssert the restored constants numeric mode too.
The manifest assertion covers
"numeric_mode": "uncertainty", but the round-trip check never verifies thatroot_constants_editorrestored that mode. A restore regression here would still pass while changing how later constant edits are parsed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_workspace_controller.py` around lines 253 - 267, Add an assertion that the constants numeric mode from the manifest ("numeric_mode": "uncertainty") was restored on the UI: after the existing root_constants_editor checks in the test (around restore_workspace and root_constants_editor.raw_text()), assert the editor's numeric-mode accessor (e.g., root_constants_editor.numeric_mode(), root_constants_editor.get_numeric_mode(), or root_constants_editor.is_uncertainty_mode()) indicates "uncertainty"/uncertainty mode so the round-trip verifies the numeric mode is preserved.tests/test_root_solving_uncertainty.py (1)
160-164: ⚡ Quick winAvoid claiming boundary “just above/below” flakiness from
mp.nstr(..., 60)here
_diagonal_condition_problemtruncatesalphaandinitial_yto 60 digits (lines 162-163), but the two threshold tests choosealphaso the resultingjacobian_conditionlands at0.5 * (1/sqrt(mp.eps))vs2 * (1/sqrt(mp.eps))(large separation). With that gap, 60-digit formatting won’t realistically flip the</>assertions, so the major flakiness risk described isn’t supported for this setup. If these tests were tightened to a truly “near-boundary” construction, increasing the emitted digits (e.g., based onprecision) would be worthwhile.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_root_solving_uncertainty.py` around lines 160 - 164, The test helper _diagonal_condition_problem currently truncates alpha and initial_y with a fixed 60-digit mp.nstr which the reviewer says is misleading for “near-boundary” flakiness; change the formatting to emit digits based on the test precision (e.g. use mp.nstr(alpha, precision+10) and mp.nstr(initial_y, precision+10) or max(60, precision+10)) so the stringified values reflect the requested precision when constructing the RootProblem (update the two mp.nstr(...) calls in _diagonal_condition_problem).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app_desktop/window_extrapolation_mixin.py`:
- Around line 473-498: _on_root_solving_finished currently only replaces
markdown/CSV and can leave previous plots and export state visible; update it to
clear any stale plot/display/export artifacts when showing root-solving results
by invoking the appropriate cleanup before setting the result (e.g. call a
plot/image cleanup helper such as self._reset_plot_state() or
self._clear_image_tab(), and clear any cached export state like
self._reset_export_state()); ensure these cleanup calls occur alongside the
existing self._set_result_text(...) / self._reset_csv_data() paths so the image
tab and export cache cannot show previous run artifacts.
In `@app_desktop/window.py`:
- Around line 1385-1387: The parser currently returns (), () when there are no
data rows which drops headers and causes header-only columns to be misclassified
as unknowns; change the no-rows branch to return the preserved headers and an
empty rows tuple (i.e., return headers, () rather than return (), ()) so callers
like _refresh_root_unknown_rows() keep column names while treating rows as
empty; locate the branch that checks "if not rows:" and update its return
accordingly.
In `@app_desktop/workspace_controller.py`:
- Around line 569-590: The early return in _restore_root_config when config is
not a dict leaves stale UI state; replace it with logic that explicitly clears
the root-solving controls before returning: call _set_text on getattr(window,
"root_equations_edit", None) with "", call _set_combo_data on getattr(window,
"root_mode_combo", None) with "auto", clear rows on getattr(window,
"root_unknowns_table", None) by calling set_rows([]) if the table has set_rows,
and call _restore_constants_editor_state(getattr(window,
"root_constants_editor", None), None); then return. Ensure these actions are
applied only when window attributes exist (use getattr checks) and keep the rest
of _restore_root_config behavior unchanged for valid dict configs.
In `@root_solving/normalization.py`:
- Line 57: The call in normalize_root_problem only invokes
_validate_scope(unknowns=unknowns, known_values=known_values,
constants=constants) and thus misses validating clean_equations for undeclared
symbols; update normalize_root_problem to validate the cleaned/normalized
equations the same way the context-based branch does: pass clean_equations (or
its normalized form) into the scope validation routine or call the same
validation helper used in the Lines 93-101 branch so that duplicate/reserved
names and undeclared symbols referenced in clean_equations are caught during
normalization (refer to _validate_scope, normalize_root_problem, and the
variable clean_equations to locate and apply the change).
In `@shared/computation_inputs.py`:
- Around line 206-210: build_data_rows currently accepts any non-empty header
but later extract_expression_symbols only recognizes identifier-shaped tokens,
causing headers like "mass (kg)" to later fail; update the header validation
where clean_headers is created to reject any header that does not match the
identifier rule used for unknowns/constants (e.g. the same isidentifier or regex
test used elsewhere), raising the existing ValueError message on invalid
headers; reference build_data_rows(), clean_headers, and
extract_expression_symbols in the change so the validation logic matches the
tokenization rules.
In `@tests/test_desktop_root_solving_ui.py`:
- Around line 8-13: The test imports from PySide6 (from
PySide6.QtCore/QtWidgets) before calling pytest.importorskip("PySide6"), causing
import-time errors in environments without PySide6; move the
pytest.importorskip("PySide6") call (and keep pytest.importorskip("pytestqt")
before any related imports) to the top of the file so the importorskip checks
run before any `from PySide6...` imports, ensuring the test is skipped instead
of failing during collection.
In `@tests/test_root_solving_expression.py`:
- Line 155: The test uses pytest.raises(...) with a regex in the match argument
that contains an unescaped metacharacter; update the pytest.raises call (the
match= argument in the with pytest.raises(...) context) to supply a raw string
literal for the pattern by prefixing the existing string with r so the regex is
treated correctly (e.g., change the match argument on the pytest.raises call to
use a raw string).
In `@tests/test_root_solving_normalization.py`:
- Line 96: Change the three pytest.raises(...) calls that pass regex strings to
use raw string literals so Ruff RUF043 is satisfied; e.g. replace the call
containing with pytest.raises(ValueError, match="Duplicate unknown|未知量重复") with
with pytest.raises(ValueError, match=r"Duplicate unknown|未知量重复"), and do the
same conversion to raw strings for the other two occurrences in the same file
(the pytest.raises(...) at the other reported locations).
In `@tests/test_root_solving_solver.py`:
- Line 174: The pytest assertion uses a regex pattern containing '|' so update
the pytest.raises call to pass a raw string for the match argument (i.e., change
match="scan|single|scalar" to match=r"scan|single|scalar") to avoid RUF043;
locate the pytest.raises(...) invocation in tests/test_root_solving_solver.py
(the test using pytest.raises(ValueError, match=...)) and prefix the match
string with r.
In `@tests/test_shared_computation_inputs.py`:
- Line 28: Update the pytest.raises calls that pass regex patterns as plain
strings to use raw string literals; specifically change the four occurrences in
tests/test_shared_computation_inputs.py (the pytest.raises(..., match=...) calls
at the locations flagged) to use match=r"collision|冲突" (and similarly prefix the
other three pattern strings with r) so the backslashes and regex metacharacters
are interpreted correctly by pytest; locate these in the test functions calling
pytest.raises and replace the match argument values to raw-string form.
---
Nitpick comments:
In `@root_solving/solver.py`:
- Around line 203-212: The loop unpacks center_x which is never used; change the
unused variable name to _center_x in the for left, center, right in zip(...)
unpack (i.e., replace center_x with _center_x) to signal intentional unused
value and avoid linter warnings, keeping the rest of the logic (center_y,
left_x, left_y, right_x, right_y) unchanged and ensuring no other references to
center_x exist in this scope.
In `@tests/test_root_solving_uncertainty.py`:
- Around line 160-164: The test helper _diagonal_condition_problem currently
truncates alpha and initial_y with a fixed 60-digit mp.nstr which the reviewer
says is misleading for “near-boundary” flakiness; change the formatting to emit
digits based on the test precision (e.g. use mp.nstr(alpha, precision+10) and
mp.nstr(initial_y, precision+10) or max(60, precision+10)) so the stringified
values reflect the requested precision when constructing the RootProblem (update
the two mp.nstr(...) calls in _diagonal_condition_problem).
In `@tests/test_workspace_controller.py`:
- Around line 253-267: Add an assertion that the constants numeric mode from the
manifest ("numeric_mode": "uncertainty") was restored on the UI: after the
existing root_constants_editor checks in the test (around restore_workspace and
root_constants_editor.raw_text()), assert the editor's numeric-mode accessor
(e.g., root_constants_editor.numeric_mode(),
root_constants_editor.get_numeric_mode(), or
root_constants_editor.is_uncertainty_mode()) indicates "uncertainty"/uncertainty
mode so the round-trip verifies the numeric mode is preserved.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 082a6eae-a033-44e1-923a-ef5e98a73c60
📒 Files selected for processing (31)
app_desktop/detected_rows_table.pyapp_desktop/panels.pyapp_desktop/parameter_table.pyapp_desktop/window.pyapp_desktop/window_extrapolation_mixin.pyapp_desktop/workers_core.pyapp_desktop/workers_qt.pyapp_desktop/workspace_controller.pypyproject.tomlroot_solving/__init__.pyroot_solving/batch.pyroot_solving/expression.pyroot_solving/formatting.pyroot_solving/models.pyroot_solving/normalization.pyroot_solving/solver.pyroot_solving/uncertainty.pyshared/computation_inputs.pyshared/expression_names.pyshared/input_normalization.pytests/test_app_desktop_workers_core.pytests/test_desktop_implicit_model_ui.pytests/test_desktop_root_solving_ui.pytests/test_root_solving_batch.pytests/test_root_solving_expression.pytests/test_root_solving_formatting.pytests/test_root_solving_normalization.pytests/test_root_solving_solver.pytests/test_root_solving_uncertainty.pytests/test_shared_computation_inputs.pytests/test_workspace_controller.py
Summary
Test plan
PYTHONPATH=. pytest -q tests/test_shared_computation_inputs.py tests/test_root_solving_normalization.py tests/test_root_solving_expression.py tests/test_root_solving_solver.py tests/test_root_solving_uncertainty.py tests/test_root_solving_batch.py tests/test_root_solving_formatting.pyQT_QPA_PLATFORM=offscreen PYTHONPATH=. pytest -q tests/test_app_desktop_workers_core.py tests/test_desktop_root_solving_ui.py tests/test_desktop_implicit_model_ui.pyruff check shared/computation_inputs.py root_solving app_desktop/panels.py app_desktop/window.py app_desktop/window_extrapolation_mixin.py app_desktop/workers_core.py app_desktop/workers_qt.py tests/test_shared_computation_inputs.py tests/test_root_solving_normalization.py tests/test_root_solving_expression.py tests/test_root_solving_solver.py tests/test_root_solving_uncertainty.py tests/test_root_solving_batch.py tests/test_root_solving_formatting.py tests/test_app_desktop_workers_core.py tests/test_desktop_root_solving_ui.py tests/test_desktop_implicit_model_ui.pypython3 -m compileall -q shared root_solving app_desktop testsQT_QPA_PLATFORM=offscreen PYTHONPATH=. pytest -q -x $(git ls-files 'tests/*.py')->1348 passed, 26 skipped, 1 warningReviews
Summary by CodeRabbit
New Features
Tests