@@ -139,6 +139,9 @@ def __init__(
139139 replica_exchange = False ,
140140 randomise_velocities = False ,
141141 perturbed_system = None ,
142+ terminal_flip_frequency = None ,
143+ terminal_flip_angle = None ,
144+ terminal_flip_max_mobile_atoms = None ,
142145 gcmc = False ,
143146 gcmc_frequency = None ,
144147 gcmc_selection = None ,
@@ -370,13 +373,30 @@ def __init__(
370373 GPU resources are available.
371374
372375 randomise_velocities: bool
373- Whether to randomise velocities at the start of each replica exchange cycle.
376+ Whether to randomise velocities at the start of each replica exchange cycle
377+ or following a terminal flip Monte Carlo move.
374378
375379 perturbed_system: str
376380 The path to a stream file containing a Sire system for the equilibrated perturbed
377381 end state (lambda = 1). This will be used as the starting conformation all lambda
378382 windows > 0.5 when performing a replica exchange simulation.
379383
384+ terminal_flip_frequency: str
385+ Frequency at which to attempt terminal ring flip Monte Carlo moves. If None
386+ (the default), no terminal flip moves will be performed. When set, terminal
387+ ring groups in perturbable molecules are detected automatically using Sire's
388+ native connectivity. This must be a multiple of 'energy_frequency'.
389+
390+ terminal_flip_angle: str
391+ Override the flip angle used for all terminal ring groups, e.g.
392+ ``"180 degrees"``. If None (the default), the angle is determined
393+ automatically for each group from its geometry.
394+
395+ terminal_flip_max_mobile_atoms: int or None
396+ Maximum number of mobile atoms allowed in a terminal ring group.
397+ Groups with more mobile atoms than this threshold are skipped during
398+ detection. Defaults to None (no limit).
399+
380400 gcmc: bool
381401 Whether to perform Grand Canonical Monte Carlo (GCMC) water insertions/deletions.
382402
@@ -559,6 +579,9 @@ def __init__(
559579 self .replica_exchange = replica_exchange
560580 self .randomise_velocities = randomise_velocities
561581 self .perturbed_system = perturbed_system
582+ self .terminal_flip_frequency = terminal_flip_frequency
583+ self .terminal_flip_angle = terminal_flip_angle
584+ self .terminal_flip_max_mobile_atoms = terminal_flip_max_mobile_atoms
562585 self .gcmc = gcmc
563586 self .gcmc_frequency = gcmc_frequency
564587 self .gcmc_selection = gcmc_selection
@@ -1994,6 +2017,80 @@ def perturbed_system(self, perturbed_system):
19942017 self ._perturbed_system = None
19952018 self ._perturbed_system_file = None
19962019
2020+ @property
2021+ def terminal_flip_frequency (self ):
2022+ return self ._terminal_flip_frequency
2023+
2024+ @terminal_flip_frequency .setter
2025+ def terminal_flip_frequency (self , terminal_flip_frequency ):
2026+ if terminal_flip_frequency is not None :
2027+ if not isinstance (terminal_flip_frequency , str ):
2028+ raise TypeError ("'terminal_flip_frequency' must be of type 'str'" )
2029+
2030+ from sire .units import picosecond
2031+
2032+ try :
2033+ t = _sr .u (terminal_flip_frequency )
2034+ except Exception :
2035+ raise ValueError (
2036+ f"Unable to parse 'terminal_flip_frequency' as a Sire GeneralUnit: "
2037+ f"{ terminal_flip_frequency } "
2038+ )
2039+
2040+ if t .value () != 0 and not t .has_same_units (picosecond ):
2041+ raise ValueError ("'terminal_flip_frequency' units are invalid." )
2042+
2043+ self ._terminal_flip_frequency = t
2044+ else :
2045+ self ._terminal_flip_frequency = None
2046+
2047+ @property
2048+ def terminal_flip_angle (self ):
2049+ return self ._terminal_flip_angle
2050+
2051+ @terminal_flip_angle .setter
2052+ def terminal_flip_angle (self , terminal_flip_angle ):
2053+ if terminal_flip_angle is not None :
2054+ if not isinstance (terminal_flip_angle , str ):
2055+ raise TypeError ("'terminal_flip_angle' must be of type 'str'" )
2056+
2057+ from sire .units import degrees
2058+
2059+ try :
2060+ a = _sr .u (terminal_flip_angle )
2061+ except Exception :
2062+ raise ValueError (
2063+ f"Unable to parse 'terminal_flip_angle' as a Sire GeneralUnit: "
2064+ f"{ terminal_flip_angle } "
2065+ )
2066+
2067+ if not a .has_same_units (degrees ):
2068+ raise ValueError ("'terminal_flip_angle' units are invalid." )
2069+
2070+ self ._terminal_flip_angle = a
2071+ else :
2072+ self ._terminal_flip_angle = None
2073+
2074+ @property
2075+ def terminal_flip_max_mobile_atoms (self ):
2076+ return self ._terminal_flip_max_mobile_atoms
2077+
2078+ @terminal_flip_max_mobile_atoms .setter
2079+ def terminal_flip_max_mobile_atoms (self , terminal_flip_max_mobile_atoms ):
2080+ if terminal_flip_max_mobile_atoms is not None :
2081+ if not isinstance (terminal_flip_max_mobile_atoms , int ):
2082+ try :
2083+ terminal_flip_max_mobile_atoms = int (terminal_flip_max_mobile_atoms )
2084+ except :
2085+ raise ValueError (
2086+ "'terminal_flip_max_mobile_atoms' must be of type 'int'"
2087+ )
2088+ if terminal_flip_max_mobile_atoms < 1 :
2089+ raise ValueError (
2090+ "'terminal_flip_max_mobile_atoms' must be greater than 0"
2091+ )
2092+ self ._terminal_flip_max_mobile_atoms = terminal_flip_max_mobile_atoms
2093+
19972094 @property
19982095 def gcmc (self ):
19992096 return self ._gcmc
0 commit comments