Skip to content

Jake update#51

Open
j77king wants to merge 7 commits into
mainfrom
jake_update
Open

Jake update#51
j77king wants to merge 7 commits into
mainfrom
jake_update

Conversation

@j77king

@j77king j77king commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

adding mlip support and spin density

Summary by CodeRabbit

  • New Features

    • Added MLIP (MACE-Polar .extxyz) processing for neutral, reduced, and oxidized datasets, generating molecule-, atom-, and bond-level outputs.
    • Added MLIP dataset path flags, plus improved --workers parallel parsing support and optional argument-file writing.
  • Bug Fixes

    • More robust charge/spin and Fukui descriptor parsing, including Mulliken spin fallback.
    • Improved filename/key matching consistency and clearer warnings when expected intermediate outputs are missing.
  • Documentation

    • Updated audit-log guidance to use a single MOLDSCRIPT.dat.
  • Tests

    • Expanded automated validation for MLIP numeric outputs and Mulliken spin parsing.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds MLIP extxyz ingestion and pipeline integration, introduces shared parallel job and logging infrastructure, refactors calculator parsers, updates terminal output handling, and adds fixtures, tests, dependencies, and documentation.

Changes

MLIP Pipeline and Parsing

Layer / File(s) Summary
MLIP extxyz ingestion and derived descriptors
moldscript/MLIP.py
Adds MLIP state discovery, extxyz parsing, canonical molecule aggregation, and derived molecular and atomic descriptors.
Shared worker and logging infrastructure
moldscript/utils.py
Adds deterministic worker execution, parallel data initialization, CPU-time helpers, filename resolution, centralized terminal output, run logging, and vectorized bond distances.
Calculator job parsing refactors
moldscript/charges.py, moldscript/fmo.py, moldscript/fukui.py, moldscript/nbo.py, moldscript/nmr.py, moldscript/spc.py
Moves calculator parsing into structured jobs with parallel execution, metadata propagation, error handling, filename resolution, and CPU-time accounting.
CLI and main pipeline integration
moldscript/argument_parser.py, moldscript/moldscript.py, moldscript/opt.py, moldscript/sterics.py
Adds MLIP, worker, and argument-file options, wires MLIP into the pipeline, propagates output settings, and updates lifecycle logging.
Downstream status and reporting output
moldscript/boltz.py, moldscript/get_df.py, moldscript/lowe.py, moldscript/min_max.py, moldscript/files.py, moldscript/__main__.py
Routes status, warning, error, and descriptor messages through centralized terminal utilities and handles missing output files.

Validation and Package Support

Layer / File(s) Summary
MLIP fixtures and tests
moldscript/examples/arbr/mlip/..., tests/test_moldscript.py
Adds neutral, reduced, and oxidized fixtures and tests for Mulliken spins, MLIP values, data shapes, and CSV outputs.
Documentation and dependencies
README.md, docs/source/README.rst, requirements.txt, setup.py
Documents the consolidated audit log and CLI options and adds periodictable, rich, and tqdm.

Estimated code review effort: 5 (Critical) | ~100 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant moldscript_main
  participant run_file_jobs
  participant CalculatorParser
  participant mlip
  participant get_df

  CLI->>moldscript_main: parse MLIP and worker options
  moldscript_main->>run_file_jobs: dispatch calculator parsing jobs
  run_file_jobs->>CalculatorParser: parse source files
  CalculatorParser-->>run_file_jobs: return descriptors and CPU times
  moldscript_main->>mlip: parse configured extxyz states
  mlip-->>moldscript_main: return file_data
  moldscript_main->>get_df: write molecule, atom, and bond CSV files
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too generic and does not describe the actual changes to MLIP and spin-density support. Rename the PR to a concise, specific summary of the main change, such as adding MLIP and spin-density support.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jake_update

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aec37f9578

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread moldscript/MLIP.py
entry["atom"]["frad"] = frad
entry["atom"]["charges_neutral"] = q_neutral

self.data_dict[key] = entry

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve existing entries when adding MLIP descriptors

When MLIP is combined with any earlier module in the same CLI run, main() passes the accumulated data_dicts into mlip, but this assignment replaces the whole molecule entry for matching keys. For extxyz files whose source_file stem matches the Gaussian-derived key, descriptors already parsed from opt/SPC/NMR/NBO/charges are silently dropped from the final CSVs and only the MLIP fields remain; merge into the existing nested mol/atom/bond dicts instead of overwriting the entry.

Useful? React with 👍 / 👎.

Comment thread moldscript/MLIP.py
Comment on lines +261 to +263
source = header.get("source_file", "")
if source:
return Path(source).stem

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize source_file state suffixes before grouping

When extxyz headers contain state-specific source_file values such as foo_neutral_vertical.com, foo_anion_Nplus1_vertical.com, and foo_cation_Nminus1_vertical.com, this early return bypasses the suffix stripping below. The three supplied state files then land under separate molecule keys, so each states dict is missing the other charge states and the vertical IE/EA/Fukui outputs become NaN; apply the same suffix normalization to the source stem before returning it.

Useful? React with 👍 / 👎.

@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: 4

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

184-242: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a merge-conflict regression test.

These tests validate the standalone mlip parser output well, but none exercise mlip(..., data_dict=<pre-populated dict>) where the same key already has mol/atom/bond data from another stage (e.g. opt/charges). Given the overwrite behavior flagged in MLIP.py (Line 192), a regression test asserting pre-existing keys are preserved after merging would help catch this class of bug going forward.

🤖 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_moldscript.py` around lines 184 - 242, Add a regression test for
the merge path in mlip when data_dict already contains an entry for the same
filename. In tests/test_moldscript.py, exercise mlip(...,
data_dict=<pre-populated dict>) with an existing key that already has
mol/atom/bond data from another stage, then assert the parser merge preserves
the pre-existing values instead of overwriting them. Use build_arbr141_parser,
mlip, and the existing arbr141_wb97xd fixture shape as the reference points.
🤖 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 `@moldscript/charges.py`:
- Around line 62-73: Guard chg_data before accessing atomcharges in the charge
parsing flow, because parse_cc_data() can return None and the current
unconditional use in the charges handling block will raise AttributeError and
stop the batch. Update the logic around the atom charge assignment and the
subsequent get_atom_spins call in charges.py to first verify chg_data is present
(following the existing pattern used for cpu_time), and only then read
atomcharges and populate self.data_dict[filename]['atom'].

In `@moldscript/MLIP.py`:
- Around line 291-303: The _state_charges helper in MLIP.py silently pads or
truncates charge arrays when len(arr) does not match natoms, which can hide
state/geometry mismatches. Update _state_charges to emit a warning whenever the
atom charge array length differs from natoms, while keeping the existing
NaN-padding behavior; use the _state_charges method and
self.atom_charge_property to locate the mismatch path and make the warning
include the expected and actual lengths.
- Around line 196-206: The _read_extxyz method currently slices atom_lines
without checking that the file actually contains the declared number of atoms,
so truncated or malformed input can slip through silently. In _read_extxyz,
after building atom_lines from lines[2:2 + natoms], validate that its length
matches natoms and raise a clear ValueError when it does not; keep the check
close to the existing natoms/header parsing logic so the failure is reported
before _parse_properties_schema or downstream atomnos/coords construction.
- Around line 117-194: The per-key record in the MLIP aggregation loop is being
overwritten by assigning a new entry to self.data_dict[key], which drops any
existing mol, atom, bond, or CPU_time data already stored for that key. Update
the logic in the state-processing block inside the MLIP class so it merges into
the existing self.data_dict[key] entry in place, preserving previously populated
nested fields while adding the new MLIP values.

---

Nitpick comments:
In `@tests/test_moldscript.py`:
- Around line 184-242: Add a regression test for the merge path in mlip when
data_dict already contains an entry for the same filename. In
tests/test_moldscript.py, exercise mlip(..., data_dict=<pre-populated dict>)
with an existing key that already has mol/atom/bond data from another stage,
then assert the parser merge preserves the pre-existing values instead of
overwriting them. Use build_arbr141_parser, mlip, and the existing
arbr141_wb97xd fixture shape as the reference points.
🪄 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

Run ID: fdea8659-82ff-4828-952c-a048c85a5471

📥 Commits

Reviewing files that changed from the base of the PR and between b6afac9 and aec37f9.

⛔ Files ignored due to path filters (3)
  • moldscript/examples/spin_examples/A1a_cat_rad_opt.log is excluded by !**/*.log
  • moldscript/examples/spin_examples/A1b_cat_rad_opt.log is excluded by !**/*.log
  • moldscript/examples/spin_examples/A1c_cat_rad_opt.log is excluded by !**/*.log
📒 Files selected for processing (8)
  • moldscript/MLIP.py
  • moldscript/argument_parser.py
  • moldscript/charges.py
  • moldscript/examples/arbr/mlip/neutral/arbr141_wb97xd_neutral_vertical.extxyz
  • moldscript/examples/arbr/mlip/oxidized/arbr141_wb97xd_cation_Nminus1_vertical.extxyz
  • moldscript/examples/arbr/mlip/reduced/arbr141_wb97xd_anion_Nplus1_vertical.extxyz
  • moldscript/moldscript.py
  • tests/test_moldscript.py

Comment thread moldscript/charges.py Outdated
Comment thread moldscript/MLIP.py
Comment on lines +117 to +194
for key, states in self.state_records.items():
base_state = self._pick_base_state(states)
base_record = states[base_state]

symbols = base_record["symbols"]
coords = np.array(base_record["coords"], dtype=float)
atomnos = np.array([self.ptable.GetAtomicNumber(sym) for sym in symbols], dtype=int)

entry = {
"mol": {},
"atom": {},
"bond": {},
"CPU_time": datetime.timedelta(0),
}

entry["atom"]["atomnos"] = atomnos
entry["bond"]["bond_length"] = self._bond_length_matrix(coords)

entry["mol"].setdefault("smiles", "")

# State energies
e_neutral_ev = self._state_energy_ev(states.get("neutral"))
e_reduced_ev = self._state_energy_ev(states.get("reduced"))
e_oxidized_ev = self._state_energy_ev(states.get("oxidized"))

if np.isfinite(e_neutral_ev):
entry["mol"]["scfenergy"] = e_neutral_ev * eV_to_hartree

else:
entry["mol"]["scfenergy"] = np.nan

if np.isfinite(e_oxidized_ev) and np.isfinite(e_neutral_ev):
ie_h = (e_oxidized_ev - e_neutral_ev) * eV_to_hartree
entry["mol"]["vertical_ie"] = ie_h
entry["mol"]["ionization_potential"] = ie_h
else:
entry["mol"]["vertical_ie"] = np.nan
entry["mol"]["ionization_potential"] = np.nan

if np.isfinite(e_reduced_ev) and np.isfinite(e_neutral_ev):
ea_h = (e_reduced_ev - e_neutral_ev) * eV_to_hartree
entry["mol"]["vertical_ea"] = ea_h
entry["mol"]["electron_affinity"] = ea_h
else:
entry["mol"]["vertical_ea"] = np.nan
entry["mol"]["electron_affinity"] = np.nan

# Dipole from the first state that provides it (neutral preferred).
dip = self._dipole_vector(states)
entry["mol"]["dipole_x"] = dip[0]
entry["mol"]["dipole_y"] = dip[1]
entry["mol"]["dipole_z"] = dip[2]
entry["mol"]["dipole"] = float(np.linalg.norm(dip)) if np.all(np.isfinite(dip)) else np.nan

q_neutral = self._state_charges(states.get("neutral"), len(atomnos))
q_reduced = self._state_charges(states.get("reduced"), len(atomnos))
q_oxidized = self._state_charges(states.get("oxidized"), len(atomnos))

# Follow sign convention already used in fukui.py
if np.all(np.isfinite(q_neutral)) and np.all(np.isfinite(q_reduced)):
fplus = -1.0 * (q_reduced - q_neutral)
else:
fplus = np.full(len(atomnos), np.nan)

if np.all(np.isfinite(q_neutral)) and np.all(np.isfinite(q_oxidized)):
fminus = -1.0 * (q_neutral - q_oxidized)
else:
fminus = np.full(len(atomnos), np.nan)

frad = 0.5 * (fplus + fminus)
entry["atom"]["fplus"] = fplus
entry["atom"]["fminus"] = fminus
entry["atom"]["frad"] = frad
entry["atom"]["charges_neutral"] = q_neutral

self.data_dict[key] = entry

return self.data_dict

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file first, then inspect the surrounding code paths.
printf '\n== moldscript/MLIP.py outline ==\n'
ast-grep outline moldscript/MLIP.py --view expanded || true

printf '\n== moldscript/MLIP.py around the assignment ==\n'
sed -n '1,260p' moldscript/MLIP.py | cat -n

printf '\n== search for data_dict construction / reuse ==\n'
rg -n "data_dict|mlip\(" moldscript.py moldscript -g '!**/__pycache__/**' || true

printf '\n== inspect charges.py pattern mentioned in the comment ==\n'
if [ -f moldscript/charges.py ]; then
  sed -n '1,240p' moldscript/charges.py | cat -n
fi

Repository: patonlab/molDscript

Length of output: 33898


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== moldscript.py around the MLIP call ==\n'
sed -n '120,150p' moldscript/moldscript.py | cat -n

printf '\n== MLIP key normalization and base-state selection ==\n'
sed -n '260,340p' moldscript/MLIP.py | cat -n

printf '\n== utils.initiate_data_dict and record_cpu_time ==\n'
sed -n '130,210p' moldscript/utils.py | cat -n
sed -n '80,115p' moldscript/utils.py | cat -n

Repository: patonlab/molDscript

Length of output: 9986


Merge MLIP fields into the existing entry
self.data_dict[key] = entry replaces the whole per-file record, so any earlier mol/atom/bond/CPU_time fields for the same key are dropped. Update the nested dicts in place instead of overwriting the entry.

🤖 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 `@moldscript/MLIP.py` around lines 117 - 194, The per-key record in the MLIP
aggregation loop is being overwritten by assigning a new entry to
self.data_dict[key], which drops any existing mol, atom, bond, or CPU_time data
already stored for that key. Update the logic in the state-processing block
inside the MLIP class so it merges into the existing self.data_dict[key] entry
in place, preserving previously populated nested fields while adding the new
MLIP values.

Comment thread moldscript/MLIP.py
Comment on lines +196 to +206
def _read_extxyz(self, file_path):
lines = file_path.read_text(encoding="utf-8").splitlines()
if len(lines) < 2:
raise ValueError(f"Invalid extxyz file: {file_path}")

natoms = int(lines[0].strip())
header_line = lines[1].strip()
header = self._parse_header_line(header_line)

schema = self._parse_properties_schema(header.get("Properties", "species:S:1:pos:R:3"))
atom_lines = lines[2 : 2 + natoms]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

No validation that the file actually contains natoms atom lines.

atom_lines = lines[2 : 2 + natoms] (Line 206) will silently yield fewer rows than natoms if the file is truncated/malformed, producing an atomnos/coords array shorter than the declared count with no error raised.

Consider asserting len(atom_lines) == natoms and raising a clear ValueError otherwise.

🤖 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 `@moldscript/MLIP.py` around lines 196 - 206, The _read_extxyz method currently
slices atom_lines without checking that the file actually contains the declared
number of atoms, so truncated or malformed input can slip through silently. In
_read_extxyz, after building atom_lines from lines[2:2 + natoms], validate that
its length matches natoms and raise a clear ValueError when it does not; keep
the check close to the existing natoms/header parsing logic so the failure is
reported before _parse_properties_schema or downstream atomnos/coords
construction.

Comment thread moldscript/MLIP.py
Comment on lines +291 to +303
def _state_charges(self, record, natoms):
if record is None:
return np.full(natoms, np.nan)
values = record["atom_props"].get(self.atom_charge_property)
if values is None:
return np.full(natoms, np.nan)
arr = np.array(values, dtype=float)
if len(arr) == natoms:
return arr
padded = np.full(natoms, np.nan)
ncopy = min(natoms, len(arr))
padded[:ncopy] = arr[:ncopy]
return padded

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Silent NaN-padding on atom-count mismatch.

If a state's charge array length differs from natoms, values are silently truncated/padded with NaN with no warning (Lines 300-303). This could mask a real geometry/state mismatch and quietly produce partially-NaN Fukui indices downstream.

Consider logging a warning when len(arr) != natoms so mismatches are visible rather than silently absorbed.

🤖 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 `@moldscript/MLIP.py` around lines 291 - 303, The _state_charges helper in
MLIP.py silently pads or truncates charge arrays when len(arr) does not match
natoms, which can hide state/geometry mismatches. Update _state_charges to emit
a warning whenever the atom charge array length differs from natoms, while
keeping the existing NaN-padding behavior; use the _state_charges method and
self.atom_charge_property to locate the mismatch path and make the warning
include the expected and actual lengths.

@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: 12

🧹 Nitpick comments (7)
moldscript/opt.py (3)

87-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use truthy/falsy checks for booleans.

Comparing directly to False or True is an anti-pattern in Python. Use not xtb and else instead.

♻️ Proposed refactor
-            if xtb == False:
+            if not xtb:
                 # convert log to smiles
                 opt_data = parse_cc_data(file_name, self.data[file_name])
                 cpu_times = opt_data.metadata.get('cpu_time') if hasattr(opt_data, 'metadata') else None
                 self.module_cpu_seconds += cpu_times_seconds(cpu_times)
                 record_cpu_time(self.data_dict, file_name, self.data[file_name], cpu_times)
-            elif xtb == True:
+            else:
🤖 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 `@moldscript/opt.py` around lines 87 - 93, Update the conditional around xtb in
the optimization flow to use `if not xtb` instead of comparing with False, and
retain the existing fallback branch as else instead of explicitly comparing with
True.

43-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unnecessary f-string prefix.

The string does not contain any formatting placeholders.

♻️ Proposed refactor
             self.args.log.write(
-                f"\nx  Could not find files to obtain optimization information. Exiting program"
+                "\nx  Could not find files to obtain optimization information. Exiting program"
             )
🤖 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 `@moldscript/opt.py` around lines 43 - 45, Update the log message in the
optimization flow to use a regular string literal instead of an f-string,
preserving the existing message text and behavior.

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clean up unused variables and simplify Python constructs.

  • The mydict lambda is defined but never used and can be safely removed.
  • The f prefix on the string at line 62 is unnecessary as there are no placeholders.
  • Retrieving the first value of the dictionary can be simplified to next(iter(self.data.values())) to avoid creating an intermediate list.
♻️ Proposed refactor
-        mydict = lambda: defaultdict(mydict)
-
-        self.args.log.write(f"-- Optimization Parameter Collection starting")
+        self.args.log.write("-- Optimization Parameter Collection starting")
         self.module_cpu_seconds = 0.0
         if all(self.data_dict.get(file_name, {}).get("CPU_time") for file_name in self.data):
             self.module_cpu_seconds = sum(
                 self.data_dict[file_name]["CPU_time"].total_seconds()
                 for file_name in self.data
             )
             report_job_progress(self.args.log, len(self.data), len(self.data))
             return self.data_dict
 
-        test_file = self.data[list(self.data.keys())[0]]
+        test_file = next(iter(self.data.values()))
🤖 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 `@moldscript/opt.py` around lines 60 - 72, In the relevant optimization setup,
remove the unused mydict lambda, change the constant log message written to
self.args.log to a normal string, and replace the list-based first-entry lookup
with next(iter(self.data.values())) when assigning test_file. Preserve the
surrounding optimization and progress-reporting behavior.
moldscript/lowe.py (1)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix copy-pasted comment.

The inline comment mentions the "atomic DataFrame", but this method is processing the bond DataFrame (bond_df).

♻️ Proposed fix
         emit('Including only lowest-energy conformer bond data into {}'.format(ensemble_bond_csv), style="cyan")
-        # Map the weights to the atomic DataFrame based on 'filename'
+        # Map the weights to the bond DataFrame based on 'filename'
         weighted_df = bond_df[bond_df['filename'].isin(self.low_confs)]
         weighted_df['filename'] = [k.rsplit('_conf',1)[0] for k in weighted_df['filename']]
         columns_order = ['filename', 'atom1_idx', 'atom1', 'atom2_idx', 'atom2'] + [col for col in weighted_df.columns if col not in ['filename', 'atom1_idx', 'atom1', 'atom2_idx', 'atom2']]
         weighted_df = weighted_df[columns_order]
         weighted_df = weighted_df.round(4)
🤖 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 `@moldscript/lowe.py` around lines 72 - 79, Update the inline comment above the
`weighted_df` assignment to refer to the bond DataFrame (`bond_df`) instead of
the atomic DataFrame, leaving the surrounding processing unchanged.
moldscript/get_df.py (1)

194-195: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix indentation for readability.

The append_run_log call is indented with 8 spaces relative to the for loop instead of the conventional 4 spaces.

♻️ Proposed fix
             for prop in props:
-                    append_run_log(f"\t- {prop}")
+                append_run_log(f"\t- {prop}")
🤖 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 `@moldscript/get_df.py` around lines 194 - 195, Correct the indentation of the
append_run_log call inside the for prop in props loop so it uses the
conventional single nested-block indentation relative to the loop.
moldscript/sterics.py (1)

23-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use truthiness check for boolean evaluation.

As suggested by static analysis, prefer if vall: or if vall is not False: over if vall != False:.

♻️ Proposed fix
-        if vall != False:
+        if vall:
🤖 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 `@moldscript/sterics.py` at line 23, Update the boolean condition in the
surrounding logic to use a truthiness check (`if vall:`) instead of comparing
`vall` with `False`, preserving the existing behavior for truthy values.

Source: Linters/SAST tools

moldscript/files.py (1)

141-147: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use idiomatic truthiness checks to prevent None stringification.

As suggested by static analysis, prefer not self.warn_suffix over self.warn_suffix == False. Additionally, evaluating the truthiness of suffix rather than explicitly checking suffix != '' prevents a scenario where suffix is None (evaluating to True) and becomes incorrectly stringified to "None".

♻️ Proposed fix
        if suffix:
            suffix = str(suffix).strip().lstrip("_")
            fullname = fullname.split("_" + suffix)[0]
        elif not self.warn_suffix:
            emit(f"Warning: no suffix provided for {self.calc}; matching will use each full filename stem.", style="yellow")
            emit(f"If {self.calc} filenames include a module tag, pass --suffix_{self.calc} so they match the optimization keys.", style="yellow")
            self.warn_suffix = True
🤖 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 `@moldscript/files.py` around lines 141 - 147, Update the suffix handling in
the surrounding filename-matching method to enter the normalization and
splitting branch only when suffix is truthy, preventing None from being
stringified; also replace the self.warn_suffix == False comparison with the
idiomatic not self.warn_suffix check while preserving the existing warning
behavior.

Source: Linters/SAST tools

🤖 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 `@moldscript/argument_parser.py`:
- Around line 196-198: Update the success message in the terminal_success call
to replace the XXX documentation placeholder with the actual Moldscript
documentation URL, preserving the existing message wording and formatting.
- Around line 111-113: Update the case-insensitive option branch in the argument
parsing logic so it checks the lowercased key against var_dict but retrieves the
value from kwargs using the original key, preventing KeyError for differently
cased options.

In `@moldscript/fmo.py`:
- Around line 29-39: Update the electrophilicity calculation in the mol_values
computation so the denominator uses hardness, defined as half of the HOMO–LUMO
gap, while preserving the existing chemical_potential and reported gap values.
Ensure global_electrophilicity is calculated as μ²/(2η) and
global_nucleophilicity continues to use its reciprocal.
- Around line 25-26: Update the dipole calculation in the fmo.py flow to compute
the norm directly from fmo_data.moments[1] using np.linalg.norm, removing the
subtraction from fmo_data.moments[0] while preserving the reported scalar dipole
value.

In `@moldscript/fukui.py`:
- Around line 162-164: Update the job-building loop in fukui_data_dict() so raw
entries whose neutral baseline file is missing are detected and skipped before
calling resolve_data_key(). Preserve processing for entries with available
neutral files, and ensure missing-neutral cases reach the existing skip behavior
rather than aborting the run.
- Around line 31-35: Update the NPA section selection in the loop that populates
list_npop so start_npop uses the final detected NPA table, matching
scfenergies[-1], rather than list_npop[0]. Preserve the existing behavior when
no NPA section is found.
- Around line 103-106: Update the vertical_ea calculation in the mol_values
mapping to subtract reduced_data["energy"] from neutral_data["energy"], while
leaving the vertical_ie calculation unchanged.

In `@moldscript/moldscript.py`:
- Around line 160-161: Update main() to initialize first_read to None alongside
data_dicts before the input-selection branches, preventing the --link path from
raising NameError. Replace the direct False comparisons in the sterics condition
with a truthy check for args.volume or args.vall, and only invoke sterics when
those options are requested and first_read contains valid structural input
rather than None or an empty value.

In `@moldscript/utils.py`:
- Around line 519-524: Update resolve_data_key and filename_match_candidates so
calculator filenames are not matched by unrestricted prefix truncation. Only
strip the configured module suffix when deriving candidates, or reject
candidates that remove molecule-name components; ensure names such as
foo_bar_spc.log do not resolve to foo when foo_bar is unavailable.
- Around line 267-275: Update initialize_run_log and the related
_LOG_PATHS_INITIALIZED handling so each new run explicitly truncates the audit
log and clears any prior path-initialization state before writing provenance.
Ensure repeated main() invocations with the same output path start with an
independent log while preserving the existing run metadata and command-line
entries.
- Around line 635-639: Update bond_data_matrix so its coordinate-extraction
fallback catches only expected attribute, indexing, and conversion failures
(such as AttributeError, IndexError, TypeError, and ValueError), rather than
using a bare except that also intercepts process-control exceptions.

In `@README.md`:
- Line 43: Update the README option description near the output PREFIX entry to
list --no_mol, --no_atom, and --no_bond as CSV-output toggles, while documenting
--no_bond_filter separately as the bond-inclusion control; keep the wording
consistent with docs/source/README.rst.

---

Nitpick comments:
In `@moldscript/files.py`:
- Around line 141-147: Update the suffix handling in the surrounding
filename-matching method to enter the normalization and splitting branch only
when suffix is truthy, preventing None from being stringified; also replace the
self.warn_suffix == False comparison with the idiomatic not self.warn_suffix
check while preserving the existing warning behavior.

In `@moldscript/get_df.py`:
- Around line 194-195: Correct the indentation of the append_run_log call inside
the for prop in props loop so it uses the conventional single nested-block
indentation relative to the loop.

In `@moldscript/lowe.py`:
- Around line 72-79: Update the inline comment above the `weighted_df`
assignment to refer to the bond DataFrame (`bond_df`) instead of the atomic
DataFrame, leaving the surrounding processing unchanged.

In `@moldscript/opt.py`:
- Around line 87-93: Update the conditional around xtb in the optimization flow
to use `if not xtb` instead of comparing with False, and retain the existing
fallback branch as else instead of explicitly comparing with True.
- Around line 43-45: Update the log message in the optimization flow to use a
regular string literal instead of an f-string, preserving the existing message
text and behavior.
- Around line 60-72: In the relevant optimization setup, remove the unused
mydict lambda, change the constant log message written to self.args.log to a
normal string, and replace the list-based first-entry lookup with
next(iter(self.data.values())) when assigning test_file. Preserve the
surrounding optimization and progress-reporting behavior.

In `@moldscript/sterics.py`:
- Line 23: Update the boolean condition in the surrounding logic to use a
truthiness check (`if vall:`) instead of comparing `vall` with `False`,
preserving the existing behavior for truthy values.
🪄 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

Run ID: c70d5f80-762a-4035-8dc4-974bbcbe2f5d

📥 Commits

Reviewing files that changed from the base of the PR and between aec37f9 and 6e58f24.

📒 Files selected for processing (22)
  • README.md
  • docs/source/README.rst
  • moldscript/MLIP.py
  • moldscript/__main__.py
  • moldscript/argument_parser.py
  • moldscript/boltz.py
  • moldscript/charges.py
  • moldscript/files.py
  • moldscript/fmo.py
  • moldscript/fukui.py
  • moldscript/get_df.py
  • moldscript/lowe.py
  • moldscript/min_max.py
  • moldscript/moldscript.py
  • moldscript/nbo.py
  • moldscript/nmr.py
  • moldscript/opt.py
  • moldscript/spc.py
  • moldscript/sterics.py
  • moldscript/utils.py
  • requirements.txt
  • setup.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • moldscript/MLIP.py

Comment on lines +111 to +113
elif key.lower() in var_dict:
vars(options)[key.lower()] = kwargs[key.lower()]
else:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix KeyError for case-insensitive option matching.

If a user provides an option with incorrect casing (e.g., --Workers 2), key.lower() in var_dict evaluates to True, but kwargs[key.lower()] will raise a KeyError because kwargs contains the original casing ("Workers"). Change the lookup to use the original key.

🐛 Proposed fix
         elif key.lower() in var_dict:
-            vars(options)[key.lower()] = kwargs[key.lower()]
+            vars(options)[key.lower()] = kwargs[key]
         else:
📝 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
elif key.lower() in var_dict:
vars(options)[key.lower()] = kwargs[key.lower()]
else:
elif key.lower() in var_dict:
vars(options)[key.lower()] = kwargs[key]
else:
🤖 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 `@moldscript/argument_parser.py` around lines 111 - 113, Update the
case-insensitive option branch in the argument parsing logic so it checks the
lowercased key against var_dict but retrieves the value from kwargs using the
original key, preventing KeyError for differently cased options.

Comment on lines +196 to +198
terminal_success(
f"o MOLDSCRIPT v {moldscript_version} is installed correctly! For more information about the available options, see the documentation in XXX"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace documentation placeholder.

The help message contains a placeholder (XXX) for the documentation link. Please update it with the actual URL.

📝 Proposed fix
-                f"o  MOLDSCRIPT v {moldscript_version} is installed correctly! For more information about the available options, see the documentation in XXX"
+                f"o  MOLDSCRIPT v {moldscript_version} is installed correctly! For more information about the available options, see the documentation at https://moldscript.readthedocs.io/"
*(Note: Please adjust the URL to the actual documentation site if it differs).*
📝 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
terminal_success(
f"o MOLDSCRIPT v {moldscript_version} is installed correctly! For more information about the available options, see the documentation in XXX"
)
terminal_success(
f"o MOLDSCRIPT v {moldscript_version} is installed correctly! For more information about the available options, see the documentation at https://moldscript.readthedocs.io/"
)
🤖 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 `@moldscript/argument_parser.py` around lines 196 - 198, Update the success
message in the terminal_success call to replace the XXX documentation
placeholder with the actual Moldscript documentation URL, preserving the
existing message wording and formatting.

Comment thread moldscript/fmo.py
Comment on lines +25 to +26
fmo_data = cc.io.ccread(source_path)
dipole = np.sqrt(np.sum((fmo_data.moments[0] - fmo_data.moments[1]) ** 2, axis=0))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'moments\[[012]\]' moldscript tests

Repository: patonlab/molDscript

Length of output: 1447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline moldscript/fmo.py --view expanded || true
echo '---'
cat -n moldscript/fmo.py | sed -n '1,220p'

Repository: patonlab/molDscript

Length of output: 7039


🌐 Web query:

cclib data_notes moments[0] moments[1] dipole vector reference point

💡 Result:

In cclib, the moments attribute is a list of one-dimensional arrays representing the molecular multipole moments of a molecule [1][2]. This attribute follows a specific structure for its elements [3][1][2]: 1. The first element (moments) is the reference point used for the multipole expansion, which is typically the center of mass of the molecule [3][1][2]. 2. The second element (moments[3]) is the dipole moment vector [3][1][2]. This vector is expressed in units of Debyes (D) [1][2]. Subsequent elements in the moments list contain higher-order electrostatic multipole moments, such as the quadrupole moments (typically the third element, moments[1]), which are provided in lexicographical order (XX, XY, XZ, YY, YZ, ZZ) and measured in Buckinghams (B) [1][2]. Any further arrays in the list represent multipole moments of even higher rank, also in lexicographical order, expressed in units of D Å^(L-1) [1][2]. By default, cclib reports the moments from the final geometry or wavefunction printed in the output file [1][2]. If multiple sets of moments are printed during a job, such as during a geometry optimization, cclib provides the last set parsed [1][2].

Citations:


🌐 Web query:

site:cclib.readthedocs.io data_notes moments dipole moment vector moments[0] moments[1]

💡 Result:

In the context of the cclib computational chemistry library, the moments attribute contains the dipole moment vector and higher electrostatic multipole moments for a molecule [1]. This attribute is structured as a list of one-dimensional arrays [1]. Within this list, the indices refer to specific data: - moments: This is the reference point used for the multipole expansion, which is typically the center of mass [1]. - moments[1]: This contains the dipole moment vector, expressed in Debyes [1]. Any subsequent arrays in the moments list (e.g., moments[2], moments[3], etc.) contain higher-rank multipole moments, such as the molecular quadrupole moments (in Buckinghams) at index 2 [1].

Citations:


Use the dipole vector directly in moldscript/fmo.py:26. moments[0] is the multipole reference point and moments[1] is the dipole vector, so subtracting them changes the reported dipole whenever the origin is nonzero. np.linalg.norm(fmo_data.moments[1]) is the right value.

🤖 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 `@moldscript/fmo.py` around lines 25 - 26, Update the dipole calculation in the
fmo.py flow to compute the norm directly from fmo_data.moments[1] using
np.linalg.norm, removing the subtraction from fmo_data.moments[0] while
preserving the reported scalar dipole value.

Comment thread moldscript/fmo.py
Comment on lines +29 to +39
softness = lumo - homo
chemical_potential = (lumo + homo) / 2
global_electrophilicity = chemical_potential**2 / (2 * softness)
mol_values = {
"dipole": dipole,
"HOMO": homo,
"LUMO": lumo,
"HOMO-LUMO_gap": softness,
"chemical_potential": chemical_potential,
"global_electrophilicity": global_electrophilicity,
"global_nucleophilicity": 1 / global_electrophilicity,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Correct the electrophilicity denominator.

Under the frontier-orbital approximation, hardness is half the HOMO–LUMO gap and electrophilicity is μ²/(2η). Using the full gap as softness and multiplying it by two halves every reported value. (pubs.rsc.org)

Proposed fix
-        softness = lumo - homo
+        gap = lumo - homo
+        hardness = gap / 2
         chemical_potential = (lumo + homo) / 2
-        global_electrophilicity = chemical_potential**2 / (2 * softness)
+        global_electrophilicity = chemical_potential**2 / (2 * hardness)
🤖 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 `@moldscript/fmo.py` around lines 29 - 39, Update the electrophilicity
calculation in the mol_values computation so the denominator uses hardness,
defined as half of the HOMO–LUMO gap, while preserving the existing
chemical_potential and reported gap values. Ensure global_electrophilicity is
calculated as μ²/(2η) and global_nucleophilicity continues to use its
reciprocal.

Comment thread moldscript/fukui.py
Comment on lines +31 to +35
for i, line in enumerate(lines):
if line.find(" Summary of Natural Population Analysis:") > -1:
list_npop.append(i + 6)
if list_npop:
start_npop = list_npop[0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the final NPA table.

Optimization outputs may contain multiple NPA sections. Selecting list_npop[0] combines first-geometry charges with scfenergies[-1], producing internally inconsistent Fukui descriptors.

-        start_npop = list_npop[0]
+        start_npop = list_npop[-1]
📝 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
for i, line in enumerate(lines):
if line.find(" Summary of Natural Population Analysis:") > -1:
list_npop.append(i + 6)
if list_npop:
start_npop = list_npop[0]
for i, line in enumerate(lines):
if line.find(" Summary of Natural Population Analysis:") > -1:
list_npop.append(i + 6)
if list_npop:
start_npop = list_npop[-1]
🤖 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 `@moldscript/fukui.py` around lines 31 - 35, Update the NPA section selection
in the loop that populates list_npop so start_npop uses the final detected NPA
table, matching scfenergies[-1], rather than list_npop[0]. Preserve the existing
behavior when no NPA section is found.

Comment thread moldscript/moldscript.py
Comment on lines +160 to +161
if args.volume != False or args.vall != False:
data_dicts = sterics(first_read, data_dicts, args.volume, args.vall, args.radius, output=args.output).dd

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle missing base inputs for sterics, resolve NameError, and avoid boolean anti-patterns.

This code segment contains three issues:

  1. Comparing directly to False is an anti-pattern. Use truthy checks (args.volume or args.vall) instead.
  2. If args.link is passed, the else block is skipped and first_read is never defined, leading to a NameError here.
  3. If sterics are requested without a base input (e.g., using only --mlip), first_read evaluates to '' and causes an AttributeError inside sterics because it expects a dictionary.

Add a fallback to initialize first_read (e.g., to None) at the top of the function and guard this block so it only runs if first_read contains valid structural files.

At the top of main() (around line 68), ensure first_read is initialized unconditionally:

    data_dicts = {}
+   first_read = None
🐛 Proposed fix for the sterics block
-    if args.volume != False or args.vall != False:
-        data_dicts = sterics(first_read, data_dicts, args.volume, args.vall, args.radius, output=args.output).dd
+    if args.volume or args.vall:
+        if not first_read:
+            terminal_error("x  Sterics calculation requires a base input (e.g., --opt) to provide geometry files.")
+            sys.exit(1)
+        data_dicts = sterics(first_read, data_dicts, args.volume, args.vall, args.radius, output=args.output).dd
🧰 Tools
🪛 Ruff (0.15.21)

[error] 160-160: Avoid inequality comparisons to False; use args.volume: for truth checks

Replace with args.volume

(E712)


[error] 160-160: Avoid inequality comparisons to False; use args.vall: for truth checks

Replace with args.vall

(E712)

🤖 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 `@moldscript/moldscript.py` around lines 160 - 161, Update main() to initialize
first_read to None alongside data_dicts before the input-selection branches,
preventing the --link path from raising NameError. Replace the direct False
comparisons in the sterics condition with a truthy check for args.volume or
args.vall, and only invoke sterics when those options are requested and
first_read contains valid structural input rather than None or an empty value.

Comment thread moldscript/utils.py
Comment on lines +267 to +275
def initialize_run_log(output_prefix, version, timestamp, reference, argv=None):
"""Create the single run-level .dat file and write run provenance."""
logger = Logger(build_log_path(output_prefix), verbose=False)
logger.write_only(
f" MOLDSCRIPT v {version} {timestamp} \n Citation: {reference}\n"
)
command_line = " ".join(["python", "-m", "moldscript", *(argv or [])])
logger.write_only(f"Command line used in MOLDSCRIPT: {command_line}")
logger.finalize()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Explicitly truncate the audit log when initializing a new run.

A second main() invocation using the same output path in one process appends to the previous run because _LOG_PATHS_INITIALIZED is never reset. This mixes independent run provenance.

Proposed fix
 def initialize_run_log(output_prefix, version, timestamp, reference, argv=None):
-    logger = Logger(build_log_path(output_prefix), verbose=False)
+    logger = Logger(build_log_path(output_prefix), verbose=False, truncate=True)

 class Logger:
-    def __init__(self, file_path=None, verbose=True):
+    def __init__(self, file_path=None, verbose=True, truncate=False):
         ...
-        self.started_new_file = key not in _LOG_PATHS_INITIALIZED
+        self.started_new_file = truncate or key not in _LOG_PATHS_INITIALIZED

Also applies to: 282-295

🤖 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 `@moldscript/utils.py` around lines 267 - 275, Update initialize_run_log and
the related _LOG_PATHS_INITIALIZED handling so each new run explicitly truncates
the audit log and clears any prior path-initialization state before writing
provenance. Ensure repeated main() invocations with the same output path start
with an independent log while preserving the existing run metadata and
command-line entries.

Comment thread moldscript/utils.py
Comment on lines +519 to +524
def resolve_data_key(fullname, data_dict, module_name=None, logger=None):
"""Resolve a module filename/key to an initialized molecule key."""
available = set(molecule_keys(data_dict))
for candidate in filename_match_candidates(fullname):
if candidate in available:
return candidate

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not resolve calculator files by unrestricted prefix truncation.

For example, foo_bar_spc.log falls back to molecule foo when foo_bar is absent, silently writing descriptors to the wrong record. Strip only the configured module suffix, or reject matches requiring removal of molecule-name components.

🤖 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 `@moldscript/utils.py` around lines 519 - 524, Update resolve_data_key and
filename_match_candidates so calculator filenames are not matched by
unrestricted prefix truncation. Only strip the configured module suffix when
deriving candidates, or reject candidates that remove molecule-name components;
ensure names such as foo_bar_spc.log do not resolve to foo when foo_bar is
unavailable.

Comment thread moldscript/utils.py
Comment on lines +635 to +639
def bond_data_matrix(data):
try:
coords = np.asarray(data.atomcoords[-1], dtype=float)
except:
coords = np.asarray(data, dtype=float)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section with line numbers
sed -n '610,670p' moldscript/utils.py | cat -n

# Find call sites and nearby handling to understand intended inputs
rg -n "bond_data_matrix|atomcoords" -S .

Repository: patonlab/molDscript

Length of output: 3673


Catch only the expected conversion errors here.
The bare except also swallows KeyboardInterrupt and SystemExit; limit it to the attribute/index/type/value failures that can happen during coordinate extraction.

Proposed fix
-        except:
+        except (AttributeError, IndexError, TypeError, ValueError):
             coords = np.asarray(data, dtype=float)
📝 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
def bond_data_matrix(data):
try:
coords = np.asarray(data.atomcoords[-1], dtype=float)
except:
coords = np.asarray(data, dtype=float)
def bond_data_matrix(data):
try:
coords = np.asarray(data.atomcoords[-1], dtype=float)
except (AttributeError, IndexError, TypeError, ValueError):
coords = np.asarray(data, dtype=float)
🧰 Tools
🪛 Ruff (0.15.21)

[error] 638-638: Do not use bare except

(E722)

🤖 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 `@moldscript/utils.py` around lines 635 - 639, Update bond_data_matrix so its
coordinate-extraction fallback catches only expected attribute, indexing, and
conversion failures (such as AttributeError, IndexError, TypeError, and
ValueError), rather than using a bare except that also intercepts
process-control exceptions.

Source: Linters/SAST tools

Comment thread README.md
- `--output PREFIX` - prepend every generated filename; append a slash to target a directory. Use `--no_mol`, `--no_atom`, `--no_bond`, or `--no_bond_filter` to tailor CSV output.
- `--substructure SMARTS` - limit atom/bond descriptors to a SMARTS match; combine with `--volume` or `--vall` and optional `--radius` list for DBSTEP buried volumes.
- `--boltz`, `--min_max`, `--lowe` - compute Boltzmann-weighted averages, min/max/range tables (using `--cut`), and lowest-energy snapshots. Adjust `--temp` (K) as needed.
- `--output PREFIX` - prepend every generated filename; append a slash to target a directory. Use `--no_mol`, `--no_atom`, `--no_bond`, or `--no_bond_filter` to tailor CSV output.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document --no_bond_filter separately from CSV toggles.

--no_bond_filter changes bond inclusion criteria; it does not disable CSV output. Keep it separate from --no_mol, --no_atom, and --no_bond, consistent with docs/source/README.rst Lines 165-169.

🤖 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 `@README.md` at line 43, Update the README option description near the output
PREFIX entry to list --no_mol, --no_atom, and --no_bond as CSV-output toggles,
while documenting --no_bond_filter separately as the bond-inclusion control;
keep the wording consistent with docs/source/README.rst.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
moldscript/argument_parser.py (1)

96-102: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Improve numeric parsing to support negative integers.

The .isdigit() check returns False for negative integers (e.g., "-4"), causing them to fall through to the float parsing block and become floats instead of integers. Using a try/except chain provides more robust parsing for all numeric types.

🐛 Proposed fix
-            elif value.isdigit():
-                value = int(value)
-            else:
-                try:
-                    value = float(value)
-                except ValueError:
-                    pass  # Keep value as a string if it can't be converted
+            else:
+                try:
+                    value = int(value)
+                except ValueError:
+                    try:
+                        value = float(value)
+                    except ValueError:
+                        pass  # Keep value as a string if it can't be converted
🤖 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 `@moldscript/argument_parser.py` around lines 96 - 102, Update the numeric
conversion branch in the argument parsing logic to attempt integer conversion
before float conversion, using exception handling so negative integer strings
such as “-4” remain integers while decimal values become floats and non-numeric
values remain strings.
🤖 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.

Outside diff comments:
In `@moldscript/argument_parser.py`:
- Around line 96-102: Update the numeric conversion branch in the argument
parsing logic to attempt integer conversion before float conversion, using
exception handling so negative integer strings such as “-4” remain integers
while decimal values become floats and non-numeric values remain strings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a987a635-0608-4749-b4df-ac595cd4fca6

📥 Commits

Reviewing files that changed from the base of the PR and between 6e58f24 and 8c52391.

📒 Files selected for processing (3)
  • README.md
  • moldscript/argument_parser.py
  • moldscript/moldscript.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • moldscript/moldscript.py

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