Skip to content

Fix Simpson integration error endpoint handling#117

Open
Yi-FanLi wants to merge 22 commits into
deepmodeling:develfrom
Yi-FanLi:fix-simpson-sys-error-tail
Open

Fix Simpson integration error endpoint handling#117
Yi-FanLi wants to merge 22 commits into
deepmodeling:develfrom
Yi-FanLi:fix-simpson-sys-error-tail

Conversation

@Yi-FanLi

@Yi-FanLi Yi-FanLi commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

Summary\n- fix Simpson integration-error estimation so fine and coarse quadratures cover the same endpoint\n- avoid a spurious large error when the final block has an even number of points\n- add a regression test for a 160-point linear integrand\n\n## Validation\n- Installed with: conda run -n dpti python -m pip install -e .\n- Recomputed melt_3200K/hti.refine: soft_on integration error changed from 7.164e-02 to 8.126e-07 eV/atom; total integration error changed to 1.944e-06 eV/atom\n- conda run -n dpti python -m pytest test_lib_utils.py from tests/: 12 passed\n- Full pytest from tests/: 110 passed, 1 pre-existing collection error in test_hti_ff_spring.py::test_spring_var_spring_multiple_element because a module-level test function takes self\n

Summary by CodeRabbit

Release Notes

  • New Features

    • Added dpti workflow CLI for orchestrating multi-step thermodynamic-integration campaigns with JSON-based configuration, concurrent execution, and state persistence
    • Support for phase-specific models in multi-phase workflows
  • Documentation

    • Added comprehensive workflow documentation and CLI reference
  • Improvements

    • Refined Simpson systematic error estimation algorithm

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a JSON-driven workflow orchestration system for composing multi-step thermodynamic-integration campaigns with state persistence and concurrent execution, adds per-phase model support in DPDT setup, rewrites the Simpson integration error algorithm, and documents the new workflow feature.

Changes

DPTI Workflow System and Thermodynamic Integration Enhancements

Layer / File(s) Summary
Workflow core implementation
dpti/workflow.py
Implements JSON-driven workflow engine with state persistence, path/command normalization, dependency resolution, concurrent execution via ThreadPoolExecutor, glob-based done_if output tracking, and status reporting.
Workflow CLI integration
dpti/main.py, dpti/__init__.py, dpti/workflow.py
Wires workflow subcommands (run, status) into main CLI parser, exports workflow module symbol, and handles argument parsing for --dry-run, --rerun-all, --from-step, and --jobs options.
Workflow execution and state tests
tests/test_workflow.py
Validates workflow execution with state persistence, skipping completed steps, glob-based done_if matching across task directories, dependency graph parallelism with explicit needs, and status reporting including state transitions from failed to completed.
Phase-specific graph model support in DPDT
dpti/gdi.py
Updates _make_tasks_onephase to symlink graph files locally, adds _get_phase_model helper to select phase-specific models from phase_i/phase_ii or fallback to top-level model, and modifies _setup_dpdt to copy graph.0.pb and graph.1.pb per phase instead of single shared artifact.
Phase-specific model and graph filename tests
tests/test_gdi_make_task.py
Verifies _setup_dpdt correctly copies phase-specific conf and graph files with matching MD5 hashes, and validates _make_tasks_onephase resolves graph filenames locally without exposing indexed names in generated LAMMPS input.
Simpson integration error algorithm rewrite
dpti/lib/utils.py, tests/test_lib_utils.py
Replaces block-based sliding-window error accumulation with direct fine-vs-coarse grid comparison. Adds test validating zero error on 160-point linear data to 12 decimal places.
Workflow documentation and docs index
docs/workflow.md, docs/index.rst
Adds workflow.md documentation describing JSON schema, command execution, glob-based completion verification, dependency semantics, CLI subcommands with options, and state file restart behavior. Updates docs index toctree to include workflow entry.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Fix Simpson integration error endpoint handling' directly matches the main bugfix in dpti/lib/utils.py, which rewrites integrate_sys_err_simpson to compare full and coarse grids covering the same endpoint, addressing the spurious error issue.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 286 lines in your changes missing coverage. Please review.
✅ Project coverage is 0.00%. Comparing base (c5326a3) to head (546f5f6).

Files with missing lines Patch % Lines
dpti/workflow.py 0.00% 255 Missing ⚠️
dpti/gdi.py 0.00% 21 Missing ⚠️
dpti/lib/utils.py 0.00% 8 Missing ⚠️
dpti/main.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@          Coverage Diff           @@
##           devel    #117    +/-   ##
======================================
  Coverage   0.00%   0.00%            
======================================
  Files         25      26     +1     
  Lines       6305    6575   +270     
======================================
- Misses      6305    6575   +270     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

57-58: 💤 Low value

Test fixture naming is semantically confusing.

Using beta.lmp (a LAMMPS config) as a stand-in for graph.1.pb (a DeepMD model) works for MD5 comparison but may confuse readers. Consider adding a comment clarifying this is intentionally using a distinct file to verify per-phase copying, or duplicating graph.pb with different content.

🤖 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_gdi_make_task.py` around lines 57 - 58, The test uses
shutil.copyfile to create graph.0.pb from "graph.pb" and graph.1.pb from
"beta.lmp", which is semantically confusing; update tests/test_gdi_make_task.py
to either (a) replace the second shutil.copyfile call so it duplicates
"graph.pb" into graph.1.pb but alters its contents slightly (e.g., different
bytes) to make a true distinct model file for MD5 comparison, or (b) keep using
"beta.lmp" but add a clear inline comment next to the
shutil.copyfile("beta.lmp", os.path.join(parent_dir, "graph.1.pb")) call
explaining that beta.lmp is intentionally used as a distinct placeholder for
testing per-phase copying and not a real DeepMD model.
🤖 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 `@docs/workflow.md`:
- Around line 44-46: The example's task "hti.gen" declares a dependency "needs":
["nvt.run"] that isn't defined in the minimal schema; either remove the "needs"
entry from the "hti.gen" object or add a corresponding task named "nvt.run"
earlier in the example so the dependency resolves (edit the JSON where "hti.gen"
and the other task definitions appear).

In `@dpti/workflow.py`:
- Around line 161-169: The step command may be empty after calling
_normalize_command(step["command"]), so before printing/using it and before
subprocess.run, validate that the returned command list is non-empty; if empty,
raise a clear exception or return an error dict (e.g., {"returncode": 1,
"error": "empty command"}) so we reject empty commands early. Locate the
validation around the code that calls _normalize_command, uses work_dir and
subprocess.run (references: _normalize_command, step["command"], work_dir,
subprocess.run) and add the guard just after normalization and before the
dry_run check.
- Around line 256-275: The loop that processes completed futures calls
future.result() which can raise and leaves the corresponding step marked
"running"; wrap the future.result() call in a try/except around the block that
updates state (the loop handling done futures) so that on any exception you
still set step_state = state["steps"][name], set step_state["finished_at"] =
_now(), set a sensible step_state["returncode"] (e.g. -1 or None), set
step_state["status"] = "failed", update state["updated_at"] = _now(), call
_dump_json(state_file, state) to persist the final state, then re-raise the
original exception; ensure this handling lives alongside the existing dry_run
and non-zero returncode branches in the same loop (referencing variables:
future, running, done, step, name, result, step_state, state_file, _dump_json,
_now).

---

Nitpick comments:
In `@tests/test_gdi_make_task.py`:
- Around line 57-58: The test uses shutil.copyfile to create graph.0.pb from
"graph.pb" and graph.1.pb from "beta.lmp", which is semantically confusing;
update tests/test_gdi_make_task.py to either (a) replace the second
shutil.copyfile call so it duplicates "graph.pb" into graph.1.pb but alters its
contents slightly (e.g., different bytes) to make a true distinct model file for
MD5 comparison, or (b) keep using "beta.lmp" but add a clear inline comment next
to the shutil.copyfile("beta.lmp", os.path.join(parent_dir, "graph.1.pb")) call
explaining that beta.lmp is intentionally used as a distinct placeholder for
testing per-phase copying and not a real DeepMD model.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26a8b6c5-5d28-4347-8dd7-e5033f7378d3

📥 Commits

Reviewing files that changed from the base of the PR and between c5326a3 and 546f5f6.

📒 Files selected for processing (10)
  • docs/index.rst
  • docs/workflow.md
  • dpti/__init__.py
  • dpti/gdi.py
  • dpti/lib/utils.py
  • dpti/main.py
  • dpti/workflow.py
  • tests/test_gdi_make_task.py
  • tests/test_lib_utils.py
  • tests/test_workflow.py

Comment thread docs/workflow.md
Comment on lines +44 to +46
"name": "hti.gen",
"needs": ["nvt.run"],
"command": ["dpti", "hti", "gen", "hti.json", "-o", "hti", "-s", "one-step"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix undefined dependency in the minimal schema example.

At Line 45, hti.gen uses "needs": ["nvt.run"], but nvt.run is not defined in that example JSON. This makes the “minimal schema” invalid as written.

Proposed fix (one option)
-      "needs": ["nvt.run"],
+      "needs": ["nvt.gen"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"name": "hti.gen",
"needs": ["nvt.run"],
"command": ["dpti", "hti", "gen", "hti.json", "-o", "hti", "-s", "one-step"],
"name": "hti.gen",
"needs": ["nvt.gen"],
"command": ["dpti", "hti", "gen", "hti.json", "-o", "hti", "-s", "one-step"],
🤖 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 `@docs/workflow.md` around lines 44 - 46, The example's task "hti.gen" declares
a dependency "needs": ["nvt.run"] that isn't defined in the minimal schema;
either remove the "needs" entry from the "hti.gen" object or add a corresponding
task named "nvt.run" earlier in the example so the dependency resolves (edit the
JSON where "hti.gen" and the other task definitions appear).

Comment thread dpti/workflow.py
Comment on lines +161 to +169
command = _normalize_command(step["command"])
print(f"[workflow] run step: {name}")
print(f"[workflow] cwd: {work_dir}")
print("[workflow] command:", " ".join(shlex.quote(item) for item in command))
if dry_run:
return {"returncode": 0}
work_dir.mkdir(parents=True, exist_ok=True)
completed = subprocess.run(command, cwd=str(work_dir))
return {"returncode": completed.returncode}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject empty commands before spawning subprocesses.

At Line 161, an empty command list is possible (for example, command: ""), which currently fails later with a less actionable runtime error.

Proposed fix
     command = _normalize_command(step["command"])
+    if not command:
+        raise ValueError(f"workflow step '{name}' has an empty command")
     print(f"[workflow] run step: {name}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
command = _normalize_command(step["command"])
print(f"[workflow] run step: {name}")
print(f"[workflow] cwd: {work_dir}")
print("[workflow] command:", " ".join(shlex.quote(item) for item in command))
if dry_run:
return {"returncode": 0}
work_dir.mkdir(parents=True, exist_ok=True)
completed = subprocess.run(command, cwd=str(work_dir))
return {"returncode": completed.returncode}
command = _normalize_command(step["command"])
if not command:
raise ValueError(f"workflow step '{name}' has an empty command")
print(f"[workflow] run step: {name}")
print(f"[workflow] cwd: {work_dir}")
print("[workflow] command:", " ".join(shlex.quote(item) for item in command))
if dry_run:
return {"returncode": 0}
work_dir.mkdir(parents=True, exist_ok=True)
completed = subprocess.run(command, cwd=str(work_dir))
return {"returncode": completed.returncode}
🧰 Tools
🪛 Ruff (0.15.15)

[error] 168-168: subprocess call: check for execution of untrusted input

(S603)

🤖 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 `@dpti/workflow.py` around lines 161 - 169, The step command may be empty after
calling _normalize_command(step["command"]), so before printing/using it and
before subprocess.run, validate that the returned command list is non-empty; if
empty, raise a clear exception or return an error dict (e.g., {"returncode": 1,
"error": "empty command"}) so we reject empty commands early. Locate the
validation around the code that calls _normalize_command, uses work_dir and
subprocess.run (references: _normalize_command, step["command"], work_dir,
subprocess.run) and add the guard just after normalization and before the
dry_run check.

Comment thread dpti/workflow.py
Comment on lines +256 to +275
for future in done:
step = running.pop(future)
name = step["name"]
result = future.result()
step_state = state["steps"][name]
step_state["finished_at"] = _now()
step_state["returncode"] = result["returncode"]
if dry_run:
step_state["status"] = "dry_run"
state["updated_at"] = _now()
_dump_json(state_file, state)
continue
if result["returncode"] != 0:
step_state["status"] = "failed"
state["updated_at"] = _now()
_dump_json(state_file, state)
raise RuntimeError(
f"workflow step '{name}' failed with return code "
f"{result['returncode']}"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure step state is finalized when worker execution raises.

At Line 259, future.result() can raise (e.g., FileNotFoundError from subprocess.run). In that path, the step remains "running" in state, which breaks resumability and status reporting.

Proposed fix
                 for future in done:
                     step = running.pop(future)
                     name = step["name"]
-                    result = future.result()
                     step_state = state["steps"][name]
                     step_state["finished_at"] = _now()
+                    try:
+                        result = future.result()
+                    except Exception as exc:
+                        step_state["status"] = "failed"
+                        step_state["error"] = repr(exc)
+                        state["updated_at"] = _now()
+                        _dump_json(state_file, state)
+                        raise RuntimeError(
+                            f"workflow step '{name}' crashed before returning a code"
+                        ) from exc
                     step_state["returncode"] = result["returncode"]
🤖 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 `@dpti/workflow.py` around lines 256 - 275, The loop that processes completed
futures calls future.result() which can raise and leaves the corresponding step
marked "running"; wrap the future.result() call in a try/except around the block
that updates state (the loop handling done futures) so that on any exception you
still set step_state = state["steps"][name], set step_state["finished_at"] =
_now(), set a sensible step_state["returncode"] (e.g. -1 or None), set
step_state["status"] = "failed", update state["updated_at"] = _now(), call
_dump_json(state_file, state) to persist the final state, then re-raise the
original exception; ensure this handling lives alongside the existing dry_run
and non-zero returncode branches in the same loop (referencing variables:
future, running, done, step, name, result, step_state, state_file, _dump_json,
_now).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant