Skip to content

Commit bc3dc82

Browse files
committed
Updates
1 parent 2f581fd commit bc3dc82

File tree

6 files changed

+60761
-80
lines changed

6 files changed

+60761
-80
lines changed

arc/job/adapters/qchem.py

Lines changed: 73 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -387,85 +387,80 @@ def write_input_file(self) -> None:
387387
with open(os.path.join(self.local_path, input_filenames[self.job_adapter]), 'w') as f:
388388
f.write(Template(input_template).render(**input_dict))
389389
def generate_qchem_scan_angles(self,start_angle: int, step: int) -> (int, int, int, int):
390-
"""
391-
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.
392-
393-
Parameters
394-
----------
395-
start_angle : int
396-
The starting angle for the scan
397-
step : int
398-
The step size for the scan
399-
400-
Returns
401-
-------
402-
scan1_start : int
403-
The starting angle for the first part of the scan
404-
scan1_end : int
405-
The ending angle for the first part of the scan
406-
scan2_start : int
407-
The starting angle for the second part of the scan
408-
scan2_end : int
409-
The ending angle for the second part of the scan
410-
"""
411-
412-
# 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
413-
if start_angle > 180:
414-
start_angle = start_angle - 360
415-
416-
417-
# This sets the end angle but does not take into account the limit of -180 to 180
418-
end_angle = start_angle - step
419-
420-
# This function wraps the scan2_start within the range of -180 to 180
421-
wrap_within_range = lambda number, addition: (number + addition) % 360 - 360 if (number + addition) % 360 > 180 else (number + addition) % 360
422-
423-
# This function converts the angles to be within the range of -180 to 180
424-
convert_angle = lambda angle: angle % 360 if angle >= 0 else ( angle % 360 if angle <= -180 else (angle % 360) - 360)
425-
426-
# This converts the angles to be within the range of -180 to 180
427-
start_angle = convert_angle(start_angle)
428-
end_angle = convert_angle(end_angle)
390+
"""Generates angles for a Q-Chem dihedral scan, split into two segments.
391+
392+
This function computes the angles for a Q-Chem dihedral scan. The scan is
393+
divided into two parts: one spanning from the start_angle to 180 degrees,
394+
and the other from -180 degrees to the calculated end_angle based on the
395+
step size.
396+
397+
Args:
398+
start_angle (int): The initial angle for the scan.
399+
step (int): The incremental step size for the scan.
400+
401+
Returns:
402+
tuple of int: A tuple containing the start and end angles for both
403+
scan segments. It includes scan1_start, scan1_end,
404+
scan2_start, and scan2_end.
405+
"""
406+
407+
# 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
408+
if start_angle > 180:
409+
start_angle = start_angle - 360
410+
411+
412+
# This sets the end angle but does not take into account the limit of -180 to 180
413+
end_angle = start_angle - step
414+
415+
# This function wraps the scan2_start within the range of -180 to 180
416+
wrap_within_range = lambda number, addition: (number + addition) % 360 - 360 if (number + addition) % 360 > 180 else (number + addition) % 360
417+
418+
# This function converts the angles to be within the range of -180 to 180
419+
convert_angle = lambda angle: angle % 360 if angle >= 0 else ( angle % 360 if angle <= -180 else (angle % 360) - 360)
420+
421+
# This converts the angles to be within the range of -180 to 180
422+
start_angle = convert_angle(start_angle)
423+
end_angle = convert_angle(end_angle)
424+
425+
if start_angle == 0 and end_angle == 0:
426+
scan1_start = start_angle
427+
scan1_end = 180
428+
scan2_start = -180
429+
scan2_end = end_angle
430+
elif start_angle == 180:
431+
# This is a special case because the scan will be from 180 to 180
432+
# This is not allowed in Q-Chem so we split it into two scans
433+
# Arguably this could be done in one scan but it is easier to do it this way
434+
# We will need to find the starting angle that when added by the step size will be 180
435+
target_sum = 180
436+
quotient = target_sum // step
437+
starting_number = target_sum - (quotient * step)
438+
scan1_start = starting_number
439+
scan1_end = 180
440+
scan2_start = -180
441+
scan2_end = scan1_start - step
442+
elif start_angle <= end_angle:
443+
scan1_start = start_angle
444+
scan1_end = start_angle + (step * ((180 - start_angle)//step))
445+
scan2_start = convert_angle(scan1_end)
446+
scan2_end = end_angle
447+
elif (start_angle + step) > 180:
448+
# 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
449+
scan1_end = start_angle
450+
scan1_start = wrap_within_range(scan1_end, step)
451+
scan2_start = 0
452+
scan2_end = 0
453+
else:
454+
scan1_start = start_angle
455+
scan1_end = start_angle + (step * ((180 - start_angle)//step))
456+
scan2_start = wrap_within_range(scan1_end, step)
457+
scan2_end = end_angle
429458

430-
if start_angle == 0 and end_angle == 0:
431-
scan1_start = start_angle
432-
scan1_end = 180
433-
scan2_start = -180
434-
scan2_end = end_angle
435-
elif start_angle == 180:
436-
# This is a special case because the scan will be from 180 to 180
437-
# This is not allowed in Q-Chem so we split it into two scans
438-
# Arguably this could be done in one scan but it is easier to do it this way
439-
# We will need to find the starting angle that when added by the step size will be 180
440-
target_sum = 180
441-
quotient = target_sum // step
442-
starting_number = target_sum - (quotient * step)
443-
scan1_start = starting_number
444-
scan1_end = 180
445-
scan2_start = -180
446-
scan2_end = scan1_start - step
447-
elif start_angle <= end_angle:
448-
scan1_start = start_angle
449-
scan1_end = start_angle + (step * ((180 - start_angle)//step))
450-
scan2_start = convert_angle(scan1_end)
451-
scan2_end = end_angle
452-
elif (start_angle + step) > 180:
453-
# 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
454-
scan1_end = start_angle
455-
scan1_start = wrap_within_range(scan1_end, step)
456-
scan2_start = 0
457-
scan2_end = 0
458-
else:
459-
scan1_start = start_angle
460-
scan1_end = start_angle + (step * ((180 - start_angle)//step))
461-
scan2_start = wrap_within_range(scan1_end, step)
462-
scan2_end = end_angle
463-
464-
if scan2_start == scan2_end:
465-
scan2_start = 0
466-
scan2_end = 0
467-
468-
return int(scan1_start), int(scan1_end), int(scan2_start), int(scan2_end)
459+
if scan2_start == scan2_end:
460+
scan2_start = 0
461+
scan2_end = 0
462+
463+
return int(scan1_start), int(scan1_end), int(scan2_start), int(scan2_end)
469464

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

arc/level_test.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,18 @@ def test_deduce_software_irc_with_both(self):
7070
self.assertEqual(level.software, 'gaussian') # gaussian is also available
7171

7272
@patch('arc.level.supported_ess', new=['qchem'])
73-
def test_deduce_software_irc_with_only_gaussian(self):
73+
def test_deduce_software_irc_with_only_qchem(self):
7474
"""Test deducing software for IRC job when only gaussian is supported."""
7575
level = Level(method='B3LYP', basis='6-311g+(d,f)')
7676
level.deduce_software(job_type='irc')
7777
self.assertEqual(level.software, 'qchem') # Only qchem is available
78+
79+
@patch('arc.level.supported_ess', new=['gaussian'])
80+
def test_deduce_software_irc_with_only_gaussian(self):
81+
"""Test deducing software for IRC job when only qchem is supported."""
82+
level = Level(method='B3LYP', basis='6-311g+(d,f)')
83+
level.deduce_software(job_type='irc')
84+
self.assertEqual(level.software, 'gaussian')
7885

7986
@patch('arc.level.supported_ess', new=[])
8087
def test_deduce_software_value_errors(self):

arc/parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def parse_frequencies(path: str,
149149

150150
def parse_normal_mode_displacement(path: str,
151151
software: Optional[str] = None,
152-
raise_error: bool = False, # TODO: Why is this true? What is it supposed to do?
152+
raise_error: bool = False,
153153
) -> Tuple[np.ndarray, np.ndarray]:
154154
"""
155155
Parse frequencies and normal mode displacement.

arc/parser_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,23 @@ def test_parse_normal_mode_displacement(self):
244244
[-0.16184923713199378, -0.3376354950974596, 0.787886990928027]], np.float64)
245245
np.testing.assert_almost_equal(normal_modes_disp[0], expected_normal_modes_disp_4_0)
246246

247+
# QChem
248+
path = os.path.join(ARC_PATH, 'arc', 'testing', 'normal_mode', 'HO2', 'qchem-freq.out')
249+
freqs, normal_modes_disp = parser.parse_normal_mode_displacement(path=path, software='qchem', raise_error=False)
250+
print(freqs)
251+
expected_freqs = np.array([1164.75, 1431.41, 3582.24], np.float64)
252+
np.testing.assert_allclose(freqs, expected_freqs, rtol=1e-5, atol=1e-8)
253+
expected_normal_modes_disp_3 = np.array([[[-0.584, 0.091, -0. ],
254+
[ 0.612, -0.107, 0. ],
255+
[-0.448, 0.253, 0. ]],
256+
[[-0.065, -0.039, -0. ],
257+
[ 0.005, 0.057, 0. ],
258+
[ 0.951, -0.294, -0. ]],
259+
[[-0.001, 0.001, 0. ],
260+
[-0.021, -0.06 , -0. ],
261+
[ 0.348, 0.935, 0. ]]])
262+
np.testing.assert_allclose(normal_modes_disp, expected_normal_modes_disp_3, rtol=1e-5, atol=1e-8)
263+
247264
def test_parse_xyz_from_file(self):
248265
"""Test parsing xyz from a file"""
249266
path1 = os.path.join(ARC_PATH, 'arc', 'testing', 'xyz', 'CH3C(O)O.gjf')
@@ -428,6 +445,12 @@ def test_parse_1d_scan_coords(self):
428445
'C', 'C', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H',
429446
'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H'))
430447

448+
path_5 = os.path.join(ARC_PATH, 'arc', 'testing', 'rotor_scans', 'qchem-pes.out')
449+
traj_5 = parser.parse_1d_scan_coords(path_5)
450+
self.assertEqual(len(traj_5), 25)
451+
self.assertEqual(traj_5[0]['symbols'], ('C', 'C', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H'))
452+
self.assertEqual(traj_5[0]['coords'][13], (-2.1002861161, 0.7502495424, -0.8796160845))
453+
431454
def test_parse_t1(self):
432455
"""Test T1 diagnostic parsing"""
433456
path = os.path.join(ARC_PATH, 'arc', 'testing', 'sp', 'mehylamine_CCSD(T).out')

0 commit comments

Comments
 (0)