Exemple #1
0
args = parser.parse_args()

print("\nArguments:", args)

#NUM PARTICLES AND BOX
n_ionpairs = 100
n_part = n_ionpairs * 2
density_si = 0.5
# g/cm^3
rho_factor_bmim_pf6 = 0.003931
box_volume = n_ionpairs / rho_factor_bmim_pf6 / density_si
box_l = box_volume**(1. / 3.)
print("\n-->Ion pairs:", n_ionpairs, "Box size:", box_l)

system = espressomd.System(box_l=[box_l, box_l, box_l])
system.virtual_sites = VirtualSitesRelative(have_velocity=True)
system.set_random_state_PRNG()

if args.visu:
    d_scale = 0.988 * 0.5
    c_ani = [1, 0, 0, 1]
    c_dru = [0, 0, 1, 1]
    c_com = [0, 0, 0, 1]
    c_cat = [0, 1, 0, 1]
    visualizer = espressomd.visualization_opengl.openGLLive(
        system,
        background_color=[1, 1, 1],
        drag_enabled=True,
        ext_force_arrows=True,
        drag_force=10,
        draw_bonds=False,
class CollisionDetection(ut.TestCase):

    """Tests interface and functionality of the collision detection / dynamic binding"""

    s = espressomd.System(box_l=[1.0, 1.0, 1.0])
    s.seed = s.cell_system.get_state()['n_nodes'] * [1234]
    np.random.seed(seed=s.seed)
    if espressomd.has_features("VIRTUAL_SITES"):
        from espressomd.virtual_sites import VirtualSitesRelative
        s.virtual_sites = VirtualSitesRelative()

    H = HarmonicBond(k=5000, r_0=0.1)
    H2 = HarmonicBond(k=25000, r_0=0.02)
    s.bonded_inter.add(H)
    s.bonded_inter.add(H2)
    s.time_step = 0.001
    s.cell_system.skin = 0.05
    s.min_global_cut = 0.112

    part_type_to_attach_vs_to = 0
    part_type_vs = 1
    part_type_to_be_glued = 2
    part_type_after_glueing = 3
    other_type = 5

    def get_state_set_state_consistency(self):
        state = self.s.collision_detection.get_params()
        self.s.collision_detection.set_params(**state)
        self.assertEqual(state, self.s.collision_detection.get_params())

    def test_00_interface_and_defaults(self):
        # Is it off by default
        self.assertEqual(self.s.collision_detection.mode, "off")
        # Make sure params cannot be set individually
        with self.assertRaises(Exception):
            self.s.collision_detection.mode = "bind_centers"

        # Verify exception throwing for unknown collision modes
        with self.assertRaises(Exception):
            self.s.collision_detection.set_params(mode=0)
            self.s.collision_detection.set_params(mode="blahblah")

        # That should work
        self.s.collision_detection.set_params(mode="off")
        self.assertEqual(self.s.collision_detection.mode, "off")

    def test_bind_centers(self):
        # Check that it leaves particles alone, wehn off
        self.s.collision_detection.set_params(mode="off")

        self.s.part.clear()
        self.s.part.add(pos=(0, 0, 0), id=0)
        self.s.part.add(pos=(0.1, 0, 0), id=1)
        self.s.part.add(pos=(0.1, 0.3, 0), id=2)
        self.s.integrator.run(0)
        self.assertEqual(self.s.part[0].bonds, ())
        self.assertEqual(self.s.part[1].bonds, ())
        self.assertEqual(self.s.part[2].bonds, ())

        # Check that it cannot be activated
        self.s.collision_detection.set_params(
            mode="bind_centers", distance=0.11, bond_centers=self.H)
        self.get_state_set_state_consistency()
        self.s.integrator.run(1, recalc_forces=True)
        bond0 = ((self.s.bonded_inter[0], 1),)
        bond1 = ((self.s.bonded_inter[0], 0),)
        self.assertTrue(
            self.s.part[0].bonds == bond0 or self.s.part[1].bonds == bond1)
        self.assertEqual(self.s.part[2].bonds, ())

        # Check that no additional bonds appear
        self.s.integrator.run(1)
        self.assertTrue(
            self.s.part[0].bonds == bond0 or self.s.part[1].bonds == bond1)
        self.assertEqual(self.s.part[2].bonds, ())

        # Check turning it off
        self.s.collision_detection.set_params(mode="off")
        self.get_state_set_state_consistency()
        self.assertEqual(self.s.collision_detection.mode, "off")

    def run_test_bind_at_point_of_collision_for_pos(self, *positions):
        positions = list(positions)
        shuffle(positions)
        self.s.part.clear()
        # Place particle which should not take part in collisions
        p = self.s.part.add(pos=(0.1, 0.3, 0))
        for pos in positions:
            p1 = self.s.part.add(pos=pos + (0, 0, 0))
            p2 = self.s.part.add(pos=pos + (0.1, 0, 0))
            if self.s.distance(p1, p) < 0.12 or self.s.distance(p2, p) < 0.12:
                raise Exception(
                    "Test particle too close to particle, which should not take part in collision")

        # 2 non-virtual + 2 virtual + one that doesn't tkae part
        expected_np = 4 * len(positions) + 1

        self.s.collision_detection.set_params(
            mode="bind_at_point_of_collision", distance=0.11, bond_centers=self.H, bond_vs=self.H2, part_type_vs=1, vs_placement=0.4)
        self.get_state_set_state_consistency()
        self.s.integrator.run(0, recalc_forces=True)
        self.verify_state_after_bind_at_poc(expected_np)

        # Integrate again and check that nothing has changed
        self.s.integrator.run(0, recalc_forces=True)
        self.verify_state_after_bind_at_poc(expected_np)

        # Check that nothing explodes, when the particles are moved.
        # In particular for parallel simulations
        self.s.thermostat.set_langevin(kT=0, gamma=0.01)
        self.s.part[:].v = 0.05, 0.01, 0.15
        self.s.integrator.run(3000)
        self.verify_state_after_bind_at_poc(expected_np)

    def verify_state_after_bind_at_poc(self, expected_np):
        self.assertEqual(len(self.s.part), expected_np)

        # At the end of test, this list should be empty
        parts_not_accounted_for = list(range(expected_np))

        # Collect pairs of non-virtual-particles found
        non_virtual_pairs = []

        # We traverse particles. We look for a vs with a bond to find the other vs.
        # From the two vs we find the two non-virtual particles
        for p in self.s.part:
            # Skip non-virtual
            if p.virtual == 0:
                continue
            # Skip vs that doesn't have a bond
            if p.bonds == ():
                continue
            # Parse the bond
            self.assertEqual(len(p.bonds), 1)
            # Bond type
            self.assertEqual(p.bonds[0][0], self.H2)
            # get partner
            p2 = self.s.part[p.bonds[0][1]]
            # Is that really a vs
            self.assertEqual(p2.virtual, 1)
            # Get base particles
            base_p1 = self.s.part[p.vs_relative[0]]
            base_p2 = self.s.part[p2.vs_relative[0]]
            # Take note of accounted-for particles
            for _p in p, p2, base_p1, base_p2:
                parts_not_accounted_for.remove(_p.id)
            self.verify_bind_at_poc_pair(base_p1, base_p2, p, p2)
        # Check particle that did not take part in collision.
        self.assertEqual(len(parts_not_accounted_for), 1)
        p = self.s.part[parts_not_accounted_for[0]]
        self.assertEqual(p.virtual, 0)
        self.assertEqual(p.bonds, ())
        parts_not_accounted_for.remove(p.id)
        self.assertEqual(parts_not_accounted_for, [])

    def verify_bind_at_poc_pair(self, p1, p2, vs1, vs2):
        bond_p1 = ((self.s.bonded_inter[0], p2.id),)
        bond_p2 = ((self.s.bonded_inter[0], p1.id),)
        self.assertTrue(p1.bonds == bond_p1 or p2.bonds == bond_p2)

        # Check for presence of vs
        # Check for bond betwen vs
        bond_vs1 = ((self.s.bonded_inter[1], vs2.id),)
        bond_vs2 = ((self.s.bonded_inter[1], vs1.id),)
        self.assertTrue(vs1.bonds == bond_vs1 or vs2.bonds == bond_vs2)

        # Vs properties
        self.assertEqual(vs1.virtual, 1)
        self.assertEqual(vs2.virtual, 1)

        # vs_relative properties
        seen = []
        for p in vs1, vs2:
            r = p.vs_relative
            rel_to = r[0]
            dist = r[1]
            # Vs is related to one of the particles
            self.assertTrue(rel_to == p1.id or rel_to == p2.id)
            # The two vs relate to two different particles
            self.assertNotIn(rel_to, seen)
            seen.append(rel_to)

            # Check placement
            if rel_to == p1.id:
                dist_centers = np.copy(p2.pos - p1.pos)
            else:
                dist_centers = p1.pos - p2.pos
            expected_pos = self.s.part[rel_to].pos_folded + \
                self.s.collision_detection.vs_placement * dist_centers
            dist = expected_pos - p.pos_folded
            dist -= np.round(dist / self.s.box_l) * self.s.box_l
            self.assertLess(np.linalg.norm(dist), 1E-12)

    @ut.skipIf(not espressomd.has_features("VIRTUAL_SITES_RELATIVE"), "VIRTUAL_SITES not compiled in")
    def test_bind_at_point_of_collision(self):
        # Single collision head node
        self.run_test_bind_at_point_of_collision_for_pos(np.array((0, 0, 0)))
        # Single collision, mixed
        self.run_test_bind_at_point_of_collision_for_pos(
            np.array((0.45, 0, 0)))
        # Single collision, non-head-node
        self.run_test_bind_at_point_of_collision_for_pos(np.array((0.7, 0, 0)))

        # head-node + mixed
        self.run_test_bind_at_point_of_collision_for_pos(
            np.array((0, 0, 0)), np.array((0.45, 0, 0)))
        # Mixed + other node
        self.run_test_bind_at_point_of_collision_for_pos(
            np.array((0.45, 0, 0)), np.array((0.7, 0, 0)))
        # Head + other
        self.run_test_bind_at_point_of_collision_for_pos(
            np.array((0.0, 0, 0)), np.array((0.7, 0, 0)))
        # Head + mixed + other
        self.run_test_bind_at_point_of_collision_for_pos(
            np.array((0.2, 0, 0)), np.array((0.95, 0, 0)), np.array((0.7, 0, 0)))

    @ut.skipIf(not espressomd.has_features("LENNARD_JONES", "VIRTUAL_SITES"), "Skipping for lack of LJ potential")
    def test_bind_at_point_of_collision_random(self):
        """Integrate lj liquid and check that no double bonds are formed
           and the number of bonds fits the number of virtual sites

        """
        self.s.part.clear()

        # Add randomly placed particles
        self.s.part.add(pos=np.random.random((200, 3)))

        # Setup Lennard-Jones
        self.s.non_bonded_inter[0, 0].lennard_jones.set_params(
            epsilon=1, sigma=0.1, cutoff=2**(1. / 6) * 0.1, shift="auto")

        # Remove overalp between particles
        self.s.integrator.set_steepest_descent(
            f_max=0,
            gamma=1,
            max_displacement=0.001)
        while self.s.analysis.energy()["total"] > len(self.s.part):
            self.s.integrator.run(10)

        # Collision detection
        self.s.collision_detection.set_params(
            mode="bind_at_point_of_collision",
            distance=0.11,
            bond_centers=self.H,
            bond_vs=self.H2,
            part_type_vs=1,
            vs_placement=0.4)
        self.get_state_set_state_consistency()

        # Integrate lj liquid
        self.s.integrator.set_vv()
        self.s.integrator.run(5000)

        # Analysis
        virtual_sites = self.s.part.select(virtual=1)
        non_virtual = self.s.part.select(virtual=0)

        # Check bonds on non-virtual particles
        bonds = []
        for p in non_virtual:
            for bond in p.bonds:
                # Sort bond partners to make them unique independently of
                # which particle got the bond
                bonds.append(tuple(sorted([p.id, bond[1]])))

        # No duplicate bonds?
        self.assertEqual(len(bonds), len(set(bonds)))

        # 2 virtual sites per bond?
        self.assertEqual(2 * len(bonds), len(virtual_sites))

        # Find pairs of bonded virtual sites
        vs_pairs = []
        for p in virtual_sites:
            # 0 or 1 bond on vs?
            self.assertTrue(len(p.bonds) in [0, 1])

            if len(p.bonds) == 1:
                vs_pairs.append((p.id, p.bonds[0][1]))

        # Number of vs pairs = number of bonds?
        self.assertEqual(len(vs_pairs), len(bonds))

        # Che3ck that vs pairs and bonds agree
        for vs_pair in vs_pairs:
            # Get corresponding non-virtual particles
            base_particles = tuple(sorted(
                [self.s.part[vs_pair[0]].vs_relative[0],
                 self.s.part[vs_pair[1]].vs_relative[0]]))

            # Is there a corresponding bond?
            self.assertTrue(base_particles in bonds)

        # Tidy
        self.s.non_bonded_inter[
            0,
            0].lennard_jones.set_params(
                epsilon=0,
                sigma=0,
         cutoff=0)

    def run_test_glue_to_surface_for_pos(self, *positions):
        positions = list(positions)
        shuffle(positions)
        self.s.part.clear()
        # Place particle which should not take part in collisions
        # In this case, it is skipped, because it is of the wrong type,
        # even if it is within range for a collision
        p = self.s.part.add(pos=positions[0], type=self.other_type)
        for pos in positions:
            # Since this is non-symmetric, we randomize order
            if np.random.random() > .5:
                p1 = self.s.part.add(
                    pos=pos + (0, 0, 0), type=self.part_type_to_attach_vs_to)
                p2 = self.s.part.add(
                    pos=pos + (0.1, 0, 0), type=self.part_type_to_be_glued)
            else:
                p2 = self.s.part.add(
                    pos=pos + (0.1, 0, 0), type=self.part_type_to_be_glued)
                p1 = self.s.part.add(
                    pos=pos + (0, 0, 0), type=self.part_type_to_attach_vs_to)

        # 2 non-virtual + 1 virtual + one that doesn't takekae part
        expected_np = 3 * len(positions) + 1

        self.s.collision_detection.set_params(
            mode="glue_to_surface", distance=0.11, distance_glued_particle_to_vs=0.02, bond_centers=self.H, bond_vs=self.H2, part_type_vs=self.part_type_vs, part_type_to_attach_vs_to=self.part_type_to_attach_vs_to, part_type_to_be_glued=self.part_type_to_be_glued, part_type_after_glueing=self.part_type_after_glueing)
        self.get_state_set_state_consistency()
        self.s.integrator.run(0, recalc_forces=True)
        self.verify_state_after_glue_to_surface(expected_np)

        # Integrate again and check that nothing has changed
        self.s.integrator.run(0, recalc_forces=True)
        self.verify_state_after_glue_to_surface(expected_np)

        # Check that nothing explodes, when the particles are moved.
        # In particular for parallel simulations
        self.s.thermostat.set_langevin(kT=0, gamma=0.01)
        self.s.part[:].v = 0.05, 0.01, 0.15
        self.s.integrator.run(3000)
        self.verify_state_after_glue_to_surface(expected_np)

    def verify_state_after_glue_to_surface(self, expected_np):
        self.assertEqual(len(self.s.part), expected_np)

        # At the end of test, this list should be empty
        parts_not_accounted_for = list(range(expected_np))

        # We traverse particles. We look for a vs, get base particle from there
        # and prtner particle via bonds
        for p in self.s.part:
            # Skip non-virtual
            if p.virtual == 0:
                continue
            # The vs shouldn't have bonds
            self.assertEqual(p.bonds, ())

            # Get base particles
            base_p = self.s.part[p.vs_relative[0]]

            # Get bound particle
            # There is a bond between the base particle and the bound particle
            # but we have no guarantee, on where its stored
            # 1. On the base particle of the vs
            p2 = None
            if len(base_p.bonds) == 1:
                self.assertEqual(base_p.bonds[0][0], self.H)
                p2 = self.s.part[base_p.bonds[0][1]]
            else:
                # We need to go through all particles to find it
                for candidate in self.s.part:
                    if candidate.id not in parts_not_accounted_for:
                        continue
                    if len(candidate.bonds) >= 1:
                        for b in candidate.bonds:
                            if b[0] == self.H and b[1] == base_p.id:
                                p2 = candidate
                if p2 is None:
                    raise Exception("Bound particle not found")
            # Take note of accounted-for particles
            parts_not_accounted_for.remove(base_p.id)
            parts_not_accounted_for.remove(p.id)
            parts_not_accounted_for.remove(p2.id)
            self.verify_glue_to_surface_pair(base_p, p, p2)
        # Check particle that did not take part in collision.
        self.assertEqual(len(parts_not_accounted_for), 1)
        p = self.s.part[parts_not_accounted_for[0]]
        self.assertEqual(p.virtual, 0)
        self.assertEqual(p.type, self.other_type)
        self.assertEqual(p.bonds, ())
        parts_not_accounted_for.remove(p.id)
        self.assertEqual(parts_not_accounted_for, [])

    def verify_glue_to_surface_pair(self, base_p, vs, bound_p):
        # Check all types
        self.assertEqual(base_p.type, self.part_type_to_attach_vs_to)
        self.assertEqual(vs.type, self.part_type_vs)
        self.assertEqual(bound_p.type, self.part_type_after_glueing)

        # Bound particle should have a bond to vs. It can additionally have a bond
        # to the base particle
        bond_to_vs_found = 0
        for b in bound_p.bonds:
            if b[0] == self.H2:
                # bond to vs
                self.assertEqual(b, (self.H2, vs.id))
                bond_to_vs_found += 1
        self.assertEqual(bond_to_vs_found, 1)
        # Vs should not have a bond
        self.assertEqual(vs.bonds, ())

        # Vs properties
        self.assertEqual(vs.virtual, 1)
        self.assertEqual(vs.vs_relative[0], base_p.id)

        # Distance vs,bound_p
        self.assertAlmostEqual(self.s.distance(vs, bound_p), 0.02, places=3)
        self.assertAlmostEqual(self.s.distance(base_p, bound_p), 0.1, places=3)
        self.assertAlmostEqual(self.s.distance(base_p, vs), 0.08, places=3)

        # base_p,vs,bound_p on a line
        self.assertGreater(np.dot(self.s.distance_vec(base_p, vs), self.s.distance_vec(base_p, bound_p))
                           / self.s.distance(base_p, vs) / self.s.distance(base_p, bound_p), 0.99)

    @ut.skipIf(not espressomd.has_features("VIRTUAL_SITES_RELATIVE"), "Skipped due to missing VIRTUAL_SITES_RELATIVE")
    def test_glue_to_surface(self):
        # Single collision head node
        self.run_test_glue_to_surface_for_pos(np.array((0, 0, 0)))
        # Single collision, mixed
        self.run_test_glue_to_surface_for_pos(np.array((0.45, 0, 0)))
        # Single collision, non-head-node
        self.run_test_glue_to_surface_for_pos(np.array((0.7, 0, 0)))

        # head-node + mixed
        self.run_test_glue_to_surface_for_pos(
            np.array((0, 0, 0)), np.array((0.45, 0, 0)))
        # Mixed + other node
        self.run_test_glue_to_surface_for_pos(
            np.array((0.45, 0, 0)), np.array((0.7, 0, 0)))
        # Head + other
        self.run_test_glue_to_surface_for_pos(
            np.array((0.0, 0, 0)), np.array((0.7, 0, 0)))
        # Head + mixed + other
        self.run_test_glue_to_surface_for_pos(
            np.array((0.2, 0, 0)), np.array((0.95, 0, 0)), np.array((0.7, 0, 0)))

    @ut.skipIf(not espressomd.has_features("VIRTUAL_SITES_RELATIVE"), "VIRTUAL_SITES not compiled in")
    def test_glue_to_surface_random(self):
        """Integrate lj liquid and check that no double bonds are formed
           and the number of bonds fits the number of virtual sites

        """
        self.s.part.clear()

        # Add randomly placed particles
        self.s.part.add(pos=np.random.random((100, 3)),
                        type=100 * [self.part_type_to_attach_vs_to])
        self.s.part.add(pos=np.random.random(
            (100, 3)), type=100 * [self.part_type_to_be_glued])
        self.s.part.add(pos=np.random.random(
            (100, 3)), type=100 * [self.other_type])

        # Setup Lennard-Jones
        self.s.non_bonded_inter[0, 0].lennard_jones.set_params(
            epsilon=1, sigma=0.1, cutoff=2**(1. / 6) * 0.1, shift="auto")

        # Remove overalp between particles
        self.s.integrator.set_steepest_descent(
            f_max=0,
            gamma=1,
            max_displacement=0.001)
        while self.s.analysis.energy()["total"] > len(self.s.part):
            self.s.integrator.run(10)

        # Collision detection
        self.s.collision_detection.set_params(
            mode="glue_to_surface", distance=0.11, distance_glued_particle_to_vs=0.02, bond_centers=self.H, bond_vs=self.H2, part_type_vs=self.part_type_vs, part_type_to_attach_vs_to=self.part_type_to_attach_vs_to, part_type_to_be_glued=self.part_type_to_be_glued, part_type_after_glueing=self.part_type_after_glueing)
        self.get_state_set_state_consistency()

        # Integrate lj liquid
        self.s.integrator.set_vv()
        self.s.integrator.run(500)

        # Analysis
        virtual_sites = self.s.part.select(virtual=1)
        non_virtual = self.s.part.select(virtual=0)
        to_be_glued = self.s.part.select(type=self.part_type_to_be_glued)
        after_glueing = self.s.part.select(type=self.part_type_after_glueing)

        # One virtual site per glued particle?
        self.assertEqual(len(after_glueing), len(virtual_sites))

        # Check bonds on non-virtual particles
        bonds_centers = []
        bonds_virtual = []
        for p in non_virtual:
            # Inert particles should not have bonds
            if p.type == self.other_type:
                self.assertEqual(len(p.bonds), 0)

            # Particles that have not yet collided should not have a bond
            if p.type == self.part_type_to_be_glued:
                self.assertEqual(len(p.bonds), 0)

            for bond in p.bonds:
                # Bond type and partner type
                # part_type_after_glueing can have a bond to a vs or to a
                # non_virtual particle
                if p.type == self.part_type_after_glueing:
                    self.assertTrue(bond[0] in (self.H, self.H2))
                    # Bonds to virtual sites:
                    if bond[0] == self.H2:
                        self.assertEqual(
                            self.s.part[bond[1]].type,
                            self.part_type_vs)
                    else:
                        self.assertEqual(
                            self.s.part[bond[1]].type,
                            self.part_type_to_attach_vs_to)
                elif p.type == self.part_type_to_attach_vs_to:
                    self.assertEqual(bond[0], self.H)
                    self.assertEqual(
                        self.s.part[bond[1]].type,
                        self.part_type_after_glueing)
                else:
                    print(p.id, p.type, p.bonds)
                    raise Exception("Particle should not have bonds. ")

                # Collect bonds
                # Sort bond partners to make them unique independently of
                # which particle got the bond
                if bond[0] == self.H:
                    bonds_centers.append(tuple(sorted([p.id, bond[1]])))
                else:
                    bonds_virtual.append(tuple(sorted([p.id, bond[1]])))

        # No duplicate bonds?
        self.assertEqual(len(bonds_centers), len(set(bonds_centers)))
        self.assertEqual(len(bonds_virtual), len(set(bonds_virtual)))

        # 1 bond between centers and one between vs and glued particle
        # per collision
        self.assertEqual(len(bonds_virtual), len(bonds_centers))

        # 1 virtual sites per bond?
        self.assertEqual(len(bonds_centers), len(virtual_sites))

        # no bonds on vs and vs particle type
        for p in virtual_sites:
            self.assertEqual(len(p.bonds), 0)
            self.assertEqual(p.type, self.part_type_vs)

        # Tidy
        self.s.non_bonded_inter[
            0,
            0].lennard_jones.set_params(
                epsilon=0,
                sigma=0,
         cutoff=0)

    @ut.skipIf(not espressomd.has_features("BOND_ANGLE"), "Tests skipped because AngleHarmonic not compiled in")
    def test_bind_three_particles(self):
        # Setup particles
        self.s.part.clear()
        dx = np.array((1, 0, 0))
        dy = np.array((0, 1, 0))
        dz = np.array((0, 0, 1))
        a = np.array((0.499, 0.499, 0.499))
        b = a + 0.1 * dx
        c = a + 0.03 * dx + 0.03 * dy
        d = a + 0.03 * dx - 0.03 * dy
        e = a - 0.1 * dx

        self.s.part.add(id=0, pos=a)
        self.s.part.add(id=1, pos=b)
        self.s.part.add(id=2, pos=c)
        self.s.part.add(id=3, pos=d)
        self.s.part.add(id=4, pos=e)

        # Setup bonds
        res = 181
        for i in range(0, res, 1):
            self.s.bonded_inter[i + 2] = AngleHarmonic(
                bend=1, phi0=float(i) / (res - 1) * np.pi)
        cutoff = 0.11
        self.s.collision_detection.set_params(
            mode="bind_three_particles", bond_centers=self.H,
                                              bond_three_particles=2, three_particle_binding_angle_resolution=res, distance=cutoff)
        self.get_state_set_state_consistency()
        self.s.integrator.run(0, recalc_forces=True)
        self.verify_triangle_binding(cutoff, self.s.bonded_inter[2], res)

        # Make sure no extra bonds appear
        self.s.integrator.run(0, recalc_forces=True)
        self.verify_triangle_binding(cutoff, self.s.bonded_inter[2], res)

        # Place the particles in two steps and make sure, the bonds are the
        # same
        self.s.part.clear()
        self.s.part.add(id=0, pos=a)
        self.s.part.add(id=2, pos=c)
        self.s.part.add(id=3, pos=d)
        self.s.integrator.run(0, recalc_forces=True)

        self.s.part.add(id=4, pos=e)
        self.s.part.add(id=1, pos=b)
        self.s.cell_system.set_domain_decomposition()
        self.s.integrator.run(0, recalc_forces=True)
        self.verify_triangle_binding(cutoff, self.s.bonded_inter[2], res)
        self.s.cell_system.set_n_square()
        self.s.part[:].bonds = ()
        self.s.integrator.run(0, recalc_forces=True)
        self.verify_triangle_binding(cutoff, self.s.bonded_inter[2], res)

    def verify_triangle_binding(self, distance, first_bond, angle_res):
        # Gather pairs
        n = len(self.s.part)
        angle_res = angle_res - 1

        expected_pairs = []
        for i in range(n):
            for j in range(i + 1, n, 1):
                if self.s.distance(self.s.part[i], self.s.part[j]) <= distance:
                    expected_pairs.append((i, j))

        # Find triangles
        # Each elemtn is a particle id, a bond id and two bond partners in
        # ascending order
        expected_angle_bonds = []
        for i in range(n):
            for j in range(i + 1, n, 1):
                for k in range(j + 1, n, 1):
                    # Ref to particles
                    p_i = self.s.part[i]
                    p_j = self.s.part[j]
                    p_k = self.s.part[k]

                    # Normalized distnace vectors
                    d_ij = np.copy(p_j.pos - p_i.pos)
                    d_ik = np.copy(p_k.pos - p_i.pos)
                    d_jk = np.copy(p_k.pos - p_j.pos)
                    d_ij /= np.sqrt(np.sum(d_ij**2))
                    d_ik /= np.sqrt(np.sum(d_ik**2))
                    d_jk /= np.sqrt(np.sum(d_jk**2))

                    if self.s.distance(p_i, p_j) <= distance and self.s.distance(p_i, p_k) <= distance:
                        id_i = first_bond._bond_id + \
                            int(np.round(
                                np.arccos(np.dot(d_ij, d_ik)) * angle_res / np.pi))
                        expected_angle_bonds.append((i, id_i, j, k))

                    if self.s.distance(p_i, p_j) <= distance and self.s.distance(p_j, p_k) <= distance:
                        id_j = first_bond._bond_id + \
                            int(np.round(
                                np.arccos(np.dot(-d_ij, d_jk)) * angle_res / np.pi))
                        expected_angle_bonds.append((j, id_j, i, k))
                    if self.s.distance(p_i, p_k) <= distance and self.s.distance(p_j, p_k) <= distance:
                        id_k = first_bond._bond_id + \
                            int(np.round(
                                np.arccos(np.dot(-d_ik, -d_jk)) * angle_res / np.pi))
                        expected_angle_bonds.append((k, id_k, i, j))

        # Gather actual pairs and actual triangles
        found_pairs = []
        found_angle_bonds = []
        for i in range(n):
            for b in self.s.part[i].bonds:
                if len(b) == 2:
                    self.assertEqual(b[0]._bond_id, self.H._bond_id)
                    found_pairs.append(tuple(sorted((i, b[1]))))
                elif len(b) == 3:
                    partners = sorted(b[1:])
                    found_angle_bonds.append(
                        (i, b[0]._bond_id, partners[0], partners[1]))
                else:
                    raise Exception(
                        "There should be only 2 and three particle bonds")

        # The order between expected and found bonds does not malways match
        # because collisions occur in random order. Sort stuff
        found_pairs = sorted(found_pairs)
        found_angle_bonds = sorted(found_angle_bonds)
        expected_angle_bonds = sorted(expected_angle_bonds)
        self.assertEqual(expected_pairs, found_pairs)

        if not expected_angle_bonds == found_angle_bonds:
            # Verbose info
            print("expected:", expected_angle_bonds)
            missing = []
            for b in expected_angle_bonds:
                if b in found_angle_bonds:
                    found_angle_bonds.remove(b)
                else:
                    missing.append(b)
            print("missing", missing)
            print("extra:", found_angle_bonds)
            print()

        self.assertEqual(expected_angle_bonds, found_angle_bonds)

    def test_zz_serialization(self):
        self.s.collision_detection.set_params(
            mode="bind_centers", distance=0.11, bond_centers=self.H)
        reduce = self.s.collision_detection.__reduce__()
        res = reduce[0](reduce[1][0])
        self.assertEqual(res.__class__.__name__, "CollisionDetection")
        self.assertEqual(res.mode, "bind_centers")
        self.assertAlmostEqual(res.distance, 0.11, delta=1E-12)
        self.assertEqual(res.bond_centers, self.H)
    def test_pos_vel_forces(self):
        system = self.system
        system.cell_system.skin = 0.3
        system.virtual_sites = VirtualSitesRelative(have_velocity=True)
        system.box_l = [10, 10, 10]
        system.part.clear()
        system.time_step = 0.004
        system.part.clear()
        system.thermostat.turn_off()
        system.non_bonded_inter[0, 0].lennard_jones.set_params(epsilon=0,
                                                               sigma=0,
                                                               cutoff=0,
                                                               shift=0)

        # Check setting of min_global_cut
        system.min_global_cut = 0.23
        self.assertEqual(system.min_global_cut, 0.23)

        # Place central particle + 3 vs
        system.part.add(rotation=(1, 1, 1),
                        pos=(0.5, 0.5, 0.5),
                        id=1,
                        quat=(1, 0, 0, 0),
                        omega_lab=(1, 2, 3))
        pos2 = (0.5, 0.4, 0.5)
        pos3 = (0.3, 0.5, 0.4)
        pos4 = (0.5, 0.5, 0.5)
        cur_id = 2
        for pos in pos2, pos3, pos4:
            system.part.add(rotation=(1, 1, 1), pos=pos, id=cur_id)
            system.part[cur_id].vs_auto_relate_to(1)
            # Was the particle made virtual
            self.assertEqual(system.part[cur_id].virtual, True)
            # Are vs relative to id and
            vs_r = system.part[cur_id].vs_relative
            # id
            self.assertEqual(vs_r[0], 1)
            # distance
            self.assertAlmostEqual(vs_r[1],
                                   system.distance(system.part[1],
                                                   system.part[cur_id]),
                                   places=6)
            cur_id += 1

        # Move central particle and Check vs placement
        system.part[1].pos = (0.22, 0.22, 0.22)
        # linear and rotation velocity on central particle
        system.part[1].v = (0.45, 0.14, 0.447)
        system.part[1].omega_lab = (0.45, 0.14, 0.447)
        system.integrator.run(0, recalc_forces=True)
        for i in [2, 3, 4]:
            self.verify_vs(system.part[i])

        # Check if still true, when non-virtual particle has rotated and a
        # linear motion
        system.part[1].omega_lab = [-5, 3, 8.4]
        system.integrator.run(10)
        for i in [2, 3, 4]:
            self.verify_vs(system.part[i])

        if espressomd.has_features("EXTERNAL_FORCES"):
            # Test transfer of forces accumulating on virtual sites
            # to central particle
            f2 = np.array((3, 4, 5))
            f3 = np.array((-4, 5, 6))
            # Add forces to vs
            system.part[2].ext_force = f2
            system.part[3].ext_force = f3
            system.integrator.run(0)
            # get force/torques on non-vs
            f = system.part[1].f
            t = system.part[1].torque_lab

            # Expected force = sum of the forces on the vs
            self.assertLess(np.linalg.norm(f - f2 - f3), 1E-6)

            # Expected torque
            # Radial components of forces on a rigid body add to the torque
            t_exp = np.cross(
                system.distance_vec(system.part[1], system.part[2]), f2)
            t_exp += np.cross(
                system.distance_vec(system.part[1], system.part[3]), f3)
            # Check
            self.assertLessEqual(np.linalg.norm(t_exp - t), 1E-6)

        # Check virtual sites without velocity
        system.virtual_sites.have_velocity = False

        v2 = system.part[2].v
        system.part[1].v = 17, -13.5, 2
        system.integrator.run(0, recalc_forces=True)
        self.assertLess(np.linalg.norm(v2 - system.part[2].v), 1E-6)
    def run_test_lj(self):
        """This fills the system with vs-based dumbells, adds a lj potential,
          integrates and verifies forces. This is to make sure that no pairs
          get lost or are outdated in the short range loop"""
        system = self.system
        system.virtual_sites = VirtualSitesRelative(have_velocity=True)
        # Parameters
        n = 90
        phi = 0.6
        sigma = 1.
        eps = .025
        cut = sigma * 2**(1. / 6.)

        kT = 2
        gamma = .5

        # box
        l = (n / 6. * np.pi * sigma**3 / phi)**(1. / 3.)

        # Setup
        system.box_l = l, l, l
        system.min_global_cut = 0.501
        system.part.clear()

        system.time_step = 0.01
        system.thermostat.turn_off()

        # Dumbells consist of 2 virtual lj spheres + central particle w/o interactions
        # For n spheres, n/2 dumbells.
        for i in range(int(n / 2)):
            # Type=1, i.e., no lj ia for the center of mass particles
            system.part.add(rotation=(1, 1, 1),
                            id=3 * i,
                            pos=random.random(3) * l,
                            type=1,
                            omega_lab=0.3 * random.random(3),
                            v=random.random(3))
            # lj spheres
            system.part.add(rotation=(1, 1, 1),
                            id=3 * i + 1,
                            pos=system.part[3 * i].pos +
                            system.part[3 * i].director / 2.,
                            type=0)
            system.part.add(rotation=(1, 1, 1),
                            id=3 * i + 2,
                            pos=system.part[3 * i].pos -
                            system.part[3 * i].director / 2.,
                            type=0)
            system.part[3 * i + 1].vs_auto_relate_to(3 * i)
            self.verify_vs(system.part[3 * i + 1], verify_velocity=False)
            system.part[3 * i + 2].vs_auto_relate_to(3 * i)
            self.verify_vs(system.part[3 * i + 2], verify_velocity=False)
        system.integrator.run(0, recalc_forces=True)
        # interactions
        system.non_bonded_inter[0, 0].lennard_jones.set_params(epsilon=eps,
                                                               sigma=sigma,
                                                               cutoff=cut,
                                                               shift="auto")
        # Remove overlap
        system.integrator.set_steepest_descent(f_max=0,
                                               gamma=0.1,
                                               max_displacement=0.1)
        while system.analysis.energy()["total"] > 10 * n:
            system.integrator.run(20)
        # Integrate
        system.integrator.set_vv()
        for i in range(10):
            # Langevin to maintain stability
            system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=42)
            system.integrator.run(50)
            system.thermostat.turn_off()
            # Constant energy to get rid of thermostat forces in the
            # verification
            system.integrator.run(2)
            # Check the virtual sites config,pos and vel of the lj spheres
            for j in range(int(n / 2)):
                self.verify_vs(system.part[3 * j + 1])
                self.verify_vs(system.part[3 * j + 2])

            # Verify lj forces on the particles. The non-virtual particles are
            # skipped because the forces on them originate from the vss and not
            # the lj interaction
            verify_lj_forces(system, 1E-10,
                             3 * np.arange(int(n / 2), dtype=int))

        # Test applying changes
        enegry_pre_change = system.analysis.energy()['total']
        pressure_pre_change = system.analysis.pressure()['total']
        system.part[0].pos = system.part[0].pos + (2.2, -1.4, 4.2)
        enegry_post_change = system.analysis.energy()['total']
        pressure_post_change = system.analysis.pressure()['total']
        self.assertNotAlmostEqual(enegry_pre_change, enegry_post_change)
        self.assertNotAlmostEqual(pressure_pre_change, pressure_post_change)

        # Turn off lj interaction
        system.non_bonded_inter[0, 0].lennard_jones.set_params(epsilon=0,
                                                               sigma=0,
                                                               cutoff=0,
                                                               shift=0)
print("\nArguments:", args)

np.random.seed(42)
# NUM PARTICLES AND BOX
n_ionpairs = 100
n_part = n_ionpairs * 2
density_si = 0.5
# g/cm^3
rho_factor_bmim_pf6 = 0.003931
box_volume = n_ionpairs / rho_factor_bmim_pf6 / density_si
box_l = box_volume**(1. / 3.)
print("\n-->Ion pairs:", n_ionpairs, "Box size:", box_l)

system = espressomd.System(box_l=[box_l, box_l, box_l])
system.virtual_sites = VirtualSitesRelative()

if args.visu:
    d_scale = 0.988 * 0.5
    c_ani = [1, 0, 0, 1]
    c_dru = [0, 0, 1, 1]
    c_com = [0, 0, 0, 1]
    c_cat = [0, 1, 0, 1]
    visualizer = espressomd.visualization_opengl.openGLLive(
        system,
        background_color=[1, 1, 1],
        drag_enabled=True,
        ext_force_arrows=True,
        drag_force=10,
        draw_bonds=False,
        quality_particles=32,