Notebook API Reference
The jungular Python API available in the in-browser simulation notebook — query and manipulate atoms, run MD, visualize, and submit jobs.
Every project’s simulation view includes a Python notebook powered by Pyodide — a full Python runtime running in your browser, with numpy and matplotlib preloaded. The notebook talks to the live molecular scene through a single module: jungular.
import jungular
print(jungular.describe())
positions = jungular.get_positions() # (N, 3) numpy array
Everything runs client-side against the system you’re currently viewing — there’s no server round-trip. numpy is also re-exported as jungular.np for convenience.
Note: the module is spelled
jungular, notjungler. All names below are accessed asjungular.<name>().
Querying the system
Read-only inspection of the atoms, bonds, box, and types in the active window.
| Function | Returns | Description |
|---|---|---|
get_current_window() | dict | Active window info: id, name, type, atom_count, bond_count. |
get_atom_count() | int | Total number of atoms. Also available as jungular.atoms. |
get_box_size() | float | Cubic simulation box edge length (Å). |
get_position(index) | dict | Single atom position {x, y, z}. |
get_positions() | np.ndarray | All positions as an (N, 3) array. Also jungular.positions. |
get_atom_types() | np.ndarray | Per-atom type IDs, uint8, shape (N,). |
get_bonds() | np.ndarray | Bond pairs as an (M, 2) array of atom indices. |
get_type_name(type_id) | str | None | Name for an atom type ID. |
get_atom_type_info(type_id) | dict | None | id, name, mass, epsilon, sigma, charge. |
get_all_atom_types() | list[dict] | Every registered atom type. |
import jungular
n = jungular.get_atom_count()
box = jungular.get_box_size()
pos = jungular.get_positions()
print(f"{n} atoms in a {box:.1f} Å box")
print("center of mass:", pos.mean(axis=0))
Manipulating atoms
Move, resize, and delete. Positions are always (N, 3) numpy arrays in Ångströms.
| Function | Returns | Description |
|---|---|---|
set_box_size(size) | None | Set the cubic box edge length. |
set_positions(positions) | None | Replace all positions from an (N, 3) array (shape is validated). |
move_atoms(indices, dx=0, dy=0, dz=0) | None | Translate the given atoms by a displacement. |
delete_atoms(indices) | int | Delete atoms (remaining atoms are renumbered); returns the count deleted. |
import jungular
import numpy as np
# Shift every atom up by 5 Å along z
pos = jungular.get_positions()
pos[:, 2] += 5.0
jungular.set_positions(pos)
# Or move a specific subset
jungular.move_atoms([0, 1, 2], dz=5.0)
Adding atoms & types
| Function | Returns | Description |
|---|---|---|
add_atom(x, y, z, type_id) | int | Add one atom; returns its new index. |
add_atoms_batch(positions, type_ids) | int | Bulk-add atoms; returns the count added. |
register_atom_type(name, mass, epsilon, sigma, charge=0.0) | int | Register a new atom type with force-field parameters; returns the type ID. |
import jungular
import numpy as np
# Register a type, then place a grid of atoms of that type
tid = jungular.register_atom_type("Ar", mass=39.95, epsilon=0.238, sigma=3.40)
coords = np.array([[x, y, 0.0] for x in range(0, 20, 4) for y in range(0, 20, 4)])
types = np.full(len(coords), tid, dtype=np.uint8)
added = jungular.add_atoms_batch(coords, types)
print(f"added {added} atoms")
Molecule groups
When a system is built from molecules, the notebook can inspect that grouping.
| Function | Returns | Description |
|---|---|---|
get_all_molecule_types() | list[dict] | Molecule types: type_id, type_name, display_name, instance_count, atom_count. |
get_all_molecules() | list[dict] | Every molecule: index, type_id, type_name, display_name, atom_indices, position. |
get_molecule(index) | dict | None | A single molecule by index. |
get_all_molecules_of_type(type_id) | list[dict] | All molecules of a given type. |
clear_molecule_tracker() | None | Reset molecule instance-tracking metadata. |
Running molecular dynamics
The notebook drives the same in-browser WASM MD engine used by the live preview. These run steps synchronously and read back energies.
| Function | Returns | Description |
|---|---|---|
step() | None | Run a single MD step. |
run_steps(n) | None | Run n MD steps. |
get_temperature() | float | Current temperature (K). |
get_potential_energy() | float | Potential energy (kcal/mol). |
get_kinetic_energy() | float | Kinetic energy (kcal/mol). |
get_total_energy() | float | Total energy (kcal/mol). |
For long runs, cooperate with the notebook’s Stop button by awaiting jungular.sleep() periodically (see Long-running loops).
import jungular
for i in range(10):
jungular.run_steps(100)
await jungular.sleep() # yield to the UI, honor Stop
print(f"step {(i+1)*100}: T={jungular.get_temperature():.1f} K, "
f"E={jungular.get_total_energy():.2f} kcal/mol")
Visualization
Highlight, select, and build surfaces on top of the scene.
| Function | Returns | Description |
|---|---|---|
refresh() | None | Force a re-render. |
highlight(indices, color='#00ffff') | None | Highlight atoms (highlights accumulate). |
clear_highlight() | None | Clear notebook highlights. |
select(indices) | None | Select atoms (drives the Effect menu). |
clear_selection() | None | Clear the current selection. |
expose(indices=None, name=None, depth_margin=2.0) | dict | Create an “expose” effect that hides occluding atoms; returns {success, error}. |
create_surface(indices, name=None, surface_type='molecular', color=None, opacity=1.0, probe_radius=1.4) | dict | Build a surface mesh. surface_type is 'molecular', 'sas', or 'vdw'. |
import jungular
import numpy as np
# Highlight every atom in the upper half of the box
pos = jungular.get_positions()
top = np.where(pos[:, 2] > jungular.get_box_size() / 2)[0]
jungular.highlight(top.tolist(), color="#ff3d7f")
Labels
Named atom groups that persist on the project.
| Function | Returns | Description |
|---|---|---|
get_atoms_with_label(label) | list[int] | Atom indices belonging to a label. |
create_label(atoms, name=None) | dict | Create a label from atom indices; returns {success, error}. |
get_all_labels() | list[dict] | All labels: name, color, atom_indices. |
Long-running loops
Python in the notebook runs on the browser’s main thread. A tight for loop would freeze the UI and can’t be stopped. To keep the interface responsive and make the Stop button work, await jungular.sleep() inside your loop — it yields to the browser and raises StopExecution if the user hit Stop.
| Function | Returns | Description |
|---|---|---|
sleep(ms=0) (async) | None | await to yield to the UI. Raises StopExecution if stopped. |
check_stop() | None | Raise StopExecution if a stop was requested. |
is_stop_requested() | bool | Non-raising stop check. |
StopExecution | exception | Raised to unwind a running cell when the user stops. |
import jungular
try:
for i in range(100_000):
jungular.run_steps(10)
if i % 10 == 0:
await jungular.sleep() # responsive + stoppable
print("done")
except jungular.StopExecution:
print("stopped by user")
Recording trajectories
Capture frames as you step, then scrub them in a Trajectory window.
| Function | Returns | Description |
|---|---|---|
write_trajectory(positions, step_number=None, box_size=None) | dict | Append a frame; returns {success, frame_index, error}. |
get_trajectory_frame_count() | int | Number of stored frames. |
get_trajectory_frame(index) | dict | None | Frame data: positions, step_number, box_size. |
clear_trajectory() | None | Delete all recorded frames. |
get_current_trajectory_frame() | dict | Data for the frame currently displayed in playback. |
import jungular
jungular.clear_trajectory()
for i in range(50):
jungular.run_steps(20)
jungular.write_trajectory(jungular.get_positions(), step_number=i * 20)
await jungular.sleep()
print(f"recorded {jungular.get_trajectory_frame_count()} frames")
Trajectory plots
Register an analysis function against a Trajectory Plot window. It’s called automatically every time the displayed frame changes and returns the data to plot.
| Function | Returns | Description |
|---|---|---|
register_plot(window_name, analyze_fn) | None | Register fn(positions, frame_index) -> dict for a plot window. |
unregister_plot(window_name) | None | Remove a registered plot function. |
analyze_fn returns one of:
{"x": [...], "y": [...]}— a single series{"y": [...]}— Y only (X becomes the index){"series1": [...], "series2": [...]}— multiple series
import jungular
def height_profile(positions, frame_index):
# Plot the z-coordinate of every atom for the current frame
return {"y": positions[:, 2].tolist()}
jungular.register_plot("Plot 1", height_profile)
Submitting a job
Hand off to the cloud. submit_job opens the Submit Job modal pre-filled with a multi-phase protocol — you still confirm and launch from the UI.
jungular.submit_job(name, phases, timestep=1.0, minimize_first=True)
Each entry in phases is a dict:
| Key | Type | Description |
|---|---|---|
name | str | Phase name. |
ensemble | str | 'NVE', 'NVT', or 'NPT'. |
time_ps | float | Duration in picoseconds. |
temperature | float (opt) | Target temperature (K). |
pressure | float (opt) | Target pressure (bar). |
output_frequency_ps | float (opt) | Time between dumps (ps, default 1.0). |
thermostat_tau | float (opt) | Thermostat coupling (fs, default 100). |
barostat_tau | float (opt) | Barostat coupling (fs, default 1000). |
import jungular
jungular.submit_job(
name="equilibration",
phases=[
{"name": "NPT equilibrate", "ensemble": "NPT", "time_ps": 100,
"temperature": 300, "pressure": 1.0},
{"name": "NVT production", "ensemble": "NVT", "time_ps": 500,
"temperature": 300},
],
timestep=1.0,
minimize_first=True,
)
Convenience helpers
| Name | Description |
|---|---|
info() | Summary dict: atom_count, box_size, atom_types, molecule_types, labels. |
describe() | Human-readable summary str of the current system. |
atoms | Alias of get_atom_count. |
positions | Alias of get_positions. |
np | The numpy module. |
_bridge | The underlying JS bridge object (advanced/internal use). |
import jungular
print(jungular.describe())
summary = jungular.info()
print(summary["atom_count"], "atoms,", len(summary["labels"]), "labels")