From c2c895691d7736f68658716c5d882efb6c3f9d0b Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 11:43:35 -0400 Subject: [PATCH 01/42] Added examples to gitignore Also removed settings.py which has no effect, since git cannot ignore files it started tracking --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index dcc8ecb492..63178e8511 100644 --- a/.gitignore +++ b/.gitignore @@ -15,12 +15,12 @@ # PyCharm project files .idea/* -# settings file -arc/settings.py - # Projects Projects/ +# Examples +examples/ + # Unit test files .coverage testing/* From cc492477e5fc19acdfd10a54b81ef2f19c1c27e7 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 11:46:49 -0400 Subject: [PATCH 02/42] Allow users to specify the number of cpu's to use per server --- arc/job/inputs.py | 2 +- arc/job/job.py | 15 +++++++++-- arc/job/submit.py | 69 ++++++++++++++++++++++++++++++++++++----------- arc/settings.py | 1 + 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/arc/job/inputs.py b/arc/job/inputs.py index 8e134b2cf6..695c3fb841 100644 --- a/arc/job/inputs.py +++ b/arc/job/inputs.py @@ -36,7 +36,7 @@ input_files = { 'gaussian': """%chk=check.chk %mem={memory}mb -%nproc=8 +%nproc={cpus} #P {job_type_1} {restricted}{method}{slash}{basis} {job_type_2} {fine} {trsh} iop(2/9=2000) diff --git a/arc/job/job.py b/arc/job/job.py index 2b46b4ade9..9ad469eef6 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -353,8 +353,18 @@ def write_submit_script(self): t_max = '{0}:00:00'.format(self.max_job_time) else: raise JobError('Could not determine format for maximal job time') + cpus = servers[self.server]['cpus'] if 'cpus' in servers[self.server] else 8 + architecture = '' + if self.server.lower() == 'pharos': + # here we're hard-coding ARC for Pharos, a Green Group server + # If your server has different node architectures, implement something similar + if cpus <= 8: + architecture = '\n#$ -l harpertown' + else: + architecture = '\n#$ -l magnycours' self.submit = submit_scripts[servers[self.server]['cluster_soft']][self.software.lower()].format( - name=self.job_server_name, un=un, t_max=t_max, mem_cpu=min(int(self.memory * 150), 16000)) + name=self.job_server_name, un=un, t_max=t_max, mem_cpu=min(int(self.memory * 150), 16000), cpus=cpus, + architecture=architecture) # Memory convertion: multiply MW value by 1200 to conservatively get it in MB, then divide by 8 to get per cup if not os.path.exists(self.local_path): os.makedirs(self.local_path) @@ -577,9 +587,10 @@ def write_input_file(self): raise e else: try: + cpus = servers[self.server]['cpus'] if 'cpus' in servers[self.server] else 8 self.input = self.input.format(memory=self.memory, method=self.method, slash=slash, basis=self.basis_set, charge=self.charge, multiplicity=self.multiplicity, - spin=self.spin, xyz=self.xyz, job_type_1=job_type_1, + spin=self.spin, xyz=self.xyz, job_type_1=job_type_1, cpus=cpus, job_type_2=job_type_2, scan=scan_string, restricted=restricted, fine=fine, shift=self.shift, trsh=self.trsh, scan_trsh=self.scan_trsh) except KeyError as e: diff --git a/arc/job/submit.py b/arc/job/submit.py index 431c2080cf..30348cf36b 100644 --- a/arc/job/submit.py +++ b/arc/job/submit.py @@ -12,7 +12,7 @@ #SBATCH -p defq #SBATCH -J {name} #SBATCH -N 1 -#SBATCH -n 8 +#SBATCH -n {cpus} #SBATCH --time={t_max} #SBATCH --mem-per-cpu 4500 @@ -48,6 +48,47 @@ rm -rf $GAUSS_SCRDIR rm -rf $WorkDir +""", + # Gaussian16 on RMG + 'gaussian16': """#!/bin/bash -l +#SBATCH -p long +#SBATCH -J {name} +#SBATCH -N 1 +#SBATCH -n {cpus} +#SBATCH --time={t_max} +#SBATCH --mem-per-cpu={mem_cpu} + +which 16 + +echo "============================================================" +echo "Job ID : $SLURM_JOB_ID" +echo "Job Name : $SLURM_JOB_NAME" +echo "Starting on : $(date)" +echo "Running on node : $SLURMD_NODENAME" +echo "Current directory : $(pwd)" +echo "============================================================" + +WorkDir=/scratch/users/{un}/$SLURM_JOB_NAME-$SLURM_JOB_ID +SubmitDir=`pwd` + +GAUSS_SCRDIR=/scratch/users/{un}/g16/$SLURM_JOB_NAME-$SLURM_JOB_ID +export GAUSS_SCRDIR + +mkdir -p $GAUSS_SCRDIR +mkdir -p $WorkDir + +cd $WorkDir +. $g16root/g16/bsd/g16.profile + +cp $SubmitDir/input.gjf . + +g16 < input.gjf > input.log +formchk check.chk check.fchk +cp * $SubmitDir/ + +rm -rf $GAUSS_SCRDIR +rm -rf $WorkDir + """, # Orca on C3DDB: @@ -55,7 +96,7 @@ #SBATCH -p defq #SBATCH -J {name} #SBATCH -N 1 -#SBATCH -n 8 +#SBATCH -n {cpus} #SBATCH --time={t_max} #SBATCH --mem-per-cpu 4500 @@ -95,7 +136,7 @@ #SBATCH -p long #SBATCH -J {name} #SBATCH -N 1 -#SBATCH -n 8 +#SBATCH -n {cpus} #SBATCH --time={t_max} #SBATCH --mem-per-cpu={mem_cpu} @@ -112,7 +153,7 @@ sdir=/scratch/{un}/$SLURM_JOB_NAME-$SLURM_JOB_ID mkdir -p $sdir -molpro -n 8 -d $sdir input.in +molpro -n {cpus} -d $sdir input.in rm -rf $sdir @@ -125,10 +166,9 @@ 'gaussian': """#!/bin/bash -l #$ -N {name} -#$ -l long -#$ -l harpertown +#$ -l long{architecture} #$ -l h_rt={t_max} -#$ -pe singlenode 6 +#$ -pe singlenode {cpus} #$ -l h=!node60.cluster #$ -cwd #$ -o out.txt @@ -152,10 +192,9 @@ 'gaussian03_pharos': """#!/bin/bash -l #$ -N {name} -#$ -l long -#$ -l harpertown +#$ -l long{architecture} #$ -l h_rt={t_max} -#$ -pe singlenode 6 +#$ -pe singlenode {cpus} #$ -l h=!node60.cluster #$ -cwd #$ -o out.txt @@ -179,10 +218,9 @@ 'qchem': """#!/bin/bash -l #$ -N {name} -#$ -l long -#$ -l harpertown +#$ -l long{architecture} #$ -l h_rt={t_max} -#$ -pe singlenode 6 +#$ -pe singlenode {cpus} #$ -l h=!node60.cluster #$ -cwd #$ -o out.txt @@ -207,10 +245,9 @@ 'molpro': """#! /bin/bash -l #$ -N {name} -#$ -l long -#$ -l harpertown +#$ -l long{architecture} #$ -l h_rt={t_max} -#$ -pe singlenode 6 +#$ -pe singlenode {cpus} #$ -l h=!node60.cluster #$ -cwd #$ -o out.txt diff --git a/arc/settings.py b/arc/settings.py index 1e64ec5151..3c0c0ed962 100644 --- a/arc/settings.py +++ b/arc/settings.py @@ -41,6 +41,7 @@ 'address': 'server2.host.edu', 'un': '', 'key': 'path_to_rsa_key', + 'cpus': 48, # optional (default: 8) } } From 7c7ebbc270fce3b4c2ba795cce400841a4703d72 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:24:50 -0400 Subject: [PATCH 03/42] Minor: changed `except Error as e` if `e` isn't being used --- arc/job/job.py | 8 ++++---- arc/species/species.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arc/job/job.py b/arc/job/job.py index 9ad469eef6..d56a2cc2c3 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -582,9 +582,9 @@ def write_input_file(self): try: self.input = input_files['mrci'].format(memory=self.memory, xyz=self.xyz, basis=self.basis_set, spin=self.spin, charge=self.charge, trsh=self.trsh) - except KeyError as e: + except KeyError: logging.error('Could not interpret all input file keys in\n{0}'.format(self.input)) - raise e + raise else: try: cpus = servers[self.server]['cpus'] if 'cpus' in servers[self.server] else 8 @@ -593,9 +593,9 @@ def write_input_file(self): spin=self.spin, xyz=self.xyz, job_type_1=job_type_1, cpus=cpus, job_type_2=job_type_2, scan=scan_string, restricted=restricted, fine=fine, shift=self.shift, trsh=self.trsh, scan_trsh=self.scan_trsh) - except KeyError as e: + except KeyError: logging.error('Could not interpret all input file keys in\n{0}'.format(self.input)) - raise e + raise if not os.path.exists(self.local_path): os.makedirs(self.local_path) with open(os.path.join(self.local_path, input_filename[self.software]), 'wb') as f: diff --git a/arc/species/species.py b/arc/species/species.py index c9d909891e..af998bd428 100644 --- a/arc/species/species.py +++ b/arc/species/species.py @@ -435,9 +435,9 @@ def from_yml_file(self, label=None): if arkane_spc.adjacency_list is not None: try: self.mol = Molecule().fromAdjacencyList(adjlist=arkane_spc.adjacency_list) - except ValueError as e: + except ValueError: print('Could not read adjlist:\n{0}'.format(arkane_spc.adjacency_list)) # should *not* be logging - raise e + raise elif arkane_spc.inchi is not None: self.mol = Molecule().fromInChI(inchistr=arkane_spc.inchi) elif arkane_spc.smiles is not None: From 6d9b41a33b7f99e286b0642821f280f2dba8b4bf Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:26:16 -0400 Subject: [PATCH 04/42] Download additional info re failed jobs Download out.txt and err.txt from OGE and slurm_.out from Slurm --- arc/job/job.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/arc/job/job.py b/arc/job/job.py index d56a2cc2c3..323fbd817b 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -673,11 +673,80 @@ def determine_job_status(self): ess_status = self._check_job_ess_status() except IOError: logging.error('Got an IOError when trying to download output file for job {0}.'.format(self.job_name)) + content = self._get_additional_job_info() + if content: + logging.info('Got the following information from the server:') + logging.info(content) + for line in content.splitlines(): + # example: + # slurmstepd: *** JOB 7752164 CANCELLED AT 2019-03-27T00:30:50 DUE TO TIME LIMIT on node096 *** + if 'cancelled' in line.lower() and 'due to time limit' in line.lower(): + logging.warning('Looks like the job was cancelled on {0} due to time limit. ' + 'Got: {1}'.format(self.server, line)) + new_max_job_time = self.max_job_time - 24 if self.max_job_time > 25 else 1 + logging.warning('Setting max job time to {0} (was {1})'.format(new_max_job_time, + self.max_job_time)) + self.max_job_time = new_max_job_time raise elif server_status == 'running': ess_status = 'running' self.job_status = [server_status, ess_status] + def _get_additional_job_info(self): + """ + Download the additional information of stdout and stderr from the server + """ + lines1, lines2 = list(), list() + content = '' + ssh = SSH_Client(self.server) + cluster_soft = servers[self.server]['cluster_soft'].lower() + if cluster_soft in ['oge', 'sge']: + remote_file_path = os.path.join(self.remote_path, 'out.txt') + local_file_path1 = os.path.join(self.local_path, 'out.txt') + try: + ssh.download_file(remote_file_path=remote_file_path, local_file_path=local_file_path1) + except (TypeError, IOError) as e: + logging.warning('Got the following error when trying to download out.txt for {0}:'.format(self.job_name)) + logging.warning(e.message) + remote_file_path = os.path.join(self.remote_path, 'err.txt') + local_file_path2 = os.path.join(self.local_path, 'err.txt') + try: + ssh.download_file(remote_file_path=remote_file_path, local_file_path=local_file_path2) + except (TypeError, IOError) as e: + logging.warning('Got the following error when trying to download err.txt for {0}:'.format(self.job_name)) + logging.warning(e.message) + if os.path.isfile(local_file_path1): + with open(local_file_path1, 'r') as f: + lines1 = f.readlines() + if os.path.isfile(local_file_path2): + with open(local_file_path2, 'r') as f: + lines2 = f.readlines() + content += ''.join([line for line in lines1]) + content += '\n' + content += ''.join([line for line in lines2]) + elif cluster_soft == 'slurm': + respond = ssh.send_command_to_server(command='ls -alF', remote_path=self.remote_path) + files = list() + for line in respond[0][0].splitlines(): + files.append(line.split()[-1]) + for file in files: + if 'slurm' in file and '.out' in file: + remote_file_path = os.path.join(self.remote_path, file) + local_file_path = os.path.join(self.local_path, file) + try: + ssh.download_file(remote_file_path=remote_file_path, local_file_path=local_file_path) + except (TypeError, IOError) as e: + logging.warning('Got the following error when trying to download {0} for {1}:'.format( + file, self.job_name)) + logging.warning(e.message) + if os.path.isfile(local_file_path): + with open(local_file_path, 'r') as f: + lines1 = f.readlines() + content += ''.join([line for line in lines1]) + content += '\n' + return content + + def _check_job_server_status(self): """ Possible statuses: `initializing`, `running`, `errored on node xx`, `done` From 82956adfd310d9650c25b9200c11143e5ac2abdb Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:27:16 -0400 Subject: [PATCH 05/42] Minor: Convert `if` to `elif` in job ess error identification --- arc/job/job.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/arc/job/job.py b/arc/job/job.py index 323fbd817b..fff3198be9 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -779,30 +779,30 @@ def _check_job_ess_status(self): reason = '' if 'l9999.exe' in line or 'l103.exe' in line: return 'unconverged' - if 'l502.exe' in line: + elif 'l502.exe' in line: return 'unconverged SCF' - if 'l103.exe' in line: + elif 'l103.exe' in line: return 'l103 internal coordinate error' - if 'Erroneous write' in line or 'Write error in NtrExt1' in line: + elif 'Erroneous write' in line or 'Write error in NtrExt1' in line: reason = 'Ran out of disk space.' - if 'l716.exe' in line: + elif 'l716.exe' in line: reason = 'Angle in z-matrix outside the allowed range 0 < x < 180.' - if 'l301.exe' in line: + elif 'l301.exe' in line: reason = 'Input Error. Either charge, multiplicity, or basis set was not specified ' \ 'correctly. Or, an atom specified does not match any standard atomic symbol.' - if 'NtrErr Called from FileIO' in line: + elif 'NtrErr Called from FileIO' in line: reason = 'Operation on .chk file was specified, but .chk was not found.' - if 'l101.exe' in line: + elif 'l101.exe' in line: reason = 'Input Error. The blank line after the coordinate section is missing, ' \ 'or charge/multiplicity was not specified correctly.' - if 'l202.exe' in line: + elif 'l202.exe' in line: reason = 'During the optimization process, either the standard orientation ' \ 'or the point group of the molecule has changed.' - if 'l401.exe' in line: + elif 'l401.exe' in line: reason = 'The projection from the old to the new basis set has failed.' - if 'malloc failed' in line or 'galloc' in line: + elif 'malloc failed' in line or 'galloc' in line: reason = 'Memory allocation failed (did you ask for too much?)' - if 'A SYNTAX ERROR WAS DETECTED' in line: + elif 'A SYNTAX ERROR WAS DETECTED' in line: reason = 'Check .inp carefully for syntax errors in keywords.' return 'errored: {0}; {1}'.format(line, reason) return 'errored: Unknown reason' From 77d27ff8b184a73635933a9bbc931b7ca09f0a50 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:27:50 -0400 Subject: [PATCH 06/42] Minor: Modification to job error messages --- arc/job/job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arc/job/job.py b/arc/job/job.py index fff3198be9..6ba3e6142a 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -859,7 +859,7 @@ def troubleshoot_server(self): if self.settings['ssh']: if servers[self.server]['cluster_soft'].lower() == 'oge': # delete present server run - logging.error('Job {name} has server status {stat} on {server}. Troubleshooting by changing node.'.format( + logging.error('Job {name} has server status "{stat}" on {server}. Troubleshooting by changing node.'.format( name=self.job_name, stat=self.job_status[0], server=self.server)) ssh = SSH_Client(self.server) ssh.send_command_to_server(command=delete_command[servers[self.server]['cluster_soft']] + @@ -893,7 +893,7 @@ def troubleshoot_server(self): elif servers[self.server]['cluster_soft'].lower() == 'slurm': # TODO: change node on Slurm # delete present server run - logging.error('Job {name} has server status {stat} on {server}. Re-running job.'.format( + logging.error('Job {name} has server status "{stat}" on {server}. Re-running job.'.format( name=self.job_name, stat=self.job_status[0], server=self.server)) ssh = SSH_Client(self.server) ssh.send_command_to_server(command=delete_command[servers[self.server]['cluster_soft']] + From 0b708245a21a4a96e70085b704cf7d91bc18877d Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:28:49 -0400 Subject: [PATCH 07/42] Make sure initial and final job times are defined before determining run time --- arc/job/job.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arc/job/job.py b/arc/job/job.py index 6ba3e6142a..3c3bad0672 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -903,4 +903,7 @@ def troubleshoot_server(self): def determine_run_time(self): """Determine the run time""" - self.run_time = self.final_time - self.initial_time + if self.initial_time is not None and self.final_time is not None: + self.run_time = self.final_time - self.initial_time + else: + self.run_time = None From e57ed270c3e1fd9b48585e5452adc37ed9e9edd6 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:29:37 -0400 Subject: [PATCH 08/42] Added fail-safe try-except blocks in ssh to download and get modified time --- arc/job/ssh.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arc/job/ssh.py b/arc/job/ssh.py index 0423062eda..1ada177b99 100644 --- a/arc/job/ssh.py +++ b/arc/job/ssh.py @@ -112,7 +112,11 @@ def _download_file(self, remote_file_path, local_file_path): Download a file from `remote_file_path` to `local_file_path`. """ sftp, ssh = self.connect() - sftp.get(remotepath=remote_file_path, localpath=local_file_path) + try: + sftp.get(remotepath=remote_file_path, localpath=local_file_path) + except IOError: + logging.warning('Got an IOError when trying to download file {0} from {1}'.format(remote_file_path, + self.server)) sftp.close() ssh.close() @@ -257,7 +261,10 @@ def try_connecting(self): def get_last_modified_time(self, remote_file_path): """returns the last modified time of `remote_file` in a datetime format""" sftp, ssh = self.connect() - timestamp = sftp.stat(remote_file_path).st_mtime + try: + timestamp = sftp.stat(remote_file_path).st_mtime + except IOError: + return None sftp.close() ssh.close() return datetime.datetime.fromtimestamp(timestamp) From c1fa4022d512715d5a89d6c2063cd7e3ed4c5bd9 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:47:30 -0400 Subject: [PATCH 09/42] Renamed `visualize_orbitals` as `run_orbitals` since this directive doesn't actually visuallizes anything Also changed the default to False, since this might take too long (longer than all other jobs for the species) --- arc/main.py | 12 ++++++------ arc/scheduler.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/arc/main.py b/arc/main.py index 89873b2632..4b16601aa8 100644 --- a/arc/main.py +++ b/arc/main.py @@ -57,7 +57,7 @@ class ARC(object): `fine` ``bool`` Whether or not to use a fine grid for opt jobs (spawns an additional job) `generate_conformers` ``bool`` Whether or not to generate conformers when an initial geometry is given `scan_rotors` ``bool`` Whether or not to perform rotor scans - `visualize_orbitals` ``bool`` Whether or not to save the molecular orbitals for visualization (default: True) + `run_orbitals` ``bool`` Whether or not to save the molecular orbitals for visualization (default: False) `use_bac` ``bool`` Whether or not to use bond additivity corrections for thermo calculations `model_chemistry` ``list`` The model chemistry in Arkane for energy corrections (AE, BAC). This can be usually determined automatically. @@ -86,7 +86,7 @@ def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn ts_guess_level='', fine=True, generate_conformers=True, scan_rotors=True, use_bac=True, model_chemistry='', ess_settings=None, initial_trsh=None, t_min=None, t_max=None, t_count=None, verbose=logging.INFO, project_directory=None, max_job_time=120, allow_nonisomorphic_2d=False, - job_memory=1500, visualize_orbitals=True): + job_memory=1500, run_orbitals=False): self.__version__ = '1.0.0' self.verbose = verbose @@ -121,7 +121,7 @@ def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn self.fine = fine self.generate_conformers = generate_conformers self.scan_rotors = scan_rotors - self.visualize_orbitals = visualize_orbitals + self.run_orbitals = run_orbitals self.use_bac = use_bac self.model_chemistry = model_chemistry if self.model_chemistry: @@ -358,7 +358,7 @@ def as_dict(self): restart_dict['fine'] = self.fine restart_dict['generate_conformers'] = self.generate_conformers restart_dict['scan_rotors'] = self.scan_rotors - restart_dict['visualize_orbitals'] = self.visualize_orbitals + restart_dict['run_orbitals'] = self.run_orbitals restart_dict['use_bac'] = self.use_bac restart_dict['model_chemistry'] = self.model_chemistry restart_dict['composite_method'] = self.composite_method @@ -446,7 +446,7 @@ def from_dict(self, input_dict, project=None, project_directory=None): self.fine = input_dict['fine'] if 'fine' in input_dict else True self.generate_conformers = input_dict['generate_conformers'] if 'generate_conformers' in input_dict else True self.scan_rotors = input_dict['scan_rotors'] if 'scan_rotors' in input_dict else True - self.visualize_orbitals = input_dict['visualize_orbitals'] if 'visualize_orbitals' in input_dict else True + self.run_orbitals = input_dict['run_orbitals'] if 'run_orbitals' in input_dict else False self.use_bac = input_dict['use_bac'] if 'use_bac' in input_dict else True self.model_chemistry = input_dict['model_chemistry'] if 'use_bac' in input_dict\ and input_dict['use_bac'] else '' @@ -628,7 +628,7 @@ def execute(self): scan_rotors=self.scan_rotors, initial_trsh=self.initial_trsh, rmgdatabase=self.rmgdb, restart_dict=self.restart_dict, project_directory=self.project_directory, max_job_time=self.max_job_time, allow_nonisomorphic_2d=self.allow_nonisomorphic_2d, - memory=self.memory, visualize_orbitals=self.visualize_orbitals, + memory=self.memory, run_orbitals=self.run_orbitals, orbitals_level=self.orbitals_level) self.save_project_info_file() diff --git a/arc/scheduler.py b/arc/scheduler.py index 6930c5c296..f3c6c3c749 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -61,7 +61,7 @@ class Scheduler(object): `fine` ``bool`` Whether or not to use a fine grid for opt jobs (spawns an additional job) `generate_conformers` ``bool`` Whether or not to generate conformers when an initial geometry is given `scan_rotors` ``bool`` Whether or not to perform rotor scans - `visualize_orbitals` ``bool`` Whether or not to save the molecular orbitals for visualization (default: Tru + `run_orbitals` ``bool`` Whether or not to save the molecular orbitals for visualization (default: Tru `output` ``dict`` Output dictionary with status and final QM file paths for all species `settings` ``dict`` A dictionary of available servers and software `initial_trsh` ``dict`` Troubleshooting methods to try by default. Keys are ESS software, values are trshs @@ -108,7 +108,7 @@ class Scheduler(object): def __init__(self, project, settings, species_list, composite_method, conformer_level, opt_level, freq_level, sp_level, scan_level, ts_guess_level, orbitals_level, project_directory, rmgdatabase, fine=False, scan_rotors=True, generate_conformers=True, initial_trsh=None, rxn_list=None, restart_dict=None, - max_job_time=120, allow_nonisomorphic_2d=False, memory=1500, testing=False, visualize_orbitals=True): + max_job_time=120, allow_nonisomorphic_2d=False, memory=1500, testing=False, run_orbitals=False): self.rmgdb = rmgdatabase self.restart_dict = restart_dict self.species_list = species_list @@ -143,7 +143,7 @@ def __init__(self, project, settings, species_list, composite_method, conformer_ self.fine = fine self.generate_conformers = generate_conformers self.scan_rotors = scan_rotors - self.visualize_orbitals = visualize_orbitals + self.run_orbitals = run_orbitals self.unique_species_labels = list() self.initial_trsh = initial_trsh if initial_trsh is not None else dict() self.save_restart = False @@ -709,7 +709,7 @@ def run_orbitals_job(self, label): Spawn orbitals job used for molecular orbital visualization Currently supporting QChem for printing the orbitals, the output could be visualized using IQMol """ - if self.visualize_orbitals and 'orbitals' not in self.job_dict[label]: + if self.run_orbitals and 'orbitals' not in self.job_dict[label]: self.run_job(label=label, xyz=self.species_dict[label].final_xyz, level_of_theory=self.orbitals_level, job_type='orbitals') From c2513f462bf9bc1b90a86b8a747d34923f74a985 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:50:26 -0400 Subject: [PATCH 10/42] Minor robustness changes to Species --- arc/species/species.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/arc/species/species.py b/arc/species/species.py index af998bd428..4d11d57e8f 100644 --- a/arc/species/species.py +++ b/arc/species/species.py @@ -194,7 +194,7 @@ def __init__(self, is_ts=False, rmg_species=None, mol=None, label=None, xyz=None keep_isomorphic=False, filter_structures=True) self.mol_list = self.rmg_species.molecule logging.info('Using localized structure {0} of species {1} for BAC determination. To use a' - ' different structure, pass the RMG:Molecule object in the `mol` parameter'.format( + ' different structure, pass the RMG:Molecule object in the `mol` parameter'.format( self.mol.toSMILES(), self.label)) self.multiplicity = self.rmg_species.molecule[0].multiplicity self.charge = self.rmg_species.molecule[0].getNetCharge() @@ -381,7 +381,12 @@ def from_dict(self, species_dict): self.optical_isomers = species_dict['optical_isomers'] if 'optical_isomers' in species_dict else None self.neg_freqs_trshed = species_dict['neg_freqs_trshed'] if 'neg_freqs_trshed' in species_dict else list() self.bond_corrections = species_dict['bond_corrections'] if 'bond_corrections' in species_dict else dict() - self.mol = Molecule().fromAdjacencyList(str(species_dict['mol'])) if 'mol' in species_dict else None + try: + self.mol = Molecule().fromAdjacencyList(str(species_dict['mol'])) if 'mol' in species_dict else None + except ValueError: + logging.error('Could not read RMG adjacency list {0}'.format(species_dict['mol'] if 'mol' + in species_dict else None)) + self.mol = None smiles = species_dict['smiles'] if 'smiles' in species_dict else None inchi = species_dict['inchi'] if 'inchi' in species_dict else None adjlist = species_dict['adjlist'] if 'adjlist' in species_dict else None @@ -613,7 +618,7 @@ def determine_symmetry(self): """ Determine external symmetry and optical isomers """ - xyz = self.final_xyz if self.final_xyz is not None else self.initial_xyz + xyz = self.final_xyz or self.initial_xyz atom_numbers = list() # List of atomic numbers coordinates = list() for line in xyz.split('\n'): @@ -662,7 +667,7 @@ def determine_multiplicity(self, smiles, adjlist, mol): elif smiles: mol = Molecule(SMILES=smiles) self.multiplicity = mol.multiplicity - elif self.initial_xyz: + elif self.initial_xyz is not None: _, atoms, _, _, _ = get_xyz_matrix(self.initial_xyz) electrons = 0 for atom in atoms: From e7f9ec8b4965932dec486db8364e58ae8a43ad73 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:53:11 -0400 Subject: [PATCH 11/42] Correcting scan_level assignments, particularly for composite methods --- arc/main.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/arc/main.py b/arc/main.py index 4b16601aa8..f252ad1a6e 100644 --- a/arc/main.py +++ b/arc/main.py @@ -173,13 +173,6 @@ def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn self.freq_level = default_levels_of_theory['freq_for_composite'].lower() logging.info('Using default level {0} for frequency calculations after composite jobs'.format( self.freq_level)) - if scan_level: - self.scan_level = scan_level.lower() - logging.info('Using {0} for rotor scans'.format(self.scan_level)) - else: - self.scan_level = default_levels_of_theory['scan_for_composite'].lower() - logging.info('Using default level {0} for rotor scans after composite jobs'.format( - self.scan_level)) elif '//' in level_of_theory: self.composite_method = '' self.opt_level = level_of_theory.lower().split('//')[1] @@ -263,8 +256,8 @@ def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn else: # This is a composite method self.scan_level = default_levels_of_theory['scan_for_composite'].lower() - logging.info('Using default level {0} for scan calculations after composite jobs'.format( - self.freq_level)) + logging.info('Using default level {0} for rotor scans after composite jobs'.format( + self.scan_level)) else: self.scan_level = '' @@ -493,13 +486,6 @@ def from_dict(self, input_dict, project=None, project_directory=None): self.freq_level = default_levels_of_theory['freq_for_composite'].lower() logging.info('Using default level {0} for frequency calculations after composite jobs'.format( self.freq_level)) - if 'scan_level' in input_dict: - self.scan_level = input_dict['scan_level'].lower() - logging.info('Using {0} for rotor scans'.format(self.scan_level)) - else: - self.scan_level = default_levels_of_theory['scan_for_composite'].lower() - logging.info('Using default level {0} for rotor scans after composite jobs'.format( - self.scan_level)) elif '//' in input_dict['level_of_theory']: self.composite_method = '' self.opt_level = input_dict['level_of_theory'].lower().split('//')[1] @@ -576,9 +562,9 @@ def from_dict(self, input_dict, project=None, project_directory=None): logging.info('Using default level {0} for rotor scans'.format(self.scan_level)) else: # This is a composite method - self.freq_level = default_levels_of_theory['scan_for_composite'].lower() - logging.info('Using default level {0} for scan calculations after composite jobs'.format( - self.freq_level)) + self.scan_level = default_levels_of_theory['scan_for_composite'].lower() + logging.info('Using default level {0} for rotor scans after composite jobs'.format( + self.scan_level)) else: self.scan_level = '' From e4f25b28bf90809c312d187422ca71526a7a7abf Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:55:10 -0400 Subject: [PATCH 12/42] Avoid paranthesis in unique species name This could be read by RMG as the species index use `_` instead --- arc/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arc/processor.py b/arc/processor.py index 4454a45b69..9f69db4bd7 100644 --- a/arc/processor.py +++ b/arc/processor.py @@ -183,7 +183,7 @@ def process(self): try: arkane_spc = arkane_species(str(species.label), species.arkane_file) except ValueError: - species.label += '_(' + str(randint(0, 999)) + ')' + species.label += '_' + str(randint(0, 999)) else: unique_arkane_species_label = True if species.mol_list: From d85deeb2116f2bfbc18d0c52c21c7d5ab77bb814 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:55:41 -0400 Subject: [PATCH 13/42] Don't crush if a stat_mech job fails --- arc/processor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arc/processor.py b/arc/processor.py index 9f69db4bd7..68db66d197 100644 --- a/arc/processor.py +++ b/arc/processor.py @@ -192,7 +192,10 @@ def process(self): stat_mech_job.applyBondEnergyCorrections = self.use_bac stat_mech_job.modelChemistry = self.model_chemistry stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor(self.model_chemistry) - stat_mech_job.execute(outputFile=output_file_path, plot=False) + try: + stat_mech_job.execute(outputFile=output_file_path, plot=False) + except Exception: + continue if species.generate_thermo: thermo_job = ThermoJob(arkane_spc, 'NASA') thermo_job.execute(outputFile=output_file_path, plot=False) From 39566d944c395d710a041cb2f96c00c36cd8571e Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:56:10 -0400 Subject: [PATCH 14/42] Append species to thermo library only after calculating thermo for it --- arc/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arc/processor.py b/arc/processor.py index 68db66d197..ab24b7ef4b 100644 --- a/arc/processor.py +++ b/arc/processor.py @@ -176,7 +176,6 @@ def process(self): species_for_thermo_lib = list() for species in self.species_dict.values(): if not species.is_ts and 'ALL converged' in self.output[species.label]['status']: - species_for_thermo_lib.append(species) output_file_path = self._generate_arkane_species_file(species) unique_arkane_species_label = False while not unique_arkane_species_label: @@ -201,6 +200,7 @@ def process(self): thermo_job.execute(outputFile=output_file_path, plot=False) species.thermo = arkane_spc.getThermoData() plotter.log_thermo(species.label, path=output_file_path) + species_for_thermo_lib.append(species) species.rmg_species = Species(molecule=[species.mol]) species.rmg_species.reactive = True From ea3f6c0e888eece982b7a32822c941742001b42f Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:57:02 -0400 Subject: [PATCH 15/42] Make sure we get initial_xyz from conformers_path before running optimization (or composite) --- arc/scheduler.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/arc/scheduler.py b/arc/scheduler.py index f3c6c3c749..1d9567d17c 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -328,10 +328,11 @@ def schedule_jobs(self): for species in self.species_dict.values(): if not species.initial_xyz and not species.final_xyz and species.conformers and species.conformer_energies: self.determine_most_stable_conformer(species.label) - if self.composite_method: - self.run_composite_job(species.label) - else: - self.run_opt_job(species.label) + if species.initial_xyz is not None: + if self.composite_method: + self.run_composite_job(species.label) + else: + self.run_opt_job(species.label) if self.generate_conformers: self.run_conformer_jobs() while self.running_jobs != {}: # loop while jobs are still running From 83c5a540e900684f9faee3a67cc9e93035421450 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 14:59:27 -0400 Subject: [PATCH 16/42] Troubleshooting improvements --- arc/scheduler.py | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/arc/scheduler.py b/arc/scheduler.py index 1d9567d17c..482eafa66f 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -483,10 +483,12 @@ def schedule_jobs(self): self.species_dict[spc.label] = spc def run_job(self, label, xyz, level_of_theory, job_type, fine=False, software=None, shift='', trsh='', memory=None, - conformer=-1, ess_trsh_methods=None, scan='', pivots=None, occ=None, scan_trsh='', scan_res=None): + conformer=-1, ess_trsh_methods=None, scan='', pivots=None, occ=None, scan_trsh='', scan_res=None, + max_job_time=None): """ A helper function for running (all) jobs """ + max_job_time = max_job_time or self.max_job_time # if it's None, set to default ess_trsh_methods = ess_trsh_methods if ess_trsh_methods is not None else list() pivots = pivots if pivots is not None else list() species = self.species_dict[label] @@ -495,7 +497,7 @@ def run_job(self, label, xyz, level_of_theory, job_type, fine=False, software=No level_of_theory=level_of_theory, multiplicity=species.multiplicity, charge=species.charge, fine=fine, shift=shift, software=software, is_ts=species.is_ts, memory=memory, trsh=trsh, ess_trsh_methods=ess_trsh_methods, scan=scan, pivots=pivots, occ=occ, initial_trsh=self.initial_trsh, - project_directory=self.project_directory, max_job_time=self.max_job_time, scan_trsh=scan_trsh, + project_directory=self.project_directory, max_job_time=max_job_time, scan_trsh=scan_trsh, scan_res=scan_res, conformer=conformer) if job.software is not None: if conformer < 0: @@ -525,12 +527,14 @@ def end_job(self, job, label, job_name): try: job.determine_job_status() # also downloads output file except IOError: - logging.warn('Tried to determine status of job {0}, but it seems like the job never ran.' - ' Re-running job.'.format(job.job_name)) - self.run_job(label=label, xyz=job.xyz, level_of_theory=job.level_of_theory, job_type=job.job_type, - fine=job.fine, software=job.software, shift=job.shift, trsh=job.trsh, memory=job.memory, - conformer=job.conformer, ess_trsh_methods=job.ess_trsh_methods, scan=job.scan, - pivots=job.pivots, occ=job.occ, scan_trsh=job.scan_trsh, scan_res=job.scan_res) + if not job.job_type in ['orbitals']: + logging.warn('Tried to determine status of job {0}, but it seems like the job never ran.' + ' Re-running job.'.format(job.job_name)) + self.run_job(label=label, xyz=job.xyz, level_of_theory=job.level_of_theory, job_type=job.job_type, + fine=job.fine, software=job.software, shift=job.shift, trsh=job.trsh, memory=job.memory, + conformer=job.conformer, ess_trsh_methods=job.ess_trsh_methods, scan=job.scan, + pivots=job.pivots, occ=job.occ, scan_trsh=job.scan_trsh, scan_res=job.scan_res, + max_job_time=job.max_job_time) if job_name in self.running_jobs[label]: self.running_jobs[label].pop(self.running_jobs[label].index(job_name)) if job.job_status[0] != 'running' and job.job_status[1] != 'running': @@ -1393,6 +1397,13 @@ def troubleshoot_ess(self, label, job, level_of_theory, job_type, conformer=-1): job.troubleshoot_server() if job.job_name not in self.running_jobs[label]: self.running_jobs[label].append(job.job_name) # mark as a running job + elif 'Ran out of disk space' in job.job_status[1]: + self.output[label]['status'] += '; Error: Could not troubleshoot {job_type} for {label}! ' \ + ' The job ran out of disc space on {server}'.format( + job_type=job_type, label=label, methods=job.ess_trsh_methods, server=job.server) + logging.error('Could not troubleshoot {job_type} for {label}! The job ran out of disc space on ' + '{server}'.format(job_type=job_type, label=label, methods=job.ess_trsh_methods, + server=job.server)) elif job.software == 'gaussian': if 'l103 internal coordinate error' in job.job_status[1]\ and 'cartesian' not in job.ess_trsh_methods and job_type == 'opt': @@ -1494,9 +1505,9 @@ def troubleshoot_ess(self, label, job, level_of_theory, job_type, conformer=-1): logging.error('Could not troubleshoot geometry optimization for {label}! Tried' ' troubleshooting with the following methods: {methods}'.format( label=label, methods=job.ess_trsh_methods)) - self.output[label]['status'] += '; Error: Could not troubleshoot geometry optimization for {label}! ' \ + self.output[label]['status'] += '; Error: Could not troubleshoot {job_type} for {label}! ' \ ' Tried troubleshooting with the following methods: {methods}'.format( - label=label, methods=job.ess_trsh_methods) + job_type=job_type, label=label, methods=job.ess_trsh_methods) elif job.software == 'qchem': if 'max opt cycles reached' in job.job_status[1] and 'max_cycles' not in job.ess_trsh_methods: # this is a common error, increase max cycles and continue running from last geometry @@ -1551,9 +1562,9 @@ def troubleshoot_ess(self, label, job, level_of_theory, job_type, conformer=-1): logging.error('Could not troubleshoot geometry optimization for {label}! Tried' ' troubleshooting with the following methods: {methods}'.format( label=label, methods=job.ess_trsh_methods)) - self.output[label]['status'] += '; Error: Could not troubleshoot geometry optimization for {label}! ' \ + self.output[label]['status'] += '; Error: Could not troubleshoot {job_type} for {label}! ' \ ' Tried troubleshooting with the following methods: {methods}'.format( - label=label, methods=job.ess_trsh_methods) + job_type=job_type, label=label, methods=job.ess_trsh_methods) elif 'molpro' in job.software: if 'additional memory (mW) required' in job.job_status[1]: # Increase memory allocation. @@ -1627,9 +1638,9 @@ def troubleshoot_ess(self, label, job, level_of_theory, job_type, conformer=-1): logging.error('Could not troubleshoot geometry optimization for {label}! Tried' ' troubleshooting with the following methods: {methods}'.format( label=label, methods=job.ess_trsh_methods)) - self.output[label]['status'] += '; Error: Could not troubleshoot geometry optimization for {label}! ' \ + self.output[label]['status'] += '; Error: Could not troubleshoot {job_type} for {label}! ' \ ' Tried troubleshooting with the following methods: {methods}'.format( - label=label, methods=job.ess_trsh_methods) + job_type=job_type, label=label, methods=job.ess_trsh_methods) def delete_all_species_jobs(self, label): """ From 9d399bd9b899e1aedabed1eb6eab069685848b04 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 16:35:24 -0400 Subject: [PATCH 17/42] Added levels_ess to settings To allow users to associate levels of theory (phrases of methods or basis sets) with the desired ESS --- arc/settings.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arc/settings.py b/arc/settings.py index 3c0c0ed962..a7e9331d68 100644 --- a/arc/settings.py +++ b/arc/settings.py @@ -45,6 +45,15 @@ } } +# List here (complete or partial) phrases of methods or basis sets you'd like to associate to specific ESS +# Avoid ascribing the same phrase to more than one server, this may cause undeterministic assignment of software +# Format is levels_ess = {ess: ['phrase1', 'phrase2'], ess2: ['phrase3', 'phrase3']} +levels_ess = { + 'gaussian': ['b3lyp', 'm062x', '3df,3pd'], + 'molpro': ['ccsd', 'cisd', 'vpz'], + 'qchem': ['m06-2x', 'def2'] +} + check_status_command = {'OGE': 'export SGE_ROOT=/opt/sge; /opt/sge/bin/lx24-amd64/qstat', 'Slurm': '/usr/bin/squeue'} From c6f2176d81315627b272ff116f8088a0d10d7cb2 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 16:41:00 -0400 Subject: [PATCH 18/42] Changed ess_settings to contain a list of servers Also renamed it to ess_settings throughout Also using levels_ess from settings --- arc/job/job.py | 133 ++++++++++++++++++++++++--------------------- arc/main.py | 138 +++++++++++++++++++++++++---------------------- arc/scheduler.py | 27 ++++++---- 3 files changed, 164 insertions(+), 134 deletions(-) diff --git a/arc/job/job.py b/arc/job/job.py index 3c3bad0672..c2237e6a6c 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -7,7 +7,7 @@ import logging from arc.settings import arc_path, servers, submit_filename, delete_command, t_max_format,\ - input_filename, output_filename, rotor_scan_resolution, list_available_nodes_command + input_filename, output_filename, rotor_scan_resolution, list_available_nodes_command, levels_ess from arc.job.submit import submit_scripts from arc.job.inputs import input_files from arc.job.ssh import SSH_Client @@ -24,7 +24,7 @@ class Job(object): Attribute Type Description ================ =================== =============================================================================== `project` ``str`` The project's name. Used for naming the directory. - `settings` ``dict`` A dictionary of available servers and software + `ess_settings` ``dict`` A dictionary of available ESS and a corresponding server list `species_name` ``str`` The species/TS name. Used for naming the directory. `charge` ``int`` The species net charge. Default is 0 `multiplicity` ``int`` The species multiplicity. @@ -77,13 +77,13 @@ class Job(object): The job ess (electronic structure software calculation) status is in job.job_status[0] and can be either `initializing` / `running` / `errored: {error type / message}` / `unconverged` / `done` """ - def __init__(self, project, settings, species_name, xyz, job_type, level_of_theory, multiplicity, project_directory, + def __init__(self, project, ess_settings, species_name, xyz, job_type, level_of_theory, multiplicity, project_directory, charge=0, conformer=-1, fine=False, shift='', software=None, is_ts=False, scan='', pivots=None, memory=1500, comments='', trsh='', scan_trsh='', ess_trsh_methods=None, initial_trsh=None, job_num=None, job_server_name=None, job_name=None, job_id=None, server=None, initial_time=None, occ=None, max_job_time=120, scan_res=None): self.project = project - self.settings=settings + self.ess_settings = ess_settings self.initial_time = initial_time self.final_time = None self.run_time = None @@ -126,60 +126,72 @@ def __init__(self, project, settings, species_name, xyz, job_type, level_of_theo self.software = self.software.lower() else: if job_type == 'orbitals': - if not 'qchem' in self.settings: + # currently we only have a script to print orbitals on QChem, + # could/should definately be elaborated to additional ESS + if 'qchem' not in self.ess_settings.keys(): logging.debug('Could not find the QChem software to compute molecular orbitals') self.software = None - self.software = 'qchem' - if job_type == 'composite': - if not self.settings['gaussian']: + else: + self.software = 'qchem' + elif job_type == 'composite': + if 'gaussian' not in self.ess_settings.keys(): raise JobError('Could not find the Gaussian software to run the composite method {0}'.format( self.method)) self.software = 'gaussian' - elif job_type in ['conformer', 'opt', 'freq', 'optfreq', 'sp']: - if 'ccs' in self.method or 'cis' in self.method or 'pv' in self.basis_set: - if self.settings['molpro']: - self.software = 'molpro' - elif self.settings['gaussian']: + else: + # use the levels_ess dictionary from settings.py: + for ess, phrase_list in levels_ess.items(): + for phrase in phrase_list: + if phrase in self.level_of_theory: + self.software = ess.lower() + if self.software is None: + # otherwise, deduce which software to use base on hard coded heuristics + if job_type in ['conformer', 'opt', 'freq', 'optfreq', 'sp']: + if 'ccs' in self.method or 'cis' in self.method or 'pv' in self.basis_set: + if 'molpro' in self.ess_settings.keys(): + self.software = 'molpro' + elif 'gaussian' in self.ess_settings.keys(): + self.software = 'gaussian' + elif 'qchem' in self.ess_settings.keys(): + self.software = 'qchem' + elif 'b3lyp' in self.method: + if 'gaussian' in self.ess_settings.keys(): + self.software = 'gaussian' + elif 'qchem' in self.ess_settings.keys(): + self.software = 'qchem' + elif 'molpro' in self.ess_settings.keys(): + self.software = 'molpro' + elif 'wb97xd' in self.method: + if 'gaussian' not in self.ess_settings.keys(): + raise JobError('Could not find the Gaussian software to run {0}/{1}'.format( + self.method, self.basis_set)) self.software = 'gaussian' - elif self.settings['qchem']: + elif 'b97' in self.method or 'm06-2x' in self.method or 'def2' in self.basis_set: + if 'qchem' not in self.ess_settings.keys(): + raise JobError('Could not find the QChem software to run {0}/{1}'.format( + self.method, self.basis_set)) self.software = 'qchem' - elif 'b3lyp' in self.method: - if self.settings['gaussian']: + elif job_type == 'scan': + if 'wb97xd' in self.method: + if 'gaussian' not in self.ess_settings.keys(): + raise JobError('Could not find the Gaussian software to run {0}/{1}'.format( + self.method, self.basis_set)) self.software = 'gaussian' - elif self.settings['qchem']: + elif 'b97' in self.method or 'm06-2x' in self.method or 'def2' in self.basis_set: + if 'qchem' not in self.ess_settings.keys(): + raise JobError('Could not find the QChem software to run {0}/{1}'.format( + self.method, self.basis_set)) self.software = 'qchem' - elif self.settings['molpro']: - self.software = 'molpro' - elif 'wb97xd' in self.method: - if not self.settings['gaussian']: - raise JobError('Could not find the Gaussian software to run {0}/{1}'.format( - self.method, self.basis_set)) - self.software = 'gaussian' - elif 'b97' in self.method or 'm06-2x' in self.method or 'def2' in self.basis_set: - if not self.settings['qchem']: - raise JobError('Could not find the QChem software to run {0}/{1}'.format( - self.method, self.basis_set)) - self.software = 'qchem' - elif job_type == 'scan': - if 'wb97xd' in self.method: - if not self.settings['gaussian']: - raise JobError('Could not find the Gaussian software to run {0}/{1}'.format( - self.method, self.basis_set)) - self.software = 'gaussian' - elif 'b97' in self.method or 'm06-2x' in self.method or 'def2' in self.basis_set: - if not self.settings['qchem']: - raise JobError('Could not find the QChem software to run {0}/{1}'.format( - self.method, self.basis_set)) - self.software = 'qchem' - else: - if self.settings['gaussian']: - self.software = 'gaussian' else: - self.software = 'qchem' - elif job_type in ['gsm', 'irc']: - if not self.settings['gaussian']: - raise JobError('Could not find the Gaussian software to run {0}'.format(job_type)) + if 'gaussian' in self.ess_settings.keys(): + self.software = 'gaussian' + else: + self.software = 'qchem' + elif job_type in ['gsm', 'irc']: + if 'gaussian' not in self.ess_settings.keys(): + raise JobError('Could not find the Gaussian software to run {0}'.format(job_type)) if self.software is None: + # if still no software was determined, just try by order, if exists: Gaussian > QChem > Molpro logging.error('job_num: {0}'.format(self.job_num)) logging.error('ess_trsh_methods: {0}'.format(self.ess_trsh_methods)) logging.error('trsh: {0}'.format(self.trsh)) @@ -190,18 +202,18 @@ def __init__(self, project, settings, species_name, xyz, job_type, level_of_theo logging.error('method: {0}'.format(self.method)) logging.error('basis_set: {0}'.format(self.basis_set)) logging.error('Could not determine software for job {0}'.format(self.job_name)) - if self.settings['gaussian']: + if 'gaussian' in self.ess_settings.keys(): logging.error('Setting it to gaussian') self.software = 'gaussian' - elif self.settings['qchem']: + elif 'qchem' in self.ess_settings.keys(): logging.error('Setting it to qchem') self.software = 'qchem' - elif self.settings['molpro']: + elif 'molpro' in self.ess_settings.keys(): logging.error('Setting it to molpro') self.software = 'molpro' - if self.settings['ssh']: - self.server = server if server is not None else self.settings[self.software] + if self.ess_settings['ssh']: + self.server = server if server is not None else self.ess_settings[self.software][0] else: self.server = None @@ -370,7 +382,7 @@ def write_submit_script(self): os.makedirs(self.local_path) with open(os.path.join(self.local_path, submit_filename[servers[self.server]['cluster_soft']]), 'wb') as f: f.write(self.submit) - if self.settings['ssh']: + if self.ess_settings['ssh']: self._upload_submit_file() def write_input_file(self): @@ -464,7 +476,8 @@ def write_input_file(self): else: job_type_1 = 'opt' if 'PRINT_ORBITALS' not in self.trsh: - self.trsh += '\n PRINT_ORBITALS TRUE\n GUI 2' + self.trsh += '\n NBO TRUE\n RUN_NBO6 TRUE\n ' \ + 'PRINT_ORBITALS TRUE\n GUI 2' elif self.job_type == 'freq': if self.software == 'gaussian': @@ -600,7 +613,7 @@ def write_input_file(self): os.makedirs(self.local_path) with open(os.path.join(self.local_path, input_filename[self.software]), 'wb') as f: f.write(self.input) - if self.settings['ssh']: + if self.ess_settings['ssh']: self._upload_input_file() def _upload_submit_file(self): @@ -644,7 +657,7 @@ def run(self): self.write_submit_script() logging.debug('writing input file...') self.write_input_file() - if self.settings['ssh']: + if self.ess_settings['ssh']: ssh = SSH_Client(self.server) logging.debug('submitting job...') # submit_job returns job server status and job server id @@ -658,7 +671,7 @@ def run(self): def delete(self): logging.debug('Deleting job {name} for {label}'.format(name=self.job_name, label=self.species_name)) - if self.settings['ssh']: + if self.ess_settings['ssh']: ssh = SSH_Client(self.server) logging.debug('deleting job...') ssh.delete_job(self.job_id) @@ -751,7 +764,7 @@ def _check_job_server_status(self): """ Possible statuses: `initializing`, `running`, `errored on node xx`, `done` """ - if self.settings['ssh']: + if self.ess_settings['ssh']: ssh = SSH_Client(self.server) return ssh.check_job_status(self.job_id) @@ -764,7 +777,7 @@ def _check_job_ess_status(self): os.remove(self.local_path_to_output_file) if os.path.exists(self.local_path_to_orbitals_file): os.remove(self.local_path_to_orbitals_file) - if self.settings['ssh']: + if self.ess_settings['ssh']: self._download_output_file() with open(self.local_path_to_output_file, 'rb') as f: lines = f.readlines() @@ -856,7 +869,7 @@ def _check_job_ess_status(self): return 'errored: Unknown reason' def troubleshoot_server(self): - if self.settings['ssh']: + if self.ess_settings['ssh']: if servers[self.server]['cluster_soft'].lower() == 'oge': # delete present server run logging.error('Job {name} has server status "{stat}" on {server}. Troubleshooting by changing node.'.format( diff --git a/arc/main.py b/arc/main.py index f252ad1a6e..01c934e2a7 100644 --- a/arc/main.py +++ b/arc/main.py @@ -61,8 +61,7 @@ class ARC(object): `use_bac` ``bool`` Whether or not to use bond additivity corrections for thermo calculations `model_chemistry` ``list`` The model chemistry in Arkane for energy corrections (AE, BAC). This can be usually determined automatically. - `settings` ``dict`` A dictionary of available servers and software - `ess_settings` ``dict`` An optional input parameter: a dictionary relating ESS to servers + `ess_settings` ``dict`` A dictionary of available ESS (keys) and a corresponding server list (values) `initial_trsh` ``dict`` Troubleshooting methods to try by default. Keys are ESS software, values are trshs 't0' ``float`` Initial time when the project was spawned `execution_time` ``str`` Overall execution time @@ -90,8 +89,7 @@ def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn self.__version__ = '1.0.0' self.verbose = verbose - self.ess_settings = ess_settings - self.settings = dict() + self.ess_settings = check_ess_settings(ess_settings) self.output = dict() self.running_jobs = dict() self.lib_long_desc = '' @@ -117,7 +115,6 @@ def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn self.t0 = time.time() # init time self.execution_time = None self.initial_trsh = initial_trsh if initial_trsh is not None else dict() - self.determine_remote() self.fine = fine self.generate_conformers = generate_conformers self.scan_rotors = scan_rotors @@ -327,6 +324,8 @@ def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn project_directory = project_directory if project_directory is not None\ else os.path.abspath(os.path.dirname(input_dict)) self.from_dict(input_dict=input_dict, project=project, project_directory=project_directory) + if self.ess_settings is None: + self.determine_ess_settings() self.restart_dict = self.as_dict() self.determine_model_chemistry() self.scheduler = None @@ -347,7 +346,7 @@ def as_dict(self): """ restart_dict = dict() restart_dict['project'] = self.project - restart_dict['ess_settings'] = self.settings + restart_dict['ess_settings'] = self.ess_settings restart_dict['fine'] = self.fine restart_dict['generate_conformers'] = self.generate_conformers restart_dict['scan_rotors'] = self.scan_rotors @@ -395,27 +394,9 @@ def from_dict(self, input_dict, project=None, project_directory=None): self.execution_time = None self.verbose = input_dict['verbose'] if 'verbose' in input_dict else self.verbose self.max_job_time = input_dict['max_job_time'] if 'max_job_time' in input_dict else 5 - self.ess_settings = input_dict['ess_settings'] if 'ess_settings' in input_dict else None + self.ess_settings = check_ess_settings(input_dict['ess_settings']) if 'ess_settings' in input_dict else None self.allow_nonisomorphic_2d = input_dict['allow_nonisomorphic_2d']\ if 'allow_nonisomorphic_2d' in input_dict else False - if self.ess_settings is not None: - self.settings['ssh'] = True - for ess, server in self.ess_settings.items(): - if ess.lower() != 'ssh': - if ess.lower() not in ['gaussian', 'qchem', 'molpro']: - raise SettingsError('Recognized ESS software are Gaussian, QChem or Molpro.' - ' Got: {0}'.format(ess)) - if server.lower() not in servers: - server_names = [name for name in servers] - raise SettingsError('Recognized servers are {0}. Got: {1}'.format(server_names, server)) - self.settings[ess.lower()] = server.lower() - elif 'ess_settings' in input_dict: - self.settings = input_dict['ess_settings'] - self.settings['ssh'] = True - else: - self.determine_remote() - logging.info('\nUsing the following settings: {0}\n'.format(self.settings)) - self.output = input_dict['output'] if 'output' in input_dict else dict() if self.output: for label, spc_output in self.output.items(): @@ -610,7 +591,7 @@ def execute(self): composite_method=self.composite_method, conformer_level=self.conformer_level, opt_level=self.opt_level, freq_level=self.freq_level, sp_level=self.sp_level, scan_level=self.scan_level, ts_guess_level=self.ts_guess_level, fine=self.fine, - settings=self.settings, generate_conformers=self.generate_conformers, + ess_settings=self.ess_settings, generate_conformers=self.generate_conformers, scan_rotors=self.scan_rotors, initial_trsh=self.initial_trsh, rmgdatabase=self.rmgdb, restart_dict=self.restart_dict, project_directory=self.project_directory, max_job_time=self.max_job_time, allow_nonisomorphic_2d=self.allow_nonisomorphic_2d, @@ -660,7 +641,7 @@ def save_project_info_file(self): txt += 'NOT using bond additivity corrections for thermo\n' if self.initial_trsh: txt += 'Using an initial troubleshooting method "{0}"'.format(self.initial_trsh) - txt += '\nUsing the following settings: {0}\n'.format(self.settings) + txt += '\nUsing the following settings: {0}\n'.format(self.ess_settings) txt += '\nConsidered the following species and TSs:\n' for species in self.arc_species_list: if species.is_ts: @@ -835,73 +816,69 @@ def determine_model_chemistry(self): logging.info('Using {0} as model chemistry for energy corrections in Arkane'.format( self.model_chemistry)) - def determine_remote(self, diagnostics=False): + def determine_ess_settings(self, diagnostics=False): """ Determine whether ARC is executed remotely and if so the available ESS software and the cluster software of the server if `diagnostics` is True, this method will not raise errors, and will print its findings """ if self.ess_settings is not None: - self.settings['ssh'] = True - for ess, server in self.ess_settings.items(): - if ess.lower() not in ['gaussian', 'qchem', 'molpro']: - raise SettingsError('Recognized ESS software are Gaussian, QChem or Molpro. Got: {0}'.format(ess)) - if server.lower() not in servers: - server_names = [name for name in servers] - raise SettingsError('Recognized servers are {0}. Got: {1}'.format(server_names, servers)) - self.settings[ess.lower()] = server.lower() - logging.info('\nUsing the following user input: {0}\n'.format(self.settings)) + self.ess_settings = check_ess_settings(self.ess_settings) + self.ess_settings['ssh'] = True return + if diagnostics: logging.info('\n\n\n ***** Running ESS diagnostics: *****\n') + # os.system('. ~/.bashrc') # TODO This might be a security risk - rethink it + if 'SSH_CONNECTION' in os.environ: # ARC is executed on a server, proceed logging.info('\n\nExecuting QM jobs locally.') if diagnostics: logging.info('ARC is being excecuted on a server (found "SSH_CONNECTION" in the os.environ dictionary') logging.info('Using distutils.spawn.find_executable() to find ESS') - self.settings['ssh'] = False + self.ess_settings['ssh'] = False g03 = find_executable('g03') g09 = find_executable('g09') g16 = find_executable('g16') if g03 or g09 or g16: if diagnostics: logging.info('Found Gaussian: g03={0}, g09={1}, g16={2}'.format(g03, g09, g16)) - self.settings['gaussian'] = True + self.ess_settings['gaussian'] = True else: if diagnostics: logging.info('Did NOT find Gaussian: g03={0}, g09={1}, g16={2}'.format(g03, g09, g16)) - self.settings['gaussian'] = False + self.ess_settings['gaussian'] = False qchem = find_executable('qchem') if qchem: - self.settings['qchem'] = True + self.ess_settings['qchem'] = True else: if diagnostics: logging.info('Did not find QChem') - self.settings['qchem'] = False + self.ess_settings['qchem'] = False molpro = find_executable('molpro') if molpro: - self.settings['molpro'] = True + self.ess_settings['molpro'] = True else: if diagnostics: logging.info('Did not find Molpro') - self.settings['molpro'] = False - if self.settings['gaussian']: + self.ess_settings['molpro'] = False + if self.ess_settings['gaussian']: logging.info('Found Gaussian') - if self.settings['qchem']: + if self.ess_settings['qchem']: logging.info('Found QChem') - if self.settings['molpro']: + if self.ess_settings['molpro']: logging.info('Found Molpro') else: # ARC is executed locally, communication with a server needs to be established if diagnostics: logging.info('ARC is being excecuted on a PC' ' (did not find "SSH_CONNECTION" in the os.environ dictionary') - self.settings['ssh'] = True + self.ess_settings['ssh'] = True logging.info('\n\nExecuting QM jobs remotely. Mapping servers...') # map servers - self.settings['gaussian'], self.settings['qchem'], self.settings['molpro'] = None, None, None + self.ess_settings['gaussian'], self.ess_settings['qchem'], self.ess_settings['molpro'] = None, None, None for server in servers.keys(): if diagnostics: logging.info('Trying {0}'.format(server)) @@ -915,9 +892,9 @@ def determine_remote(self, diagnostics=False): if g03 or g09 or g16: if diagnostics: logging.info('Found Gaussian on {3}: g03={0}, g09={1}, g16={2}'.format(g03, g09, g16, server)) - if self.settings['gaussian'] is None or 'precedence' in servers[server]\ + if self.ess_settings['gaussian'] is None or 'precedence' in servers[server]\ and servers[server]['precedence'] == 'gaussian': - self.settings['gaussian'] = server + self.ess_settings['gaussian'] = server elif diagnostics: logging.info('Did NOT find Gaussian on {3}: g03={0}, g09={1}, g16={2}'.format(g03, g09, g16, server)) cmd = '. ~/.bashrc; which qchem' @@ -925,9 +902,9 @@ def determine_remote(self, diagnostics=False): if qchem: if diagnostics: logging.info('Found QChem on {0}'.format(server)) - if self.settings['qchem'] is None or 'precedence' in servers[server]\ + if self.ess_settings['qchem'] is None or 'precedence' in servers[server]\ and servers[server]['precedence'] == 'qchem': - self.settings['qchem'] = server + self.ess_settings['qchem'] = server elif diagnostics: logging.info('Did NOT find QChem on {0}'.format(server)) cmd = '. .bashrc; which molpro' @@ -935,21 +912,21 @@ def determine_remote(self, diagnostics=False): if molpro: if diagnostics: logging.info('Found Molpro on {0}'.format(server)) - if self.settings['molpro'] is None or 'precedence' in servers[server]\ + if self.ess_settings['molpro'] is None or 'precedence' in servers[server]\ and servers[server]['precedence'] == 'molpro': - self.settings['molpro'] = server + self.ess_settings['molpro'] = server elif diagnostics: logging.info('Did NOT find Molpro on {0}'.format(server)) if diagnostics: logging.info('\n') - if self.settings['gaussian']: - logging.info('Using Gaussian on {0}'.format(self.settings['gaussian'])) - if self.settings['qchem']: - logging.info('Using QChem on {0}'.format(self.settings['qchem'])) - if self.settings['molpro']: - logging.info('Using Molpro on {0}'.format(self.settings['molpro'])) + if self.ess_settings['gaussian']: + logging.info('Using Gaussian on {0}'.format(self.ess_settings['gaussian'])) + if self.ess_settings['qchem']: + logging.info('Using QChem on {0}'.format(self.ess_settings['qchem'])) + if self.ess_settings['molpro']: + logging.info('Using Molpro on {0}'.format(self.ess_settings['molpro'])) logging.info('\n') - if not self.settings['gaussian'] and not self.settings['qchem'] and not self.settings['molpro']\ + if not self.ess_settings['gaussian'] and not self.ess_settings['qchem'] and not self.ess_settings['molpro']\ and not diagnostics: raise SettingsError('Could not find any ESS. Check your .bashrc definitions on the server.\n' 'Alternatively, you could pass a software-server dictionary to arc as `ess_settings`') @@ -972,7 +949,7 @@ def read_file(path): Read the ARC YAML input file and return the parameters in a dictionary """ if not os.path.isfile(path): - raise ValueError('Could not find the input file {0}'.format(path)) + raise InputError('Could not find the input file {0}'.format(path)) with open(path, 'r') as f: input_dict = yaml.load(stream=f) return input_dict @@ -1028,3 +1005,38 @@ def time_lapse(t0): else: d = '' return '{0}{1:02.0f}:{2:02.0f}:{3:02.0f}'.format(d, h, m, s) + + +def check_ess_settings(ess_settings): + """ + A helper function to convert servers in the ess_settings dict to lists + Assists in troubleshooting job and trying a different server + Also check ESS and servers + """ + if ess_settings is None: + return None + settings = dict() + for software, server_list in ess_settings.items(): + if software != 'ssh': + if isinstance(server_list, (str, unicode)): + settings[software] = [server_list] + elif isinstance(server_list, list): + for server in server_list: + if not isinstance(server, (str, unicode)): + raise SettingsError('Servers could only be strings. Got {0} which is {1}'.format(server, + type(server))) + settings[software.lower()] = server_list + else: + raise SettingsError('Servers in the ess_settings dictionary could either be a string or a list of ' + 'strings. Got: {0} which is a {1}'.format(server_list, type(server_list))) + # run checks: + for ess, server_list in settings.items(): + if ess.lower() not in ['gaussian', 'qchem', 'molpro'] and ess.lower() != 'ssh': + raise SettingsError('Recognized ESS software are Gaussian, QChem or Molpro. Got: {0}'.format(ess)) + for server in server_list: + if not isinstance(server, bool) and server.lower() not in servers.keys(): + server_names = [name for name in servers.keys()] + raise SettingsError('Recognized servers are {0}. Got: {1}'.format(server_names, server)) + logging.info('\nUsing the following ESS settings:\n{0}'.format(settings)) + settings['ssh'] = True # default until local ESS is implemented + return settings diff --git a/arc/scheduler.py b/arc/scheduler.py index 482eafa66f..d56fa83f6e 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -63,7 +63,7 @@ class Scheduler(object): `scan_rotors` ``bool`` Whether or not to perform rotor scans `run_orbitals` ``bool`` Whether or not to save the molecular orbitals for visualization (default: Tru `output` ``dict`` Output dictionary with status and final QM file paths for all species - `settings` ``dict`` A dictionary of available servers and software + `ess_settings` ``dict`` A dictionary of available ESS and a correcponding server list `initial_trsh` ``dict`` Troubleshooting methods to try by default. Keys are ESS software, values are trshs `restart_dict` ``dict`` A restart dictionary parsed from a YAML restart file `project_directory` ``str`` Folder path for the project: the input file path or ARC/Projects/project-name @@ -105,7 +105,7 @@ class Scheduler(object): } # Note that rotor scans are located under Species.rotors_dict """ - def __init__(self, project, settings, species_list, composite_method, conformer_level, opt_level, freq_level, + def __init__(self, project, ess_settings, species_list, composite_method, conformer_level, opt_level, freq_level, sp_level, scan_level, ts_guess_level, orbitals_level, project_directory, rmgdatabase, fine=False, scan_rotors=True, generate_conformers=True, initial_trsh=None, rxn_list=None, restart_dict=None, max_job_time=120, allow_nonisomorphic_2d=False, memory=1500, testing=False, run_orbitals=False): @@ -115,7 +115,7 @@ def __init__(self, project, settings, species_list, composite_method, conformer_ self.rxn_list = rxn_list if rxn_list is not None else list() self.project = project self.max_job_time = max_job_time - self.settings = settings + self.ess_settings = ess_settings self.project_directory = project_directory self.job_dict = dict() self.servers_jobs_ids = list() @@ -493,7 +493,7 @@ def run_job(self, label, xyz, level_of_theory, job_type, fine=False, software=No pivots = pivots if pivots is not None else list() species = self.species_dict[label] memory = memory if memory is not None else self.memory - job = Job(project=self.project, settings=self.settings, species_name=label, xyz=xyz, job_type=job_type, + job = Job(project=self.project, ess_settings=self.ess_settings, species_name=label, xyz=xyz, job_type=job_type, level_of_theory=level_of_theory, multiplicity=species.multiplicity, charge=species.charge, fine=fine, shift=shift, software=software, is_ts=species.is_ts, memory=memory, trsh=trsh, ess_trsh_methods=ess_trsh_methods, scan=scan, pivots=pivots, occ=occ, initial_trsh=self.initial_trsh, @@ -1487,14 +1487,16 @@ def troubleshoot_ess(self, label, job, level_of_theory, job_type, conformer=-1): self.run_job(label=label, xyz=xyz, level_of_theory=level_of_theory, software=job.software, job_type=job_type, fine=job.fine, trsh=trsh, ess_trsh_methods=job.ess_trsh_methods, conformer=conformer) - elif 'qchem' not in job.ess_trsh_methods and not job.job_type == 'composite': + elif 'qchem' not in job.ess_trsh_methods and not job.job_type == 'composite' and\ + 'qchem' in [ess.lower() for ess in self.ess_settings.keys()]: # Try QChem logging.info('Troubleshooting {type} job using qchem instead of {software}'.format( type=job_type, software=job.software)) job.ess_trsh_methods.append('qchem') self.run_job(label=label, xyz=xyz, level_of_theory=level_of_theory, job_type=job_type, fine=job.fine, software='qchem', ess_trsh_methods=job.ess_trsh_methods, conformer=conformer) - elif 'molpro' not in job.ess_trsh_methods and not job.job_type == 'composite': + elif 'molpro' not in job.ess_trsh_methods and not job.job_type == 'composite' \ + and 'molpro' in [ess.lower() for ess in self.ess_settings.keys()]: # Try molpro logging.info('Troubleshooting {type} job using molpro instead of {software}'.format( type=job_type, software=job.software)) @@ -1544,14 +1546,16 @@ def troubleshoot_ess(self, label, job, level_of_theory, job_type, conformer=-1): level_of_theory = 'b3lyp/6-311++g(d,p)' self.run_job(label=label, xyz=xyz, level_of_theory=level_of_theory, software=job.software, job_type=job_type, fine=job.fine, ess_trsh_methods=job.ess_trsh_methods, conformer=conformer) - elif 'gaussian' not in job.ess_trsh_methods: + elif 'gaussian' not in job.ess_trsh_methods\ + and 'gaussian' in [ess.lower() for ess in self.ess_settings.keys()]: # Try Gaussian logging.info('Troubleshooting {type} job using gaussian instead of {software}'.format( type=job_type, software=job.software)) job.ess_trsh_methods.append('gaussian') self.run_job(label=label, xyz=xyz, level_of_theory=job.level_of_theory, job_type=job_type, fine=job.fine, software='gaussian', ess_trsh_methods=job.ess_trsh_methods, conformer=conformer) - elif 'molpro' not in job.ess_trsh_methods: + elif 'molpro' not in job.ess_trsh_methods \ + and 'molpro' in [ess.lower() for ess in self.ess_settings.keys()]: # Try molpro logging.info('Troubleshooting {type} job using molpro instead of {software}'.format( type=job_type, software=job.software)) @@ -1620,14 +1624,15 @@ def troubleshoot_ess(self, label, job, level_of_theory, job_type, conformer=-1): self.run_job(label=label, xyz=xyz, level_of_theory=job.level_of_theory, software=job.software, job_type=job_type, fine=job.fine, shift=shift, memory=memory, ess_trsh_methods=job.ess_trsh_methods, conformer=conformer) - elif 'gaussian' not in job.ess_trsh_methods: + elif 'gaussian' not in job.ess_trsh_methods\ + and 'gaussian' in [ess.lower() for ess in self.ess_settings.keys()]: # Try Gaussian logging.info('Troubleshooting {type} job using gaussian instead of {software}'.format( type=job_type, software=job.software)) job.ess_trsh_methods.append('gaussian') self.run_job(label=label, xyz=xyz, level_of_theory=job.level_of_theory, job_type=job_type, fine=job.fine, software='gaussian', ess_trsh_methods=job.ess_trsh_methods, conformer=conformer) - elif 'qchem' not in job.ess_trsh_methods: + elif 'qchem' not in job.ess_trsh_methods and 'qchem' in [ess.lower() for ess in self.ess_settings.keys()]: # Try QChem logging.info('Troubleshooting {type} job using qchem instead of {software}'.format( type=job_type, software=job.software)) @@ -1671,7 +1676,7 @@ def restore_running_jobs(self): else: raise SchedulerError('Could not find species {0} in the restart file'.format(spc_label)) conformer = job_description['conformer'] if 'conformer' in job_description else -1 - job = Job(project=self.project, settings=self.settings, species_name=spc_label, + job = Job(project=self.project, ess_settings=self.ess_settings, species_name=spc_label, xyz=job_description['xyz'], job_type=job_description['job_type'], level_of_theory=job_description['level_of_theory'], multiplicity=species.multiplicity, charge=species.charge, fine=job_description['fine'], shift=job_description['shift'], From 85716a23f38ac82b7519e18c63fc246545108767 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 20:11:42 -0400 Subject: [PATCH 19/42] Tests: Adapt `ess_settings` syntax --- arc/job/jobTest.py | 6 +++--- arc/mainTest.py | 21 +++++++++------------ arc/schedulerTest.py | 18 +++++++++--------- arc/species/speciesTest.py | 4 ++-- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/arc/job/jobTest.py b/arc/job/jobTest.py index b74eaeb794..c76357d1f2 100644 --- a/arc/job/jobTest.py +++ b/arc/job/jobTest.py @@ -27,9 +27,9 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None - settings = {'gaussian': 'server1', 'molpro': 'server2', 'qchem': 'server1', 'ssh': False} - cls.job1 = Job(project='project_test', settings=settings, species_name='tst_spc', xyz='C 0.0 0.0 0.0', - job_type='opt', level_of_theory='b3lyp/6-31+g(d)', multiplicity=1, + ess_settings = {'gaussian': ['server1','server2'], 'molpro': ['server2'], 'qchem': ['server1'], 'ssh': False} + cls.job1 = Job(project='project_test', ess_settings=ess_settings, species_name='tst_spc', xyz='C 0.0 0.0 0.0', + job_type='opt', level_of_theory='b3lyp/6-31+g(d)', multiplicity=1, testing=True, project_directory=os.path.join(arc_path, 'Projects', 'project_test'), fine=True, job_num=100) cls.job1.initial_time = datetime.datetime(2019, 3, 15, 19, 53, 7, 0) cls.job1.final_time = datetime.datetime(2019, 3, 15, 19, 53, 8, 0) diff --git a/arc/mainTest.py b/arc/mainTest.py index ee39dd3f0f..af025dbbbc 100644 --- a/arc/mainTest.py +++ b/arc/mainTest.py @@ -17,7 +17,7 @@ from arc.main import ARC from arc.species.species import ARCSpecies -from arc.settings import arc_path +from arc.settings import arc_path, global_ess_settings from arc.arc_exceptions import InputError ################################################################################ @@ -31,15 +31,13 @@ class TestARC(unittest.TestCase): def test_as_dict(self): """Test the as_dict() method of ARC""" self.maxDiff = None - ess_settings = {} spc1 = ARCSpecies(label='spc1', smiles=str('CC'), generate_thermo=False) - arc0 = ARC(project='arc_test', ess_settings=ess_settings, scan_rotors=False, initial_trsh='scf=(NDump=30)', + arc0 = ARC(project='arc_test', scan_rotors=False, initial_trsh='scf=(NDump=30)', arc_species_list=[spc1]) restart_dict = arc0.as_dict() expected_dict = {'composite_method': '', 'conformer_level': 'b97-d3/6-311+g(d,p)', 'ts_guess_level': 'b3lyp/6-31+g(d,p)', - 'ess_settings': {'ssh': True}, 'fine': True, 'opt_level': 'wb97xd/6-311++g(d,p)', 'freq_level': 'wb97xd/6-311++g(d,p)', @@ -60,6 +58,7 @@ def test_as_dict(self): 'use_bac': True, 'visualize_orbitals': True, 'allow_nonisomorphic_2d': False, + 'ess_settings': global_ess_settings, 'species': [{'bond_corrections': {'C-C': 1, 'C-H': 6}, 'arkane_file': None, 'E0': None, @@ -118,7 +117,7 @@ def test_from_dict(self): 'rotors_dict': {}, 'xyzs': []}], 'use_bac': True} - arc1 = ARC(project='wrong', ess_settings=dict()) + arc1 = ARC(project='wrong') project = 'arc_project_for_testing_delete_after_usage1' project_directory = os.path.join(arc_path, 'Projects', project) arc1.from_dict(input_dict=restart_dict, project='testing_from_dict', project_directory=project_directory) @@ -133,15 +132,14 @@ def test_from_dict(self): def test_check_project_name(self): """Test project name invalidity""" - ess_settings = {} with self.assertRaises(InputError): - ARC(project='ar c', ess_settings=ess_settings) + ARC(project='ar c') with self.assertRaises(InputError): - ARC(project='ar:c', ess_settings=ess_settings) + ARC(project='ar:c') with self.assertRaises(InputError): - ARC(project='ar Date: Thu, 28 Mar 2019 20:12:01 -0400 Subject: [PATCH 20/42] Tests: Update to the run_orbitals syntax and new default value --- arc/mainTest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arc/mainTest.py b/arc/mainTest.py index af025dbbbc..bea3cf25ad 100644 --- a/arc/mainTest.py +++ b/arc/mainTest.py @@ -36,7 +36,7 @@ def test_as_dict(self): arc_species_list=[spc1]) restart_dict = arc0.as_dict() expected_dict = {'composite_method': '', - 'conformer_level': 'b97-d3/6-311+g(d,p)', + 'conformer_level': 'b3lyp/6-31+g(d,p)', 'ts_guess_level': 'b3lyp/6-31+g(d,p)', 'fine': True, 'opt_level': 'wb97xd/6-311++g(d,p)', @@ -56,7 +56,7 @@ def test_as_dict(self): 't_max': None, 't_count': None, 'use_bac': True, - 'visualize_orbitals': True, + 'run_orbitals': False, 'allow_nonisomorphic_2d': False, 'ess_settings': global_ess_settings, 'species': [{'bond_corrections': {'C-C': 1, 'C-H': 6}, From 20c5fc5a35c3828e0f8efce1ea99a9f42cbcafa6 Mon Sep 17 00:00:00 2001 From: alongd Date: Thu, 28 Mar 2019 20:15:01 -0400 Subject: [PATCH 21/42] Added ess_settings to settings.py instead of the API and removed precedence from servers dictionary Also modified determine_ess_settings() accordingly --- arc/main.py | 68 +++++++++++++++++++++++++++---------------------- arc/settings.py | 10 +++++++- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/arc/main.py b/arc/main.py index 01c934e2a7..39794224d4 100644 --- a/arc/main.py +++ b/arc/main.py @@ -19,7 +19,8 @@ from rmgpy.reaction import Reaction import arc.rmgdb as rmgdb -from arc.settings import arc_path, default_levels_of_theory, check_status_command, servers, valid_chars +from arc.settings import arc_path, default_levels_of_theory, check_status_command, servers, valid_chars,\ + global_ess_settings from arc.scheduler import Scheduler from arc.arc_exceptions import InputError, SettingsError, SpeciesError from arc.species.species import ARCSpecies @@ -83,13 +84,12 @@ class ARC(object): def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn_list=None, level_of_theory='', conformer_level='', composite_method='', opt_level='', freq_level='', sp_level='', scan_level='', ts_guess_level='', fine=True, generate_conformers=True, scan_rotors=True, use_bac=True, - model_chemistry='', ess_settings=None, initial_trsh=None, t_min=None, t_max=None, t_count=None, + model_chemistry='', initial_trsh=None, t_min=None, t_max=None, t_count=None, run_orbitals=False, verbose=logging.INFO, project_directory=None, max_job_time=120, allow_nonisomorphic_2d=False, - job_memory=1500, run_orbitals=False): + job_memory=1500, ess_settings=None): self.__version__ = '1.0.0' self.verbose = verbose - self.ess_settings = check_ess_settings(ess_settings) self.output = dict() self.running_jobs = dict() self.lib_long_desc = '' @@ -324,7 +324,8 @@ def __init__(self, input_dict=None, project=None, arc_species_list=None, arc_rxn project_directory = project_directory if project_directory is not None\ else os.path.abspath(os.path.dirname(input_dict)) self.from_dict(input_dict=input_dict, project=project, project_directory=project_directory) - if self.ess_settings is None: + self.ess_settings = check_ess_settings(ess_settings or global_ess_settings) + if self.ess_settings is None or not self.ess_settings: self.determine_ess_settings() self.restart_dict = self.as_dict() self.determine_model_chemistry() @@ -346,7 +347,6 @@ def as_dict(self): """ restart_dict = dict() restart_dict['project'] = self.project - restart_dict['ess_settings'] = self.ess_settings restart_dict['fine'] = self.fine restart_dict['generate_conformers'] = self.generate_conformers restart_dict['scan_rotors'] = self.scan_rotors @@ -372,6 +372,7 @@ def as_dict(self): restart_dict['t_count'] = self.t_count restart_dict['max_job_time'] = self.max_job_time restart_dict['allow_nonisomorphic_2d'] = self.allow_nonisomorphic_2d + restart_dict['ess_settings'] = self.ess_settings return restart_dict def from_dict(self, input_dict, project=None, project_directory=None): @@ -394,7 +395,6 @@ def from_dict(self, input_dict, project=None, project_directory=None): self.execution_time = None self.verbose = input_dict['verbose'] if 'verbose' in input_dict else self.verbose self.max_job_time = input_dict['max_job_time'] if 'max_job_time' in input_dict else 5 - self.ess_settings = check_ess_settings(input_dict['ess_settings']) if 'ess_settings' in input_dict else None self.allow_nonisomorphic_2d = input_dict['allow_nonisomorphic_2d']\ if 'allow_nonisomorphic_2d' in input_dict else False self.output = input_dict['output'] if 'output' in input_dict else dict() @@ -424,6 +424,8 @@ def from_dict(self, input_dict, project=None, project_directory=None): self.use_bac = input_dict['use_bac'] if 'use_bac' in input_dict else True self.model_chemistry = input_dict['model_chemistry'] if 'use_bac' in input_dict\ and input_dict['use_bac'] else '' + ess_settings = input_dict['ess_settings'] if 'ess_settings' in input_dict else global_ess_settings + self.ess_settings = check_ess_settings(ess_settings) if not self.fine: logging.info('\n') logging.warning('Not using a fine grid for geometry optimization jobs') @@ -822,7 +824,7 @@ def determine_ess_settings(self, diagnostics=False): and if so the available ESS software and the cluster software of the server if `diagnostics` is True, this method will not raise errors, and will print its findings """ - if self.ess_settings is not None: + if self.ess_settings is not None and not diagnostics: self.ess_settings = check_ess_settings(self.ess_settings) self.ess_settings['ssh'] = True return @@ -876,9 +878,10 @@ def determine_ess_settings(self, diagnostics=False): logging.info('ARC is being excecuted on a PC' ' (did not find "SSH_CONNECTION" in the os.environ dictionary') self.ess_settings['ssh'] = True - logging.info('\n\nExecuting QM jobs remotely. Mapping servers...') + logging.info('\n\nMapping servers...\n\n') # map servers - self.ess_settings['gaussian'], self.ess_settings['qchem'], self.ess_settings['molpro'] = None, None, None + self.ess_settings['gaussian'], self.ess_settings['qchem'],\ + self.ess_settings['molpro'] = list(), list(), list() for server in servers.keys(): if diagnostics: logging.info('Trying {0}'.format(server)) @@ -891,43 +894,46 @@ def determine_ess_settings(self, diagnostics=False): g16, _ = ssh.send_command_to_server(cmd) if g03 or g09 or g16: if diagnostics: - logging.info('Found Gaussian on {3}: g03={0}, g09={1}, g16={2}'.format(g03, g09, g16, server)) - if self.ess_settings['gaussian'] is None or 'precedence' in servers[server]\ - and servers[server]['precedence'] == 'gaussian': - self.ess_settings['gaussian'] = server + logging.info(' Found Gaussian on {3}: g03={0}, g09={1}, g16={2}'.format(g03, g09, g16, server)) + if not self.ess_settings['gaussian']: + self.ess_settings['gaussian'] = [server] + else: + self.ess_settings['gaussian'].append(server) elif diagnostics: - logging.info('Did NOT find Gaussian on {3}: g03={0}, g09={1}, g16={2}'.format(g03, g09, g16, server)) + logging.info(' Did NOT find Gaussian on {3}: g03={0}, g09={1}, g16={2}'.format(g03, g09, g16, server)) cmd = '. ~/.bashrc; which qchem' qchem, _ = ssh.send_command_to_server(cmd) if qchem: if diagnostics: - logging.info('Found QChem on {0}'.format(server)) - if self.ess_settings['qchem'] is None or 'precedence' in servers[server]\ - and servers[server]['precedence'] == 'qchem': - self.ess_settings['qchem'] = server + logging.info(' Found QChem on {0}'.format(server)) + if not self.ess_settings['qchem']: + self.ess_settings['qchem'] = [server] + else: + self.ess_settings['qchem'].append(server) elif diagnostics: - logging.info('Did NOT find QChem on {0}'.format(server)) + logging.info(' Did NOT find QChem on {0}'.format(server)) cmd = '. .bashrc; which molpro' molpro, _ = ssh.send_command_to_server(cmd) if molpro: if diagnostics: - logging.info('Found Molpro on {0}'.format(server)) - if self.ess_settings['molpro'] is None or 'precedence' in servers[server]\ - and servers[server]['precedence'] == 'molpro': - self.ess_settings['molpro'] = server + logging.info(' Found Molpro on {0}'.format(server)) + if not self.ess_settings['molpro']: + self.ess_settings['molpro'] = [server] + else: + self.ess_settings['molpro'].append(server) elif diagnostics: - logging.info('Did NOT find Molpro on {0}'.format(server)) + logging.info(' Did NOT find Molpro on {0}'.format(server)) if diagnostics: logging.info('\n') - if self.ess_settings['gaussian']: + if 'gaussian' in self.ess_settings.keys(): logging.info('Using Gaussian on {0}'.format(self.ess_settings['gaussian'])) - if self.ess_settings['qchem']: + if 'qchem' in self.ess_settings.keys(): logging.info('Using QChem on {0}'.format(self.ess_settings['qchem'])) - if self.ess_settings['molpro']: + if 'molpro' in self.ess_settings.keys(): logging.info('Using Molpro on {0}'.format(self.ess_settings['molpro'])) logging.info('\n') - if not self.ess_settings['gaussian'] and not self.ess_settings['qchem'] and not self.ess_settings['molpro']\ - and not diagnostics: + if 'gaussian' not in self.ess_settings.keys() and 'qchem' not in self.ess_settings.keys()\ + and 'molpro' not in self.ess_settings.keys() and not diagnostics: raise SettingsError('Could not find any ESS. Check your .bashrc definitions on the server.\n' 'Alternatively, you could pass a software-server dictionary to arc as `ess_settings`') elif diagnostics: @@ -1014,7 +1020,7 @@ def check_ess_settings(ess_settings): Also check ESS and servers """ if ess_settings is None: - return None + return dict() settings = dict() for software, server_list in ess_settings.items(): if software != 'ssh': diff --git a/arc/settings.py b/arc/settings.py index a7e9331d68..a25eacc0d1 100644 --- a/arc/settings.py +++ b/arc/settings.py @@ -34,7 +34,6 @@ 'address': 'server1.host.edu', 'un': '', 'key': 'path_to_rsa_key', - 'precedence': 'molpro', }, 'server2': { 'cluster_soft': 'Slurm', # Simple Linux Utility for Resource Management @@ -45,6 +44,15 @@ } } +# List here servers you'd like to associate with specific ESS. +# An ordered list of servers indicates priority +# Keeping this dictionary empty will cause ARC to scan for software on the servers defined above +global_ess_settings = { + 'gaussian': ['server1', 'server2'], + 'molpro': 'server2', + 'qchem': 'server1', +} + # List here (complete or partial) phrases of methods or basis sets you'd like to associate to specific ESS # Avoid ascribing the same phrase to more than one server, this may cause undeterministic assignment of software # Format is levels_ess = {ess: ['phrase1', 'phrase2'], ess2: ['phrase3', 'phrase3']} From c7ab677908ce2b12120b888d7d7f558fc3927326 Mon Sep 17 00:00:00 2001 From: alongd Date: Fri, 29 Mar 2019 11:38:08 -0400 Subject: [PATCH 22/42] Added testing to job --- arc/job/job.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/arc/job/job.py b/arc/job/job.py index c2237e6a6c..d221e24d9e 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -81,7 +81,7 @@ def __init__(self, project, ess_settings, species_name, xyz, job_type, level_of_ charge=0, conformer=-1, fine=False, shift='', software=None, is_ts=False, scan='', pivots=None, memory=1500, comments='', trsh='', scan_trsh='', ess_trsh_methods=None, initial_trsh=None, job_num=None, job_server_name=None, job_name=None, job_id=None, server=None, initial_time=None, occ=None, - max_job_time=120, scan_res=None): + max_job_time=120, scan_res=None, testing=False): self.project = project self.ess_settings = ess_settings self.initial_time = initial_time @@ -102,6 +102,7 @@ def __init__(self, project, ess_settings, species_name, xyz, job_type, level_of_ self.scan_trsh = scan_trsh self.scan_res = scan_res if scan_res is not None else rotor_scan_resolution self.max_job_time = max_job_time + self.testing = testing job_types = ['conformer', 'opt', 'freq', 'optfreq', 'sp', 'composite', 'scan', 'gsm', 'irc', 'ts_guess', 'orbitals'] # the 'conformer' job type is identical to 'opt', but we differentiate them to be identifiable in Scheduler @@ -382,7 +383,7 @@ def write_submit_script(self): os.makedirs(self.local_path) with open(os.path.join(self.local_path, submit_filename[servers[self.server]['cluster_soft']]), 'wb') as f: f.write(self.submit) - if self.ess_settings['ssh']: + if self.ess_settings['ssh'] and not self.testing: self._upload_submit_file() def write_input_file(self): @@ -609,12 +610,13 @@ def write_input_file(self): except KeyError: logging.error('Could not interpret all input file keys in\n{0}'.format(self.input)) raise - if not os.path.exists(self.local_path): - os.makedirs(self.local_path) - with open(os.path.join(self.local_path, input_filename[self.software]), 'wb') as f: - f.write(self.input) - if self.ess_settings['ssh']: - self._upload_input_file() + if not self.testing: + if not os.path.exists(self.local_path): + os.makedirs(self.local_path) + with open(os.path.join(self.local_path, input_filename[self.software]), 'wb') as f: + f.write(self.input) + if self.ess_settings['ssh']: + self._upload_input_file() def _upload_submit_file(self): ssh = SSH_Client(self.server) From 86bd59d7cfa2a596d8c4d053b878a4f698961260 Mon Sep 17 00:00:00 2001 From: alongd Date: Fri, 29 Mar 2019 11:38:49 -0400 Subject: [PATCH 23/42] Tests: Functions in main --- arc/mainTest.py | 153 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 43 deletions(-) diff --git a/arc/mainTest.py b/arc/mainTest.py index bea3cf25ad..597f7e66c1 100644 --- a/arc/mainTest.py +++ b/arc/mainTest.py @@ -9,16 +9,17 @@ import unittest import os import shutil +import time from rmgpy import settings from rmgpy.data.rmg import RMGDatabase from rmgpy.species import Species from rmgpy.molecule.molecule import Molecule -from arc.main import ARC +from arc.main import ARC, read_file, get_git_commit, time_lapse, check_ess_settings from arc.species.species import ARCSpecies -from arc.settings import arc_path, global_ess_settings -from arc.arc_exceptions import InputError +from arc.settings import arc_path, servers +from arc.arc_exceptions import InputError, SettingsError ################################################################################ @@ -28,9 +29,16 @@ class TestARC(unittest.TestCase): Contains unit tests for the ARC class """ + @classmethod + def setUpClass(cls): + """ + A method that is run before all unit tests in this class. + """ + cls.maxDiff = None + cls.servers = [server for server in servers.keys()] + def test_as_dict(self): """Test the as_dict() method of ARC""" - self.maxDiff = None spc1 = ARCSpecies(label='spc1', smiles=str('CC'), generate_thermo=False) arc0 = ARC(project='arc_test', scan_rotors=False, initial_trsh='scf=(NDump=30)', arc_species_list=[spc1]) @@ -52,13 +60,15 @@ def test_as_dict(self): 'scan_level': '', 'scan_rotors': False, 'sp_level': 'ccsd(t)-f12/cc-pvtz-f12', + 'job_memory': 1500, 't_min': None, 't_max': None, 't_count': None, 'use_bac': True, 'run_orbitals': False, 'allow_nonisomorphic_2d': False, - 'ess_settings': global_ess_settings, + 'ess_settings': {'gaussian': ['server1', 'server2'], + 'molpro': ['server2'], 'qchem': ['server1'], 'ssh': True}, 'species': [{'bond_corrections': {'C-C': 1, 'C-H': 6}, 'arkane_file': None, 'E0': None, @@ -82,41 +92,37 @@ def test_as_dict(self): def test_from_dict(self): """Test the from_dict() method of ARC""" restart_dict = {'composite_method': '', - 'conformer_level': 'b97-d3/6-311+g(d,p)', - 'ess_settings': {'gaussian': 'server1', - 'molpro': 'server1', - 'qchem': u'server1', - 'ssh': True}, - 'fine': True, - 'freq_level': 'wb97x-d3/6-311+g(d,p)', - 'generate_conformers': True, - 'initial_trsh': 'scf=(NDump=30)', - 'model_chemistry': 'ccsd(t)-f12/cc-pvtz-f12', - 'opt_level': 'wb97x-d3/6-311+g(d,p)', - 'output': {}, - 'project': 'arc_test', - 'rxn_list': [], - 'scan_level': '', - 'scan_rotors': False, - 'sp_level': 'ccsdt-f12/cc-pvqz-f12', - 'species': [{'bond_corrections': {'C-C': 1, 'C-H': 6}, - 'charge': 1, - 'conformer_energies': [], - 'conformers': [], - 'external_symmetry': 1, - 'final_xyz': '', - 'generate_thermo': False, - 'is_ts': False, - 'label': 'testing_spc1', - 'mol': '1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S}\n2 C u0 p0 c0 {1,S} {6,S} {7,S} {8,S}\n3 H u0 p0 c0 {1,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {2,S}\n7 H u0 p0 c0 {2,S}\n8 H u0 p0 c0 {2,S}\n', - 'multiplicity': 1, - 'neg_freqs_trshed': [], - 'number_of_rotors': 0, - 'opt_level': '', - 'optical_isomers': 1, - 'rotors_dict': {}, - 'xyzs': []}], - 'use_bac': True} + 'conformer_level': 'b97-d3/6-311+g(d,p)', + 'fine': True, + 'freq_level': 'wb97x-d3/6-311+g(d,p)', + 'generate_conformers': True, + 'initial_trsh': 'scf=(NDump=30)', + 'model_chemistry': 'ccsd(t)-f12/cc-pvtz-f12', + 'opt_level': 'wb97x-d3/6-311+g(d,p)', + 'output': {}, + 'project': 'arc_test', + 'rxn_list': [], + 'scan_level': '', + 'scan_rotors': False, + 'sp_level': 'ccsdt-f12/cc-pvqz-f12', + 'species': [{'bond_corrections': {'C-C': 1, 'C-H': 6}, + 'charge': 1, + 'conformer_energies': [], + 'conformers': [], + 'external_symmetry': 1, + 'final_xyz': '', + 'generate_thermo': False, + 'is_ts': False, + 'label': 'testing_spc1', + 'mol': '1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S}\n2 C u0 p0 c0 {1,S} {6,S} {7,S} {8,S}\n3 H u0 p0 c0 {1,S}\n4 H u0 p0 c0 {1,S}\n5 H u0 p0 c0 {1,S}\n6 H u0 p0 c0 {2,S}\n7 H u0 p0 c0 {2,S}\n8 H u0 p0 c0 {2,S}\n', + 'multiplicity': 1, + 'neg_freqs_trshed': [], + 'number_of_rotors': 0, + 'opt_level': '', + 'optical_isomers': 1, + 'rotors_dict': {}, + 'xyzs': []}], + 'use_bac': True} arc1 = ARC(project='wrong') project = 'arc_project_for_testing_delete_after_usage1' project_directory = os.path.join(arc_path, 'Projects', project) @@ -194,7 +200,7 @@ def test_restart(self): rtm = True elif 'Loading the RMG database...' in line: ldb = True - elif 'Thermodynamics for H2O2:' in line: + elif 'Thermodynamics for H2O2' in line: therm = True elif 'Sources of thermoproperties determined by RMG for the parity plots:' in line: src = True @@ -214,8 +220,13 @@ def test_restart(self): with open(os.path.join(project_directory, 'output', 'Species', 'H2O2', 'species_dictionary.txt'), 'r') as f: lines = f.readlines() - adj_list = str(''.join([line for line in lines if (line and 'H2O2' not in line)])) - mol1 = Molecule().fromAdjacencyList(adj_list) + adj_list = '' + for line in lines: + if 'H2O2' not in line: + adj_list += line + if line == '\n': + break + mol1 = Molecule().fromAdjacencyList(str(adj_list)) self.assertEqual(mol1.toSMILES(), str('OO')) thermo_library_path = os.path.join(project_directory, 'output', 'RMG libraries', 'thermo', @@ -250,6 +261,62 @@ def test_restart(self): # delete the generated library from RMG-database os.remove(new_thermo_library_path) + def test_check_ess_settings(self): + """Test the check_ess_settings function""" + ess_settings1 = {'gaussian': [self.servers[0]], 'molpro': [self.servers[1], self.servers[0]], + 'qchem': [self.servers[0]], 'ssh': False} + ess_settings2 = {'gaussian': self.servers[0], 'molpro': self.servers[1], 'qchem': self.servers[0]} + ess_settings3 = {'gaussian': self.servers[0], 'molpro': [self.servers[1], self.servers[0]], + 'qchem': self.servers[0]} + ess_settings4 = {'gaussian': self.servers[0], 'molpro': self.servers[1], 'qchem': self.servers[0], 'ssh': False} + + ess_settings1 = check_ess_settings(ess_settings1) + ess_settings2 = check_ess_settings(ess_settings2) + ess_settings3 = check_ess_settings(ess_settings3) + ess_settings4 = check_ess_settings(ess_settings4) + + ess_list = [ess_settings1, ess_settings2, ess_settings3, ess_settings4] + + for ess in ess_list: + for soft, server_list in ess.items(): + self.assertTrue(soft in ['gaussian', 'molpro', 'qchem', 'ssh']) + self.assertIsInstance(server_list, (list, bool)) + + with self.assertRaises(SettingsError): + ess_settings5 = {'nosoft': ['server1']} + check_ess_settings(ess_settings5) + with self.assertRaises(SettingsError): + ess_settings6 = {'gaussian': ['noserver']} + check_ess_settings(ess_settings6) + + def test_time_lapse(self): + """Test the time_lapse() function""" + t0 = time.time() + time.sleep(2) + lap = time_lapse(t0) + self.assertEqual(lap, '00:00:02') + + def test_get_git_commit(self): + """Test the get_git_commit() function""" + git_commit = get_git_commit() + # output format: ['fafdb957049917ede565cebc58b29899f597fb5a', 'Fri Mar 29 11:09:50 2019 -0400'] + self.assertEqual(len(git_commit[0]), 40) + self.assertEqual(len(git_commit[1].split()), 6) + + def test_read_file(self): + """Test the read_file() function""" + restart_path = os.path.join(arc_path, 'arc', 'testing', 'restart(H,H2O2,N2H3,CH3CO2).yml') + input_dict = read_file(restart_path) + self.assertIsInstance(input_dict, dict) + self.assertTrue('reactions' in input_dict) + self.assertTrue('freq_level' in input_dict) + self.assertTrue('use_bac' in input_dict) + self.assertTrue('ts_guess_level' in input_dict) + self.assertTrue('running_jobs' in input_dict) + + with self.assertRaises(InputError): + read_file('nopath') + @classmethod def tearDownClass(cls): """ From 3c2c7e5d51f1631358915b68f15157d592c65d65 Mon Sep 17 00:00:00 2001 From: alongd Date: Fri, 29 Mar 2019 11:51:11 -0400 Subject: [PATCH 24/42] Changed default levels of theory for conformers and orbitals Also minor line relocation in rotor settings --- arc/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arc/settings.py b/arc/settings.py index a25eacc0d1..2e4a007aed 100644 --- a/arc/settings.py +++ b/arc/settings.py @@ -90,13 +90,13 @@ 'molpro': 'input.out', } -default_levels_of_theory = {'conformer': 'b97-d3/6-311+g(d,p)', +default_levels_of_theory = {'conformer': 'b3lyp/6-31+g(d,p)', 'ts_guesses': 'b3lyp/6-31+g(d,p)', # used for IRC as well 'opt': 'wb97xd/6-311++g(d,p)', 'freq': 'wb97xd/6-311++g(d,p)', # should be the same level as opt 'sp': 'ccsd(t)-f12/cc-pvtz-f12', # This should be a level for which BAC is available # 'sp': 'b3lyp/6-311+g(3df,2p)', - 'orbitals': 'b3lyp/6-311++g(3df,3pd)', # save orbitals for visualization + 'orbitals': 'b3lyp/6-311+g(d,p)', # save orbitals for visualization 'scan': 'b3lyp/6-311+g(d,p)', 'scan_for_composite': 'B3LYP/CBSB7', # This is the frequency level of the CBS-QB3 method 'freq_for_composite': 'B3LYP/CBSB7', # This is the frequency level of the CBS-QB3 method @@ -114,8 +114,8 @@ rotor_scan_resolution = 8.0 # degrees. Default: 8.0 # rotor validation parameters +maximum_barrier = 40 # a rotor threshold (kJ/mol) above which the rotor is not considered. Default: 40 (~10 kcal/mol) +minimum_barrier = 0.5 # a rotor threshold (kJ/mol) below which it is considered a FreeRotor. Default: 0.5 kJ/mol inconsistency_az = 5 # maximum allowed inconsistency (kJ/mol) between initial and final rotor scan points. Default: 5 inconsistency_ab = 0.5 # maximum allowed inconsistency between consecutive points in the scan given as a fraction # of the maximum scan energy. Default: 50% -maximum_barrier = 40 # a rotor threshold (kJ/mol) above which the rotor is not considered. Default: 40 (~10 kcal/mol) -minimum_barrier = 0.5 # a rotor threshold (kJ/mol) below which it is considered a FreeRotor. Default: 0.5 kJ/mol From ed057449a890460cd3cc5a6c5477c26026a93959 Mon Sep 17 00:00:00 2001 From: alongd Date: Fri, 29 Mar 2019 14:45:18 -0400 Subject: [PATCH 25/42] Added number_of_atoms as a property of Species --- arc/species/species.py | 57 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/arc/species/species.py b/arc/species/species.py index 4d11d57e8f..567548d662 100644 --- a/arc/species/species.py +++ b/arc/species/species.py @@ -117,7 +117,7 @@ def __init__(self, is_ts=False, rmg_species=None, mol=None, label=None, xyz=None self.thermo = None self.rmg_thermo = None self.rmg_kinetics = None - self.number_of_atoms = None + self._number_of_atoms = None self.mol = mol self.mol_list = None self.multiplicity = multiplicity @@ -223,17 +223,14 @@ def __init__(self, is_ts=False, rmg_species=None, mol=None, label=None, xyz=None if self.final_xyz or self.initial_xyz: self.mol_from_xyz() # Generate bond list for applying bond corrections - if not self.bond_corrections: + if not self.bond_corrections and self.mol is not None: self.bond_corrections = self.mol.enumerate_bonds() if self.bond_corrections: self.long_thermo_description += 'Bond corrections: {0}\n'.format(self.bond_corrections) - if self.mol is not None: - self.number_of_atoms = len(self.mol.atoms) - if self.mol_list is None: - mol_copy = self.mol.copy(deep=True) - self.mol_list = mol_copy.generate_resonance_structures(keep_isomorphic=False, - filter_structures=True) + if self.mol is not None and self.mol_list is None: + mol_copy = self.mol.copy(deep=True) + self.mol_list = mol_copy.generate_resonance_structures(keep_isomorphic=False, filter_structures=True) elif not self.bond_corrections and self.generate_thermo: logging.warning('Cannot determine bond additivity corrections (BAC) for species {0} based on xyz' ' coordinates only. For better thermoproperties, provide bond corrections.') @@ -254,7 +251,7 @@ def __init__(self, is_ts=False, rmg_species=None, mol=None, label=None, xyz=None self.charge = 0 if self.multiplicity is not None and self.multiplicity < 1: raise SpeciesError('Multiplicity for species {0} is lower than 1. Got: {1}'.format( - self.label, multiplicity)) + self.label, self.multiplicity)) if not isinstance(self.multiplicity, int) and self.multiplicity is not None: raise SpeciesError('Multiplicity for species {0} is not an integer. Got: {1}, a {2}'.format( self.label, self.multiplicity, type(self.multiplicity))) @@ -271,6 +268,26 @@ def __init__(self, is_ts=False, rmg_species=None, mol=None, label=None, xyz=None if char not in valid_chars: raise SpeciesError('Species label {0} contains an invalid character: "{1}"'.format(self.label, char)) + @property + def number_of_atoms(self): + """The number of atoms in the species""" + if self._number_of_atoms is None: + if self.mol is not None: + self._number_of_atoms = len(self.mol.atoms) + elif self.final_xyz or self.initial_xyz: + xyz = self.final_xyz or self.initial_xyz + self._number_of_atoms = len(xyz.splitlines()) + elif self.is_ts: + for ts_guess in self.ts_guesses: + if ts_guess.xyz is not None: + self._number_of_atoms = len(ts_guess.xyz.splitlines()) + return self._number_of_atoms + + @number_of_atoms.setter + def number_of_atoms(self, value): + """Allow setting number of atoms, e.g. a TS might not have Molecule or xyz when initialized""" + self._number_of_atoms = value + def as_dict(self): """A helper function for dumping this object as a dictionary in a YAML file for restarting ARC""" species_dict = dict() @@ -406,7 +423,6 @@ def from_dict(self, species_dict): self.bond_corrections = self.mol.enumerate_bonds() if self.bond_corrections: self.long_thermo_description += 'Bond corrections: {0}\n'.format(self.bond_corrections) - self.number_of_atoms = len(self.mol.atoms) if self.multiplicity is None: self.multiplicity = self.mol.multiplicity if self.charge is None: @@ -657,15 +673,15 @@ def determine_symmetry(self): .format(self.label, self.external_symmetry, symmetry)) def determine_multiplicity(self, smiles, adjlist, mol): - if mol: + if mol is not None and mol.multiplicity >= 1: self.multiplicity = mol.multiplicity elif adjlist: - mol = Molecule().fromAdjacencyList(adjlist) + mol = Molecule().fromAdjacencyList(str(adjlist)) self.multiplicity = mol.multiplicity elif self.mol is not None and self.mol.multiplicity >= 1: self.multiplicity = self.mol.multiplicity elif smiles: - mol = Molecule(SMILES=smiles) + mol = Molecule(SMILES=str(smiles)) self.multiplicity = mol.multiplicity elif self.initial_xyz is not None: _, atoms, _, _, _ = get_xyz_matrix(self.initial_xyz) @@ -679,8 +695,10 @@ def determine_multiplicity(self, smiles, adjlist, mol): raise SpeciesError('Could not identify atom symbol {0}'.format(atom)) if electrons % 2 == 1: self.multiplicity = 2 + logging.warning('Assuming a multiplicity of 2 for species {0}'.format(self.label)) else: self.multiplicity = 1 + logging.warning('Assuming a multiplicity of 1 for species {0}'.format(self.label)) if self.multiplicity is None: raise SpeciesError('Could not determine multiplicity for species {0}'.format(self.label)) @@ -693,10 +711,7 @@ def is_linear(self): return False if self.number_of_atoms == 2: return True - if self.final_xyz: - xyz = self.final_xyz - else: - xyz = self.initial_xyz + xyz = self.final_xyz or self.initial_xyz if not xyz: raise SpeciesError('Cannot determine linearity for {0} without the initial/final xyz coordinates'.format( self.label)) @@ -795,14 +810,6 @@ def is_linear(self): return True return False - def determine_number_of_atoms_from_xyz(self): - """ - A helper function for determining the number of atoms from the XYZ geometry - Useful for TSs where a 2D geometry isn't known - """ - _, atoms, _, _, _ = get_xyz_matrix(self.initial_xyz) - self.number_of_atoms = len(atoms) - def make_ts_report(self): """A helper function to write content into the .ts_report attribute""" self.ts_report = '' From 84f708363359fd92506726eb8974eb727dd762d8 Mon Sep 17 00:00:00 2001 From: alongd Date: Fri, 29 Mar 2019 14:45:35 -0400 Subject: [PATCH 26/42] Tests: Species number of atoms --- arc/species/speciesTest.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arc/species/speciesTest.py b/arc/species/speciesTest.py index bd56fec929..4a03abd655 100644 --- a/arc/species/speciesTest.py +++ b/arc/species/speciesTest.py @@ -574,6 +574,30 @@ def test_append_conformers(self): self.assertEqual(spc3.conformers[2], xyzs[2]) self.assertEqual(spc3.conformer_energies[2], energies[2]) + def test_the_number_of_atoms_property(self): + """Test that the number_of_atoms property functions correctly""" + self.assertEqual(self.spc1.number_of_atoms, 6) + self.assertEqual(self.spc2.number_of_atoms, 2) + self.assertEqual(self.spc3.number_of_atoms, 7) + self.assertEqual(self.spc4.number_of_atoms, 9) + self.assertEqual(self.spc5.number_of_atoms, 6) + self.assertEqual(self.spc6.number_of_atoms, 8) + self.assertEqual(self.spc7.number_of_atoms, 24) + self.assertEqual(self.spc8.number_of_atoms, 5) + self.assertEqual(self.spc9.number_of_atoms, 2) + + xyz10 = """N 0.82269400 0.19834500 -0.33588000 +C -0.57469800 -0.02442800 0.04618900 +H -1.08412400 -0.56416500 -0.75831900 +H -0.72300600 -0.58965300 0.98098100 +H -1.07482500 0.94314300 0.15455500 +H 1.31266200 -0.68161600 -0.46770200 +H 1.32129900 0.71837500 0.38017700 + +""" + spc10 = ARCSpecies(label='spc10', xyz=xyz10) + self.assertEqual(spc10.number_of_atoms, 7) + @classmethod def tearDownClass(cls): """ From 716d0e0654388438657c811503d97d6e4e4a7e57 Mon Sep 17 00:00:00 2001 From: alongd Date: Sat, 30 Mar 2019 22:29:34 -0400 Subject: [PATCH 27/42] Minor: Chnaged syntax of input_dict['verbose'] in ARC.py --- ARC.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ARC.py b/ARC.py index d5a4cc6752..0e8e099287 100644 --- a/ARC.py +++ b/ARC.py @@ -63,10 +63,7 @@ def main(): verbose = logging.DEBUG elif args.quiet: verbose = logging.WARNING - try: - input_dict['verbose'] - except KeyError: - input_dict['verbose'] = verbose + input_dict['verbose'] = input_dict['verbose'] if 'verbose' in input_dict else verbose arc_object = ARC(input_dict=input_dict, project_directory=project_directory) arc_object.execute() From 1e26db356a2f777c493a10610387b7fbb3372920 Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 20:44:28 -0400 Subject: [PATCH 28/42] Minor: Enhanced error and debug messages in Job --- arc/job/job.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/arc/job/job.py b/arc/job/job.py index d221e24d9e..1156b79667 100644 --- a/arc/job/job.py +++ b/arc/job/job.py @@ -130,14 +130,15 @@ def __init__(self, project, ess_settings, species_name, xyz, job_type, level_of_ # currently we only have a script to print orbitals on QChem, # could/should definately be elaborated to additional ESS if 'qchem' not in self.ess_settings.keys(): - logging.debug('Could not find the QChem software to compute molecular orbitals') + logging.debug('Could not find the QChem software to compute molecular orbitals.\n' + 'ess_settings is:\n{0}'.format(self.ess_settings)) self.software = None else: self.software = 'qchem' elif job_type == 'composite': if 'gaussian' not in self.ess_settings.keys(): - raise JobError('Could not find the Gaussian software to run the composite method {0}'.format( - self.method)) + raise JobError('Could not find the Gaussian software to run the composite method {0}.\n' + 'ess_settings is:\n{1}'.format(self.ess_settings, self.method)) self.software = 'gaussian' else: # use the levels_ess dictionary from settings.py: @@ -355,7 +356,8 @@ def write_completed_job_to_csv_file(self): def write_submit_script(self): un = servers[self.server]['un'] # user name - if self.max_job_time > 9999 or self.max_job_time == 0: + if self.max_job_time > 9999 or self.max_job_time <= 0: + logging.debug('Setting max_job_time to 120 hours') self.max_job_time = 120 if t_max_format[servers[self.server]['cluster_soft']] == 'days': # e.g., 5-0:00:00 @@ -365,7 +367,8 @@ def write_submit_script(self): # e.g., 120:00:00 t_max = '{0}:00:00'.format(self.max_job_time) else: - raise JobError('Could not determine format for maximal job time') + raise JobError('Could not determine format for maximal job time.\n Format is determined by {0}, but ' + 'got {1} for {2}'.format(t_max_format, servers[self.server]['cluster_soft'], self.server)) cpus = servers[self.server]['cpus'] if 'cpus' in servers[self.server] else 8 architecture = '' if self.server.lower() == 'pharos': From 25d35f678046a389f416964ea70feacb86aa518f Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 20:48:55 -0400 Subject: [PATCH 29/42] Corrected default values for max_job_time and job_memory in main.py when loading from a dictionary. (Also added job_memory to the input file) --- arc/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arc/main.py b/arc/main.py index 39794224d4..f2458b3712 100644 --- a/arc/main.py +++ b/arc/main.py @@ -373,6 +373,7 @@ def as_dict(self): restart_dict['max_job_time'] = self.max_job_time restart_dict['allow_nonisomorphic_2d'] = self.allow_nonisomorphic_2d restart_dict['ess_settings'] = self.ess_settings + restart_dict['job_memory'] = self.memory return restart_dict def from_dict(self, input_dict, project=None, project_directory=None): @@ -394,7 +395,8 @@ def from_dict(self, input_dict, project=None, project_directory=None): self.t0 = time.time() # init time self.execution_time = None self.verbose = input_dict['verbose'] if 'verbose' in input_dict else self.verbose - self.max_job_time = input_dict['max_job_time'] if 'max_job_time' in input_dict else 5 + self.max_job_time = input_dict['max_job_time'] if 'max_job_time' in input_dict else self.max_job_time + self.memory = input_dict['job_memory'] if 'job_memory' in input_dict else self.memory self.allow_nonisomorphic_2d = input_dict['allow_nonisomorphic_2d']\ if 'allow_nonisomorphic_2d' in input_dict else False self.output = input_dict['output'] if 'output' in input_dict else dict() From 433067b13e05589d54a240bb6b4efc15db1abc78 Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 20:49:51 -0400 Subject: [PATCH 30/42] Run statemech in Processor using a single method Also output a species YAML file w/o BAC iff BAC is used for thermo --- arc/job/inputs.py | 2 +- arc/processor.py | 104 ++++++++++++++++++++++++++-------------------- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/arc/job/inputs.py b/arc/job/inputs.py index 695c3fb841..71a37dda2e 100644 --- a/arc/job/inputs.py +++ b/arc/job/inputs.py @@ -113,7 +113,7 @@ """, - 'arkane_species': """#!/usr/bin/env python + 'arkane_input_species': """#!/usr/bin/env python # -*- coding: utf-8 -*- linear = {linear}{bonds} diff --git a/arc/processor.py b/arc/processor.py index ab24b7ef4b..ed14ab8b2f 100644 --- a/arc/processor.py +++ b/arc/processor.py @@ -7,7 +7,7 @@ import logging from random import randint -from arkane.input import species as arkane_species, transitionState as arkane_transition_state,\ +from arkane.input import species as arkane_input_species, transitionState as arkane_transition_state,\ reaction as arkane_reaction from arkane.statmech import StatMechJob, assign_frequency_scale_factor from arkane.thermo import ThermoJob @@ -82,7 +82,8 @@ def _generate_arkane_species_file(self, species): output_dir = os.path.join(self.project_directory, 'output', folder_name, species.label) if not os.path.isdir(output_dir): os.makedirs(output_dir) - output_file_path = os.path.join(output_dir, species.label + '_arkane_output.py') + output_file_path = [os.path.join(output_dir, species.label + '_arkane_output.py'), + os.path.join(output_dir, species.label + '_arkane_output_no_BAC.py')] if os.path.isfile(os.path.join(output_dir, 'species_dictionary.txt')): os.remove(os.path.join(output_dir, 'species_dictionary.txt')) @@ -153,7 +154,7 @@ def _generate_arkane_species_file(self, species): # write the Arkane species input file input_file_path = os.path.join(self.project_directory, 'output', folder_name, species.label, '{0}_arkane_input.py'.format(species.label)) - input_file = input_files['arkane_species'] + input_file = input_files['arkane_input_species'] if self.use_bac and not species.is_ts: logging.info('Using the following BAC for {0}: {1}'.format(species.label, species.bond_corrections)) bonds = '\n\nbonds = {0}'.format(species.bond_corrections) @@ -180,32 +181,37 @@ def process(self): unique_arkane_species_label = False while not unique_arkane_species_label: try: - arkane_spc = arkane_species(str(species.label), species.arkane_file) + arkane_spc = arkane_input_species(str(species.label), species.arkane_file) except ValueError: species.label += '_' + str(randint(0, 999)) else: unique_arkane_species_label = True + species.rmg_species = Species(molecule=[species.mol]) + species.rmg_species.reactive = True if species.mol_list: arkane_spc.molecule = species.mol_list - stat_mech_job = StatMechJob(arkane_spc, species.arkane_file) - stat_mech_job.applyBondEnergyCorrections = self.use_bac - stat_mech_job.modelChemistry = self.model_chemistry - stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor(self.model_chemistry) - try: - stat_mech_job.execute(outputFile=output_file_path, plot=False) - except Exception: + species.rmg_species.molecule = species.mol_list # add resonance structures for thermo determination + statmech_success = self._run_statmech(arkane_spc, species.arkane_file, output_file_path[0], + use_bac=self.use_bac) + if not statmech_success: continue + if species.generate_thermo: thermo_job = ThermoJob(arkane_spc, 'NASA') - thermo_job.execute(outputFile=output_file_path, plot=False) + thermo_job.execute(outputFile=output_file_path[0], plot=False) species.thermo = arkane_spc.getThermoData() - plotter.log_thermo(species.label, path=output_file_path) + plotter.log_thermo(species.label, path=output_file_path[0]) species_for_thermo_lib.append(species) - - species.rmg_species = Species(molecule=[species.mol]) - species.rmg_species.reactive = True - if species.mol_list: - species.rmg_species.molecule = species.mol_list # add resonance structures for thermo determination + if self.use_bac and self.model_chemistry: + # If BAC was used, save another Arkane YAML file of this species with no BAC, so it can be used + # for further rate calculations if needed (where the conformer.E0 has no BAC) + statmech_success = self._run_statmech(arkane_spc, species.arkane_file, output_file_path[1], + use_bac=False) + if statmech_success: + arkane_spc.label += str('_no_BAC') + arkane_spc.thermo = None # otherwise thermo won't be calculated, although we don't really care + thermo_job = ThermoJob(arkane_spc, 'NASA') + thermo_job.execute(outputFile=output_file_path[1], plot=False) try: species.rmg_thermo = self.rmgdb.thermo.getThermoData(species.rmg_species) except ValueError: @@ -224,34 +230,16 @@ def process(self): self.copy_freq_output_for_ts(species.label) success = True rxn_list_for_kinetics_plots.append(rxn) - output_file_path = self._generate_arkane_species_file(species) + output_file_path = self._generate_arkane_species_file(species)[0] arkane_ts = arkane_transition_state(str(species.label), species.arkane_file) arkane_spc_dict[species.label] = arkane_ts - stat_mech_job = StatMechJob(arkane_ts, species.arkane_file) - stat_mech_job.applyBondEnergyCorrections = False - if self.model_chemistry: - stat_mech_job.modelChemistry = self.model_chemistry - else: - stat_mech_job.applyAtomEnergyCorrections = False - stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor(self.model_chemistry) - stat_mech_job.execute(outputFile=None, plot=False) + self._run_statmech(arkane_ts, species.arkane_file, kinetics=True) for spc in rxn.r_species + rxn.p_species: if spc.label not in arkane_spc_dict.keys(): # add an extra character to the arkane_species label to distinguish between species calculated # for thermo and species calculated for kinetics (where we don't want to use BAC) - arkane_spc = arkane_species(str(spc.label + '_'), spc.arkane_file) - stat_mech_job = StatMechJob(arkane_spc, spc.arkane_file) - arkane_spc_dict[spc.label] = arkane_spc - stat_mech_job.applyBondEnergyCorrections = False - if self.model_chemistry: - stat_mech_job.modelChemistry = self.model_chemistry - else: - stat_mech_job.applyAtomEnergyCorrections = False - stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor(self.model_chemistry) - stat_mech_job.execute(outputFile=None, plot=False) - # thermo_job = ThermoJob(arkane_spc, 'NASA') - # thermo_job.execute(outputFile=None, plot=False) - # arkane_spc.thermo = arkane_spc.getThermoData() + arkane_spc = arkane_input_species(str(spc.label + '_'), spc.arkane_file) + self._run_statmech(arkane_spc, spc.arkane_file, kinetics=True) rxn.dh_rxn298 = sum([product.thermo.getEnthalpy(298) for product in arkane_spc_dict.values() if product.label in rxn.products])\ - sum([reactant.thermo.getEnthalpy(298) for reactant in arkane_spc_dict.values() @@ -295,9 +283,35 @@ def process(self): plotter.save_kinetics_lib(rxn_list=rxn_list_for_kinetics_plots, path=libraries_path, name=self.project, lib_long_desc=self.lib_long_desc) - self.clean_output_directory() + self._clean_output_directory() + + def _run_statmech(self, arkane_spc, arkane_file, output_file_path=None, use_bac=False, kinetics=False, plot=False): + """ + A helper function for running an Arkane statmech job + `arkane_spc` is the species() function from Arkane's input.py + `arkane_file` is the Arkane species file (either .py or YAML form) + `output_file_path` is a path to the Arkane output.py file + `use_bac` is a bool flag indicating whether or not to use bond additivity corrections + `kinetics` is a bool flag indicating whether this specie sis part of a kinetics job, in which case..?? + `plot` is a bool flag indicating whether or not to plot a PDF of the calculated thermo properties + """ + success = True + stat_mech_job = StatMechJob(arkane_spc, arkane_file) + stat_mech_job.applyBondEnergyCorrections = use_bac and not kinetics and self.model_chemistry + if not kinetics or kinetics and self.model_chemistry: + # currently we have to use a model chemistry for thermo + stat_mech_job.modelChemistry = self.model_chemistry + else: + # if this is a klinetics computation and we don't have a valid model chemistry, don't bother about it + stat_mech_job.applyAtomEnergyCorrections = False + stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor(self.model_chemistry) + try: + stat_mech_job.execute(outputFile=output_file_path, plot=plot) + except Exception: + success = False + return success - def clean_output_directory(self): + def _clean_output_directory(self): """ A helper function to organize the output directory - remove redundant rotor.txt files (from kinetics jobs) @@ -328,9 +342,9 @@ def clean_output_directory(self): if os.path.exists(os.path.join(species_path, 'species')): # This is where Arkane saves the YAML file species_yaml_files = os.listdir(os.path.join(species_path, 'species')) if species_yaml_files: - species_yaml_file = species_yaml_files[0] - shutil.move(src=os.path.join(species_path, 'species', species_yaml_file), - dst=os.path.join(species_path, species_yaml_file)) + for yml_file in species_yaml_files: + shutil.move(src=os.path.join(species_path, 'species', yml_file), + dst=os.path.join(species_path, yml_file)) shutil.rmtree(os.path.join(species_path, 'species')) def copy_freq_output_for_ts(self, label): From e183ea15e965800b6cd32cab169bfb737be8c539 Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 20:50:51 -0400 Subject: [PATCH 31/42] Dont run opt if `geo` is present in the output dictionary --- arc/scheduler.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arc/scheduler.py b/arc/scheduler.py index d56fa83f6e..a57df883cf 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -294,7 +294,8 @@ def __init__(self, project, ess_settings, species_list, composite_method, confor and 'composite' not in self.job_dict[species.label]: self.run_freq_job(species.label) elif 'opt converged' not in self.output[species.label]['status']\ - and 'opt' not in self.job_dict[species.label] and not self.composite_method: + and 'opt' not in self.job_dict[species.label] and not self.composite_method \ + and 'geo' not in self.output[species.label]: self.run_opt_job(species.label) elif 'opt converged' in self.output[species.label]['status']: # opt is done @@ -863,7 +864,7 @@ def determine_most_likely_ts_conformer(self, label): if tsg.success: # 0.000239006 is the conversion factor from J/mol to kcal/mol tsg.energy = (self.species_dict[label].conformer_energies[tsg.index] - e_min) * 0.000239006 - logging.info('{0}. Method: {1}, relative energy: {2} kcal/mol, execution time: {3}'.format( + logging.info('{0}. Method: {1}, relative energy: {2} kcal/mol, guess execution time: {3}'.format( tsg.index, tsg.method, tsg.energy, tsg.execution_time)) # for TSs, only use `draw_3d()`, not `show_sticks()` which gets connectivity wrong: plotter.draw_3d(xyz=tsg.xyz) From 6579582a193be8c3cb9e30868015bb4b9390fa6a Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 20:51:55 -0400 Subject: [PATCH 32/42] Don't crush ARC if cannot order atoms --- arc/species/converter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arc/species/converter.py b/arc/species/converter.py index d25627508d..283b038b1c 100644 --- a/arc/species/converter.py +++ b/arc/species/converter.py @@ -298,7 +298,11 @@ def order_atoms_in_mol_list(ref_mol, mol_list): """Order the atoms in all molecules of mol_list by the atom order in ref_mol""" if mol_list is not None: for mol in mol_list: - order_atoms(ref_mol, mol) + try: # TODO: flag as unordered (or solve) + order_atoms(ref_mol, mol) + except SanitizationError as e: + logging.warning('Could not order atoms in\n{0}\nGot the following error:' + '\n{1}'.format(mol.toAdjacencyList, e)) def order_atoms(ref_mol, mol): From 0db95a90accbfa2eb26295b0d44e3a18020d75af Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 20:53:16 -0400 Subject: [PATCH 33/42] Correctly assing S/D BAC to aromatic species using their Kekulized form --- arc/species/species.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/arc/species/species.py b/arc/species/species.py index 567548d662..c79dadf7ed 100644 --- a/arc/species/species.py +++ b/arc/species/species.py @@ -23,6 +23,7 @@ from rmgpy.reaction import Reaction from rmgpy.species import Species from rmgpy.statmech import NonlinearRotor, LinearRotor +from rmgpy.molecule.resonance import generate_kekule_structure from arc.arc_exceptions import SpeciesError, RotorError, InputError, TSError from arc.settings import arc_path, default_ts_methods, valid_chars, minimum_barrier @@ -224,7 +225,7 @@ def __init__(self, is_ts=False, rmg_species=None, mol=None, label=None, xyz=None self.mol_from_xyz() # Generate bond list for applying bond corrections if not self.bond_corrections and self.mol is not None: - self.bond_corrections = self.mol.enumerate_bonds() + self.bond_corrections = enumerate_bonds(self.mol) if self.bond_corrections: self.long_thermo_description += 'Bond corrections: {0}\n'.format(self.bond_corrections) @@ -420,7 +421,7 @@ def from_dict(self, species_dict): self.mol_from_xyz(xyz) if self.mol is not None: if 'bond_corrections' not in species_dict: - self.bond_corrections = self.mol.enumerate_bonds() + self.bond_corrections = enumerate_bonds(self.mol) if self.bond_corrections: self.long_thermo_description += 'Bond corrections: {0}\n'.format(self.bond_corrections) if self.multiplicity is None: @@ -1407,3 +1408,15 @@ def check_species_xyz(xyz): xyz = parse_xyz_from_file(xyz) return standardize_xyz_string(xyz) return None + +def enumerate_bonds(mol): + """ + A helper function for calling Molecule.enumerate_bonds + First, get the Kekulized molecule (get the Kekule version with alternating single and double bonds if the molecule + is aromatic), since we don't have implementation for aromatic bond additivity corrections + """ + mol_list = generate_kekule_structure(mol) + if mol_list: + return mol_list[0].enumerate_bonds() + else: + return mol.enumerate_bonds() From 4e4aa9ada49209e53e724622142b9cc30dbc490c Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 20:54:12 -0400 Subject: [PATCH 34/42] TS species are allowed to be loaded w/o structure or xyz --- arc/species/species.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arc/species/species.py b/arc/species/species.py index c79dadf7ed..90e3930c6a 100644 --- a/arc/species/species.py +++ b/arc/species/species.py @@ -434,7 +434,9 @@ def from_dict(self, species_dict): filter_structures=True) if 'conformers_path' in species_dict: self.append_conformers(species_dict['conformers_path']) - if self.mol is None and self.initial_xyz is None and not self.final_xyz and not self.conformers: + if self.mol is None and self.initial_xyz is None and not self.final_xyz and not self.conformers\ + and not any([tsg.xyz for tsg in self.ts_guesses]): + # TS species are allowed to be loaded w/o a structure raise SpeciesError('Must have either mol or xyz for species {0}'.format(self.label)) if self.initial_xyz is not None and not self.final_xyz: # consider the initial guess as one of the conformers if generating others. From 923355b0830eeb053abfcb75932465f63cd6998b Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 20:54:57 -0400 Subject: [PATCH 35/42] Combine TSError for unrecognized TSGuess methods --- arc/species/species.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/arc/species/species.py b/arc/species/species.py index 90e3930c6a..4f9a235fc7 100644 --- a/arc/species/species.py +++ b/arc/species/species.py @@ -943,16 +943,16 @@ def __init__(self, method=None, reactants_xyz=None, products_xyz=None, family=No raise TSError('If no method is specified, an xyz guess must be given') self.success = True self.execution_time = 0 - if not ('user guess' in self.method or 'autotst' in self.method - or self.method in ['user guess'] + [tsm.lower() for tsm in default_ts_methods]): - raise TSError('Unrecognized method. Should be either {0}. Got: {1}'.format( - ['User guess'] + default_ts_methods, self.method)) self.reactants_xyz = reactants_xyz if reactants_xyz is not None else list() self.products_xyz = products_xyz if products_xyz is not None else list() self.rmg_reaction = rmg_reaction self.family = family # if self.family is None and self.method.lower() in ['kinbot', 'autotst']: # raise TSError('No family specified for method {0}'.format(self.method)) + if not ('user guess' in self.method or 'autotst' in self.method + or self.method in ['user guess'] + [tsm.lower() for tsm in default_ts_methods]): + raise TSError('Unrecognized method. Should be either {0}. Got: {1}'.format( + ['User guess'] + default_ts_methods, self.method)) def as_dict(self): """A helper function for dumping this object as a dictionary in a YAML file for restarting ARC""" @@ -993,10 +993,6 @@ def from_dict(self, ts_dict): raise TSError('If no method is specified, an xyz guess must be given') self.success = self.success if self.success is not None else True self.execution_time = '0' - if 'user guess' not in self.method\ - and self.method not in ['user guess'] + [tsm.lower() for tsm in default_ts_methods]: - raise TSError('Unrecognized method. Should be either {0}. Got: {1}'.format( - ['User guess'] + default_ts_methods, self.method)) self.reactants_xyz = ts_dict['reactants_xyz'] if 'reactants_xyz' in ts_dict else list() self.products_xyz = ts_dict['products_xyz'] if 'products_xyz' in ts_dict else list() self.family = ts_dict['family'] if 'family' in ts_dict else None From 73782c585658c20e9ce83f853a6fca7d36b801e2 Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 23:00:12 -0400 Subject: [PATCH 36/42] Minor: Reordered Exceptions --- arc/arc_exceptions.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/arc/arc_exceptions.py b/arc/arc_exceptions.py index e1a0187e53..563d5d62f4 100644 --- a/arc/arc_exceptions.py +++ b/arc/arc_exceptions.py @@ -13,71 +13,71 @@ class InputError(Exception): pass -class OutputError(Exception): +class JobError(Exception): """ - This exception is raised whenever an error occurs while saving output information + An exception class for exceptional behavior that occurs while working with jobs """ pass -class SettingsError(Exception): +class OutputError(Exception): """ - An exception raised when dealing with settings + This exception is raised whenever an error occurs while saving output information """ pass -class SpeciesError(Exception): +class ReactionError(Exception): """ - An exception class for exceptional behavior that occurs while working with chemical species + An exception class for exceptional behavior that occurs while working with reactions """ pass -class TSError(Exception): +class RotorError(Exception): """ - An exception class for exceptional behavior that occurs while working with transition states + An exception class for exceptional behavior that occurs while working with rotors """ pass -class ReactionError(Exception): +class SanitizationError(Exception): """ - An exception class for exceptional behavior that occurs while working with reactions + Exception class to handle errors during SMILES perception. """ pass -class RotorError(Exception): +class SchedulerError(Exception): """ - An exception class for exceptional behavior that occurs while working with rotors + An exception class for exceptional behavior that occurs while working with the scheduler """ pass -class SchedulerError(Exception): +class ServerError(Exception): """ - An exception class for exceptional behavior that occurs while working with the scheduler + An exception class for exceptional behavior that occurs while working with servers """ pass -class JobError(Exception): +class SettingsError(Exception): """ - An exception class for exceptional behavior that occurs while working with jobs + An exception raised when dealing with settings """ pass -class ServerError(Exception): +class SpeciesError(Exception): """ - An exception class for exceptional behavior that occurs while working with servers + An exception class for exceptional behavior that occurs while working with chemical species """ pass -class SanitizationError(Exception): +class TSError(Exception): """ - Exception class to handle errors during SMILES perception. + An exception class for exceptional behavior that occurs while working with transition states """ pass From 1b58718f365e83eed0fba9843cefbe22f450ec6a Mon Sep 17 00:00:00 2001 From: alongd Date: Mon, 1 Apr 2019 23:10:55 -0400 Subject: [PATCH 37/42] Don't throw a SpecieError of no structure if conformers are available --- arc/species/species.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arc/species/species.py b/arc/species/species.py index 4f9a235fc7..7373a24e2e 100644 --- a/arc/species/species.py +++ b/arc/species/species.py @@ -259,7 +259,8 @@ def __init__(self, is_ts=False, rmg_species=None, mol=None, label=None, xyz=None if not isinstance(self.charge, int): raise SpeciesError('Charge for species {0} is not an integer (got {1}, a {2})'.format( self.label, self.charge, type(self.charge))) - if not self.is_ts and self.initial_xyz is None and self.mol is None and not self.conformers: + if not self.is_ts and self.initial_xyz is None and not self.final_xyz and self.mol is None\ + and not self.conformers: raise SpeciesError('No structure (xyz, SMILES, adjList, RMG:Species, or RMG:Molecule) was given for' ' species {0}'.format(self.label)) if self.label is None: From 99dc7a06e9e508e1ce037849c2361d53fab756ab Mon Sep 17 00:00:00 2001 From: alongd Date: Tue, 2 Apr 2019 09:20:53 -0400 Subject: [PATCH 38/42] Minor: Text addition to project info file --- arc/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arc/main.py b/arc/main.py index f2458b3712..53955b37c6 100644 --- a/arc/main.py +++ b/arc/main.py @@ -645,7 +645,7 @@ def save_project_info_file(self): txt += 'NOT using bond additivity corrections for thermo\n' if self.initial_trsh: txt += 'Using an initial troubleshooting method "{0}"'.format(self.initial_trsh) - txt += '\nUsing the following settings: {0}\n'.format(self.ess_settings) + txt += '\nUsing the following ESS settings: {0}\n'.format(self.ess_settings) txt += '\nConsidered the following species and TSs:\n' for species in self.arc_species_list: if species.is_ts: From 7cb194d936453bbe3a336138655d571c838b09f8 Mon Sep 17 00:00:00 2001 From: alongd Date: Tue, 2 Apr 2019 09:22:20 -0400 Subject: [PATCH 39/42] Capture AutoTST TSErrors in Scheduler --- arc/scheduler.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arc/scheduler.py b/arc/scheduler.py index a57df883cf..e86b6b0d92 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -22,7 +22,7 @@ from arc import plotter from arc import parser from arc.job.job import Job -from arc.arc_exceptions import SpeciesError, SchedulerError +from arc.arc_exceptions import SpeciesError, SchedulerError, TSError from arc.job.ssh import SSH_Client from arc.species.species import ARCSpecies, TSGuess, determine_rotor_symmetry from arc.species.converter import get_xyz_string, molecules_from_xyz, check_isomorphism @@ -231,7 +231,11 @@ def __init__(self, project, ess_settings, species_list, composite_method, confor logging.info('Trying to generating a TS guess for {0} reaction {1} using AutoTST{2}...'.format( ts_guess.family, rxn.label, reverse)) ts_guess.t0 = datetime.datetime.now() - ts_guess.xyz = autotst(rmg_reaction=ts_guess.rmg_reaction, reaction_family=ts_guess.family) + try: + ts_guess.xyz = autotst(rmg_reaction=ts_guess.rmg_reaction, reaction_family=ts_guess.family) + except TSError as e: + logging.error('Could not generate an AutoTST guess for reaction {0}.\nGot: {1}'.format( + rxn.label, e.message)) ts_guess.success = True if ts_guess.xyz is not None else False ts_guess.execution_time = str(datetime.datetime.now() - ts_guess.t0).split('.')[0] else: From eee93c9a8b75e7536672b06de5b0d764d80e6770 Mon Sep 17 00:00:00 2001 From: alongd Date: Tue, 2 Apr 2019 09:29:28 -0400 Subject: [PATCH 40/42] BugFix: Only consider converged TSGuesses for min(energies) --- arc/scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arc/scheduler.py b/arc/scheduler.py index e86b6b0d92..a50368a4ec 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -855,7 +855,7 @@ def determine_most_likely_ts_conformer(self, label): # currently we take the most stable guess. We'll need to implement additional checks here: # - normal displacement mode of the imaginary frequency # - IRC - e_min = min(energies) + e_min = min([e for e in energies if e is not None]) i_min = energies.index(e_min) self.species_dict[label].chosen_ts = None logging.info('\n\nShowing geometry *guesses* of successful TS guess methods for {0} of {1}:'.format( From dc8a46acdfe4ca4cbe7ea3ae550ce3da614c385f Mon Sep 17 00:00:00 2001 From: alongd Date: Tue, 2 Apr 2019 14:00:11 -0400 Subject: [PATCH 41/42] BugFix: Determine stable conformers and run them Corrected conditions --- arc/scheduler.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/arc/scheduler.py b/arc/scheduler.py index a50368a4ec..1e30cc9a69 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -331,7 +331,9 @@ def schedule_jobs(self): The main job scheduling block """ for species in self.species_dict.values(): - if not species.initial_xyz and not species.final_xyz and species.conformers and species.conformer_energies: + if not species.initial_xyz and not species.final_xyz and species.conformers\ + and any([e is not None for e in species.conformer_energies]): + # the species has no xyz, but has conformers and at least one of the conformers has energy self.determine_most_stable_conformer(species.label) if species.initial_xyz is not None: if self.composite_method: @@ -567,7 +569,9 @@ def run_conformer_jobs(self): """ for label in self.unique_species_labels: if not self.species_dict[label].is_ts and 'opt converged' not in self.output[label]['status']\ - and 'opt' not in self.job_dict[label] and not self.species_dict[label].conformer_energies: + and 'opt' not in self.job_dict[label]\ + and all([e is None for e in self.species_dict[label].conformer_energies]): + # This is not a TS, opt did not converged nor running, and conformer energies were not set self.save_conformers_file(label) if not self.testing: if len(self.species_dict[label].conformers) > 1: @@ -846,7 +850,7 @@ def determine_most_likely_ts_conformer(self, label): raise SchedulerError('The determine_most_likely_ts_conformer() method only deals with transition' ' state guesses.') if all(e is None for e in self.species_dict[label].conformer_energies): - logging.error('No guess converged for TS {0}!') + logging.error('No guess converged for TS {0}!'.format(label)) # for i, job in self.job_dict[label]['conformers'].items(): # self.troubleshoot_ess(label, job, level_of_theory=job.level_of_theory, job_type='conformer', # conformer=job.conformer) From ea194507284dfdc9ea014e4acbc69314a0bf60ae Mon Sep 17 00:00:00 2001 From: alongd Date: Tue, 2 Apr 2019 21:35:20 -0400 Subject: [PATCH 42/42] Minor: Changed `if not x in` to `if x not in` --- arc/scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arc/scheduler.py b/arc/scheduler.py index 1e30cc9a69..84bd137638 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -534,7 +534,7 @@ def end_job(self, job, label, job_name): try: job.determine_job_status() # also downloads output file except IOError: - if not job.job_type in ['orbitals']: + if job.job_type not in ['orbitals']: logging.warn('Tried to determine status of job {0}, but it seems like the job never ran.' ' Re-running job.'.format(job.job_name)) self.run_job(label=label, xyz=job.xyz, level_of_theory=job.level_of_theory, job_type=job.job_type,