Modelling of nanoparticles, quantum dots and amorphous material: atomic structure and vibrational spectra
Description
Modelling and simulation tool:
- Nanoparticles (spherical and Wulff) and surface slabs
- Adsorption and solvation
- Calculation of energies and vibrational spectra
- Quantum dots and amorphous material
- GUI
import numpy as np
from ase import Atoms
from ase.build import bulk
from ase.cluster import wulff_construction, Decahedron
from ase.io import write
from ase.neighborlist import NeighborList
from ase.geometry import get_distances
class StructureBuilder:
"""
Build QD and amorphous structures with atoms + bonds
No energy/force calculations - just geometry
"""
def __init__(self):
pass
# ---------------- Quantum Dot Builder ----------------
def make_core_shell_qd(self, core='CdTe', shell='ZnS',
core_radius_A=15, shell_thickness_A=5):
"""
Build spherical core/shell QD, atomistic
core, shell: 'CdTe', 'ZnS', 'CdS', 'GaAs', etc
Returns ASE Atoms object with atoms + bonds defined by distances
"""
# Materials database: formula, structure, lattice constant A
mat_lib = {
'CdTe': {'structure': 'zincblende', 'a': 6.48, 'elements': ['Cd', 'Te']},
'CdS': {'structure': 'zincblende', 'a': 5.82, 'elements': ['Cd', 'S']},
'ZnS': {'structure': 'zincblende', 'a': 5.41, 'elements': ['Zn', 'S']},
'GaAs': {'structure': 'zincblende', 'a': 5.65, 'elements': ['Ga', 'As']},
'Si': {'structure': 'diamond', 'a': 5.43, 'elements': ['Si']},
}
if core not in mat_lib or shell not in mat_lib:
raise ValueError(f"Material not in lib. Options: {list(mat_lib.keys())}")
# 1. Build core as Wulff construction for ~spherical
core_bulk = bulk(core, mat_lib[core]['structure'], a=mat_lib[core]['a'])
surfaces = [(1, 0, 0), (1, 1, 1), (1, 1, 0)]
esurf = [1.0, 1.0, 1.0]
qd_core = wulff_construction(core_bulk, surfaces, esurf,
size=core_radius_A, rounding='above')
# 2. Build shell: make big shell crystal, carve spherical shell
shell_bulk = bulk(shell, mat_lib[shell]['structure'], a=mat_lib[shell]['a'])
shell_supercell = shell_bulk * (8, 8, 8)
core_center = qd_core.get_center_of_mass()
core_max_r = np.max(np.linalg.norm(qd_core.positions - core_center, axis=1))
shell_pos = shell_supercell.positions
shell_center = shell_supercell.get_center_of_mass()
dist = np.linalg.norm(shell_pos - shell_center, axis=1)
r_inner = core_max_r + 1.0 # 1A gap to avoid overlap
r_outer = core_max_r + shell_thickness_A
shell_mask = (dist >= r_inner) & (dist <= r_outer)
qd_shell = shell_supercell[shell_mask]
qd_shell.positions += core_center - shell_center
# 3. Combine
qd = qd_core + qd_shell
qd.center(vacuum=10.0)
print(f"QD built: {core} core {len(qd_core)} atoms, {shell} shell {len(qd_shell)} atoms")
print(f"Total: {len(qd)} atoms, diameter ~{(r_outer*2)/10:.1f} nm")
return qd
def make_core_shell_shell_qd(self, core='CdTe', shell1='CdS', shell2='ZnS',
r_core_A=12, t_shell1_A=4, t_shell2_A=4):
"""CdTe/CdS/ZnS style"""
# Build core + shell1 first
qd = self.make_core_shell_qd(core, shell1, r_core_A, t_shell1_A)
# Then add shell2 around it
core_radius = r_core_A + t_shell1_A
qd2 = self.make_core_shell_qd(shell1, shell2, core_radius, t_shell2_A)
# Extract only the outer shell atoms from qd2
shell2_atoms = qd2[len(qd):]
qd += shell2_atoms
print(f"Added 2nd shell: {shell2}, total now {len(qd)} atoms")
return qd
# ---------------- Amorphous Builder ----------------
def make_amorphous(self, material='Si', structure='diamond', a=5.43,
size=(3,3,3), density_scale=0.95, disorder=0.3):
"""
Create amorphous structure via random displacement + scaling
This is FAST but not MD. For real melt-quench use LAMMPS/MACE.
disorder: 0=crytal, 0.3=moderate amorphization, 0.6=heavy
density_scale: 0.95 = amorphous is 5% less dense than crystal
"""
crystal = bulk(material, structure, a=a, cubic=True) * size
# 1. Scale to amorphous density
crystal.set_cell(crystal.cell * (1/density_scale)**(1/3), scale_atoms=True)
# 2. Random displacements to break symmetry
pos = crystal.get_positions()
bond_len = a * 0.25 * np.sqrt(3) # typical bond for diamond/zincblende
pos += np.random.normal(0, bond_len * disorder, pos.shape)
crystal.set_positions(pos)
# 3. Remove atoms that are too close after displacement
i, j, d = get_distances(crystal.positions, pbc=True)
min_dist = bond_len * 0.6
too_close = (d < min_dist) & (i < j)
if np.any(too_close):
mask = np.ones(len(crystal), dtype=bool)
for ii, jj in zip(i[too_close], j[too_close]):
mask[jj] = False # delete j atom
crystal = crystal[mask]
print(f"Amorphous {material}: {len(crystal)} atoms, density scale {density_scale}")
return crystal
def add_bonds_to_atoms(self, atoms, scale=1.2):
"""
Add bond info using covalent radii. Saves to.xyz with bond info.
ASE doesn't store bonds by default, but Ovito/VESTA will read them from distances.
"""
cutoffs = NeighborList([scale * r for r in atoms.get_atomic_numbers()],
self_interaction=False, bothways=True)
cutoffs.update(atoms)
return atoms # bonds are implicit via distances
def save(self, atoms, filename):
"""Save with atoms + bonds..xyz,.cif,.pdb all work"""
atoms = self.add_bonds_to_atoms(atoms)
write(filename, atoms)
print(f"Saved {filename} - open in Ovito/VESTA to see atoms + bonds")
# ---------------- Examples ----------------
if __name__ == "__main__":
builder = StructureBuilder()
# 1. CdTe/CdS/ZnS QD: 3nm core, 0.4nm CdS, 0.4nm ZnS
qd = builder.make_core_shell_shell_qd(
core='CdTe', shell1='CdS', shell2='ZnS',
r_core_A=15, t_shell1_A=4, t_shell2_A=4
)
builder.save(qd, 'CdTe_CdS_ZnS_QD.xyz')
# 2. Amorphous ZnS shell material by itself
a_zns = builder.make_amorphous('ZnS', 'zincblende', a=5.41,
size=(4,4,4), density_scale=0.97, disorder=0.35)
builder.save(a_zns, 'a-ZnS.xyz')
# 3. Amorphous SiO2 - need to define it first
# For SiO2 you’d build beta-cristobalite, but here’s quick way:
sio2 = bulk('SiO2', 'beta_cristobalite', a=7.16) * (2,2,2)
sio2.positions += np.random.normal(0, 0.4, sio2.positions.shape)
builder.save(sio2, 'a-SiO2.xyz')
# 4. Simple CdTe QD, no shell
cdte_qd = builder.make_core_shell_qd('CdTe', 'CdTe', core_radius_A=20, shell_thickness_A=0)
builder.save(cdte_qd, 'CdTe_QD.xyz')
Here's a GUI for the structure builder.
Uses `tkinter` so it runs anywhere with no extra installs. You get buttons for QDs + amorphous materials, pick sizes/materials, and save `.xyz` to view in Ovito.
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import numpy as np
from ase import Atoms
from ase.build import bulk
from ase.cluster import wulff_construction
from ase.io import write
from ase.geometry import get_distances
import threading
class StructureBuilderGUI:
def __init__(self, root):
self.root = root
self.root.title("QD + Amorphous Structure Builder")
self.root.geometry("600x500")
# Materials database
self.mat_lib = {
'CdTe': {'structure': 'zincblende', 'a': 6.48},
'CdS': {'structure': 'zincblende', 'a': 5.82},
'ZnS': {'structure': 'zincblende', 'a': 5.41},
'GaAs': {'structure': 'zincblende', 'a': 5.65},
'Si': {'structure': 'diamond', 'a': 5.43},
'Ge': {'structure': 'diamond', 'a': 5.66},
'SiO2': {'structure': 'beta_cristobalite', 'a': 7.16},
}
self.atoms = None
self.setup_ui()
def setup_ui(self):
notebook = ttk.Notebook(self.root)
notebook.pack(fill='both', expand=True, padx=10, pady=10)
# Tab 1: Quantum Dot Builder
qd_frame = ttk.Frame(notebook)
notebook.add(qd_frame, text='Quantum Dot')
self.setup_qd_tab(qd_frame)
# Tab 2: Amorphous Builder
amor_frame = ttk.Frame(notebook)
notebook.add(amor_frame, text='Amorphous Material')
self.setup_amorphous_tab(amor_frame)
# Bottom: Status + Save
bottom_frame = ttk.Frame(self.root)
bottom_frame.pack(fill='x', padx=10, pady=5)
self.status_var = tk.StringVar(value="Ready")
ttk.Label(bottom_frame
1. Dependencies get heavy fast
CLI = just `pip install ase`.
GUI = `pip install ase tkinter` for basic, or `pyqt5` / `pyside6` for nice. Tkinter comes with Python but looks like 1998. Qt looks modern but is 100+ MB.
2. 3D visualization is the pain point
Building atoms is easy. Showing atoms + bonds rotating in real time needs `py3Dmol`, `nglview`, or `vtk`. That’s where GUIs get complex.
3. web vs desktop
Desktop: tkinter, PyQt, DearPyGui
Web: Streamlit, Gradio, Dash
Each has different pros/cons.
Here’s a GUI using Streamlit:
Streamlit = 1-file, runs in browser, 3D viewer included. Best balance for a science tool.
python
import streamlit as st
import numpy as np
from ase import Atoms
from ase.build import bulk
from ase.cluster import wulff_construction
from ase.io import write
from stmol import showmol
import py3Dmol
import tempfile
st.set_page_config(layout="wide")
st.title("QD + Amorphous Structure Builder")
class StructureBuilder:
def make_core_shell_qd(self, core='CdTe', shell='ZnS',
core_radius_A=15, shell_thickness_A=5):
mat_lib = {
'CdTe': {'structure': 'zincblende', 'a': 6.48},
'CdS': {'structure': 'zincblende', 'a': 5.82},
'ZnS': {'structure': 'zincblende', 'a': 5.41},
'Si': {'structure': 'diamond', 'a': 5.43},
}
core_bulk = bulk(core, mat_lib[core]['structure'], a=mat_lib[core]['a'])
surfaces = [(1, 0, 0), (1, 1, 1)]
esurf = [1.0, 1.0]
qd_core = wulff_construction(core_bulk, surfaces, esurf,
size=core_radius_A, rounding='above')
shell_bulk = bulk(shell, mat_lib[shell]['structure'], a=mat_lib[shell]['a'])
shell_supercell = shell_bulk * (8, 8, 8)
core_center = qd_core.get_center_of_mass()
core_max_r = np.max(np.linalg.norm(qd_core.positions - core_center, axis=1))
shell_pos = shell_supercell.positions
shell_center = shell_supercell.get_center_of_mass()
dist = np.linalg.norm(shell_pos - shell_center, axis=1)
r_inner = core_max_r + 1.0
r_outer = core_max_r + shell_thickness_A
shell_mask = (dist >= r_inner) & (dist <= r_outer)
qd_shell = shell_supercell[shell_mask]
qd_shell.positions += core_center - shell_center
qd = qd_core + qd_shell
qd.center(vacuum=5.0)
return qd
def make_amorphous(self, material='Si', structure='diamond', a=5.43,
size=3, density_scale=0.95, disorder=0.3):
crystal = bulk(material, structure, a=a, cubic=True) * (size,size,size)
crystal.set_cell(crystal.cell * (1/density_scale)**(1/3), scale_atoms=True)
pos = crystal.get_positions()
bond_len = a * 0.25 * np.sqrt(3)
pos += np.random.normal(0, bond_len * disorder, pos.shape)
crystal.set_positions(pos)
return crystal
builder = StructureBuilder()
Sidebar controls
mode = st.sidebar.selectbox("Mode", ["Quantum Dot", "Amorphous Material"])
if mode == "Quantum Dot":
st.sidebar.subheader("QD Parameters")
core = st.sidebar.selectbox("Core", ['CdTe', 'CdS', 'ZnS', 'Si'])
shell = st.sidebar.selectbox("Shell", ['ZnS', 'CdTe', 'Si'])
r_core = st.sidebar.slider("Core radius [A]", 5, 30, 15)
t_shell = st.sidebar.slider("Shell thickness [A]", 0, 15, 5)
if st.sidebar.button("Build QD"):
with st.spinner("Building..."):
qd = builder.make_core_shell_qd(core, shell, r_core, t_shell)
st.session_state['atoms'] = qd
st.success(f"Built {core}/{shell} QD: {len(qd)} atoms")
else:
st.sidebar.subheader("Amorphous Parameters")
mat = st.sidebar.selectbox("Material", ['Si', 'ZnS', 'CdTe'])
mat_dict = {'Si': ('diamond', 5.43), 'ZnS': ('zincblende', 5.41), 'CdTe': ('zincblende', 6.48)}
struct, a = mat_dict[mat]
size = st.sidebar.slider("Supercell size", 2, 5, 3)
density = st.sidebar.slider("Density scale", 0.90, 1.0, 0.95)
disorder = st.sidebar.slider("Disorder", 0.0, 0.6, 0.3)
if st.sidebar.button("Build Amorphous"):
with st.spinner("Building..."):
amorph = builder.make_amorphous(mat, struct, a, size, density, disorder)
st.session_state['atoms'] = amorph
st.success(f"Built a-{mat}: {len(amorph)} atoms")
3D viewer + download
if 'atoms' in st.session_state:
atoms = st.session_state['atoms']
col1, col2 = st.columns([2,1])
with col1:
st.subheader("3D View - atoms + bonds")
with tempfile.NamedTemporaryFile(suffix='.xyz', delete=False) as tmp:
write(tmp.name, atoms)
xyz = open(tmp.name).read()
view = py3Dmol.view(width=600, height=400)
view.addModel(xyz, 'xyz')
view.setStyle({'sphere': {'scale': 0.3}, 'stick': {'radius': 0.15}})
view.zoomTo()
showmol(view, height=400, width=600)
with col2:
st.subheader("Info")
st.write(f"**Atoms:** {len(atoms)}")
st.write(f"**Formula:** {atoms.get_chemical_formula()}")
st.write(f"**Cell:** {atoms.cell[0,0]:.1f} A")
# Download buttons
with tempfile.NamedTemporaryFile(suffix='.xyz', delete=False) as tmp:
write(tmp.name, atoms)
st.download_button("Download XYZ", open(tmp.name).read(),
file_name="structure.xyz")
with tempfile.NamedTemporaryFile(suffix='.cif', delete=False) as tmp:
write(tmp.name, atoms)
st.download_button("Download CIF", open(tmp.name).read(),
file_name="structure.cif")
Install + run:
pip install streamlit ase stmol py3Dmol
streamlit run app.py
Opens in browser. Sliders for core/shell size, dropdowns for materials. 3D view rotates, shows bonds. http://Download.xyz/.cif buttons.
Files
nanosuite_builder-simulator.pdf
Files
(146.9 kB)
| Name | Size | Download all |
|---|---|---|
|
md5:6d086bf8136119e29955f86cc2e6ed21
|
146.9 kB | Preview Download |