Skip to content
Open
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
89 changes: 89 additions & 0 deletions gamd/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,95 @@ def compute_total_simulation_length(self):
self.total_simulation_length = self.conventional_md \
+ self.gamd_equilibration + self.gamd_production

def validate_step_configuration(self):
"""
Validate the step configuration for common errors.

This method performs the same validation as the integrator but at the
configuration level, allowing for earlier error detection.

Raises
------
ValueError
If any validation check fails
"""
errors = []

# Check that cumulative values are greater than prep values
if self.conventional_md <= self.conventional_md_prep:
errors.append(
f"<conventional-md> ({self.conventional_md}) must be greater than "
f"<conventional-md-prep> ({self.conventional_md_prep}). "
f"The conventional-md value should be cumulative (prep + actual conventional MD steps)."
)

if self.gamd_equilibration <= self.gamd_equilibration_prep:
errors.append(
f"<gamd-equilibration> ({self.gamd_equilibration}) must be greater than "
f"<gamd-equilibration-prep> ({self.gamd_equilibration_prep}). "
f"The gamd-equilibration value should be cumulative (prep + actual GaMD equilibration steps)."
)

# Check divisibility requirements
if self.averaging_window_interval > 0:
if self.conventional_md % self.averaging_window_interval != 0:
errors.append(
f"<conventional-md> ({self.conventional_md}) must be divisible by "
f"<averaging-window-interval> ({self.averaging_window_interval}). "
f"Current remainder: {self.conventional_md % self.averaging_window_interval}"
)

if self.gamd_equilibration % self.averaging_window_interval != 0:
errors.append(
f"<gamd-equilibration> ({self.gamd_equilibration}) must be divisible by "
f"<averaging-window-interval> ({self.averaging_window_interval}). "
f"Current remainder: {self.gamd_equilibration % self.averaging_window_interval}"
)

# Check minimum values
if self.conventional_md_prep <= 0:
errors.append("<conventional-md-prep> must be greater than 0")

if self.gamd_equilibration_prep <= 0:
errors.append("<gamd-equilibration-prep> must be greater than 0")

if self.gamd_production <= 0:
errors.append("<gamd-production> must be greater than 0")

if self.averaging_window_interval <= 0:
errors.append("<averaging-window-interval> must be greater than 0")

# Check minimum values for averaging
if self.conventional_md < self.averaging_window_interval:
errors.append(
f"<conventional-md> ({self.conventional_md}) must be at least as large as "
f"<averaging-window-interval> ({self.averaging_window_interval})"
)

if self.gamd_equilibration < self.averaging_window_interval:
errors.append(
f"<gamd-equilibration> ({self.gamd_equilibration}) must be at least as large as "
f"<averaging-window-interval> ({self.averaging_window_interval})"
)

# If there are errors, raise a comprehensive error message
if errors:
error_msg = (
"Invalid step configuration in XML file:\n\n" +
"\n".join(f" • {error}" for error in errors) +
"\n\nRemember:\n" +
" • <conventional-md> should be CUMULATIVE (prep + actual conventional MD steps)\n" +
" • <gamd-equilibration> should be CUMULATIVE (prep + actual GaMD equilibration steps)\n" +
" • Both <conventional-md> and <gamd-equilibration> must be divisible by <averaging-window-interval>\n" +
" • All prep values are individual stage lengths, not cumulative\n\n" +
"Example valid configuration:\n" +
" <conventional-md-prep>200000</conventional-md-prep>\n" +
" <conventional-md>400000</conventional-md> <!-- 200k prep + 200k actual -->\n" +
" <gamd-equilibration-prep>200000</gamd-equilibration-prep>\n" +
" <gamd-equilibration>400000</gamd-equilibration> <!-- 200k prep + 200k actual -->"
)
raise ValueError(error_msg)


class IntegratorConfig:
def __init__(self):
Expand Down
2 changes: 2 additions & 0 deletions gamd/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ def parse_file(self, filename):
"Spelling error?", tag.tag)

self.config.integrator.number_of_steps.compute_total_simulation_length()
# Validate the step configuration after parsing
self.config.integrator.number_of_steps.validate_step_configuration()
return


Expand Down
125 changes: 125 additions & 0 deletions gamd/stage_integrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ def __init__(self, group_dict, boost_type, boost_method,
"""
CustomIntegrator.__init__(self, dt)

# Validate stage step configuration
self._validate_stage_configuration(ntcmdprep, ntcmd, ntebprep, nteb, nstlim, ntave)

if ntcmd < ntave or ntcmd % ntave != 0:
raise ValueError(
"ntcmd must be greater than and a multiple of ntave.")
Expand Down Expand Up @@ -314,6 +317,128 @@ def get_debug_step(self, counter):

return results

def _validate_stage_configuration(self, ntcmdprep, ntcmd, ntebprep, nteb, nstlim, ntave):
"""
Validate the GaMD stage configuration parameters to ensure proper stage boundaries.

This method checks for common configuration errors that would prevent the simulation
from transitioning between stages correctly.

Parameters
----------
ntcmdprep : int
Number of conventional MD preparatory steps (individual stage length)
ntcmd : int
Total number of conventional MD steps (cumulative, including prep)
ntebprep : int
Number of GaMD pre-equilibration steps (individual stage length)
nteb : int
Total number of GaMD equilibration steps (cumulative, including prep)
nstlim : int
Total number of simulation steps
ntave : int
Averaging window interval

Raises
------
ValueError
If any validation check fails, with a detailed error message
"""

errors = []

# Check that cumulative values are greater than prep values
if ntcmd <= ntcmdprep:
errors.append(
f"conventional-md ({ntcmd}) must be greater than conventional-md-prep ({ntcmdprep}). "
f"The conventional-md value should be cumulative (prep + actual conventional MD steps)."
)

if nteb <= ntebprep:
errors.append(
f"gamd-equilibration ({nteb}) must be greater than gamd-equilibration-prep ({ntebprep}). "
f"The gamd-equilibration value should be cumulative (prep + actual GaMD equilibration steps)."
)

# Check that all stage boundaries are valid
stage_1_end = ntcmdprep
stage_2_start = ntcmdprep + 1
stage_2_end = ntcmd
stage_3_start = ntcmd + 1
stage_3_end = ntcmd + ntebprep
stage_4_start = ntcmd + ntebprep + 1
stage_4_end = ntcmd + nteb
stage_5_start = ntcmd + nteb + 1
stage_5_end = nstlim

if stage_2_start > stage_2_end:
errors.append(
f"Stage 2 (Conventional MD) has invalid boundaries: start={stage_2_start}, end={stage_2_end}. "
f"This occurs when conventional-md ({ntcmd}) <= conventional-md-prep ({ntcmdprep})."
)

if stage_4_start > stage_4_end:
errors.append(
f"Stage 4 (GaMD Equilibration) has invalid boundaries: start={stage_4_start}, end={stage_4_end}. "
f"This occurs when gamd-equilibration ({nteb}) <= gamd-equilibration-prep ({ntebprep})."
)

if stage_5_start > stage_5_end:
errors.append(
f"Stage 5 (GaMD Production) has invalid boundaries: start={stage_5_start}, end={stage_5_end}. "
f"This occurs when total simulation length ({nstlim}) is too small."
)

# Check divisibility requirements
if ntcmd % ntave != 0:
errors.append(
f"conventional-md ({ntcmd}) must be divisible by averaging-window-interval ({ntave}). "
f"Current remainder: {ntcmd % ntave}"
)

if nteb % ntave != 0:
errors.append(
f"gamd-equilibration ({nteb}) must be divisible by averaging-window-interval ({ntave}). "
f"Current remainder: {nteb % ntave}"
)

# Check minimum values
if ntcmdprep <= 0:
errors.append("conventional-md-prep must be greater than 0")

if ntebprep <= 0:
errors.append("gamd-equilibration-prep must be greater than 0")

if ntcmd < ntave:
errors.append(f"conventional-md ({ntcmd}) must be at least as large as averaging-window-interval ({ntave})")

if nteb < ntave:
errors.append(f"gamd-equilibration ({nteb}) must be at least as large as averaging-window-interval ({ntave})")

# Check that total simulation length is consistent
expected_nstlim = ntcmd + nteb + (nstlim - (ntcmd + nteb)) # This is just nstlim, but let's check it's reasonable
if stage_5_end != nstlim:
errors.append(f"Internal error: stage_5_end ({stage_5_end}) != nstlim ({nstlim})")

# If there are errors, raise a comprehensive error message
if errors:
error_msg = (
"Invalid GaMD stage configuration detected:\n\n" +
"\n".join(f" • {error}" for error in errors) +
"\n\nStage boundaries would be:\n" +
f" Stage 1 (Conv MD Prep): 0 to {stage_1_end}\n" +
f" Stage 2 (Conv MD): {stage_2_start} to {stage_2_end}\n" +
f" Stage 3 (GaMD Eq Prep): {stage_3_start} to {stage_3_end}\n" +
f" Stage 4 (GaMD Eq): {stage_4_start} to {stage_4_end}\n" +
f" Stage 5 (GaMD Prod): {stage_5_start} to {stage_5_end}\n\n" +
"Please check your XML configuration. Remember:\n" +
" • conventional-md should be CUMULATIVE (prep + actual conventional MD steps)\n" +
" • gamd-equilibration should be CUMULATIVE (prep + actual GaMD equilibration steps)\n" +
" • Both conventional-md and gamd-equilibration must be divisible by averaging-window-interval\n" +
" • All prep values are individual stage lengths, not cumulative"
)
raise ValueError(error_msg)

def _add_stage_one_instructions(self):
self.beginIfBlock("stepCount <= " + str(self.stage_1_end))
# -------------------------------
Expand Down