Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/calcflow/io/orca/blocks/charges.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ def matches(self, line: str, state: ParseState) -> bool:
Check if this line marks the beginning of an atomic charges block.

Returns True for either MULLIKEN or LOEWDIN atomic charges headers.
Note: No state flag check - allows parser to run multiple times (once per method).
Allows the parser to run twice (once per method); stops after both are parsed.
"""
return bool(MULLIKEN_START_PAT.search(line)) or bool(LOEWDIN_START_PAT.search(line))
if state.parsed_mulliken and state.parsed_loewdin:
return False
if "MULLIKEN" not in line and "LOEWDIN" not in line:
return False
if not state.parsed_mulliken and MULLIKEN_START_PAT.search(line):
return True
return not state.parsed_loewdin and bool(LOEWDIN_START_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
"""
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/orca/blocks/dispersion.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ def matches(self, line: str, state: ParseState) -> bool:
"""
Check if this line marks the start of a dispersion block.
"""
if "DISPERSION CORRECTION" not in line:
return False
if state.parsed_dispersion:
return False
return bool(START_PAT.search(line))
Expand Down
4 changes: 4 additions & 0 deletions src/calcflow/io/orca/blocks/finalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

class FinalEnergyParser:
def matches(self, line: str, state: ParseState) -> bool:
if "FINAL SINGLE POINT ENERGY" not in line:
return False
return state.final_energy is None and bool(FINAL_ENERGY_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
Expand All @@ -19,6 +21,8 @@ def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState)

class TerminationParser:
def matches(self, line: str, state: ParseState) -> bool:
if "ORCA TERMINATED" not in line:
return False
return state.termination_status == "UNKNOWN" and bool(NORMAL_TERM_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/orca/blocks/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

class GeometryParser:
def matches(self, line: str, state: ParseState) -> bool:
if "CARTESIAN COORDINATES" not in line:
return False
return not state.parsed_geometry and bool(GEOMETRY_START_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/orca/blocks/orbitals.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def matches(self, line: str, state: ParseState) -> bool:
Returns:
True if this is the start of an orbital block and hasn't been parsed yet.
"""
if "ORBITAL ENERGIES" not in line:
return False
if state.parsed_orbitals:
return False
return bool(ORBITAL_ENERGIES_START_PAT.search(line))
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/orca/blocks/timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def __init__(self):

def matches(self, line: str, state: ParseState) -> bool:
"""Matches on total wall time line or module timing lines."""
if "..." not in line and "TOTAL RUN TIME" not in line:
return False
if state.parsed_timing:
return False
return bool(TOTAL_TIME_PAT.search(line)) or bool(MODULE_TIME_PAT.search(line))
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/adc/excited_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class AdcExcitedStatesParser(BlockParser):
START_PAT: ClassVar[re.Pattern] = re.compile(r"Excited State Summary")

def matches(self, line: str, state: ParseState) -> bool:
if "Excited State Summary" not in line:
return False
if state.parsed_adc_excited:
return False
return bool(self.START_PAT.search(line))
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/adc/ground_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class AdcGroundStateParser(BlockParser):
DAVIDSON_PAT: ClassVar[re.Pattern] = re.compile(r"Starting Davidson for excited states")

def matches(self, line: str, state: ParseState) -> bool:
if "A D C" not in line:
return False
if state.parsed_adc_gs:
return False
return bool(self.BANNER_PAT.search(line))
Expand Down
4 changes: 3 additions & 1 deletion src/calcflow/io/qchem/blocks/charges.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ def matches(self, line: str, state: ParseState) -> bool:
Returns True only if we haven't already parsed Mulliken charges and the line
contains the Mulliken charges header.
"""
return bool(MULLIKEN_START_PAT.search(line)) and not state.parsed_mulliken
if "Mulliken" not in line:
return False
return not state.parsed_mulliken and bool(MULLIKEN_START_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
"""
Expand Down
4 changes: 3 additions & 1 deletion src/calcflow/io/qchem/blocks/cm5.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ class Cm5Parser:
"""

def matches(self, line: str, state: ParseState) -> bool:
return bool(CM5_START_PAT.search(line)) and not state.parsed_cm5
if "Charge Model 5" not in line:
return False
return not state.parsed_cm5 and bool(CM5_START_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
logger.debug("Parsing QChem CM5 charges block.")
Expand Down
6 changes: 4 additions & 2 deletions src/calcflow/io/qchem/blocks/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ class GeometryParser:

def matches(self, line: str, state: ParseState) -> bool:
"""Checks if the line starts either the input or standard geometry block."""
if INPUT_GEOM_START_PAT.search(line) and not state.parsed_geometry:
if "$molecule" not in line.lower() and "Standard Nuclear Orientation" not in line:
return False
if not state.parsed_geometry and INPUT_GEOM_START_PAT.search(line):
# We use a single 'parsed_geometry' flag for the input geometry
return True
# The final geometry is part of the main calculation, not a separate block to be parsed once
# For now, let's assume we only parse the final geometry once.
return (STANDARD_GEOM_START_PAT.search(line) is not None) and state.final_geometry is None
return state.final_geometry is None and bool(STANDARD_GEOM_START_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
"""Delegates to the appropriate parsing method based on the start line."""
Expand Down
4 changes: 3 additions & 1 deletion src/calcflow/io/qchem/blocks/hirshfeld.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class HirshfeldParser:
"""

def matches(self, line: str, state: ParseState) -> bool:
return bool(HIRSHFELD_START_PAT.search(line)) and not state.parsed_hirshfeld
if "Hirshfeld" not in line:
return False
return not state.parsed_hirshfeld and bool(HIRSHFELD_START_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
logger.debug("Parsing QChem Hirshfeld charges block.")
Expand Down
4 changes: 3 additions & 1 deletion src/calcflow/io/qchem/blocks/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def matches(self, line: str, state: ParseState) -> bool:
"""
if state.metadata.software_version is not None:
return False
return QCHEM_VERSION_PAT.search(line) is not None
if "Q-Chem" not in line:
return False
return bool(QCHEM_VERSION_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
"""
Expand Down
4 changes: 3 additions & 1 deletion src/calcflow/io/qchem/blocks/multipole.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ class MultipoleParser:

def matches(self, line: str, state: ParseState) -> bool:
"""Check if this line marks the beginning of the multipole moments block."""
return bool(MULTIPOLE_START_PAT.search(line)) and not state.parsed_multipole
if "Multipole Moments" not in line:
return False
return not state.parsed_multipole and bool(MULTIPOLE_START_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
"""
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/orbitals.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class OrbitalsParser:

def matches(self, line: str, state: ParseState) -> bool:
"""Check if this line marks the start of the orbital energies block."""
if "Orbital Energies" not in line:
return False
if state.parsed_orbitals:
return False
return bool(ORBITAL_BLOCK_START_PAT.search(line))
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/scf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class ScfParser:
"""Parses the main SCF block, including iterations, final energies, and SMD summaries."""

def matches(self, line: str, state: ParseState) -> bool:
if "General SCF" not in line:
return False
return not state.parsed_scf and bool(SCF_START_PAT.search(line))

def parse(self, iterator: PeekableIterator, start_line: str, state: ParseState) -> None:
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/tddft/excitations.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def matches(self, line: str, state: ParseState) -> bool:

Matches either TDA or full TDDFT block start, and only if not already parsed.
"""
if "Excitation" not in line:
return False
if state.parsed_tddft_tda and state.parsed_tddft_full:
return False

Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/tddft/gs_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def matches(self, line: str, state: ParseState) -> bool:

Matches "Ground State (Reference) :" and only if not already parsed.
"""
if "Ground State" not in line:
return False
if state.parsed_tddft_gs_ref:
return False

Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/tddft/nto.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ def matches(self, line: str, state: ParseState) -> bool:

Returns False if already parsed.
"""
if "NTO" not in line:
return False
if state.parsed_nto:
return False

Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/tddft/trans_dm.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class TransitionDensityMatrixParser(BlockParser):
END_PAT: ClassVar[re.Pattern] = re.compile(r"^\s*-{5,}\s*$")

def matches(self, line: str, state: ParseState) -> bool:
if "Transition Density" not in line:
return False
if state.parsed_tddft_trans_dm:
return False
return bool(self.START_PAT.search(line))
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/tddft/unrel_dm.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class UnrelaxedDensityMatrixParser(BlockParser):
END_PAT: ClassVar[re.Pattern] = re.compile(r"^\s*-{5,}\s*$")

def matches(self, line: str, state: ParseState) -> bool:
if "Unrelaxed" not in line:
return False
if state.parsed_tddft_unrelaxed_dm:
return False
return bool(self.START_PAT.search(line))
Expand Down
2 changes: 2 additions & 0 deletions src/calcflow/io/qchem/blocks/timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class TimingParser:

def matches(self, line: str, state: ParseState) -> bool:
"""Matches on the total job time line."""
if "Total job time" not in line:
return False
if state.parsed_timing:
return False
return bool(TOTAL_TIME_PAT.search(line))
Expand Down
Loading