Example #1
0
    def test_xyz_format_conversion(self):
        """Test conversions from string to list xyz formats"""
        xyz_str0 = """N       2.24690600   -0.00006500    0.11597700
C      -1.05654800    1.29155000   -0.02642500
C      -1.05661400   -1.29150400   -0.02650600
C      -0.30514100    0.00000200    0.00533200
C       1.08358900   -0.00003400    0.06558000
H      -0.39168300    2.15448600   -0.00132500
H      -1.67242600    1.35091400   -0.93175000
H      -1.74185400    1.35367700    0.82742800
H      -0.39187100   -2.15447800    0.00045500
H      -1.74341400   -1.35278100    0.82619100
H      -1.67091600   -1.35164600   -0.93286400
"""

        xyz_list, atoms, x, y, z = get_xyz_matrix(xyz_str0)

        # test all forms of input for get_xyz_string():
        xyz_str1 = get_xyz_string(coords=xyz_list, symbols=atoms)
        xyz_str2 = get_xyz_string(coords=xyz_list, numbers=[7, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1])
        mol, _ = molecules_from_xyz(xyz_str0)
        xyz_str3 = get_xyz_string(coords=xyz_list, mol=mol)

        self.assertEqual(xyz_str0, xyz_str1)
        self.assertEqual(xyz_str1, xyz_str2)
        self.assertEqual(xyz_str2, xyz_str3)
        self.assertEqual(atoms, ['N', 'C', 'C', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H'])
        self.assertEqual(x, [2.246906, -1.056548, -1.056614, -0.305141, 1.083589, -0.391683, -1.672426, -1.741854,
                             -0.391871, -1.743414, -1.670916])
        self.assertEqual(y[1], 1.29155)
        self.assertEqual(z[-1], -0.932864)
Example #2
0
def main(reaction_label=None, reaction_family=None):
    """
    Run AutoTST to generate a TS guess
    Currently only works for H Abstraction
    """
    if reaction_label is None:
        # Parse the command-line arguments (requires the argparse module)
        args = parse_command_line_arguments()
        reaction_label = str(args.reaction_label)
        reaction_family = str(args.reaction_family)
    reaction_family = str(
        'H_Abstraction') if reaction_family is None else reaction_family

    try:
        reaction = AutoTST_Reaction(label=reaction_label,
                                    reaction_family=reaction_family)
    except AssertionError as e:
        logger.error(
            'Could not generate a TS guess using AutoTST for reaction {0}'.
            format(reaction_label))
        raise TSError('Could not generate AutoTST guess:\n{0}'.format(
            e.message))
    else:
        positions = reaction.ts.ase_ts.get_positions()
        numbers = reaction.ts.ase_ts.get_atomic_numbers()
        xyz_guess = get_xyz_string(coords=positions, numbers=numbers)

        xyz_path = os.path.join(arc_path, 'arc', 'ts', 'auto_tst.xyz')

        with open(xyz_path, 'w') as f:
            f.write(xyz_guess)
Example #3
0
def rotate_molecule_to_lowest_conformer(
    arkane_log,
    local_path,
    input_filename,
    multiplicity=1,
    charge=0,
):
    """
    Given the input file, rotate the molecule to lowest conformer
    args:
        arkane_log: the gaussian file read by Arkane
        local_path (str): local path to the job
        input_filename (str): the name of the input file
        multiplicity (int)
        charge (int)
    return:
        xyz (str): the xyz string used in arc geometry or gaussian geometry input
    """
    # Find the degree to rotate
    v_list, angle = arkane_log.loadScanEnergies()
    v_list = np.array(v_list, np.float64)
    point = np.argmin(v_list)
    res = 360.0 / (len(v_list) - 1)
    deg_increment = point * res

    # Find the xyz geometry
    coords, numbers, _ = arkane_log.loadGeometry()
    coords = coords.tolist()
    xyz = get_xyz_string(coords=coords, numbers=numbers)

    # Find the pivot and scan
    pivot, scan, _, _ = find_scan_info_from_input_file(local_path,
                                                       input_filename)

    # Change the structure
    mol = molecules_from_xyz(xyz, multiplicity=multiplicity, charge=charge)[1]
    conf, rd_mol, indx_map = rdkit_conf_from_mol(mol, coords)
    rd_scan = [indx_map[i - 1] for i in scan]
    new_xyz = set_rdkit_dihedrals(conf,
                                  rd_mol,
                                  indx_map,
                                  rd_scan,
                                  deg_increment=deg_increment)
    return get_xyz_string(coords=new_xyz, numbers=numbers)
Example #4
0
    def test_get_xyz_string(self):
        """Test conversion of xyz array to string format"""
        xyz_array = [[-0.06618943, -0.12360663, -0.07631983],
                     [-0.79539707, 0.86755487, 1.02675668],
                     [-0.68919931, 0.25421823, -1.34830853],
                     [0.01546439, -1.54297548, 0.44580391],
                     [1.59721519, 0.47861334, 0.00711],
                     [1.94428095, 0.40772394, 1.03719428],
                     [2.20318015, -0.14715186, -0.64755729],
                     [1.59252246, 1.5117895, -0.33908352],
                     [-0.8785689, -2.02453514, 0.38494433],
                     [-1.34135876, 1.49608206, 0.53295071]]
        symbols = ['S', 'O', 'O', 'N', 'C', 'H', 'H', 'H', 'H', 'H']
        xyz_expected = """S      -0.06618943   -0.12360663   -0.07631983
O      -0.79539707    0.86755487    1.02675668
O      -0.68919931    0.25421823   -1.34830853
N       0.01546439   -1.54297548    0.44580391
C       1.59721519    0.47861334    0.00711000
H       1.94428095    0.40772394    1.03719428
H       2.20318015   -0.14715186   -0.64755729
H       1.59252246    1.51178950   -0.33908352
H      -0.87856890   -2.02453514    0.38494433
H      -1.34135876    1.49608206    0.53295071
"""
        xyz1 = converter.get_xyz_string(xyz=xyz_array, symbol=symbols)
        self.assertEqual(xyz1, xyz_expected)
        number = [16, 8, 8, 7, 6, 1, 1, 1, 1, 1]
        xyz2 = converter.get_xyz_string(xyz=xyz_array, number=number)
        self.assertEqual(xyz2, xyz_expected)
        mol = Molecule().fromAdjacencyList(
            str("""1  S u0 p0 c0 {2,D} {3,S} {4,D} {5,S}
2  O u0 p2 c0 {1,D}
3  O u0 p2 c0 {1,S} {6,S}
4  N u0 p1 c0 {1,D} {7,S}
5  C u0 p0 c0 {1,S} {8,S} {9,S} {10,S}
6  H u0 p0 c0 {3,S}
7  H u0 p0 c0 {4,S}
8  H u0 p0 c0 {5,S}
9  H u0 p0 c0 {5,S}
10 H u0 p0 c0 {5,S}"""))
        xyz3 = converter.get_xyz_string(xyz=xyz_array, mol=mol)
        self.assertEqual(xyz3, xyz_expected)
Example #5
0
def parse_xyz_from_file(path):
    """
    Parse xyz coordinated from:
    .xyz - XYZ file
    .gjf - Gaussian input file
    .out or .log - ESS output file (Gaussian, QChem, Molpro)
    other - Molpro or QChem input file
    """
    lines = _get_lines_from_file(path)
    file_extension = os.path.splitext(path)[1]

    xyz = None
    relevant_lines = list()

    if file_extension == '.xyz':
        relevant_lines = lines[2:]
    elif file_extension == '.gjf':
        start_parsing = False
        for line in lines:
            if start_parsing and line and line != '\n' and line != '\r\n':
                relevant_lines.append(line)
            elif start_parsing:
                break
            else:
                splits = line.split()
                if len(splits) == 2 and all([s.isdigit() for s in splits]):
                    start_parsing = True
    elif 'out' in file_extension or 'log' in file_extension:
        log = determine_qm_software(fullpath=path)
        coords, number, _ = log.loadGeometry()
        xyz = get_xyz_string(coords=coords, numbers=number)
    else:
        record = False
        for line in lines:
            if '$end' in line or '}' in line:
                break
            if record and len(line.split()) == 4:
                relevant_lines.append(line)
            elif '$molecule' in line:
                record = True
            elif 'geometry={' in line:
                record = True
        if not relevant_lines:
            raise ParserError(
                'Could not parse xyz coordinates from file {0}'.format(path))
    if xyz is None and relevant_lines:
        xyz = ''.join([line for line in relevant_lines if line])
    return standardize_xyz_string(xyz)
Example #6
0
def parse_xyz_from_file(path):
    """
    Parse xyz coordinated from:
    .xyz - XYZ file
    .gjf - Gaussian input file
    .out or .log - ESS output file (Gaussian, QChem, Molpro)
    other - Molpro or QChem input file
    """
    with open(path, 'r') as f:
        lines = f.readlines()
    _, file_extension = os.path.splitext(path)

    xyz = None
    relevant_lines = list()

    if file_extension == '.xyz':
        relevant_lines = lines[2:]
    elif file_extension == '.gjf':
        for line in lines[5:]:
            if line and line != '\n' and line != '\r\n':
                relevant_lines.append(line)
            else:
                break
    elif 'out' in file_extension or 'log' in file_extension:
        log = Log(path='')
        log.determine_qm_software(fullpath=path)
        coord, number, mass = log.software_log.loadGeometry()
        xyz = get_xyz_string(xyz=coord, number=number)
    else:
        record = False
        for line in lines:
            if '$end' in line or '}' in line:
                break
            if record and len(line.split()) == 4:
                relevant_lines.append(line)
            elif '$molecule' in line:
                record = True
            elif 'geometry={' in line:
                record = True
        if not relevant_lines:
            raise InputError(
                'Could not parse xyz coordinates from file {0}'.format(path))
    if xyz is None and relevant_lines:
        xyz = ''.join([line for line in relevant_lines if line])
    return xyz
Example #7
0
def trsh_negative_freq(label, log_file, neg_freqs_trshed=None, job_types=None):
    """
    Troubleshooting cases where non-TS species have negative frequencies.
    We take +/-1.1 displacements, generating several new initial geometries.

    Args:
        label (str): The species label.
        log_file (str): The frequency job log file.
        neg_freqs_trshed (list, optional): A list of negative frequencies the species was troubleshooted for.
        job_types (list, optional): The job types used for ARC, e.g., ['opt', '1d_rotors'].

    Todo:
        * get all torsions of the molecule (if weren't already generated),
          identify atom/s with largest displacements (top 2)
          determine torsions with unique PIVOTS where these atoms are in the "scan" and "top" but not pivotal
          generate a 360 scan using 30 deg increments and append all 12 results as conformers
          (consider rotor symmetry to append less conformers?)
        * Write our own frequencies parsers

    Returns:
        current_neg_freqs_trshed (list): The current troubleshooted negative frequencies.
    Returns:
        conformers (list): The new conformers to try optimizing.
    Returns:
        output_errors (list): Errors to report.
    Returns:
        output_warnings (list): Warnings to report.

    Raises:
        TrshError: If a negative frequency could not be determined.
    """
    neg_freqs_trshed = neg_freqs_trshed if neg_freqs_trshed is not None else list(
    )
    job_types = job_types if job_types is not None else ['1d_rotors']
    output_errors, output_warnings, conformers, current_neg_freqs_trshed = list(
    ), list(), list(), list()
    factor = 1.1
    ccparser = cclib.io.ccopen(str(log_file), logging.CRITICAL)
    try:
        data = ccparser.parse()
    except AttributeError:
        # see https://github.com/cclib/cclib/issues/754
        logger.error(
            'Could not troubleshoot negative frequency for species {0}'.format(
                label))
        output_errors.append(
            'Error: Could not troubleshoot negative frequency; ')
        return [], [], output_errors, []
    vibfreqs = data.vibfreqs
    vibdisps = data.vibdisps
    atomnos = data.atomnos
    atomcoords = data.atomcoords
    if len(neg_freqs_trshed) > 10:
        logger.error(
            'Species {0} was troubleshooted for negative frequencies too many times.'
            .format(label))
        if '1d_rotors' not in job_types:
            logger.error(
                'The rotor scans feature is turned off, '
                'cannot troubleshoot geometry using dihedral modifications.')
            output_warnings.append('1d_rotors = False; ')
        logger.error('Invalidating species.')
        output_errors.append(
            'Error: Encountered negative frequencies too many times; ')
    else:
        neg_freqs_idx = list()  # store indices w.r.t. vibfreqs
        largest_neg_freq_idx = 0  # index in vibfreqs
        for i, freq in enumerate(vibfreqs):
            if freq < 0:
                neg_freqs_idx.append(i)
                if vibfreqs[i] < vibfreqs[largest_neg_freq_idx]:
                    largest_neg_freq_idx = i
            else:
                # assuming frequencies are ordered, break after the first positive freq encountered
                break
        if vibfreqs[largest_neg_freq_idx] >= 0 or len(neg_freqs_idx) == 0:
            raise TrshError(
                'Could not determine a negative frequency for species {0} '
                'while troubleshooting for it.'.format(label))
        if len(neg_freqs_idx) == 1 and not len(neg_freqs_trshed):
            # species has one negative frequency, and has not been troubleshooted for it before
            logger.info(
                'Species {0} has a negative frequency ({1}). Perturbing its geometry using the respective '
                'vibrational displacements'.format(
                    label, vibfreqs[largest_neg_freq_idx]))
            neg_freqs_idx = [
                largest_neg_freq_idx
            ]  # indices of the negative frequencies to troubleshoot for
        elif len(neg_freqs_idx) == 1 and any([
                np.allclose(vibfreqs[0], vf, rtol=1e-04, atol=1e-02)
                for vf in neg_freqs_trshed
        ]):
            # species has one negative frequency, and has been troubleshooted for it before
            factor = 1 + 0.1 * (len(neg_freqs_trshed) + 1)
            logger.info(
                'Species {0} has a negative frequency ({1}) for the {2} time. Perturbing its geometry using '
                'the respective vibrational displacements, this time using a larger factor (x {3})'
                .format(label, vibfreqs[largest_neg_freq_idx],
                        len(neg_freqs_trshed), factor))
            neg_freqs_idx = [
                largest_neg_freq_idx
            ]  # indices of the negative frequencies to troubleshoot for
        elif len(neg_freqs_idx) > 1 and not any([
                np.allclose(vibfreqs[0], vf, rtol=1e-04, atol=1e-02)
                for vf in neg_freqs_trshed
        ]):
            # species has more than one negative frequency, and has not been troubleshooted for it before
            logger.info(
                'Species {0} has {1} negative frequencies. Perturbing its geometry using the vibrational '
                'displacements of its largest negative frequency, {2}'.format(
                    label, len(neg_freqs_idx), vibfreqs[largest_neg_freq_idx]))
            neg_freqs_idx = [
                largest_neg_freq_idx
            ]  # indices of the negative frequencies to troubleshoot for
        elif len(neg_freqs_idx) > 1 and any([
                np.allclose(vibfreqs[0], vf, rtol=1e-04, atol=1e-02)
                for vf in neg_freqs_trshed
        ]):
            # species has more than one negative frequency, and has been troubleshooted for it before
            logger.info(
                'Species {0} has {1} negative frequencies. Perturbing its geometry using the vibrational'
                ' displacements of ALL negative frequencies'.format(
                    label, len(neg_freqs_idx)))
        current_neg_freqs_trshed = [
            round(vibfreqs[i], 2) for i in neg_freqs_idx
        ]  # record trshed negative freqs
        atomcoords = atomcoords[
            -1]  # it's a list within a list, take the last geometry
        for neg_freq_idx in neg_freqs_idx:
            displacement = vibdisps[neg_freq_idx]
            xyz1 = atomcoords + factor * displacement
            xyz2 = atomcoords - factor * displacement
            conformers.append(get_xyz_string(coords=xyz1, numbers=atomnos))
            conformers.append(get_xyz_string(coords=xyz2, numbers=atomnos))
    return current_neg_freqs_trshed, conformers, output_errors, output_warnings