Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
calvinp0 committed Feb 14, 2024
1 parent 2f581fd commit bc3dc82
Show file tree
Hide file tree
Showing 6 changed files with 60,761 additions and 80 deletions.
151 changes: 73 additions & 78 deletions arc/job/adapters/qchem.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,85 +387,80 @@ def write_input_file(self) -> None:
with open(os.path.join(self.local_path, input_filenames[self.job_adapter]), 'w') as f:
f.write(Template(input_template).render(**input_dict))
def generate_qchem_scan_angles(self,start_angle: int, step: int) -> (int, int, int, int):
"""
Generates the angles for a Q-Chem scan. The scan is split into two parts, one from start_angle to 180, and one from -180 to end_angle.
Parameters
----------
start_angle : int
The starting angle for the scan
step : int
The step size for the scan
Returns
-------
scan1_start : int
The starting angle for the first part of the scan
scan1_end : int
The ending angle for the first part of the scan
scan2_start : int
The starting angle for the second part of the scan
scan2_end : int
The ending angle for the second part of the scan
"""

# First, we need to check that the start_angle is within the range of -180 to 180, and if not, convert it to be within that range
if start_angle > 180:
start_angle = start_angle - 360


# This sets the end angle but does not take into account the limit of -180 to 180
end_angle = start_angle - step

# This function wraps the scan2_start within the range of -180 to 180
wrap_within_range = lambda number, addition: (number + addition) % 360 - 360 if (number + addition) % 360 > 180 else (number + addition) % 360

# This function converts the angles to be within the range of -180 to 180
convert_angle = lambda angle: angle % 360 if angle >= 0 else ( angle % 360 if angle <= -180 else (angle % 360) - 360)

# This converts the angles to be within the range of -180 to 180
start_angle = convert_angle(start_angle)
end_angle = convert_angle(end_angle)
"""Generates angles for a Q-Chem dihedral scan, split into two segments.
This function computes the angles for a Q-Chem dihedral scan. The scan is
divided into two parts: one spanning from the start_angle to 180 degrees,
and the other from -180 degrees to the calculated end_angle based on the
step size.
Args:
start_angle (int): The initial angle for the scan.
step (int): The incremental step size for the scan.
Returns:
tuple of int: A tuple containing the start and end angles for both
scan segments. It includes scan1_start, scan1_end,
scan2_start, and scan2_end.
"""

# First, we need to check that the start_angle is within the range of -180 to 180, and if not, convert it to be within that range
if start_angle > 180:
start_angle = start_angle - 360


# This sets the end angle but does not take into account the limit of -180 to 180
end_angle = start_angle - step

# This function wraps the scan2_start within the range of -180 to 180
wrap_within_range = lambda number, addition: (number + addition) % 360 - 360 if (number + addition) % 360 > 180 else (number + addition) % 360

# This function converts the angles to be within the range of -180 to 180
convert_angle = lambda angle: angle % 360 if angle >= 0 else ( angle % 360 if angle <= -180 else (angle % 360) - 360)

# This converts the angles to be within the range of -180 to 180
start_angle = convert_angle(start_angle)
end_angle = convert_angle(end_angle)

if start_angle == 0 and end_angle == 0:
scan1_start = start_angle
scan1_end = 180
scan2_start = -180
scan2_end = end_angle
elif start_angle == 180:
# This is a special case because the scan will be from 180 to 180
# This is not allowed in Q-Chem so we split it into two scans
# Arguably this could be done in one scan but it is easier to do it this way
# We will need to find the starting angle that when added by the step size will be 180
target_sum = 180
quotient = target_sum // step
starting_number = target_sum - (quotient * step)
scan1_start = starting_number
scan1_end = 180
scan2_start = -180
scan2_end = scan1_start - step
elif start_angle <= end_angle:
scan1_start = start_angle
scan1_end = start_angle + (step * ((180 - start_angle)//step))
scan2_start = convert_angle(scan1_end)
scan2_end = end_angle
elif (start_angle + step) > 180:
# This is a special case because the scan will be from, for example, 178 to 178 for the first scan. Therefore, we should make it a single scan from end angle, 178, step size
scan1_end = start_angle
scan1_start = wrap_within_range(scan1_end, step)
scan2_start = 0
scan2_end = 0
else:
scan1_start = start_angle
scan1_end = start_angle + (step * ((180 - start_angle)//step))
scan2_start = wrap_within_range(scan1_end, step)
scan2_end = end_angle

if start_angle == 0 and end_angle == 0:
scan1_start = start_angle
scan1_end = 180
scan2_start = -180
scan2_end = end_angle
elif start_angle == 180:
# This is a special case because the scan will be from 180 to 180
# This is not allowed in Q-Chem so we split it into two scans
# Arguably this could be done in one scan but it is easier to do it this way
# We will need to find the starting angle that when added by the step size will be 180
target_sum = 180
quotient = target_sum // step
starting_number = target_sum - (quotient * step)
scan1_start = starting_number
scan1_end = 180
scan2_start = -180
scan2_end = scan1_start - step
elif start_angle <= end_angle:
scan1_start = start_angle
scan1_end = start_angle + (step * ((180 - start_angle)//step))
scan2_start = convert_angle(scan1_end)
scan2_end = end_angle
elif (start_angle + step) > 180:
# This is a special case because the scan will be from, for example, 178 to 178 for the first scan. Therefore, we should make it a single scan from end angle, 178, step size
scan1_end = start_angle
scan1_start = wrap_within_range(scan1_end, step)
scan2_start = 0
scan2_end = 0
else:
scan1_start = start_angle
scan1_end = start_angle + (step * ((180 - start_angle)//step))
scan2_start = wrap_within_range(scan1_end, step)
scan2_end = end_angle

if scan2_start == scan2_end:
scan2_start = 0
scan2_end = 0

return int(scan1_start), int(scan1_end), int(scan2_start), int(scan2_end)
if scan2_start == scan2_end:
scan2_start = 0
scan2_end = 0

return int(scan1_start), int(scan1_end), int(scan2_start), int(scan2_end)

def generate_scan_angles(self, req_angle: int, step: int) -> (int, int):

Expand Down
9 changes: 8 additions & 1 deletion arc/level_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,18 @@ def test_deduce_software_irc_with_both(self):
self.assertEqual(level.software, 'gaussian') # gaussian is also available

@patch('arc.level.supported_ess', new=['qchem'])
def test_deduce_software_irc_with_only_gaussian(self):
def test_deduce_software_irc_with_only_qchem(self):
"""Test deducing software for IRC job when only gaussian is supported."""
level = Level(method='B3LYP', basis='6-311g+(d,f)')
level.deduce_software(job_type='irc')
self.assertEqual(level.software, 'qchem') # Only qchem is available

@patch('arc.level.supported_ess', new=['gaussian'])
def test_deduce_software_irc_with_only_gaussian(self):
"""Test deducing software for IRC job when only qchem is supported."""
level = Level(method='B3LYP', basis='6-311g+(d,f)')
level.deduce_software(job_type='irc')
self.assertEqual(level.software, 'gaussian')

@patch('arc.level.supported_ess', new=[])
def test_deduce_software_value_errors(self):
Expand Down
2 changes: 1 addition & 1 deletion arc/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def parse_frequencies(path: str,

def parse_normal_mode_displacement(path: str,
software: Optional[str] = None,
raise_error: bool = False, # TODO: Why is this true? What is it supposed to do?
raise_error: bool = False,
) -> Tuple[np.ndarray, np.ndarray]:
"""
Parse frequencies and normal mode displacement.
Expand Down
23 changes: 23 additions & 0 deletions arc/parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,23 @@ def test_parse_normal_mode_displacement(self):
[-0.16184923713199378, -0.3376354950974596, 0.787886990928027]], np.float64)
np.testing.assert_almost_equal(normal_modes_disp[0], expected_normal_modes_disp_4_0)

# QChem
path = os.path.join(ARC_PATH, 'arc', 'testing', 'normal_mode', 'HO2', 'qchem-freq.out')
freqs, normal_modes_disp = parser.parse_normal_mode_displacement(path=path, software='qchem', raise_error=False)
print(freqs)
expected_freqs = np.array([1164.75, 1431.41, 3582.24], np.float64)
np.testing.assert_allclose(freqs, expected_freqs, rtol=1e-5, atol=1e-8)
expected_normal_modes_disp_3 = np.array([[[-0.584, 0.091, -0. ],
[ 0.612, -0.107, 0. ],
[-0.448, 0.253, 0. ]],
[[-0.065, -0.039, -0. ],
[ 0.005, 0.057, 0. ],
[ 0.951, -0.294, -0. ]],
[[-0.001, 0.001, 0. ],
[-0.021, -0.06 , -0. ],
[ 0.348, 0.935, 0. ]]])
np.testing.assert_allclose(normal_modes_disp, expected_normal_modes_disp_3, rtol=1e-5, atol=1e-8)

def test_parse_xyz_from_file(self):
"""Test parsing xyz from a file"""
path1 = os.path.join(ARC_PATH, 'arc', 'testing', 'xyz', 'CH3C(O)O.gjf')
Expand Down Expand Up @@ -428,6 +445,12 @@ def test_parse_1d_scan_coords(self):
'C', 'C', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H',
'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H'))

path_5 = os.path.join(ARC_PATH, 'arc', 'testing', 'rotor_scans', 'qchem-pes.out')
traj_5 = parser.parse_1d_scan_coords(path_5)
self.assertEqual(len(traj_5), 25)
self.assertEqual(traj_5[0]['symbols'], ('C', 'C', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H'))
self.assertEqual(traj_5[0]['coords'][13], (-2.1002861161, 0.7502495424, -0.8796160845))

def test_parse_t1(self):
"""Test T1 diagnostic parsing"""
path = os.path.join(ARC_PATH, 'arc', 'testing', 'sp', 'mehylamine_CCSD(T).out')
Expand Down

0 comments on commit bc3dc82

Please sign in to comment.