Skip to content

Commit

Permalink
Merge pull request #153 from ReactionMechanismGenerator/shared_libraries
Browse files Browse the repository at this point in the history
Allow users to use a shared RMG library between different T3 projects
  • Loading branch information
alongd committed Jul 10, 2024
2 parents aef0265 + c43622e commit 6176491
Show file tree
Hide file tree
Showing 8 changed files with 479 additions and 293 deletions.
5 changes: 3 additions & 2 deletions examples/commented/input.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ t3:
max_RMG_exceptions_allowed: 10 # optional, maximum number of times RMG is allowed to crash, default: 10
max_RMG_walltime: '00:02:00:00' # optional, default: ``None``
max_T3_walltime: '01:00:00:00' # optional, default: ``None``
library_name: T3 # optional, default: 'T3'
save_libraries_directly_in_rmgdb: False # optional, whether to save the RMG kinetics and thermo libraries directly in the RMG-database repository, default: ``False``
library_name: T3 # optional, this is the name of the RMG libraries T3 creates as saves as output. default: 'T3'
shared_library_name: T3_shared # optional, this is the name of RMG libraries (kinetics and thermo) created inside the respective RMG database paths that several T3 concurrent projects may share. default: ``None``
external_library_path: None # optional, path to an external RMG library to use for saving shared libraries, default: ``None`` (i.e., use the RMG database path)
num_sa_per_temperature_range: 3 # optional, if a range of temperatures is given this argument specifies how many equally distributed points to generate for local SA runs, default: 3
num_sa_per_pressure_range: 3 # optional, if a range of pressures is given this argument specifies how many equally distributed points to generate for local SA runs, default: 3
num_sa_per_volume_range: 3 # optional, if a range of volumes is given this argument specifies how many equally distributed points to generate for local SA runs, default: 3
Expand Down
194 changes: 53 additions & 141 deletions t3/main.py

Large diffs are not rendered by default.

33 changes: 32 additions & 1 deletion t3/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from pydantic import BaseModel, conint, confloat, constr, root_validator, validator

from t3.common import VALID_CHARS
from t3.simulate.factory import _registered_simulate_adapters


Expand Down Expand Up @@ -43,7 +44,8 @@ class T3Options(BaseModel):
max_rmg_processes: Optional[conint(ge=1)] = None
max_rmg_iterations: Optional[conint(ge=1)] = None
library_name: constr(max_length=255) = 'T3lib'
save_libraries_directly_in_rmgdb: bool = False
shared_library_name: Optional[constr(max_length=255)] = None
external_library_path: Optional[constr(max_length=255)] = None
num_sa_per_temperature_range: conint(ge=1) = 3
num_sa_per_pressure_range: conint(ge=1) = 3
num_sa_per_volume_range: conint(ge=1) = 3
Expand All @@ -63,6 +65,35 @@ def check_collision_violators_rates(cls, value, values):
values['collision_violators_thermo'] = True
return value

@validator('library_name')
def check_library_name(cls, value):
"""T3Options.library_name validator"""
for char in value:
if char not in VALID_CHARS:
raise ValueError(f'The library name "{value}" contains an invalid character: {char}.\n'
f'Only the following characters are allowed:\n{VALID_CHARS}')
return value

@validator('shared_library_name')
def check_shared_library_name(cls, value):
"""T3Options.shared_library_name validator"""
if value is not None:
for char in value:
if char not in VALID_CHARS + '/':
raise ValueError(f'The shared library name "{value}" contains an invalid character: {char}.\n'
f'Only the following characters are allowed:\n{VALID_CHARS}')
return value

@validator('external_library_path')
def check_external_library_path(cls, value):
"""T3Options.external_library_path validator"""
if value is not None:
for char in value:
if char not in VALID_CHARS + '/':
raise ValueError(f'The external library path "{value}" contains an invalid character: {char}.\n'
f'Only the following characters are allowed:\n{VALID_CHARS}')
return value


class T3Sensitivity(BaseModel):
"""
Expand Down
9 changes: 5 additions & 4 deletions t3/simulate/rmg_constantTP.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
import itertools
import os
import pandas as pd
import re
import shutil
from typing import List, Optional
from typing import List, Optional, TYPE_CHECKING

from rmgpy.kinetics.diffusionLimited import diffusion_limiter
from rmgpy.rmg.listener import SimulationProfilePlotter, SimulationProfileWriter
Expand All @@ -21,11 +20,13 @@

from t3.common import get_chem_to_rmg_rxn_index_map, get_species_by_label, get_values_within_range, \
get_observable_label_from_header, get_parameter_from_header, time_lapse
from t3.logger import Logger
from t3.simulate.adapter import SimulateAdapter
from t3.simulate.factory import register_simulate_adapter
from t3.utils.writer import write_rmg_input_file

if TYPE_CHECKING:
from t3.logger import Logger


class RMGConstantTP(SimulateAdapter):
"""
Expand Down Expand Up @@ -63,7 +64,7 @@ def __init__(self,
t3: dict,
rmg: dict,
paths: dict,
logger: Logger,
logger: 'Logger',
atol: float = 1e-16,
rtol: float = 1e-8,
observable_list: Optional[list] = None,
Expand Down
189 changes: 189 additions & 0 deletions t3/utils/libraries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
"""
t3 utils libraries module
for working with RMG thermo and kinetics libraries
"""

import datetime
import os
import shutil
import time

from typing import Dict, TYPE_CHECKING

from rmgpy.data.kinetics import KineticsLibrary
from rmgpy.data.thermo import ThermoLibrary
from rmgpy.kinetics import Arrhenius, KineticsData
from rmgpy.reaction import Reaction
from rmgpy.thermo import NASAPolynomial, NASA, ThermoData, Wilhoit
from rmgpy.species import Species

if TYPE_CHECKING:
from t3.logger import Logger


def add_to_rmg_libraries(library_name: str,
shared_library_name: str,
paths: Dict[str, str],
logger: 'Logger',
):
"""
Creates RMG libraries in the RMG database repository if they don't already exist,
and appends with the respective entries from the libraries generated by ARC.
Args:
library_name (str): The name of the RMG library.
shared_library_name (str): The name of an RMG database library shared between T3 projects.
paths (Dict[str, str]): T3's dictionary of paths.
logger (Logger): Instance of T3's Logger class.
"""
for token in ['thermo', 'kinetics']:
arc_lib_path, t3_lib_path, shared_lib_path = \
paths[f'ARC {token} lib'], paths[f'T3 {token} lib'], paths[f'shared T3 {token} lib']
if token == 'thermo':
local_context = {
'ThermoData': ThermoData,
'Wilhoit': Wilhoit,
'NASAPolynomial': NASAPolynomial,
'NASA': NASA,
}
else:
local_context = {
'KineticsData': KineticsData,
'Arrhenius': Arrhenius,
}
for to_lib_path, lib_name, race in zip([shared_lib_path, t3_lib_path],
[library_name, shared_library_name],
[True, False]):
if os.path.isfile(arc_lib_path) and to_lib_path is not None and os.path.isfile(to_lib_path):
append_to_rmg_library(library_name=lib_name,
from_lib_path=arc_lib_path,
to_lib_path=to_lib_path,
local_context=local_context,
lib_type=token,
logger=logger,
race=race,
)
else:
# The destination library (T3's or the shared) doesn't exist. Just copy the library generated by ARC.
if os.path.isfile(arc_lib_path) and to_lib_path is not None:
if not os.path.isdir(os.path.dirname(to_lib_path)):
os.makedirs(os.path.dirname(to_lib_path))
shutil.copy(arc_lib_path, to_lib_path)


def append_to_rmg_library(library_name: str,
from_lib_path: str,
to_lib_path: str,
local_context: dict,
lib_type: str,
logger: 'Logger',
race: bool = False,
):
"""
Append the entries from the ARC-generated library to an RMG library.
Args:
library_name (str): The name of the RMG library.
from_lib_path (str): The path to the ARC-generated library.
to_lib_path (str): The path to the RMG library to append to.
local_context (dict): The local context to use when loading the libraries.
lib_type (str): The type of the library (either 'thermo' or 'kinetics').
logger (Logger): Instance of T3's Logger class.
race (bool, optional): Whether to take measures to avoid a race condition when appending to the library.
"""
race_path = os.path.join(os.path.dirname(to_lib_path), f'{library_name}.race')
if race:
race_free = check_race_condition(race_path)
if not race_free:
logger.error(f'Could not write to library {to_lib_path} due to a race condition.\n'
f'Check whether it is safe to delete the {race_path} file to continue.')
return
from_lib, to_lib = (ThermoLibrary(), ThermoLibrary()) if lib_type == 'thermo' \
else (KineticsLibrary(), KineticsLibrary())
to_lib.load(path=to_lib_path, local_context=local_context, global_context=dict())
from_lib.load(path=from_lib_path, local_context=local_context, global_context=dict())
from_description = from_lib.long_desc
description_to_append = '\n'
append = False
for line in from_description.splitlines():
if 'Overall time since project initiation' in line:
append = False
if append:
description_to_append += line + '\n'
if 'Considered the following' in line:
append = True
to_lib.long_desc += description_to_append
for entry in from_lib.entries.values():
skip_entry = False
if lib_type == 'thermo':
entry_species = Species(molecule=[entry.item])
entry_species.generate_resonance_structures(keep_isomorphic=False, filter_structures=True)
for existing_entry in to_lib.entries.values():
if entry_species.is_isomorphic(existing_entry.item):
if entry.label != existing_entry.label:
logger.warning(f"Not adding species {entry.label} to the {library_name} thermo library, "
f"the species seems to already exist under the label {existing_entry.label}.")
skip_entry = True
break
elif lib_type == 'kinetics':
entry_reaction = Reaction(reactants=entry.item.reactants[:],
products=entry.item.products[:],
specific_collider=entry.item.specific_collider,
kinetics=entry.data,
duplicate=entry.item.duplicate,
reversible=entry.item.reversible,
allow_pdep_route=entry.item.allow_pdep_route,
elementary_high_p=entry.item.elementary_high_p,
)
for existing_entry in to_lib.entries.values():
if entry_reaction.is_isomorphic(existing_entry.item):
logger.warning(f"Not adding reaction {entry.label} to the {library_name} kinetics library, "
f"the reaction seems to already exist under the label {existing_entry.label}.")
skip_entry = True
break
if not skip_entry:
to_lib.entries[entry.label] = entry
to_lib.save(path=to_lib_path)
lift_race_condition(race_path)


def check_race_condition(race_path: str,
) -> bool:
"""
Check for a race condition and avoid one by creating a race holder file.
Args:
race_path (str): The path to the race file to check.
Returns:
bool: Whether there is no race condition and T3 may continue (True) or an unavoidable race exists (False).
"""
counter = 0
while os.path.isfile(race_path):
with open(race_path, 'r') as f:
content = f.read()
if content:
creation_date = content.split(' ')[-1]
creation_datetime = datetime.datetime.strptime(creation_date, "%H%M%S_%b%d_%Y")
time_delta = datetime.datetime.now() - creation_datetime
if time_delta.total_seconds() > 1000:
lift_race_condition(race_path)
return True
counter += 1
time.sleep(10)
if counter > 1000:
return False
with open(race_path, 'w') as f:
f.write(f'Race created at {datetime.datetime.now().strftime("%H%M%S_%b%d_%Y")}')
return True


def lift_race_condition(race_path: str) -> None:
"""
Lift the race condition by deleting the race holder file.
Args:
race_path (str): The path to the race file to check.
"""
if os.path.isfile(race_path):
os.remove(race_path)
Loading

0 comments on commit 6176491

Please sign in to comment.