def test_lj(self): lj_eps = 1.92 lj_sig = 1.03 lj_cut = 1.123 lj_shift = 0.92 self.system.non_bonded_inter[0, 0].lennard_jones.set_params( epsilon=lj_eps, sigma=lj_sig, cutoff=lj_cut, shift=lj_shift) for i in range(113): self.system.part[1].pos = self.system.part[1].pos + self.step self.system.integrator.run(recalc_forces=True, steps=0) # Calculate energies E_sim = self.system.analysis.energy()["non_bonded"] E_ref = tests_common.lj_potential( (i + 1) * self.step_width, lj_eps, lj_sig, lj_cut, shift=lj_shift) # Calculate forces f0_sim = self.system.part[0].f f1_sim = self.system.part[1].f f1_ref = self.axis * \ tests_common.lj_force(espressomd, r=(i + 1) * self.step_width, eps=lj_eps, sig=lj_sig, cutoff=lj_cut) # Check that energies match, ... self.assertFractionAlmostEqual(E_sim, E_ref) # force equals minus the counter-force ... self.assertTrue((f0_sim == -f1_sim).all()) # and has correct value. self.assertItemsFractionAlmostEqual(f1_sim, f1_ref) self.system.non_bonded_inter[0, 0].lennard_jones.set_params(epsilon=0.)
def lj_cos2_potential(r, epsilon, sigma, offset, width): V = 0. r_min = offset + np.power(2., 1. / 6.) * sigma r_cut = r_min + width if r < r_min: V = tests_common.lj_potential(r, epsilon=epsilon, sigma=sigma, offset=offset, cutoff=r_cut, shift=0.) elif r < r_cut: V = -epsilon * np.power(np.cos(np.pi / (2. * width) * (r - r_min)), 2) return V
def lj_cos_potential(r, epsilon, sigma, cutoff, offset): V = 0. r_min = offset + np.power(2., 1. / 6.) * sigma r_cut = cutoff + offset if r < r_min: V = tests_common.lj_potential(r, epsilon=epsilon, sigma=sigma, cutoff=cutoff, offset=offset, shift=0.) elif r < r_cut: alpha = np.pi / \ (np.power(r_cut - offset, 2) - np.power(r_min - offset, 2)) beta = np.pi - np.power(r_min - offset, 2) * alpha V = 0.5 * epsilon * \ (np.cos(alpha * np.power(r - offset, 2) + beta) - 1.) return V
class WidomInsertionTest(ut.TestCase): """Test the implementation of the widom insertion. The excess chemical potential is calculated for identical particles in a 20 cubed box with a single particle, interacting via a LJ-potential (cut-off at 5 sigma).""" N0 = 1 TEMPERATURE = 0.5 TYPE_HA = 0 CHARGE_HA = 0 LJ_EPS = 1.0 LJ_SIG = 1.0 LJ_CUT = 5 BOX_L = 2 * LJ_CUT LJ_SHIFT = lj_potential(LJ_CUT, LJ_EPS, LJ_SIG, LJ_CUT + 1, 0.0) radius = np.linspace(1e-10, LJ_CUT, 1000) # numerical integration for radii smaller than the cut-off in spherical # coordinates integrateUpToCutOff = 4 * np.pi * np.trapz(radius**2 * np.exp( -lj_potential(radius, LJ_EPS, LJ_SIG, LJ_CUT, LJ_SHIFT) / TEMPERATURE), x=radius) # numerical solution for V_lj=0 => corresponds to the volume (as exp(0)=1) integreateRest = (BOX_L**3 - 4.0 / 3.0 * np.pi * LJ_CUT**3) # calculate excess chemical potential of the system, see Frenkel Smith, # p 174. Note: He uses scaled coordinates, which is why we need to divide # by the box volume target_mu_ex = -TEMPERATURE * \ np.log((integrateUpToCutOff + integreateRest) / BOX_L**3) system = espressomd.System(box_l=np.ones(3) * BOX_L) system.cell_system.set_n_square() system.seed = system.cell_system.get_state()['n_nodes'] * [2] np.random.seed(69) # make reaction code fully deterministic system.cell_system.skin = 0.4 volume = np.prod(system.box_l) # cuboid box Widom = reaction_ensemble.WidomInsertion(temperature=TEMPERATURE, seed=1) def setUp(self): self.system.part.add(id=0, pos=0.5 * self.system.box_l, type=self.TYPE_HA) self.system.non_bonded_inter[self.TYPE_HA, self.TYPE_HA].lennard_jones.set_params( epsilon=self.LJ_EPS, sigma=self.LJ_SIG, cutoff=self.LJ_CUT, shift="auto") self.Widom.add_reaction(reactant_types=[], reactant_coefficients=[], product_types=[self.TYPE_HA], product_coefficients=[1], default_charges={self.TYPE_HA: self.CHARGE_HA}) def test_widom_insertion(self): system = WidomInsertionTest.system Widom = WidomInsertionTest.Widom target_mu_ex = WidomInsertionTest.target_mu_ex system.seed = system.cell_system.get_state()['n_nodes'] * [ np.random.randint(5) ] num_samples = 100000 for _ in range(num_samples): # 0 for insertion reaction Widom.measure_excess_chemical_potential(0) mu_ex = Widom.measure_excess_chemical_potential(0) deviation_mu_ex = abs(mu_ex[0] - target_mu_ex) # error self.assertLess( deviation_mu_ex - 1e-3, 0.0, msg= "\nExcess chemical potential for single LJ-particle computed via widom insertion gives a wrong value.\n" + " average mu_ex: " + str(mu_ex[0]) + " mu_ex_std_err: " + str(mu_ex[1]) + " target_mu_ex: " + str(target_mu_ex))
class WidomInsertionTest(ut.TestCase): """Test the implementation of the widom insertion. The excess chemical potential is calculated for identical particles in a 20 cubed box with a single particle, interacting via a LJ-potential (cut-off at 5 sigma).""" N0 = 1 TEMPERATURE = 0.5 TYPE_HA = 0 CHARGE_HA = 0 LJ_EPS = 1.0 LJ_SIG = 1.0 LJ_CUT = 5 BOX_L = 2 * LJ_CUT LJ_SHIFT = tests_common.lj_potential( LJ_CUT, LJ_EPS, LJ_SIG, LJ_CUT + 1.0, 0.0) radius = np.linspace(1e-10, LJ_CUT, 1000) # numerical integration for radii smaller than the cut-off in spherical # coordinates integrateUpToCutOff = 4 * np.pi * np.trapz( radius**2 * np.exp(-tests_common.lj_potential(radius, LJ_EPS, LJ_SIG, LJ_CUT, LJ_SHIFT) / TEMPERATURE), x=radius) # numerical solution for V_lj=0 => corresponds to the volume (as exp(0)=1) integreateRest = (BOX_L**3 - 4.0 / 3.0 * np.pi * LJ_CUT**3) # calculate excess chemical potential of the system, see Frenkel Smith, # p 174. Note: He uses scaled coordinates, which is why we need to divide # by the box volume target_mu_ex = -TEMPERATURE * \ np.log((integrateUpToCutOff + integreateRest) / BOX_L**3) system = espressomd.System(box_l=np.ones(3) * BOX_L) system.cell_system.set_n_square() np.random.seed(69) # make reaction code fully deterministic system.cell_system.skin = 0.4 Widom = espressomd.reaction_ensemble.WidomInsertion( kT=TEMPERATURE, seed=1) # Set the hidden particle type to the lowest possible number to speed # up the simulation Widom.set_non_interacting_type(type=1) def setUp(self): self.system.part.add(pos=0.5 * self.system.box_l, type=self.TYPE_HA) self.system.non_bonded_inter[self.TYPE_HA, self.TYPE_HA].lennard_jones.set_params( epsilon=self.LJ_EPS, sigma=self.LJ_SIG, cutoff=self.LJ_CUT, shift="auto") self.Widom.add_reaction( reactant_types=[], reactant_coefficients=[], product_types=[self.TYPE_HA], product_coefficients=[1], default_charges={self.TYPE_HA: self.CHARGE_HA}) def test_widom_insertion(self): num_samples = 10000 particle_insertion_potential_energy_samples = [] for _ in range(num_samples): # 0 for insertion reaction particle_insertion_potential_energy = self.Widom.calculate_particle_insertion_potential_energy( reaction_id=0) particle_insertion_potential_energy_samples.append( particle_insertion_potential_energy) mu_ex_mean, mu_ex_Delta = self.Widom.calculate_excess_chemical_potential( particle_insertion_potential_energy_samples=particle_insertion_potential_energy_samples) deviation_mu_ex = abs(np.mean(mu_ex_mean) - self.target_mu_ex) self.assertLess( deviation_mu_ex, 1e-3, msg="\nExcess chemical potential for single LJ-particle computed via Widom insertion is wrong.\n" + f" average mu_ex: {np.mean(mu_ex_mean):.4f}" + f" mu_ex_std_err: {np.std(mu_ex_Delta):.5f}" + f" target_mu_ex: {self.target_mu_ex:.4f}" )