def testScalarQuantityConstructor(self): """ Tests creating a Quantity using the Quantity constructor """ self.assertTrue(u.is_quantity(u.Quantity(5, u.centimeters))) self.assertTrue(u.is_quantity(u.Quantity(5, u.centimeters**-1))) x = u.Quantity(value=5.0, unit=100.0 * u.meters) self.assertTrue(u.is_quantity(x)) self.assertEqual(x, 500 * u.meters)
def testNumpyDivision(self): """ Tests that division of numpy Quantities works correctly """ x = u.Quantity(np.asarray([1., 2.]), u.nanometers) y = u.Quantity(np.asarray([3., 4.]), u.picoseconds) xy = x / y self.assertTrue(u.is_quantity(xy)) self.assertEqual(xy.unit, u.nanometers / u.picoseconds) self.assertEqual(xy[0].value_in_unit(u.nanometers / u.picoseconds), 1 / 3) self.assertEqual(xy[1].value_in_unit(u.nanometers / u.picoseconds), 0.5)
def _parse(self, fname): crdfile = open(fname, 'r') readingHeader = True while readingHeader: line = crdfile.readline() if not len(line): raise CharmmFileError('Premature end of file') line = line.strip() words = line.split() if len(line) != 0: if words[0] == 'ENERGIES' or words[0] == '!ENERGIES': readingHeader = False else: self.header.append(line.strip()) else: self.header.append(line.strip()) for row in range(len(self.header)): if len(self.header[row].strip()) != 0: line = self.header[row].strip().split() if line[0][0:5] == 'NATOM' or line[0][0:6] == '!NATOM': try: line = self.header[row + 1].strip().split() self.natom = int(line[0]) self.npriv = int(line[1]) # num. previous steps self.nstep = int(line[2]) # num. steps in file self.nsavc = int(line[3]) # coord save frequency self.nsavv = int(line[4]) # velocities " self.jhstrt = int(line[5]) # Num total steps? break except (ValueError, IndexError) as e: raise CharmmFileError('Problem parsing CHARMM restart') self._scan(crdfile, '!XOLD') self._get_formatted_crds(crdfile, self.positionsold) self._scan(crdfile, '!VX') self._get_formatted_crds(crdfile, self.velocities) self._scan(crdfile, '!X') self._get_formatted_crds(crdfile, self.positions) # Convert velocities to angstroms/ps self.velocities = [v * ONE_TIMESCALE for v in self.velocities] # Add units to positions and velocities self.positions = u.Quantity(self.positions, u.angstroms) self.positionsold = u.Quantity(self.positionsold, u.angstroms) self.velocities = u.Quantity(self.velocities, u.angstroms / u.picoseconds)
def testNumpyDeepCopy(self): """ Check that deepcopy on numpy array does not strip units """ x = u.Quantity(np.zeros((2, 3)), u.nanometer) y = copy.deepcopy(x) self.assertTrue(np.all(x == y)) self.assertTrue(u.is_quantity(x)) self.assertTrue(u.is_quantity(y))
def testDimensionless(self): """ Tests the properties of unit.dimensionless """ x = 5 * u.dimensionless y = u.Quantity(5, u.dimensionless) self.assertTrue(u.is_quantity(x)) self.assertTrue(u.is_quantity(y)) self.assertNotEqual(x, 5) self.assertNotEqual(y, 5) self.assertEqual(x, y) self.assertEqual(x.value_in_unit_system(u.si_unit_system), 5) self.assertEqual(x.value_in_unit_system(u.cgs_unit_system), 5) self.assertEqual(x.value_in_unit_system(u.md_unit_system), 5) x = u.Quantity(1.0, u.dimensionless) y = u.Quantity(1.0, u.dimensionless) self.assertIsNot(x, y) self.assertEqual(x, y)
def testCollectionQuantityOperations(self): """ Tests that Quantity collections behave correctly """ # Tests that __getitem__ returns a unit s = [1, 2, 3, 4] * u.angstroms self.assertTrue(u.is_quantity(s[0])) for i, val in enumerate(s): self.assertTrue(u.is_quantity(val)) self.assertEqual(val, (i + 1) * u.angstroms) # Tests that __setitem__ fails when an incompatible type is added def fail(s): s[0] = 5 self.assertRaises(AttributeError, lambda: fail(s)) def fail(s): s[0] = 5 * u.joules self.assertRaises(TypeError, lambda: fail(s)) def fail(s): s[0] /= 10 * u.meters self.assertRaises(AttributeError, lambda: fail(s)) # Tests that __setitem__ converts to the unit of the container s[0] = 1 * u.nanometers self.assertEqual(s[0]._value, 10) # Tests that __setitem__ handles slice assignment correctly x = [0, 1, 2, 3, 4] * u.kelvin x[2:4] = [-2, -3] * u.kelvin self.assertEqual(x._value, [0, 1, -2, -3, 4]) # Tests standard unit conversions x = [1, 2, 3] * u.centimeters self.assertEqual(x / u.millimeters, [10, 20, 30]) # Test the construction of a container in which each element is a # Quantity, passed to the Quantity constructor x = u.Quantity([1 * u.angstrom, 2 * u.nanometer, 3 * u.angstrom]) self.assertEqual(x._value, [1, 20, 3]) self.assertEqual(x.unit, u.angstrom) x = u.Quantity((1, 2, 3)) self.assertTrue(u.is_quantity(x)) self.assertTrue(x.unit.is_dimensionless()) x = u.Quantity(([1 * u.angstrom, 2 * u.nanometer, 3 * u.angstrom], [1 * u.angstrom, 4 * u.nanometer, 3 * u.angstrom])) self.assertEqual(x._value, ([1, 20, 3], [1, 40, 3])) self.assertEqual(x.unit, u.angstrom) self.assertTrue(u.is_quantity(u.Quantity([])))
def testReduceUnit(self): """ Tests the reduce_unit functionality """ x = u.nanometer**2 / u.angstrom**2 self.assertEqual(str(x), 'nanometer**2/(angstrom**2)') self.assertTrue(x.is_dimensionless()) q = u.Quantity(2.0, x) self.assertEqual(str(q), '2.0 nm**2/(A**2)') self.assertEqual(q.reduce_unit(), 200)
def testMutableQuantityOperations(self): " Tests that mutable Quantity objects do not get unexpectedly changed " # This used to be a bug -- t and s._value were the same object, so # changing t would also change s silently s = [1, 2, 3, 4] * u.angstroms t = s / u.angstroms self.assertEqual(t, [1, 2, 3, 4]) self.assertEqual(s, u.Quantity([1, 2, 3, 4], u.angstroms)) t[0] = 2 self.assertEqual(t, [2, 2, 3, 4]) self.assertEqual(s, u.Quantity([1, 2, 3, 4], u.angstroms)) t = s.value_in_unit(u.angstroms) self.assertEqual(t, [1, 2, 3, 4]) self.assertEqual(s, u.Quantity([1, 2, 3, 4], u.angstroms)) t[0] = 2 self.assertEqual(t, [2, 2, 3, 4]) self.assertEqual(s, u.Quantity([1, 2, 3, 4], u.angstroms)) s = [1, 2, 3, 4] * u.nanometers self.assertEqual(s, u.Quantity([1, 2, 3, 4], u.nanometers)) t = s.in_units_of(u.nanometers) self.assertEqual(s, t) t[0] = 1 * u.meters self.assertAlmostEqualQuantities( t, u.Quantity([1e9, 2, 3, 4], u.nanometers)) self.assertEqual(s, u.Quantity([1, 2, 3, 4], u.nanometers))
def testNumpyFunctions(self): """ Tests various numpy attributes that they result in Quantities """ a = u.Quantity(np.arange(10), u.seconds) self.assertEqual(a.max(), 9 * u.seconds) self.assertEqual(a.min(), 0 * u.seconds) self.assertEqual(a.mean(), 4.5 * u.seconds) self.assertAlmostEqualQuantities(a.std(), 2.8722813232690143 * u.seconds) b = a.reshape((5, 2)) self.assertTrue(u.is_quantity(b))
def test_setPositions_units(self): n_particles = self.simulation.context.getSystem().getNumParticles() input = unit.Quantity(np.random.randn(n_particles, 3), unit.angstroms) self.simulation.context.setPositions(input) output = self.simulation.context.getState( getPositions=True).getPositions(asNumpy=True) np.testing.assert_array_almost_equal( input.value_in_unit(unit.nanometers), output.value_in_unit(unit.nanometers))
def _parse(self, fname): with open(fname, 'r') as crdfile: line = crdfile.readline() while len(line.strip()) == 0: # Skip whitespace, as a precaution line = crdfile.readline() intitle = True while intitle: self.title.append(line.strip()) line = crdfile.readline() if len(line.strip()) == 0: intitle = False elif line.strip()[0] != '*': intitle = False else: intitle = True while len(line.strip()) == 0: # Skip whitespace line = crdfile.readline() try: self.natom = int(line.strip().split()[0]) for _ in range(self.natom): line = crdfile.readline().strip().split() self.atomno.append(int(line[0])) self.resno.append(int(line[1])) self.resname.append(line[2]) self.attype.append(line[3]) pos = Vec3(float(line[4]), float(line[5]), float(line[6])) self.positions.append(pos) self.segid.append(line[7]) self.weighting.append(float(line[9])) if self.natom != len(self.positions): raise CharmmFileError( "Error parsing CHARMM .crd file: %d " "atoms requires %d positions (not %d)" % (self.natom, self.natom, len(self.positions))) except (ValueError, IndexError): raise CharmmFileError('Error parsing CHARMM coordinate file') # Apply units to the positions now. Do it this way to allow for # (possible) numpy functionality in the future. self.positions = u.Quantity(self.positions, u.angstroms)
def get_off_molecule(self, include_conformers: bool = True) -> off.Molecule: """Build and openforcefield.topology.Molecule representation of the input molecule. Parameters: include_conformers: If `True` all of the input conformers are included else they are dropped. """ molecule = self.attributes.to_openff_molecule() molecule.name = self.index if include_conformers: for conformer in self.initial_molecules: geometry = unit.Quantity(np.array(conformer.geometry), unit=unit.bohr) molecule.add_conformer(geometry.in_units_of(unit.angstrom)) return molecule
def testQuantityMaths(self): """ Tests dimensional analysis & maths on and b/w Quantity objects """ x = 1.3 * u.meters y = 75.2 * u.centimeters self.assertEqual((x + y) / u.meters, 2.052) self.assertEqual((x - y) / u.meters, 0.548) self.assertEqual(x / y, 1.3 / 0.752) self.assertEqual(x * y, 1.3 * 0.752 * u.meters**2) d1 = 2.0 * u.meters d2 = 2.0 * u.nanometers self.assertEqual(d1 + d2, (2 + 2e-9) * u.meters) self.assertAlmostEqual((d2 + d1 - (2e9 + 2) * u.nanometers)._value, 0, places=6) self.assertEqual(d1 + d1, 4.0 * u.meters) self.assertEqual(d1 - d1, 0.0 * u.meters) self.assertEqual(d1 / d1, 1.0) self.assertEqual(d1 * u.meters, 2.0 * u.meters**2) self.assertEqual(u.kilograms * (d1 / u.seconds) * (d1 / u.seconds), 4 * u.kilograms * u.meters**2 / u.seconds**2) self.assertEqual(u.kilograms * (d1 / u.seconds)**2, 4 * u.kilograms * u.meters**2 / u.seconds**2) self.assertEqual(d1**3, 8.0 * u.meters**3) x = d1**(3 / 2) self.assertAlmostEqual(x._value, math.sqrt(2)**3) self.assertEqual(x.unit, u.meters**(3 / 2)) self.assertAlmostEqual((d1**0.5)._value, math.sqrt(2)) self.assertEqual((d1**0.5).unit, u.meters**0.5) comp = (3.0 + 4.0j) * u.meters self.assertTrue(u.is_quantity(comp)) self.assertEqual(comp.unit, u.meters) self.assertEqual(str(comp), '(3+4j) m') self.assertEqual(comp + comp, (6.0 + 8.0j) * u.meters) self.assertEqual(comp - comp, 0 * u.meters) self.assertEqual(comp * comp, (3.0 + 4.0j)**2 * u.meters**2) self.assertAlmostEqual(comp / comp, 1) self.assertAlmostEqual(1.5 * u.nanometers / u.meters, 1.5e-9, places=15) self.assertEqual((2.3 * u.meters)**2, 2.3**2 * u.meters**2) x = 4.3 * u.meters self.assertEqual(x / u.centimeters, 430) self.assertEqual(str(x / u.seconds), '4.3 m/s') self.assertEqual(str(8.4 / (4.2 * u.centimeters)), '2.0 /cm') x = 1.2 * u.meters self.assertEqual(x * 5, u.Quantity(6.0, u.meters))
def add_molecule(self, molecule: off.Molecule) -> bool: """ Add a molecule to the molecule list after checking that it is not present already. If it is de-duplicate the record and condense the conformers and metadata. Args: molecule: The molecule and its conformers which we should try and add to the result. Returns: `True` if the molecule is already present and `False` if not. """ # always strip the atom map as it is not preserved in a workflow if "atom_map" in molecule.properties: del molecule.properties["atom_map"] # make a unique molecule hash independent of atom order or conformers molecule_hash = molecule.to_inchikey(fixed_hydrogens=True) if not self.skip_unique_check and molecule_hash in self._molecules: # we need to align the molecules and transfer the coords and properties # get the mapping, drop some comparisons to match inchikey isomorphic, mapping = off.Molecule.are_isomorphic( molecule, self._molecules[molecule_hash], return_atom_map=True, formal_charge_matching=False, bond_order_matching=False, ) assert isomorphic is True # transfer any torsion indexes for similar fragments if "dihedrals" in molecule.properties: # we need to transfer the properties; get the current molecule dihedrals indexer # if one is missing create a new one current_indexer = self._molecules[ molecule_hash].properties.get("dihedrals", TorsionIndexer()) # update it with the new molecule info current_indexer.update( torsion_indexer=molecule.properties["dihedrals"], reorder_mapping=mapping, ) # store it back self._molecules[molecule_hash].properties[ "dihedrals"] = current_indexer if molecule.n_conformers != 0: # transfer the coordinates for conformer in molecule.conformers: new_conformer = np.zeros((molecule.n_atoms, 3)) for i in range(molecule.n_atoms): new_conformer[i] = conformer[mapping[i]].value_in_unit( unit.angstrom) new_conf = unit.Quantity(value=new_conformer, unit=unit.angstrom) # check if the conformer is already on the molecule for old_conformer in self._molecules[ molecule_hash].conformers: if old_conformer.tolist() == new_conf.tolist(): break else: self._molecules[molecule_hash].add_conformer( new_conformer * unit.angstrom) else: # molecule already in list and coords not present so just return return True else: if molecule.n_conformers == 0: # make sure this is a list to avoid errors molecule._conformers = [] self._molecules[molecule_hash] = molecule return False
def _equil_NPT_OpenMM_protocol_0(topology, positions, temperature=300.0 * _unit.kelvin, pressure=1.0 * _unit.atmosphere, time=1.0 * _unit.nanosecond, forcefield=None, verbose=True, progress_bar=True): import numpy as np import openmm.app as app import openmm as mm from openmmtools.integrators import LangevinIntegrator, GeodesicBAOABIntegrator if progress_bar: from tqdm import tqdm else: def tqdm(arg): return arg #item needs to be openmm.modeller forcefield = app.ForceField("amber99sbildn.xml", "tip3p.xml") topology = item.topology positions = item.positions system = forcefield_generator.createSystem(topology, contraints=app.HBonds, nonbondedMethod=app.PME, nonbondedCutoff=1.0 * _unit.nanometers, rigidWater=True, ewaldErrorTolerance=0.0005) ## Thermodynamic State kB = _unit.BOLTZMANN_CONSTANT_kB * _unit.AVOGADRO_CONSTANT_NA temperature = temperature pressure = pressure ## Barostat barostat_frequency = 25 # steps barostat = mm.MonteCarloBarostat(pressure, temperature, barostat_frequency) system.addForce(barostat) ## Integrator friction = 1.0 / _unit.picosecond step_size = 2.0 * _unit.femtoseconds integrator = LangevinIntegrator(temperature, friction, step_size) integrator.setConstraintTolerance(0.00001) ## Platform platform = mm.Platform.getPlatformByName('CUDA') properties = {'CudaPrecision': 'mixed'} ## Simulation simulation = app.Simulation(topology, system, integrator, platform, properties) simulation.context.setPositions(positions) simulation.context.setVelocitiesToTemperature(temperature) time_equilibration = time time_iteration = 0.2 * _unit.picoseconds number_iterations = int(time_equilibration / time_iteration) steps_iteration = int(time_iteration / step_size) steps_equilibration = number_iterations * steps_iteration ## Reporters net_mass, n_degrees_of_freedom = m3t.get(system, net_mass=True, n_degrees_of_freedom=True) niters = number_iterations data = dict() data['time'] = _unit.Quantity(np.zeros([niters], np.float64), _unit.picoseconds) data['potential'] = _unit.Quantity(np.zeros([niters], np.float64), _unit.kilocalories_per_mole) data['kinetic'] = _unit.Quantity(np.zeros([niters], np.float64), _unit.kilocalories_per_mole) data['volume'] = _unit.Quantity(np.zeros([niters], np.float64), _unit.angstroms**3) data['density'] = _unit.Quantity(np.zeros([niters], np.float64), _unit.gram / _unit.centimeters**3) data['kinetic_temperature'] = unit.Quantity(np.zeros([niters], np.float64), _unit.kelvin) for iteration in tqdm(range(number_iterations)): integrator.step(steps_iteration) state = simulation.context.getState(getEnergy=True) time = state.getTime() potential_energy = state.getPotentialEnergy() kinetic_energy = state.getKineticEnergy() volume = state.getPeriodicBoxVolume() density = (net_mass / volume).in_units_of(unit.gram / unit.centimeter**3) kinetic_temperature = (2.0 * kinetic_energy / kB / n_degrees_of_freedom).in_units_of( unit.kelvin) # (1/2) ndof * kB * T = KE data['time'][iteration] = time data['potential'] = potential_energy data['kinetic'] = kinetic_energy data['volume'] = volume data['density'] = density data['kinetic_temperature'] = kinetic_temperature final_state = simulation.context.getState(getPositions=True, getVelocities=True) final_positions = final_state.getPositions() final_velocities = final_state.getVelocities() return final_positions, final_velocities, data
def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": """ Runs OpenMM on given structure, inputs, in vacuum. """ self.found(raise_error=True) try: import openmm from openmm import unit except ImportError: from simtk import openmm, unit with capture_stdout(): from openff.toolkit import topology as offtop # Failure flag ret_data = {"success": False} # generate basis, not given if not input_model.model.basis: raise InputError("Method must contain a basis set.") if isinstance(input_model.model.basis, BasisSet): raise InputError( "QCSchema BasisSet for model.basis not implemented since not suitable for OpenMM." ) # Make sure we are using smirnoff or antechamber basis = input_model.model.basis.lower() if basis in ["smirnoff", "antechamber"]: with capture_stdout(): # try and make the molecule from the cmiles cmiles = None if input_model.molecule.extras: cmiles = input_model.molecule.extras.get( "canonical_isomeric_explicit_hydrogen_mapped_smiles", None) if cmiles is None: cmiles = input_model.molecule.extras.get( "cmiles", {} ).get( "canonical_isomeric_explicit_hydrogen_mapped_smiles", None) if cmiles is not None: off_mol = offtop.Molecule.from_mapped_smiles( mapped_smiles=cmiles) # add the conformer conformer = unit.Quantity(value=np.array( input_model.molecule.geometry), unit=unit.bohr) off_mol.add_conformer(conformer) else: # Process molecule with RDKit rdkit_mol = RDKitHarness._process_molecule_rdkit( input_model.molecule) # Create an Open Force Field `Molecule` from the RDKit Molecule off_mol = offtop.Molecule(rdkit_mol) # now we need to create the system openmm_system = self._generate_openmm_system( molecule=off_mol, method=input_model.model.method, keywords=input_model.keywords) else: raise InputError( "Accepted bases are: {'smirnoff', 'antechamber', }") # Need an integrator for simulation even if we don't end up using it really integrator = openmm.VerletIntegrator(1.0 * unit.femtoseconds) # Set platform to CPU explicitly platform = openmm.Platform.getPlatformByName("CPU") # Set number of threads to use # if `nthreads` is `None`, OpenMM default of all logical cores on # processor will be used nthreads = config.ncores if nthreads is None: nthreads = os.environ.get("OPENMM_CPU_THREADS") if nthreads: properties = {"Threads": str(nthreads)} else: properties = {} # Initialize context context = openmm.Context(openmm_system, integrator, platform, properties) # Set positions from our Open Force Field `Molecule` context.setPositions(off_mol.conformers[0]) # Compute the energy of the configuration state = context.getState(getEnergy=True) # Get the potential as a unit.Quantity, put into units of hartree q = state.getPotentialEnergy( ) / unit.hartree / unit.AVOGADRO_CONSTANT_NA ret_data["properties"] = {"return_energy": q} # Execute driver if input_model.driver == "energy": ret_data["return_result"] = ret_data["properties"]["return_energy"] elif input_model.driver == "gradient": # Compute the forces state = context.getState(getForces=True) # Get the gradient as a unit.Quantity with shape (n_atoms, 3) gradient = state.getForces(asNumpy=True) # Convert to hartree/bohr and reformat as 1D array q = (gradient / (unit.hartree / unit.bohr) ).reshape(-1) / unit.AVOGADRO_CONSTANT_NA # Force to gradient ret_data["return_result"] = -1 * q else: raise InputError( f"Driver {input_model.driver} not implemented for OpenMM.") ret_data["success"] = True ret_data["extras"] = input_model.extras # Move several pieces up a level ret_data["provenance"] = Provenance( creator="openmm", version=openmm.version.short_version, nthreads=nthreads) return AtomicResult(**{**input_model.dict(), **ret_data})
def setUp(self): self.data = unit.Quantity(np.arange(300), unit.nanometers)
def test_molecule_and_water_offxml(coumarin, water, tmpdir, rfree_data, parameters): """Test that an offxml can parameterize a molecule and water mixture.""" coumarin_copy = coumarin.copy(deep=True) MBISCharges.apply_symmetrisation(coumarin_copy) with tmpdir.as_cwd(): # run the lj method to make sure the parameters match alpha = rfree_data.pop("alpha") beta = rfree_data.pop("beta") lj = LennardJones612(free_parameters=rfree_data, alpha=alpha, beta=beta) # get new Rfree data lj.run(coumarin_copy) # remake rfree rfree_data["alpha"] = alpha rfree_data["beta"] = beta _combine_molecules_offxml( molecules=[coumarin_copy], parameters=parameters, rfree_data=rfree_data, filename="combined.offxml", water_model="tip3p", ) combinded_ff = ForceField( "combined.offxml", load_plugins=True, allow_cosmetic_attributes=True ) mixed_top = Topology.from_molecules( molecules=[ Molecule.from_rdkit(water.to_rdkit()), Molecule.from_rdkit(coumarin_copy.to_rdkit()), ] ) system = combinded_ff.create_openmm_system(topology=mixed_top) # make sure we have 3 constraints assert system.getNumConstraints() == 3 # check each constraint reference_constraints = [ [0, 1, unit.Quantity(0.9572, unit=unit.angstroms)], [0, 2, unit.Quantity(0.9572, unit=unit.angstroms)], [1, 2, unit.Quantity(1.5139006545247014, unit=unit.angstroms)], ] for i in range(3): a, b, constraint = system.getConstraintParameters(i) assert a == reference_constraints[i][0] assert b == reference_constraints[i][1] assert constraint == reference_constraints[i][2].in_units_of( unit.nanometers ) # now loop over the forces and make sure water was correctly parameterised forces = dict((force.__class__.__name__, force) for force in system.getForces()) nonbonded_force: openmm.NonbondedForce = forces["NonbondedForce"] # first check water has the correct parameters water_reference = [ [ unit.Quantity(-0.834, unit.elementary_charge), unit.Quantity(3.1507, unit=unit.angstroms), unit.Quantity(0.1521, unit=unit.kilocalorie_per_mole), ], [ unit.Quantity(0.417, unit.elementary_charge), unit.Quantity(1, unit=unit.angstroms), unit.Quantity(0, unit=unit.kilocalorie_per_mole), ], [ unit.Quantity(0.417, unit.elementary_charge), unit.Quantity(1, unit=unit.angstroms), unit.Quantity(0, unit=unit.kilocalorie_per_mole), ], ] for i in range(3): charge, sigma, epsilon = nonbonded_force.getParticleParameters(i) assert charge == water_reference[i][0] assert sigma.in_units_of(unit.angstroms) == water_reference[i][1] assert ( epsilon.in_units_of(unit.kilocalorie_per_mole) == water_reference[i][2] ) # now check coumarin for i in range(coumarin_copy.n_atoms): ref_params = coumarin_copy.NonbondedForce[(i,)] charge, sigma, epsilon = nonbonded_force.getParticleParameters(i + 3) assert charge.value_in_unit(unit.elementary_charge) == float( ref_params.charge ) assert sigma.value_in_unit(unit.nanometers) == ref_params.sigma assert epsilon.value_in_unit(unit.kilojoule_per_mole) == ref_params.epsilon
def openmm_energy(prm, structure, coords, box=None, cutoff=None, switch_dist=None): from openmm import unit from openmm import app import openmm from parmed.amber import AmberParm if box is not None and not np.all(box == 0): if cutoff is None: raise RuntimeError( "You need to provide a cutoff when passing a box") a = unit.Quantity( (box[0] * unit.angstrom, 0 * unit.angstrom, 0 * unit.angstrom)) b = unit.Quantity( (0 * unit.angstrom, box[1] * unit.angstrom, 0 * unit.angstrom)) c = unit.Quantity( (0 * unit.angstrom, 0 * unit.angstrom, box[2] * unit.angstrom)) structure.box_vectors = (a, b, c) if isinstance(structure, AmberParm): system = structure.createSystem( nonbondedMethod=app.CutoffPeriodic, nonbondedCutoff=0 if cutoff is None else cutoff * unit.angstrom, switchDistance=0 if switch_dist is None else switch_dist * unit.angstrom, ) else: system = structure.createSystem( prm, nonbondedMethod=app.CutoffPeriodic, nonbondedCutoff=0 if cutoff is None else cutoff * unit.angstrom, switchDistance=0 if switch_dist is None else switch_dist * unit.angstrom, ) system.setDefaultPeriodicBoxVectors(a, b, c) else: if isinstance(structure, AmberParm): system = structure.createSystem() else: system = structure.createSystem(prm) disableDispersionCorrection(system) integrator = openmm.LangevinIntegrator(300 * unit.kelvin, 1 / unit.picoseconds, 2 * unit.femtoseconds) try: platform = openmm.Platform.getPlatformByName("CUDA") properties = {"CudaPrecision": "double"} except Exception: platform = openmm.Platform.getPlatformByName("CPU") properties = {} context = openmm.Context(system, integrator, platform, properties) # Run OpenMM with given coordinates context.setPositions(coords * unit.angstrom) energies = parmed.openmm.energy_decomposition(structure, context) state = context.getState(getForces=True) forces = state.getForces(asNumpy=True).value_in_unit( unit.kilocalories_per_mole / unit.angstrom) if "angle" not in energies: energies["angle"] = 0 if "dihedral" not in energies: energies["dihedral"] = 0 if "improper" not in energies: energies["improper"] = 0 return energies, forces
def testNumpyQuantity(self): """ Tests that numpy arrays can form Quantity values """ q = u.Quantity(np.array([1, 2, 3]), u.centimeters) self.assertTrue(u.is_quantity(q)) self.assertIsInstance(q._value, np.ndarray) self.assertTrue(np.all(q / u.millimeters == np.array([1, 2, 3]) * 10))
def testString(self): """ Tests unit handling with strings, which should be dimensionless """ s = u.Quantity("string") self.assertEqual(s.value_in_unit_system(u.md_unit_system), "string")
def testUnaryOperators(self): """ Tests unary operators on units """ self.assertEqual(-(2.3 * u.meters), u.Quantity(-2.3, u.meters)) self.assertEqual(-(2.3 * u.meters), -u.Quantity(2.3, u.meters)) self.assertEqual(+(2.3 * u.meters), u.Quantity(2.3, u.meters)) self.assertEqual(2.3 * u.meters, +u.Quantity(2.3, u.meters))
def __init__(self, pdb_line, pdbstructure=None, extraParticleIdentifier='EP'): """Create a new pdb.Atom from an ATOM or HETATM line. Example line: ATOM 2209 CB TYR A 299 6.167 22.607 20.046 1.00 8.12 C 00000000011111111112222222222333333333344444444445555555555666666666677777777778 12345678901234567890123456789012345678901234567890123456789012345678901234567890 ATOM line format description from http://deposit.rcsb.org/adit/docs/pdb_atom_format.html: COLUMNS DATA TYPE CONTENTS -------------------------------------------------------------------------------- 1 - 6 Record name "ATOM " 7 - 11 Integer Atom serial number. 13 - 16 Atom Atom name. 17 Character Alternate location indicator. 18 - 20 Residue name Residue name. 22 Character Chain identifier. 23 - 26 Integer Residue sequence number. 27 AChar Code for insertion of residues. 31 - 38 Real(8.3) Orthogonal coordinates for X in Angstroms. 39 - 46 Real(8.3) Orthogonal coordinates for Y in Angstroms. 47 - 54 Real(8.3) Orthogonal coordinates for Z in Angstroms. 55 - 60 Real(6.2) Occupancy (Default = 1.0). 61 - 66 Real(6.2) Temperature factor (Default = 0.0). 73 - 76 LString(4) Segment identifier, left-justified. 77 - 78 LString(2) Element symbol, right-justified. 79 - 80 LString(2) Charge on the atom. """ # We might modify first/final status during _finalize() methods self.is_first_atom_in_chain = False self.is_final_atom_in_chain = False self.is_first_residue_in_chain = False self.is_final_residue_in_chain = False # Start parsing fields from pdb line self.record_name = pdb_line[0:6].strip() if pdbstructure is not None and pdbstructure._atom_numbers_are_hex: self.serial_number = int(pdb_line[6:11], 16) else: try: self.serial_number = int(pdb_line[6:11]) except: try: self.serial_number = int(pdb_line[6:11], 16) pdbstructure._atom_numbers_are_hex = True except: # Just give it the next number in sequence. self.serial_number = pdbstructure._next_atom_number self.name_with_spaces = pdb_line[12:16] alternate_location_indicator = pdb_line[16] self.residue_name_with_spaces = pdb_line[17:20] # In some MD codes, notably ffamber in gromacs, residue name has a fourth character in # column 21 possible_fourth_character = pdb_line[20:21] if possible_fourth_character != " ": # Fourth character should only be there if official 3 are already full if len(self.residue_name_with_spaces.strip()) != 3: raise ValueError('Misaligned residue name: %s' % pdb_line) self.residue_name_with_spaces += possible_fourth_character self.residue_name = self.residue_name_with_spaces.strip() self.chain_id = pdb_line[21] if pdbstructure is not None and pdbstructure._residue_numbers_are_hex: self.residue_number = int(pdb_line[22:26], 16) else: try: self.residue_number = int(pdb_line[22:26]) except: try: self.residue_number = int(pdb_line[22:26], 16) pdbstructure._residue_numbers_are_hex = True except: # When VMD runs out of hex values it starts filling the residue ID field with ****. # Look at the most recent atoms to figure out whether this is a new residue or not. if pdbstructure._current_model is None or pdbstructure._current_model._current_chain is None or pdbstructure._current_model._current_chain._current_residue is None: # This is the first residue in the model. self.residue_number = pdbstructure._next_residue_number else: currentRes = pdbstructure._current_model._current_chain._current_residue if currentRes.name_with_spaces != self.residue_name_with_spaces: # The residue name has changed. self.residue_number = pdbstructure._next_residue_number elif self.name_with_spaces in currentRes.atoms_by_name: # There is already an atom with this name. self.residue_number = pdbstructure._next_residue_number else: self.residue_number = currentRes.number self.insertion_code = pdb_line[26] # coordinates, occupancy, and temperature factor belong in Atom.Location object x = float(pdb_line[30:38]) y = float(pdb_line[38:46]) z = float(pdb_line[46:54]) try: occupancy = float(pdb_line[54:60]) except: occupancy = 1.0 try: temperature_factor = unit.Quantity(float(pdb_line[60:66]), unit.angstroms**2) except: temperature_factor = unit.Quantity(0.0, unit.angstroms**2) self.locations = {} loc = Atom.Location(alternate_location_indicator, unit.Quantity(Vec3(x, y, z), unit.angstroms), occupancy, temperature_factor, self.residue_name_with_spaces) self.locations[alternate_location_indicator] = loc self.default_location_id = alternate_location_indicator # segment id, element_symbol, and formal_charge are not always present self.segment_id = pdb_line[72:76].strip() self.element_symbol = pdb_line[76:78].strip() try: self.formal_charge = int(pdb_line[78:80]) except ValueError: self.formal_charge = None # figure out atom element if self.element_symbol == extraParticleIdentifier: self.element = 'EP' else: try: # Try to find a sensible element symbol from columns 76-77 self.element = element.get_by_symbol(self.element_symbol) except KeyError: self.element = None if pdbstructure is not None: pdbstructure._next_atom_number = self.serial_number + 1 pdbstructure._next_residue_number = self.residue_number + 1
def _combine_molecules_offxml( molecules: List["Ligand"], parameters: List[str], rfree_data: Dict[str, Dict[str, Union[str, float]]], filename: str, water_model: Literal["tip3p"] = "tip3p", ): """ Main worker function to build the combined offxmls. """ if sum([molecule.extra_sites.n_sites for molecule in molecules]) > 0: raise NotImplementedError( "Virtual sites can not be safely converted into offxml format yet." ) if sum([molecule.RBTorsionForce.n_parameters for molecule in molecules]) > 0: raise NotImplementedError( "RBTorsions can not yet be safely converted into offxml format yet." ) try: from chemper.graphs.cluster_graph import ClusterGraph except ModuleNotFoundError: raise ModuleNotFoundError( "chemper is required to make an offxml, please install with `conda install chemper -c conda-forge`." ) fit_ab = False # if alpha and beta should be fit if "AB" in parameters: fit_ab = True rfree_codes = set() # keep track of all rfree codes used by these molecules # create the master ff offxml = ForceField(allow_cosmetic_attributes=True, load_plugins=True) offxml.author = f"QUBEKit_version_{qubekit.__version__}" offxml.date = datetime.now().strftime("%Y_%m_%d") # get all of the handlers _ = offxml.get_parameter_handler("Constraints") bond_handler = offxml.get_parameter_handler("Bonds") angle_handler = offxml.get_parameter_handler("Angles") proper_torsions = offxml.get_parameter_handler("ProperTorsions") improper_torsions = offxml.get_parameter_handler("ImproperTorsions") _ = offxml.get_parameter_handler( "Electrostatics", handler_kwargs={"scale14": 0.8333333333, "version": 0.3} ) using_plugin = False if parameters: # if we want to optimise the Rfree we need our custom handler vdw_handler = offxml.get_parameter_handler( "QUBEKitvdWTS", allow_cosmetic_attributes=True ) using_plugin = True else: vdw_handler = offxml.get_parameter_handler( "vdW", allow_cosmetic_attributes=True ) library_charges = offxml.get_parameter_handler("LibraryCharges") for molecule in molecules: rdkit_mol = molecule.to_rdkit() bond_types = molecule.bond_types # for each bond type collection create a single smirks pattern for bonds in bond_types.values(): graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[bonds], layers="all" ) qube_bond = molecule.BondForce[bonds[0]] bond_handler.add_parameter( parameter_kwargs={ "smirks": graph.as_smirks(), "length": qube_bond.length * unit.nanometers, "k": qube_bond.k * unit.kilojoule_per_mole / unit.nanometers**2, } ) angle_types = molecule.angle_types for angles in angle_types.values(): graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[angles], layers="all", ) qube_angle = molecule.AngleForce[angles[0]] angle_handler.add_parameter( parameter_kwargs={ "smirks": graph.as_smirks(), "angle": qube_angle.angle * unit.radian, "k": qube_angle.k * unit.kilojoule_per_mole / unit.radians**2, } ) torsion_types = molecule.dihedral_types for dihedrals in torsion_types.values(): graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[dihedrals], layers="all", ) qube_dihedral = molecule.TorsionForce[dihedrals[0]] proper_torsions.add_parameter( parameter_kwargs={ "smirks": graph.as_smirks(), "k1": qube_dihedral.k1 * unit.kilojoule_per_mole, "k2": qube_dihedral.k2 * unit.kilojoule_per_mole, "k3": qube_dihedral.k3 * unit.kilojoule_per_mole, "k4": qube_dihedral.k4 * unit.kilojoule_per_mole, "periodicity1": qube_dihedral.periodicity1, "periodicity2": qube_dihedral.periodicity2, "periodicity3": qube_dihedral.periodicity3, "periodicity4": qube_dihedral.periodicity4, "phase1": qube_dihedral.phase1 * unit.radians, "phase2": qube_dihedral.phase2 * unit.radians, "phase3": qube_dihedral.phase3 * unit.radians, "phase4": qube_dihedral.phase4 * unit.radians, "idivf1": 1, "idivf2": 1, "idivf3": 1, "idivf4": 1, } ) improper_types = molecule.improper_types for torsions in improper_types.values(): impropers = [ (improper[1], improper[0], *improper[2:]) for improper in torsions ] graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[impropers], layers="all" ) qube_improper = molecule.ImproperTorsionForce[torsions[0]] # we need to multiply each k value by as they will be applied as trefoil see # <https://openforcefield.github.io/standards/standards/smirnoff/#impropertorsions> for more details # we assume we only have a k2 term for improper torsions via a periodic term improper_torsions.add_parameter( parameter_kwargs={ "smirks": graph.as_smirks(), "k1": qube_improper.k2 * 3 * unit.kilojoule_per_mole, "periodicity1": qube_improper.periodicity2, "phase1": qube_improper.phase2 * unit.radians, } ) atom_types = {} for atom_index, cip_type in molecule.atom_types.items(): atom_types.setdefault(cip_type, []).append((atom_index,)) for sym_set in atom_types.values(): graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[sym_set], layers="all" ) qube_non_bond = molecule.NonbondedForce[sym_set[0]] rfree_code = _get_parameter_code( molecule=molecule, atom_index=sym_set[0][0] ) atom_data = { "smirks": graph.as_smirks(), } if rfree_code in parameters or fit_ab: # keep track of present codes to optimise rfree_codes.add(rfree_code) if using_plugin: # this is to be refit atom = molecule.atoms[qube_non_bond.atoms[0]] atom_data["volume"] = atom.aim.volume * unit.angstroms**3 else: atom_data["epsilon"] = qube_non_bond.epsilon * unit.kilojoule_per_mole atom_data["sigma"] = qube_non_bond.sigma * unit.nanometers vdw_handler.add_parameter(parameter_kwargs=atom_data) charge_data = dict( (f"charge{param.atoms[0] + 1}", param.charge * unit.elementary_charge) for param in molecule.NonbondedForce ) charge_data["smirks"] = molecule.to_smiles(mapped=True) library_charges.add_parameter(parameter_kwargs=charge_data) # now loop over all the parameters to be fit and add them as cosmetic attributes to_parameterize = [] for parameter_to_fit in parameters: if parameter_to_fit != "AB" and parameter_to_fit in rfree_codes: setattr( vdw_handler, f"{parameter_to_fit.lower()}free", unit.Quantity( rfree_data[parameter_to_fit]["r_free"], unit=unit.angstroms ), ) to_parameterize.append(f"{parameter_to_fit.lower()}free") if fit_ab: vdw_handler.alpha = rfree_data["alpha"] vdw_handler.beta = rfree_data["beta"] to_parameterize.extend(["alpha", "beta"]) if to_parameterize: vdw_handler.add_cosmetic_attribute("parameterize", ", ".join(to_parameterize)) # now add a water model to the force field _add_water_model( force_field=offxml, water_model=water_model, using_plugin=using_plugin ) offxml.to_file(filename=filename)