User Tutorial

In the IPython Notebook, the ipymd package must first be imported:

import ipymd
print ipymd.version()
0.4.2

Basic Atom Creation and Visualisation

The input for a basic atomic visualisation, is a pandas Dataframe that specifies the coordinates, size and color of each atom in the following manner:

import pandas as pd
df = pd.DataFrame(
        [[2,3,4,1,[0, 0, 255],1],
         [1,3,3,1,'orange',1],
         [4,3,1,1,'blue',1]],
        columns=['x','y','z','radius','color','transparency'])

Distances are measured in Angstroms, and colors can be defined in [r,g,b] format (0 to 255) or as a string defined in available_colors.

print(ipymd.available_colors()['reds'])
['light_salmon', 'salmon', 'dark_salmon', 'light_coral', 'indian_red', 'crimson', 'fire_brick', 'dark_red', 'red']

The Visualise_Sim class can then be used to setup a visualisation, which is returned in the form of a PIL image.

vis = ipymd.visualise_sim.Visualise_Sim()
vis.add_atoms(df)
img1 = vis.get_image(size=400,quality=5)
img1
_images/output_10_0.png

To convert this into an image viewable in IPython, simply parse it to the visualise function.

vis.visualise(img1)
_images/output_12_0.png

Extending this basic procedure, additional objects can be added to the visualisation, the viewpoint can be rotated and multiple images can be output at once, as shown in the following example:

vis.add_axes(length=0.2, offset=(-0.3,0))
vis.add_box([5,0,0],[0,5,0],[0,0,5])
vis.add_plane([[5,0,0],[0,5,2]],alpha=0.3)
vis.add_hexagon([[1,0,0],[0,0,.5]],[0,0,2],color='green')

img_ex1 = vis.get_image(xrot=45, yrot=45)
#img2 = vis.draw_colorlegend(img2,1,2)
vis.visualise([img1,img_ex1])
_images/output_14_0.png

Atom Creation From Other Sources

The ipymd.data_input module includes a number of classes to automate the intial creation of the atoms Dataframe, from various sources.

All classes will return a sub-class of DataInput, that requires the setup_data method to be run first, and then the get_atoms method returns the atoms Dataframe and the get_meta_data method returns a Pandas Series (which includes the vertexes and origin of the simulation box).

Crystal Parameters

This class allows atoms to be created in ordered crystal, as defined by their space group and crystal parameters:

data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]], ['Na', 'Cl'],
    225, cellpar=[5.4, 5.4, 5.4, 90, 90, 90],
    repetitions=[5, 5, 5])
meta = data.get_meta_data()
print meta
atoms_df = data.get_atom_data()
atoms_df.head(2)
origin                                 (0.0, 0.0, 0.0)
a                                     (27.0, 0.0, 0.0)
b                       (1.65327317885e-15, 27.0, 0.0)
c         (1.65327317885e-15, 1.65327317885e-15, 27.0)
dtype: object
id type x y z transparency color radius
0 1 Na 0.000000e+00 0.0 0.0 1.0 light_salmon 1.0
1 2 Na 3.306546e-16 2.7 2.7 1.0 light_salmon 1.0
vis2 = ipymd.visualise_sim.Visualise_Sim()
vis2.add_axes()
vis2.add_box(meta.a,meta.b,meta.c, meta.origin)
vis2.add_atoms(atoms_df)
images = [vis2.get_image(xrot=xrot,yrot=45) for xrot in [0,45]]
vis2.visualise(images, columns=2)
_images/output_20_0.png

A dataframe is available which lists the alternative names for each space group:

df = ipymd.data_input.crystal.get_spacegroup_df()
df.loc[[1,194,225]]
System_type Point group Short_name Full_name Schoenflies Fedorov Shubnikov Fibrifold
Number
1 triclinic 1 P1 P 1 $C_1^1$ 1s $(a/b/c)\cdot 1$ -
194 hexagonal 6/m 2/m 2/m P63/mmc P 63/m 2/m 2/c $D_{6h}^4$ 88a $(c:(a/a))\cdot m:6_3\cdot m$ -
225 cubic 4/m 3 2/m Fm3m F 4/m 3 2/m $O_h^5$ 73s $\left ( \frac{a+c}{2}/\frac{b+c}{2}/\frac{a+b... $2^{-}:2$

Crystallographic Information Files

.cif files are a common means to store crystallographic data and can be loaded as follows:

cif_path = ipymd.get_data_path('example_crystal.cif')
data = ipymd.data_input.cif.CIF()
data.setup_data(cif_path)
meta = data.get_meta_data()
vis = ipymd.visualise_sim.Visualise_Sim()
vis.basic_vis(data.get_atom_data(), data.get_meta_data(),
              xrot=45,yrot=45)
_images/output_25_0.png

NB: at present, fractional occupancies of lattice sites are returned in the atom Dataframe, but cannot be visualised as such. It is intended that eventually occupancy will be visualised by partial spheres.

data.get_atom_data().head(1)
type x y z occupancy transparency color radius
0 Fe 4.363536 2.40065 22.642804 1.0 1.0 light_salmon 1.0

Lammps Input Data

The input data for LAMMPS simulations (supplied to read_data) can be input. Note that the get_atom_data method requires that the atom_style is defined, in order to define what each data column refers to.

lammps_path = ipymd.get_data_path('lammps_input.data')
data = ipymd.data_input.lammps.LAMMPS_Input()
data.setup_data(lammps_path,atom_style='charge')
vis = ipymd.visualise_sim.Visualise_Sim()
vis.basic_vis(data.get_atom_data(), data.get_meta_data(),
              xrot=45,yrot=45)
_images/output_30_0.png

Lammps Output Data

Output data can be read in the form of a single file or, it is advisable for efficiency, that a single file is output for each timestep, where * is used to define the variable section of the filename. The get_atoms and get_simulation_box methods now take a variable to define which configuration is returned.

lammps_path = ipymd.get_data_path('atom_onefile.dump')
data = ipymd.data_input.lammps.LAMMPS_Output()
data.setup_data(lammps_path)
print data.count_configs()

vis = ipymd.visualise_sim.Visualise_Sim()
vis.basic_vis(data.get_atom_data(99), data.get_meta_data(99),
              spheres=True,xrot=45,yrot=45,quality=5)
99
_images/output_33_1.png
lammps_path = ipymd.get_data_path(['atom_dump','atoms_*.dump'])
data = ipymd.data_input.lammps.LAMMPS_Output()
data.setup_data(lammps_path)
print data.count_configs()

vis = ipymd.visualise_sim.Visualise_Sim()
vis.basic_vis(data.get_atom_data(99), data.get_meta_data(99),
              spheres=False,xrot=90,yrot=0)
99
_images/output_34_1.png

Atom Manipulation

The atoms Dataframe is already very easy to manipulate using the standard pandas methods. But an Atom_Manipulation class has also been created to carry out standard atom manipulations, such as setting variables dependant on atom type or altering the geometry, as shown in this example:

data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]], ['Na', 'Cl'],
    225, cellpar=[5.4, 5.4, 5.4,60,60,60],
    repetitions=[5, 5, 5])
meta = data.get_meta_data()

manipulate_atoms = ipymd.atom_manipulation.Atom_Manipulation

new_df = manipulate_atoms(data.get_atom_data(), data.get_meta_data(),undos=2)

new_df.apply_map({'Na':1.5, 'Cl':1},'radius')
new_df.apply_map('color','color',default='grey')
new_df.change_type_variable('Na', 'transparency', 0.5)
new_df.slice_fraction(cmin=0.3, cmax=0.7,update_uc=False)

vis2 = ipymd.visualise_sim.Visualise_Sim()
vis2.add_box(*new_df.meta[['a','b','c','origin']])
vis2.add_axes([meta.a,meta.b,meta.c],offset=(-1.3,-0.7))
vis2.add_atoms(new_df.df, spheres=True)

img1 = vis2.get_image(xrot=45,yrot=45)

vis2.remove_atoms()
new_df.repeat_cell((-1,1),(-1,1),(-1,1))
new_df.color_by_variable('z')
vis2.add_atoms(new_df.df, spheres=True)
vis2.add_box(*new_df.meta[['a','b','c','origin']])
img2 = vis2.get_image(xrot=90,yrot=0)
img3 = ipymd.plotting.create_colormap_image(new_df.df.z.min(), new_df.df.z.max(),
                                            horizontal=True,title='z position',size=150)

vis2.visualise([img1,img2, (280,1), img3], columns=2)
_images/output_37_0.png

NB: default atom variables (such as color and radii can be set using the apply_map method and any column name from those given in ipymd.shared.atom_data():

ipymd.shared.atom_data().head(1)
Num ARENeg RCov RBO RVdW MaxBnd Mass ElNeg Ionization ElAffinity Name color_chemlab color_chemlab_light color
Symb
H 1 2.2 0.31 0.31 1.1 1 1.00794 2.2 13.5984 0.754204 Hydrogen white snow (191, 191, 191)

Geometric Analysis

Given the simple and flexible form of the atomic data and visualisation, it is now easier to add more complex geometric analysis. These analyses are being contained in the atom_analysis package, and some initial examples are detailed below. Functions in the atom_analysis.nearest_neighbour package are based on the scipy.spatial.cKDTree algorithm for identifying nearest neighbours.

Atomic Coordination

The two examples below show computation of the coordination of Na, w.r.t Cl, in a simple NaCl crystal (which should be 6). The first does not include a consideration of the repeating boundary conditions, and so outer atoms have a lower coordination number. But the latter computation provides a method which takes this into consideration, by repeating the Cl lattice in each direction before computation.

data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]], ['Na', 'Cl'],
    225, cellpar=[5.4, 5.4, 5.4, 90, 90, 90],
    repetitions=[5, 5, 5])
df = data.get_atom_data()
meta = data.get_meta_data()

df = ipymd.atom_analysis.nearest_neighbour.coordination_bytype(df, 'Na','Cl')

new_df = manipulate_atoms(df,meta)
new_df.filter_variables('Na')
new_df.color_by_variable('coord_Na_Cl',minv=3,maxv=7)

vis = ipymd.visualise_sim.Visualise_Sim()
vis.add_axes(offset=(0,-0.3))
vis.add_box(*meta[['a','b','c','origin']])
vis.add_atoms(new_df.df)

img = vis.get_image(xrot=45,yrot=45)

img2 = ipymd.plotting.create_legend_image(new_df.df.coord_Na_Cl,new_df.df.color, title='Na Coordination',size=150,colbytes=True)

vis.visualise([img,img2],columns=2)
_images/output_44_0.png
data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]], ['Na', 'Cl'],
    225, cellpar=[5.4, 5.4, 5.4, 90, 90, 90],
    repetitions=[5, 5, 5])
df = data.get_atom_data()
meta = data.get_meta_data()

df = ipymd.atom_analysis.nearest_neighbour.coordination_bytype(
    df, 'Na','Cl',repeat_meta=meta)

new_df = manipulate_atoms(df)
new_df.filter_variables('Na')
new_df.color_by_variable('coord_Na_Cl',minv=3,maxv=7)

vis = ipymd.visualise_sim.Visualise_Sim()
vis.add_box(*meta[['a','b','c','origin']])
vis.add_axes(offset=(0,-0.3))
vis.add_atoms(new_df.df)

img = vis.get_image(xrot=45,yrot=45)

img2 = ipymd.plotting.create_legend_image(new_df.df.coord_Na_Cl,new_df.df.color, title='Na Coordination',size=150,colbytes=True)

vis.visualise([img,img2],columns=2)
_images/output_45_0.png

Atomic Structure Comparison

compare_to_lattice takes each atomic coordinate in df1 and computes the distance to the nearest atom (i.e. lattice site) in df2:

import numpy as np
data1 = ipymd.data_input.crystal.Crystal()
data1.setup_data(
    [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]], ['Na', 'Cl'],
    225, cellpar=[5.4, 5.4, 5.4, 90, 90, 90],
    repetitions=[5, 5, 5])
df1 = data1.get_atom_data()

print(('Average distance to nearest atom (identical)',
       np.mean(ipymd.atom_analysis.nearest_neighbour.compare_to_lattice(df1,df1))))

data2 = ipymd.data_input.crystal.Crystal()
data2.setup_data(
    [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]], ['Na', 'Cl'],
    225, cellpar=[5.41, 5.4, 5.4, 90, 90, 90],
    repetitions=[5, 5, 5])
df2 = data2.get_atom_data()

print(('Average distance to nearest atom (different)',
       np.mean(ipymd.atom_analysis.nearest_neighbour.compare_to_lattice(df1,df2))))
('Average distance to nearest atom (identical)', 0.0)
('Average distance to nearest atom (different)', 0.022499999999999343)

Common Neighbour Analysis (CNA)

CNA (Honeycutt and Andersen, J. Phys. Chem. 91, 4950) is an algorithm to compute a signature for pairs of atoms, which is designed to characterize the local structural environment. Typically, CNA is used as an effective filtering method to classify atoms in crystalline systems (Faken and Jonsson, Comput. Mater. Sci. 2, 279, with the goal to get a precise understanding of which atoms are associated with which phases, and which are associated with defects.

Common signatures for nearest neighbours are:

  • FCC = 12 x 4,2,1
  • HCP = 6 x 4,2,1 & 6 x 4,2,2
  • BCC = 6 x 6,6,6 & 8 x 4,4,4
  • Diamond = 12 x 5,4,3 & 4 x 6,6,3

which are tested below:

data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0.0, 0.0, 0.0]], ['Al'],
    225, cellpar=[4.05, 4.05, 4.05, 90, 90, 90],
    repetitions=[5, 5, 5])
fcc_meta = data.get_meta_data()
fcc_df = data.get_atom_data()

data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0.33333,0.66667,0.25000]], ['Mg'],
    194, cellpar=[3.21, 3.21, 5.21, 90, 90, 120],
    repetitions=[5,5,5])
hcp_meta = data.get_meta_data()
hcp_df = data.get_atom_data()

data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0,0,0]], ['Fe'],
    229, cellpar=[2.866, 2.866, 2.866, 90, 90, 90],
    repetitions=[5,5,5])
bcc_meta = data.get_meta_data()
bcc_df = data.get_atom_data()

data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0,0,0]], ['C'],
    227, cellpar=[3.57, 3.57, 3.57, 90, 90, 90],
    repetitions=[2,2,2])
diamond_meta = data.get_meta_data()
diamond_df = data.get_atom_data()
print(ipymd.atom_analysis.nearest_neighbour.cna_sum(fcc_df,repeat_meta=fcc_meta))
print(ipymd.atom_analysis.nearest_neighbour.cna_sum(hcp_df,repeat_meta=hcp_meta))
print(ipymd.atom_analysis.nearest_neighbour.cna_sum(bcc_df,repeat_meta=bcc_meta))
print(ipymd.atom_analysis.nearest_neighbour.cna_sum(diamond_df,upper_bound=10,max_neighbours=16,
                                                    repeat_meta=diamond_meta))
Counter({'4,2,1': 6000})
Counter({'4,2,2': 1500, '4,2,1': 1500})
Counter({'6,6,6': 2000, '4,4,4': 1500})
Counter({'5,4,3': 768, '6,6,3': 256})

For each atom, the CNA for each nearest-neighbour can be output:

ipymd.atom_analysis.nearest_neighbour.common_neighbour_analysis(hcp_df,repeat_meta=hcp_meta).head(5)
id type x y z transparency color radius cna
0 1 Mg -0.000016 1.853304 1.3025 1.0 light_salmon 1.0 {u'4,2,2': 6, u'4,2,1': 6}
1 2 Mg 1.605016 0.926638 3.9075 1.0 light_salmon 1.0 {u'4,2,2': 6, u'4,2,1': 6}
2 3 Mg -0.000016 1.853304 6.5125 1.0 light_salmon 1.0 {u'4,2,2': 6, u'4,2,1': 6}
3 4 Mg 1.605016 0.926638 9.1175 1.0 light_salmon 1.0 {u'4,2,2': 6, u'4,2,1': 6}
4 5 Mg -0.000016 1.853304 11.7225 1.0 light_salmon 1.0 {u'4,2,2': 6, u'4,2,1': 6}

This can be used to produce a plot identifying likely structure of an unknown structure:

lammps_path = ipymd.get_data_path('thermalized_troilite.dump')
data = ipymd.data_input.lammps.LAMMPS_Output()
data.setup_data(lammps_path)
df = data.get_atom_data()
df = df[df.type==1]
plot = ipymd.atom_analysis.nearest_neighbour.cna_plot(df,repeat_meta=data.get_meta_data())
plot.display_plot()
_images/output_56_0.png

A visualisation of the probable local character of each atom can also be created. Note the accuracy parameter in the cna_categories method allows for more robust fitting to the ideal signatures:

lammps_path = ipymd.get_data_path('thermalized_troilite.dump')
data = ipymd.data_input.lammps.LAMMPS_Output()
data.setup_data(lammps_path)
df = data.get_atom_data()
meta = data.get_meta_data()
df = df[df.type==1]
df = ipymd.atom_analysis.nearest_neighbour.cna_categories(
    df,repeat_meta=meta,accuracy=0.7)
manip = ipymd.atom_manipulation.Atom_Manipulation(df)
manip.color_by_categories('cna')
#manip.apply_colormap({'Other':'blue','FCC':'green','HCP':'red'}, type_col='cna')
manip.change_type_variable('Other','transparency',0.5,type_col='cna')
atom_df = manip.df

vis = ipymd.visualise_sim.Visualise_Sim()
vis.add_box(*meta[['a','b','c','origin']])
vis.add_atoms(atom_df)

img = vis.get_image(xrot=90)

img2 = ipymd.plotting.create_legend_image(atom_df.cna,atom_df.color,
                title='CNA Category\nof Fe Sublattice',size=150,colbytes=True)

vis.visualise([img,img2],columns=2)
_images/output_58_0.png

Vacany Identification

The vacancy_identification method finds grid sites with no atoms within a specified distance:

cif_path = ipymd.get_data_path('pyr_4C_monoclinic.cif')
data = ipymd.data_input.cif.CIF()
data.setup_data(cif_path)
cif4c_df, cif4c_meta = data.get_atom_data(), data.get_meta_data()
vac_df = ipymd.atom_analysis.nearest_neighbour.vacancy_identification(cif4c_df,0.2,2.3,cif4c_meta,
                                         radius=2.3,remove_dups=True)
vis = ipymd.visualise_sim.Visualise_Sim()
vis.add_atoms(vac_df)
vis.add_box(*cif4c_meta[['a','b','c','origin']])
vis.add_atoms(cif4c_df)
vis.visualise([vis.get_image(xrot=90,yrot=10),
               vis.get_image(xrot=45,yrot=45)],columns=2)
_images/output_61_0.png

XRD Spectrum Prediction

This is an implementation of the virtual x-ray diffraction pattern algorithm, from http://http://dx.doi.org/10.1007/s11837-013-0829-3.

data = ipymd.data_input.crystal.Crystal()
data.setup_data(
    [[0,0,0]], ['Fe'],
    229, cellpar=[2.866, 2.866, 2.866, 90, 90, 90],
    repetitions=[5,5,5])

meta = data.get_meta_data()
atoms_df = data.get_atom_data()

wlambda = 1.542 # Angstrom (Cu K-alpha)
thetas, Is = ipymd.atom_analysis.spectral.compute_xrd(atoms_df,meta,wlambda)
plot = ipymd.atom_analysis.spectral.plot_xrd_hist(thetas,Is,wlambda=wlambda,barwidth=1)
plot.axes.set_xlim(20,90)
plot.display_plot(True)
_images/output_64_0.png

The predicted spectrum peaks (for alpha-Fe) shows good correlation with experimentally derived data:

from IPython.display import Image
exp_path = ipymd.get_data_path('xrd_fe_bcc_Cu_kalpha.png',
                          module=ipymd.atom_analysis)
Image(exp_path,width=380)
_images/output_66_0.png

System Analysis

Within the LAMMPS_Output class there is also the option to read in a systems data file, with a log of global variables for each simulation timestep.

data = ipymd.data_input.lammps.LAMMPS_Output()
sys_path = ipymd.get_data_path('system.dump')
data.setup_data(sys_path=sys_path)
sys_data = data.get_meta_data_all()
sys_data.head()
time natoms a b vol press temp peng keng teng enth
config
2 200 5880 3.997601 3.997601 106784.302378 2568.163297 6.616167 -576911.132565 115.942920 -576795.189644 -572795.687453
3 400 5880 3.993996 3.993997 106591.809864 1560.503603 8.739034 -576962.187377 153.144425 -576809.042952 -574383.189834
4 600 5880 3.989881 3.989882 106372.285789 364.540620 8.262727 -576965.242403 144.797535 -576820.444868 -576254.921821
5 800 5880 3.986465 3.986465 106190.203677 -586.959616 7.597382 -576960.911674 133.137903 -576827.773772 -577736.783571
6 1000 5880 3.988207 3.988208 106283.055838 128.391396 4.990469 -576921.379605 87.453896 -576833.925708 -576634.915276
ax = sys_data.plot('time','temp',legend=False)
ax.set_xlabel('Time (fs)')
ax.set_ylabel('Temperature (K)');
ax.grid()
_images/output_70_0.png

Plotting

Plotting is handled by the Plotter class, which is mainly a useful wrapper around matplotlib.

df = pd.DataFrame(
        [[2,3,4,1,[0, 0, 255],1],
         [1,3,3,1,'orange',1],
         [4,3,1,1,'blue',1]],
        columns=['x','y','z','radius','color','transparency'])
vis = ipymd.visualise_sim.Visualise_Sim()
vis.add_atoms(df)
vis.add_axes(length=0.2, offset=(-0.3,0))
vis.add_box([5,0,0],[0,5,0],[0,0,5])
vis.add_plane([[5,0,0],[0,5,2]],alpha=0.3)
vis.add_hexagon([[1,0,0],[0,0,.5]],[0,0,2],color='green')
img_ex1 = vis.get_image(xrot=45, yrot=45)

with ipymd.plotting.style('xkcd'):
    plot = ipymd.plotting.Plotter(figsize=(5,3))
    plot.axes.scatter([0,0.5,1.2],[0,0.5,1],s=30)
    plot.axes.set_xlabel('some variable')
    plot.axes.set_ylabel('some other variable')
    plot.add_image_annotation(img_ex1,(230,100),(0.5,0.5),zoom=0.5)
    plot.display_plot(tight_layout=True)
_images/output_73_0.png

Matplotlib also has an animation capability:

import numpy as np
x_iter = [np.linspace(0, 2, 1000) for i in range(100)]
def y_iter(x_iter):
    for i,x in enumerate(x_iter):
        yield np.sin(2 * np.pi * (x - 0.01 * i))

with ipymd.plotting.style('ggplot'):
    line_anim = ipymd.plotting.animation_line(x_iter,y_iter(x_iter),
                                              xlim=(0,2),ylim=(-2,2),incl_controls=False)
line_anim

Recent IPython Notebook version have brought powerful new interactive features, such as Javascript widgets:

One could imagine using this feature to overlay time-dependant field information on to 2D visualiations of atomic configurations:

# visualise atoms
df = pd.DataFrame(
        [[2,3,4,1,'gray',0.6],
         [1,3,3,1,'gray',0.6],
         [4,3,1,1,'gray',0.6]],
        columns=['x','y','z','radius','color','transparency'])
vis = ipymd.visualise_sim.Visualise_Sim()
vis.add_atoms(df,illustrate=True)
img1 = vis.get_image(size=400,quality=5,xrot=90)

plot = ipymd.plotting.Plotter()
plot.add_image(img1,width=2,height=2,origin=(-1,-1))

# setup contour iterators
import numpy as np
from itertools import izip
from matplotlib.mlab import bivariate_normal

x_iter = [np.linspace(-1, 1, 1000) for i in range(100)]
y_iter = [np.linspace(-1, 1, 1000) for i in range(100)]
def z_iter(x_iter,y_iter):
    for i,(x,y) in enumerate(izip(x_iter,y_iter)):
        X, Y = np.meshgrid(x, y)
        yield bivariate_normal(X, Y, 0.005*(i+1), 0.005*(i+1),0.5,0.5)

# create contour visualisation
with ipymd.plotting.style('ggplot'):
    c_anim = ipymd.plotting.animation_contourf(x_iter,y_iter,z_iter(x_iter,y_iter),
                                      xlim=(-1,1),ylim=(-1,1),
                                      cmap='PuBu_r',alpha=0.5,plot=plot)
c_anim


Once Loop Reflect