Bidisperse simulations

In this notebook we will show how to do simulations with different types of particles. The same principle should be applied for simulations with several types of traps.

[1]:
import os
import sys

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

sys.path.insert(0, '../../')

import magcolloids as mgc

from IPython.display import HTML

idx = pd.IndexSlice
[2]:
%reload_ext autoreload
%autoreload 2
[3]:
ureg = mgc.ureg

Several types of particles:

A particles object creates one type of particles, with a fixed set of parameters. In the simulation, this particle is copied many times to the positions given by positions.

To create a simulation with several types of particles, it is necessary to create different instances of particles. The sim and world objects accept an array of particles as arguments.

[4]:
region = np.array([20,20,4.1])
[5]:
particles = mgc.particles(
    np.array([[-5,0,0],[0,5,0],[5,0,0]])*ureg.um,
    radius = 2*ureg.um,
    susceptibility = 1,
    diffusion=0.07*ureg.um**2/ureg.s,
    density = 0*ureg.kg/ureg.m**3,
    temperature=300*ureg.K)

particles2 = mgc.particles(
    np.array([[-5,-3,0],[0,-3,0],[5,-3,0],[5,5,0]])*ureg.um,
    radius = 1*ureg.um,
    susceptibility = 1,
    diffusion=0.07*ureg.um**2/ureg.s,
    density = 0*ureg.kg/ureg.m**3,
    temperature=300*ureg.K)
[6]:
field = mgc.field(magnitude = 3*ureg.mT, frequency = 100*ureg.Hz, angle = 90*ureg.degrees)

world = mgc.world([particles,particles2], temperature = 300*ureg.K,
                  region=region*ureg.um, boundaries = ['p','p','f'], walls = [False,False,True],
                  dipole_cutoff = 20*ureg.um)

sim = mgc.sim(dir_name = "bidisperse/", file_name = "test_particles",
       timestep = 1e-4*ureg.s, framerate = 30*ureg.Hz, total_time = 30*ureg.s,
       particles = [particles,particles2], world = world, field = field,
       output = ["x","y","z","mux","muy","muz"])
[7]:
sim.generate_scripts()
sim.run()
sim.load()
[8]:
trj = sim.lazy_read[:]
[9]:
plt.figure(figsize=(3,3),dpi=150)
ax = mgc.draw_trj(trj,sim,cmap="plasma")
for i, trj_i in trj.groupby("id"):
    if all(trj_i.type==1):
        ax.plot(trj_i.x,trj_i.y, color="red",linewidth = 0.5)
_images/BidisperseSimulations_12_0.png

Bidisperse traps

Just as particles object, the bistable_trap object creates a type of trap. Here as well, we must create different types of traps to give them different parameters.

However, we must also specify the particles that will be affected by each trap. This is done by the particles argument. If we want a specific bistable_trap object to act only on some of the copies of the object particles, we can give it a subset specification. The subset can be a slice, or an array of indices which points to the particle in position \(i\) inside the particles object.

[11]:
region = np.array([20,20,4.1])
[12]:
particles = mgc.particles(
    np.array([[-5,0,0],[0,5,0],[5,0,0]])*ureg.um,
    radius = 2*ureg.um,
    susceptibility = 1,
    diffusion=0.07*ureg.um**2/ureg.s,
    density = 0*ureg.kg/ureg.m**3,
    temperature=300*ureg.K)

traps = mgc.bistable_trap(
                np.array([[-5,0,0],[0,5,0]])*ureg.um,
                np.array([[1,0,0],[0,1,0]]),
                particles, subsets = slice(0,2),
                distance = 2*ureg.um,
                height = 0 * ureg.pN*ureg.nm,
                stiffness = 3e-4 * ureg.pN/ureg.nm)

traps2 = mgc.bistable_trap(
                np.array([[5,0,0]])*ureg.um,
                np.array([[1,0,0]]),
                particles, subsets = [2],
                distance = 0*ureg.um,
                height = 0 * ureg.pN*ureg.nm,
                stiffness = 3e-4 * ureg.pN/ureg.nm)
[13]:
field = mgc.field(magnitude = 0*ureg.mT, frequency = 100*ureg.Hz, angle = 90*ureg.degrees)

world = mgc.world(particles, [traps,traps2], temperature = 300*ureg.K,
                  region=region*ureg.um, boundaries = ['p','p','f'], walls = [False,False,True],
                  dipole_cutoff = 20*ureg.um)

sim = mgc.sim(dir_name = "bidisperse/", file_name = "test_traps",
       timestep = 1e-4*ureg.s, framerate = 30*ureg.Hz, total_time = 60*ureg.s,
       particles = particles,traps = [traps,traps2], world = world, field = field,
       output = ["x","y","z","mux","muy","muz"])
[14]:
sim.generate_scripts()
sim.run()
sim.load()

trj = sim.lazy_read[:]
[15]:
plt.figure(figsize=(3,3),dpi=150)
ax = mgc.draw_trj(trj[trj.type==1],sim,cmap="plasma")

for i, trj_i in trj.groupby("id"):
    if all(trj_i.type==1):
        ax.plot(trj_i.x,trj_i.y, color="red")
    else:
        ax.plot(trj_i.x,trj_i.y,'.',color="k", linewidth = .5)
_images/BidisperseSimulations_19_0.png

What happens if some of the traps are pair traps?

Pair traps are not assigned a bond to a specific trap, but instead they act on whatever particles are inside their cutoff (pair traps must have a cutoff).

[16]:
region = np.array([20,20,4.1])
[17]:
particles = mgc.particles(
    np.array([[-5,0,0],[0,5,0],[5,0,0]])*ureg.um,
    radius = 2*ureg.um,
    susceptibility = 1,
    diffusion=0.07*ureg.um**2/ureg.s,
    density = 0*ureg.kg/ureg.m**3,
    temperature=300*ureg.K)

traps = mgc.bistable_trap(
                np.array([[-5,0,0],[0,5,0]])*ureg.um,
                np.array([[1,0,0],[0,1,0]]),
                particles, subsets = slice(0,2),
                distance = 2*ureg.um,
                height = 0 * ureg.pN*ureg.nm,
                stiffness = 3e-4 * ureg.pN/ureg.nm)

traps2 = mgc.bistable_trap(
                np.array([[5,0,0]])*ureg.um,
                np.array([[1,0,0]]),
                particles, subsets = [2],
                distance = 0*ureg.um,
                height = 0 * ureg.pN*ureg.nm,
                stiffness = 1e-4 * ureg.pN/ureg.nm,
                cutoff = 1*ureg.um)
[18]:
field = mgc.field(magnitude = 0*ureg.mT, frequency = 100*ureg.Hz, angle = 90*ureg.degrees)

world = mgc.world(particles, [traps,traps2], temperature = 300*ureg.K,
                  region=region*ureg.um, boundaries = ['p','p','f'], walls = [False,False,True],
                  dipole_cutoff = 20*ureg.um)
[19]:
sim = mgc.sim(dir_name = "bidisperse/", file_name = "test_traps_pair",
       timestep = 1e-4*ureg.s, framerate = 30*ureg.Hz, total_time = 60*ureg.s,
       particles = particles,traps = [traps,traps2], world = world, field = field,
       output = ["x","y","z","mux","muy","muz"])
[20]:
sim.generate_scripts()
sim.run()
sim.load()

trj = sim.lazy_read[:]
[21]:
plt.figure(figsize=(3,3),dpi=150)
ax = mgc.draw_trj(trj[trj.type==1],sim,cmap="plasma")

for i, trj_i in trj.groupby("id"):
    if all(trj_i.type==1):
        ax.plot(trj_i.x,trj_i.y, color="red")
    else:
        ax.plot(trj_i.x,trj_i.y,'.',color="k", linewidth = .5)
_images/BidisperseSimulations_26_0.png

This also works well. In principle, a single particle should be able to be traped by several traps if they have a cutoff. Let’s see if that works.

[22]:
region = np.array([20,20,4.1])
[23]:
particles = mgc.particles(
    np.array([[-2.5,-5,0],[0,5,0],[2.5,-5,0]])*ureg.um,
    radius = 2*ureg.um,
    susceptibility = 0,
    diffusion=0.07*ureg.um**2/ureg.s,
    density = 0*ureg.kg/ureg.m**3,
    temperature=300*ureg.K)

traps = mgc.bistable_trap(
                np.array([[-1,-5,0],[0,5,0]])*ureg.um,
                np.array([[1,0,0],[0,1,0]]),
                particles, subsets = slice(0,2),
                distance = 2*ureg.um,
                height = 0 * ureg.pN*ureg.nm,
                stiffness = 3e-4 * ureg.pN/ureg.nm)

traps2 = mgc.bistable_trap(
                np.array([[1,-5,0]])*ureg.um,
                np.array([[1,0,0]]),
                particles, subsets = [2],
                distance = 0*ureg.um,
                height = 0 * ureg.pN*ureg.nm,
                stiffness = 1e-6 * ureg.pN/ureg.nm,
                cutoff = 10*ureg.um)
[24]:
field = mgc.field(magnitude = 0*ureg.mT, frequency = 100*ureg.Hz, angle = 90*ureg.degrees)

world = mgc.world(particles, [traps,traps2], temperature = 300*ureg.K,
                  region=region*ureg.um, boundaries = ['p','p','f'], walls = [False,False,True],
                  dipole_cutoff = 20*ureg.um)
[25]:
sim = mgc.sim(dir_name = "bidisperse/", file_name = "test_traps_pair",
       timestep = 1e-4*ureg.s, framerate = 30*ureg.Hz, total_time = 60*ureg.s,
       particles = particles,traps = [traps,traps2], world = world, field = field,
       output = ["x","y","z","mux","muy","muz"])
[26]:
sim.generate_scripts()
sim.run()
sim.load()

trj = sim.lazy_read[:]
[27]:
plt.figure(figsize=(3,3),dpi=150)
ax = mgc.draw_trj(trj[trj.type==1],sim,cmap="plasma")

for i, trj_i in trj.groupby("id"):
    if all(trj_i.type==1):
        ax.plot(trj_i.x,trj_i.y, color="red")
    else:
        ax.plot(trj_i.x,trj_i.y,'.',color="k", linewidth = .5)
_images/BidisperseSimulations_33_0.png

This works, but keep in mind that all particles see the pair trap. Currently, I don’t know how to make some particles see the pair trap and some not. Maybe this can be implemented in the future by defining several particle types.

Bidisperse traps and bidisperse particles

If the list of particle objects particles, has \(N_p\) particle types, and there are \(N_t\) traps of type \(t\), subsets should be a list of \(N_t\) pairs. The pair p = subsets[i] indicates trap \(i\) is to be bonded with particle \(p[1]\) in the list particles[p[0]].

[28]:
large_particles = mgc.particles(
    np.array([[0,2.5,0]])*ureg.um,
    radius = 2*ureg.um,
    susceptibility = 0,
    diffusion=0.07*ureg.um**2/ureg.s,
    density = 0*ureg.kg/ureg.m**3,
    temperature=300*ureg.K)

small_particles = mgc.particles(
    np.array([[-2.5,0,0],[0,0,0]])*ureg.um,
    radius = 1*ureg.um,
    susceptibility = 0,
    diffusion=0.07*ureg.um**2/ureg.s,
    density = 0*ureg.kg/ureg.m**3,
    temperature=300*ureg.K)

## The trap at [-2.5,0,0] should be paired with the first small particle.
## The trap at [2.5,0,0] should be paired with the first large particle.

long_traps = mgc.bistable_trap(
                np.array([[-2.5,0,0],[0,2.5,0]])*ureg.um,
                np.array([[1,0,0],[0,1,0]]),
                [large_particles,small_particles], subsets = [[1,0],[0,0]],
                distance = 2*ureg.um,
                height = 0 * ureg.pN*ureg.nm,
                stiffness = 3e-4 * ureg.pN/ureg.nm)

## The trap at [0,0,0] should be paired with the second small particle.
short_trap = mgc.bistable_trap(
                np.array([[0,0,0]])*ureg.um,
                np.array([[1,0,0]]),
                [large_particles,small_particles], subsets = [[1,1]],
                distance = 0*ureg.um,
                height = 0 * ureg.pN*ureg.nm,
                stiffness = 3e-4 * ureg.pN/ureg.nm)
[29]:
field = mgc.field(magnitude = 0*ureg.mT, frequency = 100*ureg.Hz, angle = 90*ureg.degrees)

world = mgc.world([large_particles,small_particles],
                  [long_traps, short_trap], temperature = 300*ureg.K,
                  region=region*ureg.um, boundaries = ['p','p','f'],
                  walls = [False,False,True],
                  dipole_cutoff = 20*ureg.um)
[30]:
sim = mgc.sim(dir_name = "bidisperse/", file_name = "test_traps_bibi",
       timestep = 1e-4*ureg.s, framerate = 30*ureg.Hz, total_time = 60*ureg.s,
       particles = [large_particles,small_particles],
       traps = [long_traps, short_trap], world = world, field = field,
       output = ["x","y","z","mux","muy","muz"])
[33]:
sim.generate_scripts()
sim.run()
sim.load()

trj = sim.lazy_read[:]
[34]:
plt.figure(figsize=(3,3),dpi=150)
ax = mgc.draw_trj(trj.query("type<3"),sim,cmap="plasma")

for i, trj_i in trj.groupby("id"):
    if all(trj_i.type<3):
        ax.plot(trj_i.x,trj_i.y)
    else:
        ax.plot(trj_i.x,trj_i.y,'.',color="k", linewidth = .5)
_images/BidisperseSimulations_41_0.png

This last part probably breaks the behaviour above