Skip to content

Solid-State Structures

Visualize crystals, surfaces, and periodic systems with unit cell display.

See the MolecularViewer API reference for the full parameter list.

Live Demo: Cu(111) Surface — Fixed Atoms (FixAtoms Constraint)

Cu(111) 4-layer slab with the bottom 2 layers frozen. Fixed atoms keep their element color and are marked with a semi-transparent yellow overlay.

from ase.build import fcc111
from ase.constraints import FixAtoms
from aseview import MolecularViewer
import numpy as np

slab = fcc111('Cu', size=(3, 3, 4), vacuum=8.0)

# Fix bottom 2 layers
z_coords = slab.get_positions()[:, 2]
z_sorted = np.sort(np.unique(np.round(z_coords, 2)))
fixed_z = set(z_sorted[:2])
fixed_indices = [i for i, pos in enumerate(slab.get_positions())
                 if round(pos[2], 2) in fixed_z]
slab.set_constraint(FixAtoms(indices=fixed_indices))

viewer = MolecularViewer(slab, style="metallic", showConstraint=True, showCell=True)
viewer.show()

Live Demo: Silicon Crystal

Silicon in diamond structure (2x2x2 supercell):


Live Demo: NaCl Crystal

Sodium chloride in rocksalt structure:


Live Demo: Partial Occupancy And Periodic Polyhedra

The center site below is 60% Fe and 30% Mn, with the unfilled 10% shown as a fully opaque white vacancy sector rather than an open or transparent gap. Its oxygen coordination crosses the periodic cell boundary. One-hop bonded atoms starts off, so the viewer initially stays inside the selected cell. Turn it on to reveal the boundary-completion atoms, bonds, and full octahedron. Cell-boundary atoms separately mirrors sites lying exactly on a face, edge, or corner. Replicas added with Add Periodicity (±x, ±y, or ±z) remain connected to one another.

import numpy as np
from ase import Atoms
from aseview import MolecularViewer

atoms = Atoms(
    symbols=["Fe", "O", "O", "O"],
    scaled_positions=[
        [0.5, 0.5, 0.5],
        [0.0, 0.5, 0.5],
        [0.5, 0.0, 0.5],
        [0.5, 0.5, 0.0],
    ],
    cell=np.eye(3) * 4.0,
    pbc=True,
)
atoms.info["occupancy"] = {
    "0": {"Fe": 0.6, "Mn": 0.3},
    "1": {"O": 1.0},
    "2": {"O": 1.0},
    "3": {"O": 1.0},
}
atoms.new_array("spacegroup_kinds", np.arange(len(atoms), dtype=int))

viewer = MolecularViewer(
    atoms,
    showCell=True,
    showBond=True,
    showPolyhedron=True,
    polyhedronColorMode="geometry",
)
viewer.show()

Live Demo: Larger Partial-Occupancy Perovskite

This 2×2×2 perovskite contains 40 representative atoms. Every A site is Ba₀.₅₅Sr₀.₃₅□₀.₁₀, every B site is Ti₀.₇₅Zr₀.₂₅, and each oxygen site is 95% occupied. The default Cartoon view demonstrates outlined partial sectors; switch among all ten Style options to compare their matching mesh or billboard treatment. White sectors always denote unoccupied fractions. Bonds and Ti–O polyhedra start hidden to keep the larger structure legible, and can be enabled from Display Settings and Polyhedron Settings.

import numpy as np
from ase import Atoms
from aseview import MolecularViewer

unit_cell = Atoms(
    symbols=["Ba", "Ti", "O", "O", "O"],
    scaled_positions=[
        [0.0, 0.0, 0.0],
        [0.5, 0.5, 0.5],
        [0.5, 0.5, 0.0],
        [0.5, 0.0, 0.5],
        [0.0, 0.5, 0.5],
    ],
    cell=np.eye(3) * 4.02,
    pbc=True,
)
atoms = unit_cell.repeat((2, 2, 2))
atoms.info["occupancy"] = {
    "0": {"Ba": 0.55, "Sr": 0.35},
    "1": {"Ti": 0.75, "Zr": 0.25},
    "2": {"O": 0.95},
}
kind_by_symbol = {"Ba": 0, "Ti": 1, "O": 2}
atoms.new_array(
    "spacegroup_kinds",
    np.array([kind_by_symbol[s] for s in atoms.get_chemical_symbols()]),
)

viewer = MolecularViewer(
    atoms,
    style="cartoon",
    showCell=True,
    showBond=False,
    showPolyhedron=False,
    polyhedronCenterElements=["Ti"],
    polyhedronNeighborElements=["O"],
    polyhedronColorMode="geometry",
    viewDirection=[-1.0, 0.65, -0.35],
    viewUp=[0.0, 0.0, 1.0],
)
viewer.show()

Live Demo: Au(111) + H₂O Adsorption

H₂O molecule adsorbing on Au(111) surface - relaxation trajectory with energy plot:


Live Demo: Graphene Phonons

Graphene nanoribbon with phonon normal modes (breathing, ZA, ZO modes):


Live Demo: Carbon Nanotube Vibrations

(5,0) Carbon nanotube with vibrational modes (RBM, longitudinal, G-band):


Live Demo: FCC Copper

Copper FCC crystal (3x3x3 supercell):


Building Crystals

Bulk Structures

from ase.build import bulk
from aseview import MolecularViewer

# Diamond structure (Si, C, Ge)
si = bulk('Si', 'diamond', a=5.43, cubic=True)
si = si * (2, 2, 2)  # 2x2x2 supercell

viewer = MolecularViewer(si, showCell=True)
viewer.show()
aseview POSCAR --style metallic
aseview structure.cif

Common Crystal Structures

Structure ASE Function Example
FCC bulk('Cu', 'fcc', a=3.61) Cu, Ag, Au, Al, Ni
BCC bulk('Fe', 'bcc', a=2.87) Fe, W, Cr, Mo
Diamond bulk('Si', 'diamond', a=5.43) Si, Ge, C
Rocksalt bulk('NaCl', 'rocksalt', a=5.64) NaCl, MgO, LiF
Zincblende bulk('GaAs', 'zincblende', a=5.65) GaAs, ZnS
Wurtzite bulk('ZnO', 'wurtzite', ...) ZnO, GaN
HCP bulk('Mg', 'hcp', a=3.21, c=5.21) Mg, Ti, Zn

Supercells

# Create supercell
atoms = bulk('Si', 'diamond', a=5.43)
supercell = atoms * (3, 3, 3)  # 3x3x3 supercell

viewer = MolecularViewer(supercell, showCell=True)
viewer.show()

Surfaces and Slabs

FCC Surfaces

from ase.build import fcc111, fcc100, fcc110

# Au(111) surface - 4 layers, 3x3 cell, 5A vacuum
au111 = fcc111('Au', size=(3, 3, 4), vacuum=5.0)

# Pt(100) surface
pt100 = fcc100('Pt', size=(4, 4, 3), vacuum=6.0)

viewer = MolecularViewer(au111, style="metallic", showCell=True)
viewer.show()

Surface Adsorption Trajectory

from ase.io import read
from aseview import MolecularViewer

# Read relaxation trajectory (e.g., from VASP or ASE optimizer)
traj = read("adsorption_relax.traj", index=":")

# Visualize with energy plot
viewer = MolecularViewer(
    traj,
    style="metallic",
    showCell=True,
    showEnergyPlot=True  # Shows energy convergence
)
viewer.show()

BCC Surfaces

from ase.build import bcc111, bcc100, bcc110

# Fe(110) surface
fe110 = bcc110('Fe', size=(3, 3, 4), vacuum=5.0)

viewer = MolecularViewer(fe110, showCell=True)
viewer.show()

General Surface

from ase.build import surface

# Create any Miller index surface
atoms = bulk('Cu', 'fcc', a=3.61)
cu_211 = surface(atoms, (2, 1, 1), layers=4, vacuum=5.0)

viewer = MolecularViewer(cu_211, showCell=True)
viewer.show()

Low-Dimensional Materials

Graphene

from ase.build import graphene_nanoribbon

# Zigzag nanoribbon
gnr = graphene_nanoribbon(4, 6, type='zigzag', saturated=True, vacuum=5.0)

# Armchair nanoribbon
gnr_arm = graphene_nanoribbon(4, 6, type='armchair', saturated=True, vacuum=5.0)

viewer = MolecularViewer(gnr, style="cartoon", showCell=True)
viewer.show()

Carbon Nanotubes

from ase.build import nanotube

# (n, m) nanotube indices
cnt_6_0 = nanotube(6, 0, length=4, vacuum=5.0)   # Zigzag
cnt_6_6 = nanotube(6, 6, length=4, vacuum=5.0)   # Armchair
cnt_8_4 = nanotube(8, 4, length=4, vacuum=5.0)   # Chiral

viewer = MolecularViewer(cnt_6_0, style="neon", backgroundColor="#000000")
viewer.show()

Phonon / Vibrational Modes

Visualize phonon modes for periodic systems using NormalViewer:

CNT Radial Breathing Mode

from ase.build import nanotube
from aseview import NormalViewer
import numpy as np

cnt = nanotube(5, 0, length=2, vacuum=5.0)
positions = cnt.get_positions()
center = positions.mean(axis=0)

# Create radial breathing mode (RBM)
mode_rbm = []
for pos in positions:
    r = pos[:2] - center[:2]
    r_norm = np.linalg.norm(r)
    if r_norm > 0.1:
        disp = r / r_norm * 0.4  # Radial displacement
        mode_rbm.append([disp[0], disp[1], 0.0])
    else:
        mode_rbm.append([0.0, 0.0, 0.0])

viewer = NormalViewer(
    cnt,
    mode_vectors=[mode_rbm],
    frequencies=[280.0],  # RBM frequency
    showModeVector=True,
    style="neon",
    backgroundColor="#000000"
)
viewer.show()

Graphene Phonons

from ase.build import graphene_nanoribbon
from aseview import NormalViewer
import numpy as np

graphene = graphene_nanoribbon(3, 3, type='zigzag', saturated=False, vacuum=5.0)
positions = graphene.get_positions()

# Out-of-plane ZA mode
mode_za = []
for pos in positions:
    phase = 0.5 * (pos[0] + pos[1])
    mode_za.append([0.0, 0.0, 0.4 * np.sin(phase)])

viewer = NormalViewer(
    graphene,
    mode_vectors=[mode_za],
    frequencies=[450.0],
    showModeVector=True,
    style="cartoon"
)
viewer.show()

Unit Cell Display

Toggle unit cell visibility:

viewer = MolecularViewer(
    crystal,
    showCell=True,      # Show unit cell
    cellLineWidth=2.0,  # Cell line thickness
    cellColor="#888888" # Cell color
)
viewer.show()

Reading Structure Files

VASP

from ase.io import read
from aseview import MolecularViewer

# Read POSCAR/CONTCAR
atoms = read("POSCAR")
viewer = MolecularViewer(atoms, showCell=True)
viewer.show()
aseview POSCAR
aseview CONTCAR

CIF Files

atoms = read("structure.cif", fractional_occupancies=True)
viewer = MolecularViewer(atoms, showCell=True)
viewer.show()

ASE represents a disordered crystallographic site with one representative atom and stores its alternatives in atoms.info["occupancy"], indexed through atoms.arrays["spacegroup_kinds"]. aseview preserves that metadata automatically. Mixed sites are rendered as occupancy-proportional sectors in the selected atom style; an occupancy sum below one adds an opaque white vacancy sector so the atom remains closed. Bond and polyhedron topology uses ASE's representative element for the site. See the small periodic demo and larger perovskite demo for rendered results.

The optional frame field is also accepted directly:

frame = {
    "symbols": ["Fe"],
    "positions": [[0, 0, 0]],
    "cell": [[5, 0, 0], [0, 5, 0], [0, 0, 5]],
    "pbc": [True, True, True],
    "species": [[
        {"symbol": "Fe", "occupancy": 0.6},
        {"symbol": "Mn", "occupancy": 0.4},
    ]],
}
MolecularViewer(frame).show()
aseview crystal.cif

Other Formats

Format Extension Example
VASP POSCAR, CONTCAR aseview POSCAR
CIF .cif aseview structure.cif
XSF .xsf aseview charge.xsf
Quantum ESPRESSO .in aseview pw.in -f espresso-in
LAMMPS .data aseview system.data -f lammps-data

Trajectory for Solid-State

MD Trajectory

from ase.io import read
from aseview import MolecularViewer

# Read VASP MD trajectory
traj = read("XDATCAR", index=":")

viewer = MolecularViewer(traj, showCell=True, showEnergyPlot=True)
viewer.show()
# VASP MD trajectory
aseview XDATCAR -i :

# ASE trajectory format
aseview md.traj -i :

Relaxation Trajectory

# Read optimization trajectory
from ase.io import read

opt_traj = read("relax.traj", index=":")

viewer = MolecularViewer(
    opt_traj,
    showCell=True,
    showEnergyPlot=True
)
viewer.show()

Style Recommendations

Structure Type Recommended Style
Metals metallic
Semiconductors glossy
Ionic crystals default
Carbon materials cartoon or neon
Surfaces metallic

Tips for Large Systems

For systems with many atoms:

viewer = MolecularViewer(
    large_system,
    atomSize=0.3,        # Smaller atoms
    bondThickness=0.08,  # Thinner bonds
    bondThreshold=0.9    # Fewer bonds detected
)
viewer.show()