class SamplerSystem(object): def __init__(self, natoms=6): self.system = LJClusterSENS(natoms, 5.) self.ndof = natoms * 3 - 6 self.natoms = natoms self.database = build_database(self.system, nminima=2) self.sampler = SASampler(self.database.minima(), self.ndof) def sample_configuration(self, Emax): m, x = self.sampler.sample_coords(Emax) E = self.sampler.compute_energy(x, m) return E, x def __call__(self, *args, **kwargs): Emax = args[2] # print "walking with Emax", Emax E, x = self.sample_configuration(Emax) res = Result(x=x, energy=E, naccept=5, nsteps=10, Emax=Emax) return res
class TestBuildDatabase(unittest.TestCase): def setUp(self): self.natoms = 6 self.system = LJCluster(self.natoms) # create a database self.database = self.system.create_database() # add some minima to the database bh = self.system.get_basinhopping(self.database, outstream=None) while self.database.number_of_minima() < 2: bh.run(1) get_thermodynamic_information(self.system, self.database) get_all_normalmodes(self.system, self.database) self.ndof = self.natoms * 3 - 6 self.sampler = SASampler(self.database.minima(), self.ndof) # def test(self): # for m in self.database.minima(): # print m.energy # print self.sampler.compute_weights(-11.7) # for i in range(10): # m = self.sampler.sample_minimum(-11) # print m._id def compute_weights(self, Emax, k): print [m.energy for m in self.database.minima()], Emax lweights = [ - np.log(m.pgorder) + 0.5 * k * np.log(Emax - m.energy) - 0.5 * m.fvib for m in self.database.minima() if m.energy < Emax] lweights = np.array(lweights) if lweights.size <= 1: return lweights lwmax = lweights.max() lweights -= lwmax return np.exp(lweights) def test1(self): Emax = -11.7 minima, weights = self.sampler.compute_weights(0.) minima, weights = self.sampler.compute_weights(Emax) self.assertAlmostEqual(weights[0], 0.81256984, 3) self.assertAlmostEqual(weights[1], 1., 7) new_weights = self.compute_weights(Emax, self.ndof) print weights print new_weights def test2(self): Emax = -11.7 minima, weights = self.sampler.compute_weights(Emax) minima, weights = self.sampler.compute_weights(Emax) self.assertAlmostEqual(weights[0], 0.81256984, 3) self.assertAlmostEqual(weights[1], 1., 7) def test3(self): m = self.sampler.sample_minimum(-11.7) self.assertIn(m, self.database.minima()) def test4(self): """check that the HSA energy is computed correctly""" pot = self.system.get_potential() for m in self.database.minima(): x = m.coords.copy() x += np.random.uniform(-1e-3, 1e-3, x.shape) ehsa = self.sampler.compute_energy(x, m, x0=m.coords) ecalc = pot.getEnergy(x) ecompare = (ehsa - ecalc) / (ecalc - m.energy) print ehsa - m.energy, ecalc - m.energy, m.energy, ecompare self.assertAlmostEqual(ecompare, 0., 1) def test_rotation(self): """assert that the HSA energy is *not* invariant under rotation""" pot = self.system.get_potential() aa = rotations.random_aa() rmat = rotations.aa2mx(aa) from pele.mindist import TransformAtomicCluster tform = TransformAtomicCluster(can_invert=True) for m in self.database.minima(): x = m.coords.copy() # randomly move the atoms by a small amount x += np.random.uniform(-1e-3, 1e-3, x.shape) ehsa1 = self.sampler.compute_energy(x, m, x0=m.coords) ecalc = pot.getEnergy(x) # now rotate by a random matrix xnew = x.copy() tform.rotate(xnew, rmat) ehsa2 = self.sampler.compute_energy(xnew, m, x0=m.coords) ecalc2 = pot.getEnergy(xnew) self.assertAlmostEqual(ecalc, ecalc2, 5) self.assertNotAlmostEqual(ehsa1, ehsa2, 1) # def test_rotation_2(self): # """assert that the HSA energy *is* invariant under rotation *if* the initial coords are also rotated""" # pot = self.system.get_potential() # aa = rotations.random_aa() # rmat = rotations.aa2mx(aa) # from pele.mindist import TransformAtomicCluster # tform = TransformAtomicCluster(can_invert=True) # for m in self.database.minima(): # x = m.coords.copy() # # randomly move the atoms by a small amount # x += np.random.uniform(-1e-3, 1e-3, x.shape) # ehsa1 = self.sampler.compute_energy(x, m, x0=m.coords) # ecalc = pot.getEnergy(x) # # now rotate by a random matrix # xnew = x.copy() # tform.rotate(xnew, rmat) # xmnew = m.coords.copy() # tform.rotate(xmnew, rmat) # ehsa2 = self.sampler.compute_energy(xnew, m, x0=xmnew) # ecalc2 = pot.getEnergy(xnew) # self.assertAlmostEqual(ecalc, ecalc2, 5) # self.assertAlmostEqual(ehsa1, ehsa2, 3) def test_permutation(self): """assert that the HSA energy is not invariant under permutation""" pot = self.system.get_potential() perm = range(self.natoms) np.random.shuffle(perm) from pele.mindist import TransformAtomicCluster tform = TransformAtomicCluster(can_invert=True) for m in self.database.minima(): x = m.coords.copy() # randomly move the atoms by a small amount x += np.random.uniform(-1e-3, 1e-3, x.shape) ehsa1 = self.sampler.compute_energy(x, m, x0=m.coords) ecalc = pot.getEnergy(x) # now rotate by a random matrix xnew = x.copy() xnew = tform.permute(xnew, perm) ehsa2 = self.sampler.compute_energy(xnew, m, x0=m.coords) ecalc2 = pot.getEnergy(xnew) self.assertAlmostEqual(ecalc, ecalc2, 5) self.assertNotAlmostEqual(ehsa1, ehsa2, 1)
class NestedSamplingSAExact(NestedSampling): """overload get_starting_configuration() in order to introduce sampling from known minima Parameters ---------- system : pele system object nreplicas : int number of replicas takestep : callable object to do the step taking. must be callable and have attribute takestep.stepsize minima : list of Minimum objects """ def __init__(self, system, nreplicas, mc_runner, minima, energy_accuracy, compare_structures=None, mindist=None, copy_minima=True, config_tests=None, minimizer=None, debug=True, **kwargs): super(NestedSamplingSAExact, self).__init__(system, nreplicas, mc_runner, **kwargs) self.debug = debug if copy_minima: self.minima = [_UnboundMinimum(m) for m in minima] else: self.minima = minima if self.verbose: self._check_minima() self.compare_structures = compare_structures if compare_structures is None: try: self.compare_structures = self.system.get_compare_exact() except NotImplementedError or AttributeError: pass self.mindist = mindist if mindist is None: try: self.mindist = self.system.get_mindist() except NotImplementedError or AttributeError: pass self.config_tests = config_tests if config_tests is None: try: self.config_tests = self.system.get_config_tests() except NotImplementedError or AttributeError: pass self.minimizer = minimizer if self.minimizer is None: self.minimizer = self.system.get_minimizer() self.minima_searcher = _MinimaSearcher(self.minima, energy_accuracy, self.compare_structures) self.sa_sampler = SASampler(self.minima, self.system.k) self.count_sampled_minima = 0 def _check_minima(self): for m in self.minima: assert m.energy is not None assert m.fvib is not None assert m.pgorder is not None assert m.normal_modes is not None def _compute_energy_in_SA(self, replica): """compute the energy of the coordinates in replica.coords in the harmonic superposition approximation This is where most of the difficulty is in the algorithm. This is also where all the system dependence is """ # quench to nearest minimum qresult = self.minimizer(replica.x) # check if that minimum is in the database. reject if not m, transformation = self.minima_searcher.get_minima( qresult.energy, qresult.coords) if m is None: return None # put replica.coords into best alignment with the structure stored in m.coords # this involves accounting for symmetries of the Hamiltonian like translational, # rotational and permutations symmetries. You can use the coordinates in qresult.coords # to help find the best permutation. Ultimately it must be aligned with the structure in m.coords # The hessian eigenvectors were computed with a given permutation # e.g. account for trivial translational and rotational degrees of freedom x = replica.x.copy() if transformation is not None: # transformation is the set of transformations that put qresult.coords into exact # alignment with m.coords. If we apply these transformations to replica.x then # replica.x will be in good (although not perfect) alignment already with m.coords x = self.compare_structures.apply_transformation(x, transformation) # Do a final round of optimization to further improve the alignment if self.mindist is not None: dist, x0, x = self.mindist(m.coords.copy(), x) if self.debug: diff = np.linalg.norm(x0 - m.coords) if diff > .01: with open("error.xyz", "w") as fout: from pele.utils.xyz import write_xyz write_xyz(fout, x0) write_xyz(fout, m.coords) raise Exception( "warning, mindist appears to have changed x0. the norm of the difference is %g" % diff) assert (x0 == m.coords).all() energy = self.sa_sampler.compute_energy(x, m) return energy def _attempt_swap(self, replica, Emax): # sample a configuration from the harmonic superposition approximation m, xsampled = self.sa_sampler.sample_coords(Emax) # if the configuration fails the config test then reject the swap # print "attempting swap" if self.config_tests is not None: for test in self.config_tests: if not test(coords=xsampled): return None # if the energy returned by full energy function is too high, then reject the swap Esampled = self.system.get_energy(xsampled) if Esampled >= Emax: return None # compute the energy of the replica within the superposition approximation. E_SA = self._compute_energy_in_SA(replica) # reject if the energy is too high if E_SA is None or E_SA >= Emax: # no swap done return None if self.verbose: print "accepting swap: Eold %g Enew %g Eold_SA %g Emax %g" % ( replica.energy, Esampled, E_SA, Emax) self.count_sampled_minima += 1 return Replica(xsampled, Esampled, from_random=False) def do_monte_carlo_chain(self, replicas, Emax): # replicas = super(NestedSamplingSAExact, self).do_monte_carlo_chain(replicas, Emax) # try to swap this configuration with one sampled from the HSA for i in xrange(len(replicas)): r = replicas[i] rnew = self._attempt_swap(r, Emax) if rnew is not None: replicas[i] = rnew # do a monte carlo walk replicas = super(NestedSamplingSAExact, self).do_monte_carlo_chain(replicas, Emax) return replicas
class TestBuildDatabase(unittest.TestCase): def setUp(self): self.natoms = 6 self.system = LJCluster(self.natoms) # create a database self.database = self.system.create_database() # add some minima to the database bh = self.system.get_basinhopping(self.database, outstream=None) while self.database.number_of_minima() < 2: bh.run(1) get_thermodynamic_information(self.system, self.database) get_all_normalmodes(self.system, self.database) self.ndof = self.natoms * 3 - 6 self.sampler = SASampler(self.database.minima(), self.ndof) # def test(self): # for m in self.database.minima(): # print m.energy # print self.sampler.compute_weights(-11.7) # for i in range(10): # m = self.sampler.sample_minimum(-11) # print m._id def test1(self): Emax = -11.7 minima, weights = self.sampler.compute_weights(0.) minima, weights = self.sampler.compute_weights(Emax) self.assertAlmostEqual(weights[0], 0.81256984, 3) self.assertAlmostEqual(weights[1], 1., 7) def test2(self): Emax = -11.7 minima, weights = self.sampler.compute_weights(Emax) minima, weights = self.sampler.compute_weights(Emax) self.assertAlmostEqual(weights[0], 0.81256984, 3) self.assertAlmostEqual(weights[1], 1., 7) def test3(self): m = self.sampler.sample_minimum(-11.7) self.assertIn(m, self.database.minima()) def test4(self): """check that the HSA energy is computed correctly""" pot = self.system.get_potential() for m in self.database.minima(): x = m.coords.copy() x += np.random.uniform(-1e-3, 1e-3, x.shape) ehsa = self.sampler.compute_energy(x, m, x0=m.coords) ecalc = pot.getEnergy(x) ecompare = (ehsa - ecalc) / (ecalc - m.energy) print ehsa - m.energy, ecalc - m.energy, m.energy, ecompare self.assertAlmostEqual(ecompare, 0., 1) def test_rotation(self): """assert that the HSA energy is *not* invariant under rotation""" pot = self.system.get_potential() aa = rotations.random_aa() rmat = rotations.aa2mx(aa) from pele.mindist import TransformAtomicCluster tform = TransformAtomicCluster(can_invert=True) for m in self.database.minima(): x = m.coords.copy() # randomly move the atoms by a small amount x += np.random.uniform(-1e-3, 1e-3, x.shape) ehsa1 = self.sampler.compute_energy(x, m, x0=m.coords) ecalc = pot.getEnergy(x) # now rotate by a random matrix xnew = x.copy() tform.rotate(xnew, rmat) ehsa2 = self.sampler.compute_energy(xnew, m, x0=m.coords) ecalc2 = pot.getEnergy(xnew) self.assertAlmostEqual(ecalc, ecalc2, 5) self.assertNotAlmostEqual(ehsa1, ehsa2, 1) # def test_rotation_2(self): # """assert that the HSA energy *is* invariant under rotation *if* the initial coords are also rotated""" # pot = self.system.get_potential() # aa = rotations.random_aa() # rmat = rotations.aa2mx(aa) # from pele.mindist import TransformAtomicCluster # tform = TransformAtomicCluster(can_invert=True) # for m in self.database.minima(): # x = m.coords.copy() # # randomly move the atoms by a small amount # x += np.random.uniform(-1e-3, 1e-3, x.shape) # ehsa1 = self.sampler.compute_energy(x, m, x0=m.coords) # ecalc = pot.getEnergy(x) # # now rotate by a random matrix # xnew = x.copy() # tform.rotate(xnew, rmat) # xmnew = m.coords.copy() # tform.rotate(xmnew, rmat) # ehsa2 = self.sampler.compute_energy(xnew, m, x0=xmnew) # ecalc2 = pot.getEnergy(xnew) # self.assertAlmostEqual(ecalc, ecalc2, 5) # self.assertAlmostEqual(ehsa1, ehsa2, 3) def test_permutation(self): """assert that the HSA energy is not invariant under permutation""" pot = self.system.get_potential() perm = range(self.natoms) np.random.shuffle(perm) from pele.mindist import TransformAtomicCluster tform = TransformAtomicCluster(can_invert=True) for m in self.database.minima(): x = m.coords.copy() # randomly move the atoms by a small amount x += np.random.uniform(-1e-3, 1e-3, x.shape) ehsa1 = self.sampler.compute_energy(x, m, x0=m.coords) ecalc = pot.getEnergy(x) # now rotate by a random matrix xnew = x.copy() xnew = tform.permute(xnew, perm) ehsa2 = self.sampler.compute_energy(xnew, m, x0=m.coords) ecalc2 = pot.getEnergy(xnew) self.assertAlmostEqual(ecalc, ecalc2, 5) self.assertNotAlmostEqual(ehsa1, ehsa2, 1)
class NestedSamplingSAExact(NestedSampling): """overload get_starting_configuration() in order to introduce sampling from known minima Parameters ---------- system : pele system object nreplicas : int number of replicas takestep : callable object to do the step taking. must be callable and have attribute takestep.stepsize minima : list of Minimum objects """ def __init__(self, system, nreplicas, mc_runner, minima, energy_accuracy, compare_structures=None, mindist=None, copy_minima=True, config_tests=None, minimizer=None, debug=True, **kwargs): super(NestedSamplingSAExact, self).__init__(system, nreplicas, mc_runner, **kwargs) self.debug = debug if copy_minima: self.minima = [_UnboundMinimum(m) for m in minima] else: self.minima = minima if self.verbose: self._check_minima() self.compare_structures = compare_structures if compare_structures is None: try: self.compare_structures = self.system.get_compare_exact() except NotImplementedError or AttributeError: pass self.mindist = mindist if mindist is None: try: self.mindist = self.system.get_mindist() except NotImplementedError or AttributeError: pass self.config_tests = config_tests if config_tests is None: try: self.config_tests = self.system.get_config_tests() except NotImplementedError or AttributeError: pass self.minimizer = minimizer if self.minimizer is None: self.minimizer = self.system.get_minimizer() self.minima_searcher = _MinimaSearcher(self.minima, energy_accuracy, self.compare_structures) self.sa_sampler = SASampler(self.minima, self.system.k) self.count_sampled_minima = 0 def _check_minima(self): for m in self.minima: assert m.energy is not None assert m.fvib is not None assert m.pgorder is not None assert m.normal_modes is not None def _compute_energy_in_SA(self, replica): """compute the energy of the coordinates in replica.coords in the harmonic superposition approximation This is where most of the difficulty is in the algorithm. This is also where all the system dependence is """ # quench to nearest minimum qresult = self.minimizer(replica.x) # check if that minimum is in the database. reject if not m, transformation = self.minima_searcher.get_minima(qresult.energy, qresult.coords) if m is None: return None # put replica.coords into best alignment with the structure stored in m.coords # this involves accounting for symmetries of the Hamiltonian like translational, # rotational and permutations symmetries. You can use the coordinates in qresult.coords # to help find the best permutation. Ultimately it must be aligned with the structure in m.coords # The hessian eigenvectors were computed with a given permutation # e.g. account for trivial translational and rotational degrees of freedom x = replica.x.copy() if transformation is not None: # transformation is the set of transformations that put qresult.coords into exact # alignment with m.coords. If we apply these transformations to replica.x then # replica.x will be in good (although not perfect) alignment already with m.coords x = self.compare_structures.apply_transformation(x, transformation) # Do a final round of optimization to further improve the alignment if self.mindist is not None: dist, x0, x = self.mindist(m.coords.copy(), x) if self.debug: diff = np.linalg.norm(x0 - m.coords) if diff > .01: with open("error.xyz", "w") as fout: from pele.utils.xyz import write_xyz write_xyz(fout, x0) write_xyz(fout, m.coords) raise Exception("warning, mindist appears to have changed x0. the norm of the difference is %g" % diff) assert (x0 == m.coords).all() energy = self.sa_sampler.compute_energy(x, m) return energy def _attempt_swap(self, replica, Emax): # sample a configuration from the harmonic superposition approximation m, xsampled = self.sa_sampler.sample_coords(Emax) # if the configuration fails the config test then reject the swap # print "attempting swap" if self.config_tests is not None: for test in self.config_tests: if not test(coords=xsampled): return None # if the energy returned by full energy function is too high, then reject the swap Esampled = self.system.get_energy(xsampled) if Esampled >= Emax: return None # compute the energy of the replica within the superposition approximation. E_SA = self._compute_energy_in_SA(replica) # reject if the energy is too high if E_SA is None or E_SA >= Emax: # no swap done return None if self.verbose: print "accepting swap: Eold %g Enew %g Eold_SA %g Emax %g" % (replica.energy, Esampled, E_SA, Emax) self.count_sampled_minima += 1 return Replica(xsampled, Esampled, from_random=False) def do_monte_carlo_chain(self, replicas, Emax): # replicas = super(NestedSamplingSAExact, self).do_monte_carlo_chain(replicas, Emax) # try to swap this configuration with one sampled from the HSA for i in xrange(len(replicas)): r = replicas[i] rnew = self._attempt_swap(r, Emax) if rnew is not None: replicas[i] = rnew # do a monte carlo walk replicas = super(NestedSamplingSAExact, self).do_monte_carlo_chain(replicas, Emax) return replicas