Ejemplo n.º 1
0
def main_parameterize(arguments=None):

    args = getArgumentParser().parse_args(args=arguments)

    if not os.path.exists(args.filename):
        raise ValueError('File %s cannot be found' % args.filename)

    method_map = {'GAFF': FFTypeMethod.GAFF, 'GAFF2': FFTypeMethod.GAFF2, 'CGENFF': FFTypeMethod.CGenFF_2b6}
    methods = [method_map[method] for method in args.forcefield]

    # Get RTF and PRM file names
    rtfFile, prmFile = None, None
    if args.rtf_prm:
        rtfFile, prmFile = args.rtf_prm

    # Create a queue for QM
    if args.queue == 'local':
        queue = LocalCPUQueue()
    elif args.queue == 'Slurm':
        queue = SlurmQueue(_configapp=args.code.lower())
    elif args.queue == 'LSF':
        queue = LsfQueue(_configapp=args.code.lower())
    elif args.queue == 'PBS':
        queue = PBSQueue()  # TODO: configure
    elif args.queue == 'AceCloud':
        queue = AceCloudQueue()  # TODO: configure
        queue.groupname = args.groupname
        queue.hashnames = True
    else:
        raise NotImplementedError

    # Override default ncpus
    if args.ncpus:
        logger.info('Overriding ncpus to {}'.format(args.ncpus))
        queue.ncpu = args.ncpus
    if args.memory:
        logger.info('Overriding memory to {}'.format(args.memory))
        queue.memory = args.memory

    # Create a QM object
    if args.code == 'Psi4':
        qm = Psi4()
    elif args.code == 'Gaussian':
        qm = Gaussian()
    else:
        raise NotImplementedError

    # This is for debugging only!
    if args.fake_qm:
        qm = FakeQM2()
        logger.warning('Using FakeQM')

    # Get rotatable dihedral angles
    mol = Molecule(args.filename)
    mol = canonicalizeAtomNames(mol)
    mol, equivalents, all_dihedrals = getEquivalentsAndDihedrals(mol)
    netcharge = args.charge if args.charge is not None else int(round(np.sum(mol.charge)))

    if args.list:
        print('\n === Parameterizable dihedral angles of {} ===\n'.format(args.filename))
        with open('torsions.txt', 'w') as fh:
            for dihedral in all_dihedrals:
                dihedral_name = '-'.join(mol.name[list(dihedral[0])])
                print('  {}'.format(dihedral_name))
                fh.write(dihedral_name+'\n')
        print()
        sys.exit(0)

    # Set up the QM object
    qm.theory = args.theory
    qm.basis = args.basis
    qm.solvent = args.environment
    qm.queue = queue
    qm.charge = netcharge

    # Select which dihedrals to fit
    parameterizable_dihedrals = [list(dih[0]) for dih in all_dihedrals]
    if len(args.dihedral) > 0:
        all_dihedral_names = ['-'.join(mol.name[list(dihedral[0])]) for dihedral in all_dihedrals]
        parameterizable_dihedrals = []
        for dihedral_name in args.dihedral:
            if dihedral_name not in all_dihedral_names:
                raise ValueError('%s is not recognized as a rotatable dihedral angle' % dihedral_name)
            parameterizable_dihedrals.append(list(all_dihedrals[all_dihedral_names.index(dihedral_name)][0]))

    # Print arguments
    print('\n === Arguments ===\n')
    for key, value in vars(args).items():
        print('{:>12s}: {:s}'.format(key, str(value)))

    print('\n === Parameterizing %s ===\n' % args.filename)
    for method in methods:
        print(" === Fitting for %s ===\n" % method.name)
        printReport(mol, netcharge, equivalents, all_dihedrals)

        parameters, mol = fftype(mol, method=method, rtfFile=rtfFile, prmFile=prmFile, netcharge=args.charge)
        if isinstance(qm, FakeQM2):
            qm._parameters = parameters

        # Copy the molecule to preserve initial coordinates
        orig_coor = mol.coords.copy()

        # Update B3LYP to B3LYP-D3
        # TODO: this is silent and not documented stuff
        if qm.theory == 'B3LYP':
            qm.correction = 'D3'

        # Update basis sets
        # TODO: this is silent and not documented stuff
        if netcharge < 0 and qm.solvent == 'vacuum':
            if qm.basis == '6-31G*':
                qm.basis = '6-31+G*'
            if qm.basis == 'cc-pVDZ':
                qm.basis = 'aug-cc-pVDZ'
            logger.info('Changing basis sets to %s' % qm.basis)

        # Minimize molecule
        if args.minimize:
            print('\n == Minimizing ==\n')
            mol = minimize(mol, qm, args.outdir)

        # Fit ESP charges
        if args.fit_charges:
            print('\n == Fitting ESP charges ==\n')

            # Set random number generator seed
            if args.seed:
                np.random.seed(args.seed)

            # Select the atoms with fixed charges
            fixed_atom_indices = getFixedChargeAtomIndices(mol, args.fix_charge)

            # Fit ESP charges
            mol, _, esp_charges, qm_dipole = fitCharges(mol, qm, args.outdir, fixed=fixed_atom_indices)

            # Print dipoles
            logger.info('QM dipole: %f %f %f; %f' % tuple(qm_dipole))
            mm_dipole = getDipole(mol)
            if np.all(np.isfinite(mm_dipole)):
                logger.info('MM dipole: %f %f %f; %f' % tuple(mm_dipole))
            else:
                logger.warning('MM dipole cannot be computed. Check if elements are detected correctly.')

        # Fit dihedral angle parameters
        if args.fit_dihedral:
            print('\n == Fitting dihedral angle parameters ==\n')

            # Set random number generator seed
            if args.seed:
                np.random.seed(args.seed)

            # Invent new atom types for dihedral atoms
            mol, originaltypes = inventAtomTypes(mol, parameterizable_dihedrals, equivalents)
            parameters = recreateParameters(mol, originaltypes, parameters)
            parameters = createMultitermDihedralTypes(parameters)
            if isinstance(qm, FakeQM2):
                qm._parameters = parameters

            # Fit the parameters
            fitDihedrals(mol, qm, method, parameters, all_dihedrals, parameterizable_dihedrals, args.outdir, geomopt=args.optimize_dihedral)

        # Output the FF parameters
        print('\n == Writing results ==\n')
        writeParameters(mol, parameters, qm, method, netcharge, args.outdir, original_coords=orig_coor)

        # Write energy file
        energyFile = os.path.join(args.outdir, 'parameters', method.name, _qm_method_name(qm), 'energies.txt')
        printEnergies(mol, parameters, energyFile)
        logger.info('Write energy file: %s' % energyFile)
Ejemplo n.º 2
0
def main_parameterize(arguments=None):

    args = getArgumentParser().parse_args(args=arguments)

    if not os.path.exists(args.filename):
        raise ValueError('File %s cannot be found' % args.filename)

    method_map = {
        'GAFF': FFTypeMethod.GAFF,
        'GAFF2': FFTypeMethod.GAFF2,
        'CGENFF': FFTypeMethod.CGenFF_2b6
    }
    methods = [method_map[method] for method in args.forcefield]

    # Get RTF and PRM file names
    rtfFile, prmFile = None, None
    if args.rtf_prm:
        rtfFile, prmFile = args.rtf_prm

    # Create a queue for QM
    if args.queue == 'local':
        queue = LocalCPUQueue()
    elif args.queue == 'Slurm':
        queue = SlurmQueue(_configapp=args.code.lower())
    elif args.queue == 'LSF':
        queue = LsfQueue(_configapp=args.code.lower())
    elif args.queue == 'PBS':
        queue = PBSQueue()  # TODO: configure
    elif args.queue == 'AceCloud':
        queue = AceCloudQueue()  # TODO: configure
        queue.groupname = args.groupname
        queue.hashnames = True
    else:
        raise NotImplementedError

    # Override default ncpus
    if args.ncpus:
        logger.info('Overriding ncpus to {}'.format(args.ncpus))
        queue.ncpu = args.ncpus
    if args.memory:
        logger.info('Overriding memory to {}'.format(args.memory))
        queue.memory = args.memory

    # Create a QM object
    if args.code == 'Psi4':
        qm = Psi4()
    elif args.code == 'Gaussian':
        qm = Gaussian()
    else:
        raise NotImplementedError

    # This is for debugging only!
    if args.fake_qm:
        qm = FakeQM2()
        logger.warning('Using FakeQM')

    # Get rotatable dihedral angles
    mol = Molecule(args.filename)
    mol = canonicalizeAtomNames(mol)
    mol, equivalents, all_dihedrals = getEquivalentsAndDihedrals(mol)
    netcharge = args.charge if args.charge is not None else int(
        round(np.sum(mol.charge)))

    if args.list:
        print('\n === Parameterizable dihedral angles of {} ===\n'.format(
            args.filename))
        with open('torsions.txt', 'w') as fh:
            for dihedral in all_dihedrals:
                dihedral_name = '-'.join(mol.name[list(dihedral[0])])
                print('  {}'.format(dihedral_name))
                fh.write(dihedral_name + '\n')
        print()
        sys.exit(0)

    # Set up the QM object
    qm.theory = args.theory
    qm.basis = args.basis
    qm.solvent = args.environment
    qm.queue = queue
    qm.charge = netcharge

    # Select which dihedrals to fit
    parameterizable_dihedrals = [list(dih[0]) for dih in all_dihedrals]
    if len(args.dihedral) > 0:
        all_dihedral_names = [
            '-'.join(mol.name[list(dihedral[0])]) for dihedral in all_dihedrals
        ]
        parameterizable_dihedrals = []
        for dihedral_name in args.dihedral:
            if dihedral_name not in all_dihedral_names:
                raise ValueError(
                    '%s is not recognized as a rotatable dihedral angle' %
                    dihedral_name)
            parameterizable_dihedrals.append(
                list(
                    all_dihedrals[all_dihedral_names.index(dihedral_name)][0]))

    # Print arguments
    print('\n === Arguments ===\n')
    for key, value in vars(args).items():
        print('{:>12s}: {:s}'.format(key, str(value)))

    print('\n === Parameterizing %s ===\n' % args.filename)
    for method in methods:
        print(" === Fitting for %s ===\n" % method.name)
        printReport(mol, netcharge, equivalents, all_dihedrals)

        parameters, mol = fftype(mol,
                                 method=method,
                                 rtfFile=rtfFile,
                                 prmFile=prmFile,
                                 netcharge=args.charge)
        if isinstance(qm, FakeQM2):
            qm._parameters = parameters

        # Copy the molecule to preserve initial coordinates
        orig_coor = mol.coords.copy()

        # Update B3LYP to B3LYP-D3
        # TODO: this is silent and not documented stuff
        if qm.theory == 'B3LYP':
            qm.correction = 'D3'

        # Update basis sets
        # TODO: this is silent and not documented stuff
        if netcharge < 0 and qm.solvent == 'vacuum':
            if qm.basis == '6-31G*':
                qm.basis = '6-31+G*'
            if qm.basis == 'cc-pVDZ':
                qm.basis = 'aug-cc-pVDZ'
            logger.info('Changing basis sets to %s' % qm.basis)

        # Minimize molecule
        if args.minimize:
            print('\n == Minimizing ==\n')
            mol = minimize(mol, qm, args.outdir)

        # Fit ESP charges
        if args.fit_charges:
            print('\n == Fitting ESP charges ==\n')

            # Set random number generator seed
            if args.seed:
                np.random.seed(args.seed)

            # Select the atoms with fixed charges
            fixed_atom_indices = getFixedChargeAtomIndices(
                mol, args.fix_charge)

            # Fit ESP charges
            mol, _, esp_charges, qm_dipole = fitCharges(
                mol, qm, args.outdir, fixed=fixed_atom_indices)

            # Print dipoles
            logger.info('QM dipole: %f %f %f; %f' % tuple(qm_dipole))
            mm_dipole = getDipole(mol)
            if np.all(np.isfinite(mm_dipole)):
                logger.info('MM dipole: %f %f %f; %f' % tuple(mm_dipole))
            else:
                logger.warning(
                    'MM dipole cannot be computed. Check if elements are detected correctly.'
                )

        # Fit dihedral angle parameters
        if args.fit_dihedral:
            print('\n == Fitting dihedral angle parameters ==\n')

            # Set random number generator seed
            if args.seed:
                np.random.seed(args.seed)

            # Invent new atom types for dihedral atoms
            mol, originaltypes = inventAtomTypes(mol,
                                                 parameterizable_dihedrals,
                                                 equivalents)
            parameters = recreateParameters(mol, originaltypes, parameters)
            parameters = createMultitermDihedralTypes(parameters)
            if isinstance(qm, FakeQM2):
                qm._parameters = parameters

            # Fit the parameters
            fitDihedrals(mol,
                         qm,
                         method,
                         parameters,
                         all_dihedrals,
                         parameterizable_dihedrals,
                         args.outdir,
                         geomopt=args.optimize_dihedral)

        # Output the FF parameters
        print('\n == Writing results ==\n')
        writeParameters(mol,
                        parameters,
                        qm,
                        method,
                        netcharge,
                        args.outdir,
                        original_coords=orig_coor)

        # Write energy file
        energyFile = os.path.join(args.outdir, 'parameters', method.name,
                                  _qm_method_name(qm), 'energies.txt')
        printEnergies(mol, parameters, energyFile)
        logger.info('Write energy file: %s' % energyFile)
Ejemplo n.º 3
0
def main_parameterize(arguments=None, progress=None):

    from htmd.parameterization.parameterset import recreateParameters, createMultitermDihedralTypes, inventAtomTypes
    from htmd.parameterization.util import detectChiralCenters, scanDihedrals, filterQMResults, minimize

    progress = progress if callable(progress) else lambda x: None

    logger.info('===== Parameterize =====')

    # Parse arguments
    parser = getArgumentParser()
    args = parser.parse_args(args=arguments)
    _printArguments(args)

    # Validate arguments
    if args.fake_qm and args.nnp:
        raise ValueError('FakeQM and NNP are not compatible')

    # Configure loggers
    if args.debug:
        logger.setLevel(logging.DEBUG)
        logger.debug(sys.argv[1:])

    # Deprecation warnings
    # TODO remove at some point of time
    if args.minimize is not parser.get_default('minimize'):
        raise DeprecationWarning('Use `--min-type` instead.')
    if args.optimize_dihedral is not parser.get_default('optimize_dihedral'):
        raise DeprecationWarning('Use `--scan-type` instead.')

    # Get a molecule and check its validity
    progress('Prepare the molecule')
    mol = _prepare_molecule(args)

    # Preserve the initial molecule
    initial_mol = mol.copy()

    # Select dihedral angles to parameterize
    progress('Detect dihedral angles')
    selected_dihedrals = _select_dihedrals(mol, args)

    # Get a queue
    queue = _get_queue(args)

    # Get QM calculators
    qm_calculator = None
    qm_name = ''
    if args.min_type == 'qm' or args.charge_type == 'ESP' or (
            args.fit_dihedral and not args.nnp):
        qm_calculator = _get_qm_calculator(args, queue)
        qm_name = '{}/{}'.format(qm_calculator.theory, qm_calculator.basis)
        if args.fake_qm:
            qm_name = 'Fake QM'

    # Get NNP calculators
    nnp_calculator = None
    nnp_name = ''
    if args.nnp:
        nnp_calculator = _get_nnp_calculator(args, queue)
        nnp_name = args.nnp

    # Set the reference calculator
    if args.nnp:
        ref_calculator = nnp_calculator
        ref_name = nnp_name
    else:
        ref_calculator = qm_calculator
        ref_name = qm_name
    logger.info('Reference method: {}'.format(ref_name))

    # Assign atom types and initial force field parameters
    progress('Assign atom types and initial parameters')
    mol, parameters = _get_initial_parameters(mol, args)

    # Assign initial atomic charges, if needed
    progress('Assign initial atomic charges')
    mol = _fit_initial_charges(mol, args, initial_mol.atomtype)

    # Geometry minimization
    # TODO refactor
    if args.min_type != 'None':
        progress('Optimize geometry')
        logger.info(' === Geometry minimization ===')

        if args.min_type == 'mm':
            logger.info('Model: MM with the initial force field parameters')
        elif args.min_type == 'qm':
            logger.info('Model: the reference method')
        else:
            raise ValueError()

        # Set parameters for the fake QM
        if args.fake_qm:
            assert not args.nnp
            ref_calculator._parameters = parameters

        # Detect chiral centers
        initial_chiral_centers = detectChiralCenters(
            mol, atom_types=initial_mol.atomtype)

        mm_minimizer = None
        if args.min_type == 'mm':
            from htmd.qm.custom import OMMMinimizer
            mm_minimizer = OMMMinimizer(mol, parameters)

        # Minimize molecule
        mol = minimize(mol,
                       ref_calculator,
                       args.outdir,
                       min_type=args.min_type,
                       mm_minimizer=mm_minimizer)

        # TODO print minimization status
        # Check if the chiral center hasn't changed during the minimization
        chiral_centers = detectChiralCenters(mol,
                                             atom_types=initial_mol.atomtype)
        if initial_chiral_centers != chiral_centers:
            raise RuntimeError(
                'Chiral centers have changed during the minimization: '
                '{} --> {}'.format(initial_chiral_centers, chiral_centers))

    # Fit charges
    progress('Assign atomic charges')
    mol = _fit_charges(mol, args, qm_calculator, initial_mol.atomtype)

    # Scan dihedrals and fit parameters
    # TODO refactor
    if len(selected_dihedrals) > 0:

        from htmd.parameterization.dihedral import DihedralFitting  # Slow import

        progress('Scan dihedral angles')
        logger.info('=== Dihedral angle scanning ===')

        if args.dihed_opt_type == 'None':
            logger.info('Dihedral scanning: static')
        elif args.dihed_opt_type == 'mm':
            logger.info(
                'Dihedral scanning: minimized with MM (using the initial force field parameters)'
            )
        elif args.dihed_opt_type == 'qm':
            logger.info(
                'Dihedral scanning: minimized with the reference method')
        else:
            raise ValueError()

        # Recreate MM minimizer now that charges have been fitted
        mm_minimizer = None
        if args.dihed_opt_type == 'mm':
            from htmd.qm.custom import OMMMinimizer
            mm_minimizer = OMMMinimizer(mol, parameters)

        # Set parameters for the fake QM
        if args.fake_qm:
            assert not args.nnp
            ref_calculator._parameters = parameters

        # Scan dihedral angles
        scan_results = scanDihedrals(mol,
                                     ref_calculator,
                                     selected_dihedrals,
                                     args.outdir,
                                     scan_type=args.dihed_opt_type,
                                     mm_minimizer=mm_minimizer)

        # Filter scan results
        scan_results = filterQMResults(scan_results, mol=initial_mol)
        logger.info('Valid rotamers:')
        for idihed, (dihedral, results) in enumerate(
                zip(selected_dihedrals, scan_results)):
            dihed_name = '-'.join(mol.name[list(dihedral)])
            logger.info('  {:2d}: {}: {}'.format(idihed, dihed_name,
                                                 len(results)))

            if len(results) < 13:
                raise RuntimeError(
                    'Less than 13 valid rotamers for {} dihedral. '
                    'Not enough for fitting!'.format(dihed_name))

        logger.info('=== Dihedral parameter fitting ===')

        # Invent new atom types for dihedral atoms
        progress('Create new atom types')
        old_types = mol.atomtype
        mol, initial_types = inventAtomTypes(mol, selected_dihedrals)
        parameters = recreateParameters(mol, initial_types, parameters)
        parameters = createMultitermDihedralTypes(parameters)
        logger.info('Assign atom with new atom types:')
        for name, old_type, new_type in zip(mol.name, old_types, mol.atomtype):
            if old_type != new_type:
                logger.info('   {:4s} : {:6s} --> {:6s}'.format(
                    name, old_type, new_type))

        # Set random number generator seed
        if args.seed:
            np.random.seed(args.seed)

        # Fit the dihedral parameters
        progress('Fit dihedral angle parameters')
        df = DihedralFitting()
        df.parameters = parameters
        df.molecule = mol
        df.dihedrals = selected_dihedrals
        df.qm_results = scan_results
        df.num_iterations = args.dihed_num_iterations
        df.fit_type = args.dihed_fit_type
        df.result_directory = os.path.join(args.outdir, 'parameters',
                                           args.forcefield)

        # In case of FakeQM, the initial parameters are set to zeros.
        # It prevents DihedralFitting class from cheating :D
        if args.fake_qm:
            df.zeroed_parameters = True

        # Fit dihedral parameters
        parameters = df.run()

        # Plot dihedral profiles
        plot_dir = os.path.join(args.outdir, 'parameters', args.forcefield,
                                'plots')
        os.makedirs(plot_dir, exist_ok=True)
        df.plotConformerEnergies(plot_dir, ref_name=ref_name)
        for idihed in range(len(df.dihedrals)):
            df.plotDihedralEnergies(idihed, plot_dir, ref_name=ref_name)

    # Output the parameters and other results
    progress('Output results')
    _output_results(mol, parameters, initial_mol.coords, args)