perf: fast-path substring guards + short-circuit ordering fixes in parser matches()#36
Conversation
…n all parser matches() methods - Add 'if KEYWORD not in line: return False' fast-path guards to 22 parsers (15 Q-Chem + 7 ORCA), reducing matches() cost from O(regex) to O(substring) on non-matching lines for the common case - Fix reversed short-circuit ordering in 5 parsers where regex ran before the state flag check: GeometryParser (qchem), MullikenParser, HirshfeldParser, Cm5Parser, MultipoleParser — state flag (O(1)) now always precedes regex - Add combined state guard to ORCA ChargesParser (no guard existed): stops after both Mulliken and Loewdin are parsed; also adds MULLIKEN/LOEWDIN fast-path keyword check - Q-Chem TerminationParser intentionally left without a fast-path guard: its ERROR_TERM_PAT is a broad 4-keyword IGNORECASE catch-all; the existing termination_status guard already short-circuits once status is decided
Pytest Coverage Report 🧪 |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the performance of the parsing logic within the calcflow library for both Q-Chem and ORCA output files. By introducing early exit conditions and optimizing the order of checks in "matches()" methods, the changes aim to reduce computational overhead, particularly when processing large files with many non-matching lines. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces significant performance improvements by adding fast-path substring guards to matches() methods and fixing short-circuit ordering. The changes are well-implemented across numerous parsers for both ORCA and Q-Chem. I've identified a few minor areas for further improvement: one for an additional performance gain in the ORCA charges parser, and a couple of minor style consistency suggestions for regex checks.
…ataParser - ORCA ChargesParser: check each flag individually before its regex so Loewdin regex is skipped after Mulliken is parsed (and vice versa), rather than waiting until both flags are set - Q-Chem GeometryParser, MetadataParser: replace 'is not None' with bool() for consistency with all other parsers in the codebase
Pytest Coverage Report 🧪 |
Summary
Fast-path
inguards added to 22matches()methods (15 Q-Chem + 7 ORCA parsers): each now begins with a cheapif "KEYWORD" not in line: return Falsebefore invoking the regex engine. On non-matching lines (the common case), this reducesmatches()cost from O(regex) to O(substring).Short-circuit ordering fixed in 5 parsers where regex ran before the boolean state flag:
GeometryParser(Q-Chem),MullikenParser,HirshfeldParser,Cm5Parser,MultipoleParser. The O(1) state flag check now always precedes the O(n) regex.ORCA
ChargesParserhad no state guard at all — it ran two regex searches on every line for the entire file even after both Mulliken and Loewdin were parsed. Added a combined completion guard (if state.parsed_mulliken and state.parsed_loewdin: return False) plus a keyword fast-path ("MULLIKEN"/"LOEWDIN").Q-Chem
TerminationParserintentionally left without a fast-path guard: itsERROR_TERM_PATis a broad 4-keywordIGNORECASEcatch-all; the existingtermination_status != "UNKNOWN"guard already short-circuits once the status is decided.Files changed (22)
Q-Chem:
metadata.py,geometry.py,scf.py,charges.py,hirshfeld.py,cm5.py,orbitals.py,multipole.py,timing.py,tddft/excitations.py,tddft/nto.py,tddft/gs_ref.py,tddft/unrel_dm.py,tddft/trans_dm.py,adc/ground_state.py,adc/excited_states.pyORCA:
charges.py,geometry.py,orbitals.py,dispersion.py,finalization.py(both parsers),timing.pyVerification
All 1924 tests pass (
uv run pytest --tb=short -q).uvx ruff checkclean on all changed files.Greptile Summary
This PR adds O(substring) fast-path keyword guards to 22
matches()methods across Q-Chem and ORCA parsers, and fixes short-circuit evaluation ordering in 5 parsers so that cheap O(1) state-flag checks always precede O(n) regex searches. The net effect is that the hot path — the overwhelming majority of lines in an output file that don't begin any block — now returnsFalseafter a singleinmembership test rather than invoking the regex engine.Key changes:
matches()now opens withif "LITERAL" not in line: return False, where"LITERAL"is always a substring of the corresponding compiled regex pattern, guaranteeing no false negatives.GeometryParser,MullikenParser,HirshfeldParser,Cm5Parser,MultipoleParser): expressions likebool(PAT.search(line)) and not state.parsed_Xwere reversed tonot state.parsed_X and bool(PAT.search(line))so the O(1) flag is evaluated first under Python's short-circuitand.ChargesParserreceived a combined completion guard (if state.parsed_mulliken and state.parsed_loewdin: return False) plus keyword guards, eliminating two unbounded regex searches per line for the entire file length once both charge types are captured.Confidence Score: 5/5
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[CoreParser: next line] --> B{Keyword guard} B -->|KEYWORD not in line - skip| A B -->|KEYWORD in line - proceed| C{State flag check} C -->|already parsed - skip| A C -->|not yet parsed - proceed| D[Regex search] D -->|no match| A D -->|match| E[parse called - state updated] E --> ALast reviewed commit: 77044d1