import logging
import os
from chemsmart.jobs.gaussian.settings import (
GaussianIRCJobSettings,
GaussianJobSettings,
GaussianQMMMJobSettings,
GaussianTDDFTJobSettings,
)
from chemsmart.settings.user import ChemsmartUserSettings
from chemsmart.utils.mixins import RegistryMixin
user_settings = ChemsmartUserSettings()
logger = logging.getLogger(__name__)
project_settings_registry: list[str] = []
# Type annotation required for Python's type checker (e.g., mypy)
# which has stricter requirements when working in contexts where type inference
# is insufficient or ambiguous.
# This is not due to Python 3.9 itself,
# but type checking rules enforced by tools
# like mypy or stricter typing practices.
[docs]
class GaussianProjectSettings(RegistryMixin):
"""
Base class for Gaussian project settings with default configurations.
This class provides a framework for managing Gaussian calculation settings
across different job types. It defines default settings for common quantum
chemistry calculations including geometry optimization, transition state
searches, and various analysis methods.
Attributes:
PROJECT_NAME (str): Identifier for the project configuration.
functional (str, optional): DFT functional to use for calculations.
small_basis (str, optional): Basis set for initial/fast calculations.
large_basis (str, optional): Basis set for high-accuracy calculations.
"""
PROJECT_NAME = "general"
functional = None
small_basis = None
large_basis = None
[docs]
def main_settings(self):
"""
Get main Gaussian settings with key default values.
Returns:
GaussianJobSettings: Default job
settings with functional and basis set.
"""
default_gaussian_job_settings = GaussianJobSettings.default()
default_gaussian_job_settings.functional = self.functional
default_gaussian_job_settings.basis = self.small_basis
return default_gaussian_job_settings
[docs]
def opt_settings(self):
"""
Get default settings for geometry optimization jobs.
Returns:
GaussianJobSettings: Settings
configured for structure optimization.
"""
settings = self.main_settings().copy()
settings.jobtype = "opt"
return settings
[docs]
def modred_settings(self):
"""
Get default settings for modified redundant coordinate optimization.
Returns:
GaussianJobSettings: Settings for constrained optimization jobs.
"""
settings = self.main_settings().copy()
settings.jobtype = "modred"
return settings
[docs]
def ts_settings(self):
"""
Get default settings for transition state optimization jobs.
Returns:
GaussianJobSettings: Settings for transition state searches.
"""
settings = self.main_settings().copy()
settings.jobtype = "ts"
return settings
[docs]
def irc_settings(self):
"""
Get default settings for Intrinsic Reaction Coordinate calculations.
Returns:
GaussianIRCJobSettings: IRC-specific
settings with frequency disabled.
"""
settings = self.main_settings().copy()
# Convert to IRC-specific settings class
settings = GaussianIRCJobSettings(**settings.__dict__)
settings.jobtype = "irc"
settings.freq = False
return settings
[docs]
def scan_settings(self):
"""
Get default settings for potential energy surface scan calculations.
Returns:
GaussianJobSettings: Settings for
coordinate scanning with frequency disabled.
"""
settings = self.main_settings().copy()
settings.jobtype = "scan"
settings.freq = False
return settings
[docs]
def nci_settings(self):
"""
Get default settings for Non-Covalent Interaction analysis.
Returns:
GaussianJobSettings: Settings for
NCI analysis with frequency disabled.
"""
settings = self.main_settings().copy()
settings.jobtype = "nci"
settings.freq = False
return settings
[docs]
def wbi_settings(self):
"""
Get default settings for Wiberg Bond Index calculations.
Returns:
GaussianJobSettings: Settings for
WBI analysis with frequency disabled.
"""
settings = self.main_settings().copy()
settings.jobtype = "wbi"
settings.freq = False
return settings
[docs]
def sp_settings(self):
"""
Get default settings for single point energy calculations.
Uses the large basis set for higher accuracy and disables frequency
calculations for computational efficiency.
Returns:
GaussianJobSettings: Settings for single point calculations.
"""
settings = self.main_settings().copy()
settings.jobtype = "sp"
settings.freq = False # Disable frequency calculation for efficiency
settings.basis = self.large_basis # Use high-accuracy basis set
return settings
[docs]
def qmmm_settings(self):
"""Gaussian default settings for qmmm job."""
settings = self.main_settings().copy()
settings = GaussianQMMMJobSettings(**settings.__dict__)
settings.jobtype = "qmmm"
settings.freq = False
return settings
[docs]
@classmethod
def from_project(cls, project):
"""
Create project settings instance based on project name.
Searches for project configuration in the following order:
1. User-defined project settings directory
2. Chemsmart test project configurations
Args:
project (str): Name of the project configuration to load.
Returns:
GaussianProjectSettings: Configured settings instance.
Raises:
FileNotFoundError: If no configuration
is found for the specified project.
"""
# First try user-defined project settings
user_project_settings = cls._from_user_project_name(project)
if user_project_settings is not None:
return user_project_settings
else:
# Fall back to chemsmart test project settings
chemsmart_test_project_settings = (
cls._from_chemsmart_test_projects(project)
)
if chemsmart_test_project_settings is not None:
return chemsmart_test_project_settings
# Generate helpful error message with available options
templates_path = os.path.join(os.path.dirname(__file__), "templates")
raise FileNotFoundError(
f"No project settings implemented for {project}.\n\n"
f"Place new gaussian project settings .yaml file in {user_settings.user_gaussian_settings_dir}.\n\n"
f"Templates for such settings.yaml files are available at {templates_path}\n\n "
f"Currently available projects: {user_settings.all_available_gaussian_projects}"
)
@classmethod
def _from_projects_manager(cls, manager):
"""
Create settings from a project manager instance.
Args:
manager: Project settings manager instance.
Returns:
YamlGaussianProjectSettings or None: YAML-based
settings instance if successful, None if failed.
"""
try:
return manager.create()
except FileNotFoundError:
return None
@classmethod
def _from_user_project_name(cls, project_name):
"""
Load project settings from user directory based on project name.
Args:
project_name (str): Name of the project configuration file.
Returns:
YamlGaussianProjectSettings or None: YAML-based
settings instance if found, None otherwise.
"""
project_name_yaml_path = os.path.join(
ChemsmartUserSettings().user_gaussian_settings_dir,
f"{project_name}.yaml",
)
user_settings_manager = GaussianProjectSettingsManager(
filename=project_name_yaml_path
)
settings = cls._from_projects_manager(user_settings_manager)
if settings is not None:
return settings
@classmethod
def _from_chemsmart_test_projects(cls, project_name):
"""
Load project settings from chemsmart test projects directory.
Args:
project_name (str): Name of the test project configuration.
Returns:
YamlGaussianProjectSettings or None: YAML-based
settings instance if found, None otherwise.
"""
current_file_dir = os.path.dirname(os.path.abspath(__file__))
test_projects_dir = os.path.join(
current_file_dir, "../../tests/data/GaussianTests/project_yaml"
)
project_name_yaml_path = os.path.join(
test_projects_dir, f"{project_name}.yaml"
)
project_settings_manager = GaussianProjectSettingsManager(
filename=project_name_yaml_path
)
settings = cls._from_projects_manager(project_settings_manager)
if settings is not None:
return settings
[docs]
class YamlGaussianProjectSettings(GaussianProjectSettings):
"""
YAML-based implementation of Gaussian project settings.
This class loads and manages Gaussian calculation settings from YAML
configuration files. It provides specific settings for each job type
as defined in the YAML configuration.
Attributes:
PROJECT_NAME (str): Set to "yaml" to identify YAML-based settings.
"""
PROJECT_NAME = "yaml"
def __init__(
self,
opt_settings,
modred_settings,
ts_settings,
irc_settings,
scan_settings,
nci_settings,
sp_settings,
td_settings,
wbi_settings,
qmmm_settings,
):
"""
Initialize YAML-based project settings.
Args:
opt_settings: Settings for optimization jobs.
modred_settings: Settings for modified redundant coordinate jobs.
ts_settings: Settings for transition state jobs.
irc_settings: Settings for IRC calculations.
scan_settings: Settings for coordinate scanning jobs.
nci_settings: Settings for NCI analysis jobs.
sp_settings: Settings for single point calculations.
td_settings: Settings for TD-DFT calculations.
wbi_settings: Settings for Wiberg bond index calculations.
"""
self._opt_settings = opt_settings
self._modred_settings = modred_settings
self._ts_settings = ts_settings
self._irc_settings = irc_settings
self._scan_settings = scan_settings
self._nci_settings = nci_settings
self._sp_settings = sp_settings
self._td_settings = td_settings
self._wbi_settings = wbi_settings
self._qmmm_settings = qmmm_settings
[docs]
def opt_settings(self):
return self._opt_settings
[docs]
def modred_settings(self):
return self._modred_settings
[docs]
def ts_settings(self):
return self._ts_settings
[docs]
def irc_settings(self):
return self._irc_settings
[docs]
def scan_settings(self):
return self._scan_settings
[docs]
def nci_settings(self):
return self._nci_settings
[docs]
def sp_settings(self):
return self._sp_settings
[docs]
def td_settings(self):
return self._td_settings
[docs]
def wbi_settings(self):
return self._wbi_settings
[docs]
def qmmm_settings(self):
return self._qmmm_settings
[docs]
@classmethod
def from_yaml(cls, filename):
builder = YamlGaussianProjectSettingsBuilder(filename=filename)
return builder.build()
[docs]
class YamlGaussianProjectSettingsBuilder:
"""
Builder class for constructing YAML-based Gaussian project settings.
This class reads YAML configuration files and builds appropriate
GaussianJobSettings instances for each job type. It handles the
mapping between YAML configuration and specific settings classes.
Attributes:
filename (str): Path to the YAML configuration file.
"""
def __init__(self, filename):
"""
Initialize the builder with a YAML configuration file.
Args:
filename (str): Path to the YAML configuration file.
"""
self.filename = filename
[docs]
def build(self):
"""
Build a complete YamlGaussianProjectSettings instance.
Reads the YAML configuration and creates settings for all supported
job types including optimization, transition states, IRC, scans, etc.
Returns:
YamlGaussianProjectSettings: Fully configured project settings.
"""
# Build settings for each supported job type
opt_settings = self._project_settings_for_job(jobtype="opt")
modred_settings = self._project_settings_for_job(jobtype="modred")
ts_settings = self._project_settings_for_job(jobtype="ts")
irc_settings = self._project_settings_for_job(jobtype="irc")
scan_settings = self._project_settings_for_job(jobtype="scan")
nci_settings = self._project_settings_for_job(jobtype="nci")
sp_settings = self._project_settings_for_job(jobtype="sp")
td_settings = self._project_settings_for_job(jobtype="td")
wbi_settings = self._project_settings_for_job(jobtype="wbi")
qmmm_settings = self._project_settings_for_job(jobtype="qmmm")
# Create the project settings instance
project_settings = YamlGaussianProjectSettings(
opt_settings=opt_settings,
modred_settings=modred_settings,
ts_settings=ts_settings,
irc_settings=irc_settings,
scan_settings=scan_settings,
nci_settings=nci_settings,
sp_settings=sp_settings,
td_settings=td_settings,
wbi_settings=wbi_settings,
qmmm_settings=qmmm_settings,
)
# Set project name from filename and return
name = self._parse_project_name()
project_settings.PROJECT_NAME = name
return project_settings
def _read_config(self):
"""
Read and parse the YAML configuration file.
Returns:
dict: Parsed YAML configuration data.
"""
from chemsmart.jobs.settings import read_molecular_job_yaml
return read_molecular_job_yaml(self.filename, program="gaussian")
def _project_settings_for_job(self, jobtype):
"""
Create job-specific settings from YAML configuration.
Maps job types to appropriate settings classes and creates
configured instances based on YAML data.
Args:
jobtype (str): Type of job (opt, ts, irc, td, etc.).
Returns:
GaussianJobSettings: Configured
settings for the specified job type.
Raises:
RuntimeError: If configuration for the job type is not found.
"""
# Map job types to their specific settings classes
settings_mapping = {
"irc": GaussianIRCJobSettings,
"td": GaussianTDDFTJobSettings,
"qmmm": GaussianQMMMJobSettings,
}
try:
jobtype_config = self._read_config().get(jobtype)
if jobtype_config is not None:
# Use specific settings class if available, otherwise default
return settings_mapping.get(
jobtype, GaussianJobSettings
).from_dict(jobtype_config)
except KeyError as e:
available_jobs = list(self._read_config().keys())
raise RuntimeError(
f"Gaussian settings for job {jobtype} cannot be found!\n"
f"Available Gaussian jobs with settings are: {available_jobs}"
) from e
def _parse_project_name(self):
"""
Extract project name from the YAML filename.
Returns:
str: Project name derived from filename (without extension).
"""
return os.path.basename(self.filename).split(".")[0]
[docs]
class GaussianProjectSettingsManager:
"""
Manager for Gaussian project settings from YAML configuration files.
Provides management interface for loading Gaussian computational chemistry
project settings from YAML files in a specified folder structure. Handles
file validation and project settings creation.
Attributes:
filename (str): Absolute path to the YAML configuration file.
"""
def __init__(self, filename):
"""
Initialize the Gaussian project settings manager.
Args:
filename (str): Path to YAML configuration file containing
Gaussian project settings.
Raises:
ValueError: If filename is None or not specified.
"""
if filename is None:
raise ValueError("filename is not specified")
self.filename = os.path.abspath(filename)
[docs]
def create(self):
"""
Create project settings from the specified YAML file.
Loads and parses the YAML configuration file to create a complete
Gaussian project settings instance with all job configurations.
Returns:
YamlGaussianProjectSettings: Configured project settings loaded
from the YAML file.
Raises:
FileNotFoundError: If the specified YAML file does not exist.
ValueError: If the YAML file is malformed or invalid.
"""
return YamlGaussianProjectSettings.from_yaml(self.filename)