DiffractionSim#

Rotate a unit cell and watch its electron diffraction pattern follow. The left panel draws the cell, the right panel the nanobeam pattern, CBED disks, or the Kikuchi pattern of the same orientation. Kinematical and Bloch wave intensities are computed in the browser, so dragging the crystal needs no Python round trip.

from quantem.widget import DiffractionSim

sim = DiffractionSim("Si (diamond cubic)", zone_axis=[1, 1, 0])
sim

The crystal can be a preset name (DiffractionSim.presets_available()), a CIF path, an ASE Atoms object, or a quantem Crystal.

Requirements#

DiffractionSim needs quantem 0.1.10 or later, for quantem.diffraction.Crystal and quantem.diffraction.bloch. quantem is a base dependency of quantem.widget except on Intel Macs and Windows on ARM; there, constructing the widget raises an ImportError that names this requirement, and the DiffractionSim tests are skipped.

Reference#

class quantem.widget.diffraction_sim.DiffractionSim(*args: t.Any, **kwargs: t.Any)#

Interactive unit cell and diffraction pattern simulator.

The left panel draws the unit cell, the right panel the diffraction pattern of the same orientation. Dragging either panel tilts the crystal. Kinematical and Bloch wave intensities, CBED disks and Kikuchi lines are computed in the browser from a reflection list prepared in Python.

Requires quantem 0.1.10 or later (quantem.diffraction.Crystal and bloch), which brings ASE and spglib.

Parameters:
  • crystal (Crystal, ase.Atoms, str, pathlib.Path or None, optional) – A quantem Crystal, an ASE Atoms object, a CIF path, or the name of a preset (see presets_available()). None starts with silicon, or with the preset named in state.

  • zone_axis (sequence of 3 float, optional) – Initial zone axis [uvw] along the beam. None keeps the identity orientation (c axis along the beam).

  • voltage_kV (float, default 200) – Accelerating voltage in kV (the beam energy in keV), above 0 and up to 1000. Changing it recomputes the reflection list.

  • k_max_inv_A (float, default 4) – Largest scattering vector of the reflection list, in 1/Å, from 0.2 to 8. Changing it recomputes the reflection list.

  • pattern_range_inv_A (float, optional) – Scattering vector at the edge of the nanobeam and CBED panel, in 1/Å, from 0.2 to k_max_inv_A. None uses 3, or k_max_inv_A when that is smaller.

  • mode ({"nanobeam", "cbed", "kikuchi"}, default "nanobeam") – Nanobeam spots, convergent-beam disks, or the wide-angle Kikuchi pattern. "kossel", the earlier name of "kikuchi", is accepted and stored as "kikuchi".

  • render ({"markers", "disks", "pixels"}, default "markers") – Nanobeam: markers sized by intensity, disks of the convergence semiangle with brightness by intensity, or a pixelated pattern. Kikuchi: vector lines ("markers"), or the pixel lookup of the reference pattern ("pixels", see compute_kossel_reference()).

  • dynamical (bool, default True) – Bloch wave intensities; False uses kinematical |F|^2 with a Gaussian excitation envelope of width sigma_excitation_inv_A.

  • thickness_A (float, default 500) – Specimen thickness in Å, above 0 and up to 10,000.

  • semiangle_mrad (float, default 2) – Convergence semiangle in mrad (CBED disks and nanobeam disk radius), above 0 and up to 50.

  • precession_deg (float, default 0) – Precession half angle in degrees, 0 to 10. Intensities are averaged over n_precession incident tilts on the precession ring.

  • n_precession (int, default 24) – Incident tilts on the precession ring (half as many while dragging), 3 to 360.

  • sigma_excitation_inv_A (float, default 0.02) – Width of the kinematical excitation envelope, in 1/Å.

  • sg_max_inv_A (float, default 0.05) – Excitation error cutoff selecting the Bloch beams, in 1/Å. Reflections outside it take thin-slab intensities.

  • quality ({"fast", "medium", "fine"}, default "medium") – Bloch beam cap and CBED tilt sampling.

  • field_mrad (float, default 50) – Half angle of the Kikuchi pattern field of view, in mrad.

  • show_kikuchi (bool, default False) – Overlay the Kikuchi line pairs on the nanobeam and CBED patterns.

  • view_from ({"detector", "gun"}, default "detector") – Viewpoint shared by both panels (no UI control). "detector" looks up the column from the detector side: the exit face of the cell is nearest you and tilts together with the Laue circle and the Kikuchi pattern. "gun" is the operator’s view down the column; the entrance face is nearest and tilts opposite to the pattern.

  • scaling ({"linear", "power", "log"}, default "linear") – Intensity scaling of the pixel renderings.

  • power (float, default 0.5) – Exponent of the "power" scaling, 0.05 to 1.

  • marker_power (float, default 0.5) – Marker area scales as intensity**marker_power (0.5: square root of the intensity), 0.05 to 1. For nanobeam disks, the brightness exponent.

  • marker_size_px (float, default 20) – Radius in CSS pixels of the strongest marker at a panel width of 420.

  • cmap (str, default "inferno") – Colormap of the pixel renderings: a name of the shared widget set ("inferno", "turbo", "gray", …), the same name with a "_r" suffix to reverse it, or "turbo_black" and "turbo_black_r", imported from quantem.

  • vmin_pct (float, default 0, 100) – Contrast window of the pixel renderings, in percent of the displayed intensity range.

  • vmax_pct (float, default 0, 100) – Contrast window of the pixel renderings, in percent of the displayed intensity range.

  • show_labels (bool, default True) – Label the cell axes a, b, c.

  • show_hkl (bool, default True) – Label the strongest reflections (or Kikuchi lines) with their hkl.

  • show_cell_axes (bool, default True) – Draw the cell axes.

  • n_cells (sequence of 3 int, default (1, 1, 1)) – Block of cells drawn in the left panel, 1 to 6 along each of a, b, c.

  • polyhedra (bool, default False) – Draw coordination polyhedra around every species except the most numerous one; around every atom of an elemental crystal.

  • show_ewald (bool, default True) – Side view of the Ewald sphere below the cell: the reciprocal lattice points near the x-z plane, the sphere through the origin (z stretched), and the excited reflections in green.

  • rotation_step_deg (float, default 15) – Step of the x, y, z rotation buttons, in degrees.

  • rotation_speed_deg_per_s (float, default 6) – Speed of the continuous rotation buttons, in degrees per second.

  • panel_width_px (int, default 420) – Width of the pattern panel in CSS pixels, 220 to 2000. The frontend shrinks it to fit a narrower container.

  • title (str, default "Diffraction Simulator") – Heading above the panels.

  • save_state (bool, default False) – Embed the full interactive state, including the Kikuchi reference pattern, in a saved notebook. By default the reference pattern is left out and a PNG of both panels, captured by the frontend, is saved for viewing the notebook without a kernel.

  • state (dict, str or pathlib.Path, optional) – Settings from state_dict() or a file written by save(), applied after the other arguments. Its preset loads that crystal unless crystal is given.

orientation#

Crystal orientation as a unit quaternion (w, x, y, z).

Type:

list of 4 float

preset#

Name of the loaded preset, or "" for other crystals. Setting it loads that preset.

Type:

str

cmap_luts#

Read-only. Lookup tables of the colormaps imported from quantem.

Type:

dict

status#

Progress message shown under the panels.

Type:

str

export_status#

Result of the last HTML export.

Type:

str

Notes

Bloch couplings are absorptive Weickenmeier-Kohl factors when every element has tabulated factors, and the elastic couplings gamma F_g / pi otherwise; the info line under the panels says “absorptive” only in the first case.

Examples

>>> from quantem.widget import DiffractionSim
>>> sim = DiffractionSim("Si (diamond cubic)", zone_axis=[1, 1, 0], mode="cbed")
>>> sim.thickness_A = 800
>>> sim.save("si_110.json")
>>> sim.export_html("si_110.html")
static presets_available() → list[str]#

Names of the built-in crystal structures.

Returns:

Preset names accepted by the constructor, set_crystal(), the preset trait and the presets option of export_html().

Return type:

list of str

property crystal: quantem.diffraction.crystal.Crystal#

The loaded quantem.diffraction.crystal.Crystal.

set_crystal(crystal: quantem.diffraction.crystal.Crystal | Atoms | str | pathlib.Path) → DiffractionSim#

Load a crystal and recompute the reflection list.

Parameters:

crystal (Crystal, ase.Atoms, str or pathlib.Path) – A quantem Crystal, an ASE Atoms object, a CIF path, or a preset name.

Returns:

The widget, for chaining.

Return type:

DiffractionSim

Raises:
  • ValueError – If a string is neither a preset name nor an existing CIF file.

  • TypeError – If crystal is none of the supported types.

set_zone_axis(zone_axis: Sequence[float], in_plane_deg: float = 0.0) → DiffractionSim#

Put a crystal direction [uvw] along the beam.

Parameters:
  • zone_axis (sequence of 3 float) – Direct-lattice direction u*a + v*b + w*c, not all zero.

  • in_plane_deg (float, default 0) – Rotation about the beam, in degrees.

Returns:

The widget, for chaining.

Return type:

DiffractionSim

Raises:

ValueError – If zone_axis is not three finite numbers, or is zero.

compute_kossel_reference(thicknesses_A: Sequence[float] = (300.0, 600.0, 1000.0), angle_step_mrad: float = 3.0, k_max_inv_A: float = 1.0) → DiffractionSim#

Compute the reference pattern for the pixel rendering of the Kikuchi mode.

Takes about a minute for silicon at 3 mrad. Results are cached per energy, thicknesses, step and k_max_inv_A.

Parameters:
  • thicknesses_A (sequence of float, default (300, 600, 1000)) – Specimen thicknesses in Å; the browser interpolates between them.

  • angle_step_mrad (float, default 3) – Angular step of the reference grid, in mrad.

  • k_max_inv_A (float, default 1) – Largest scattering vector of the Bloch calculation, in 1/Å.

Returns:

The widget, for chaining.

Return type:

DiffractionSim

state_dict() → dict[str, Any]#

Return the settings that restore the view, as a plain dict.

Returns:

Trait name to value for the crystal preset, beam energy, orientation, mode and every display setting. The reflection list, Kikuchi reference pattern and colormap tables are derived data and are not included.

Return type:

dict

load_state_dict(state: dict[str, Any]) → DiffractionSim#

Apply settings from state_dict(); unknown keys are ignored.

A non-empty preset loads that crystal. Otherwise the current crystal is kept, so a CIF or ASE crystal is passed to the constructor again together with state.

Parameters:

state (dict) – Settings, as returned by state_dict(), or the versioned envelope written by save().

Returns:

The widget, for chaining.

Return type:

DiffractionSim

save(path: str | Path) → None#

Write the settings of state_dict() to a versioned JSON file.

Parameters:

path (str or pathlib.Path) – Destination file. Missing parent directories are created.

export_html(path: str | Path | None = None, *, title: str | None = None, presets: Sequence[str] | None = None) → Path#

Write a standalone HTML viewer with the current state.

The page carries the widget runtime, so it opens with no network and no kernel, and every simulation runs in the browser: rotating the cell and changing thickness, mode or display settings need no Python. The beam energy and k_max_inv_A are fixed to the exported values, and the Kikuchi reference pattern is included when it was computed.

Parameters:
  • path (str or pathlib.Path, optional) – Destination HTML file. Missing parent directories are created. Defaults to <crystal>_<mode>_diffractionsim.html in the working directory.

  • title (str, optional) – Browser page title; defaults to the title trait.

  • presets (sequence of str, optional) – Additional preset structures to embed so the crystal menu works in the page. Each adds its reflection list to the file.

Returns:

The written file.

Return type:

pathlib.Path

Physics#

voltage_kV and k_max_inv_A set the reflection list, computed in Python: the reciprocal lattice points within k_max_inv_A, each with its kinematical |F_g|^2 (Lobato and Van Dyck factors) and its Bloch coupling U_g (absorptive Weickenmeier and Kohl factors at voltage_kV). Changing either trait recomputes the list.

With dynamical=True, reflections within sg_max_inv_A of the Ewald sphere enter a Bloch wave calculation; the rest take thin-slab intensities. With dynamical=False, every reflection takes |F_g|^2 with a Gaussian excitation envelope of width sigma_excitation_inv_A. precession_deg averages the pattern over n_precession incident tilts on the precession cone.

mode is "nanobeam", "cbed" or "kikuchi". "kossel" is accepted as the earlier name of "kikuchi" and stored as "kikuchi". The pixel rendering of the Kikuchi mode needs a reference pattern from compute_kossel_reference() (about a minute for silicon at 3 mrad).

Units are in the trait names: _keV, _A (Å), _inv_A (1/Å), _mrad, _deg, _px (CSS pixels), _pct (percent).

Data sources#

Data

Source

Used for

Kinematical scattering factors

Lobato & Van Dyck, Acta Cryst. A 70, 636 (2014), doi:10.1107/S205327331401643X, from quantem

|F_g|^2 in Python; js/diffractionsim-web/lobato.ts holds the same parameters, copied from quantem’s data/lobato.json (MIT license), for CIFs loaded in the web build

Absorptive Bloch couplings

Weickenmeier & Kohl, Acta Cryst. A 47, 590 (1991), doi:10.1107/S0108767391004774, from quantem

U_g of every crystal set up in Python, including the web build presets; js/diffractionsim-web/wk.ts ports quantem’s compute_WK_factor (MIT license) for CIFs loaded in the web build

Atom colors and radii

ASE (jmol_colors, covalent_radii)

Cell drawing

CIFs loaded in the browser (web build only) get the same absorptive Weickenmeier-Kohl couplings, with core loss, phonon absorption and a Debye-Waller factor for an RMS thermal displacement of 0.05 Å (thermal_sigma_A). The factors are tabulated every 0.0025 1/Å and interpolated; a CIF of the silicon preset reproduces the preset couplings to 1e-5 of the largest coupling (js/diffractionsim-web/wk.test.ts).

Interactive controls#

Control

Trait

Expected effect

Crystal menu

preset

Loads the preset and recomputes the reflection list

Energy menu

voltage_kV

Recomputes the reflection list at the new energy

Zone axis field, Go

orientation

Puts [uvw] (or [uvtw] for hexagonal cells) along the beam

Copy

none

Copies both panels as one PNG

Export

none

PNG of both panels, or a standalone HTML page

Reset

orientation

Returns to the identity orientation

Drag the cell

orientation

Tilts the crystal; the near face follows the pointer

Drag the pattern

orientation

Tilts the crystal so the pattern follows the pointer

Shift-drag or two-finger twist

orientation

Rotates about the beam

Double-click a disk

orientation

Tilts to the two-beam condition of that reflection

Double-click empty space

orientation

Moves the Laue circle centre to the click

x, y, z buttons and Step

rotation_step_deg

Rotates about the screen axes by the step

Spin ↔ ↕ and speed

rotation_speed_deg_per_s

Rotates continuously about a screen axis

Cell Axes, Axis Labels, Polyhedra, Ewald Sphere

show_cell_axes, show_labels, polyhedra, show_ewald

Cell panel overlays

Cells

n_cells

Number of cells drawn along a, b, c (1 to 6)

Nanobeam / CBED / Kikuchi

mode

Pattern type

Markers / Disks / Pixels (Lines / Pixels)

render

Pattern rendering

Dynamical

dynamical

Bloch wave or kinematical intensities

hkl Labels

show_hkl

Labels the strongest reflections or lines

Kikuchi Lines

show_kikuchi

Overlays Kikuchi line pairs on nanobeam and CBED

Quality

quality

Bloch beam cap and CBED tilt sampling

Compute Reference

kossel_json

Computes the Kikuchi reference pattern in Python

Thickness

thickness_A

Specimen thickness

Semiangle

semiangle_mrad

CBED and nanobeam disk radius

Precession

precession_deg

Precession half angle

Pattern Range

pattern_range_inv_A

Scattering vector at the panel edge

Field Half-Angle

field_mrad

Kikuchi pattern half angle

Excitation Error σ

sigma_excitation_inv_A

Kinematical envelope width (Dynamical off)

Marker sliders

marker_power, marker_size_px

Marker area exponent and largest radius

Scale, Exponent, Color, histogram

scaling, power, cmap, vmin_pct, vmax_pct

Pixel rendering display

cmap takes the shared widget colormaps, their _r reversals, and turbo_black and turbo_black_r, whose lookup tables come from quantem.core.visualization (trait cmap_luts).

State#

sim.save("si_110.json")
sim2 = DiffractionSim(state="si_110.json")

state_dict() holds the settings that restore the view: preset, energy, orientation, mode and every display setting. It does not hold the reflection list or the Kikuchi reference pattern, which are recomputed. A crystal from a CIF or ASE is passed again: DiffractionSim("my.cif", state="my.json").

With save_state=False (the default), a saved notebook keeps the settings and a PNG of both panels, which the frontend captures after the view settles, and leaves out the Kikuchi reference pattern. save_state=True embeds everything.

HTML export#

sim.export_html("si_110.html", presets=["Al (fcc)", "Ti (hcp)"])

The page follows the HTML export protocol and carries the widget runtime, so it opens with no network. All simulations run in the browser: rotation, thickness, mode and display changes need no Python. The beam energy and k_max_inv_A are fixed to the exported values; presets embeds additional structures for the crystal menu. The Export menu in the widget writes the same page through the export bridge. The page holds no image data, so there are no encoding or binning options.

Web pages and Dash apps#

npm run build:web (not npm run build) writes packages/quantem.diffsim/src/quantem/diffsim/static/diffraction-sim.js, a single ES module with export default { render }, built from js/diffractionsim-web/ without React. Every calculation runs in the browser, including the structure factors of a structure given as CIF text, so a page needs no Python and no quantem. The module is published on PyPI as quantem.diffsim, which has no dependencies and adds a few helpers:

import quantem.diffsim as diffsim

diffsim.save_html("si.html", structure, zone_axis=[1, 1, 0], title="Si")  # one offline page
diffsim.settings(structure, zone_axis=[0, 0, 1], mode="nanobeam")         # the dict the module reads

structure can be a pymatgen Structure (written as its conventional standard cell, with its space group), an ASE Atoms object, CIF text or a CIF path. A structure passed this way replaces the structure menu and the CIF button. The page also shows a “Powered by quantEM” link to the quantEM documentation (show_logo=False removes it).

In a Dash app, quantem.diffsim.dash.register(app) serves the module from the app’s Flask server and adds one clientside callback, and quantem.diffsim.dash.layout(name) places a simulator. A server callback that writes diffsim.settings(...) to quantem.diffsim.dash.settings_id(name) redraws it, for example when the page shows a new structure. The package README has a complete example.

The preset structure menu is js/diffractionsim-web/presets.ts (about 1 MB), generated by scripts/diffraction_sim_presets.py at 200 keV and k_max 3.0 1/Å. The file is committed because the web build needs it; run the script again after changing the presets or the crystal code (--check compares with the committed file). The settings are listed at the top of js/diffractionsim-web/index.ts and use the trait names; the web build defaults to CBED with Kikuchi lines on. A MyST page loads the same module with the anywidget directive:

:::{anywidget} diffraction-sim.js
{"preset": "Si (diamond cubic)", "zone_axis": [1, 1, 0], "thickness_A": 400}
:::