Back to docs Reference

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, not jungler. All names below are accessed as jungular.<name>().


Querying the system

Read-only inspection of the atoms, bonds, box, and types in the active window.

FunctionReturnsDescription
get_current_window()dictActive window info: id, name, type, atom_count, bond_count.
get_atom_count()intTotal number of atoms. Also available as jungular.atoms.
get_box_size()floatCubic simulation box edge length (Å).
get_position(index)dictSingle atom position {x, y, z}.
get_positions()np.ndarrayAll positions as an (N, 3) array. Also jungular.positions.
get_atom_types()np.ndarrayPer-atom type IDs, uint8, shape (N,).
get_bonds()np.ndarrayBond pairs as an (M, 2) array of atom indices.
get_type_name(type_id)str | NoneName for an atom type ID.
get_atom_type_info(type_id)dict | Noneid, 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.

FunctionReturnsDescription
set_box_size(size)NoneSet the cubic box edge length.
set_positions(positions)NoneReplace all positions from an (N, 3) array (shape is validated).
move_atoms(indices, dx=0, dy=0, dz=0)NoneTranslate the given atoms by a displacement.
delete_atoms(indices)intDelete 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

FunctionReturnsDescription
add_atom(x, y, z, type_id)intAdd one atom; returns its new index.
add_atoms_batch(positions, type_ids)intBulk-add atoms; returns the count added.
register_atom_type(name, mass, epsilon, sigma, charge=0.0)intRegister 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.

FunctionReturnsDescription
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 | NoneA single molecule by index.
get_all_molecules_of_type(type_id)list[dict]All molecules of a given type.
clear_molecule_tracker()NoneReset 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.

FunctionReturnsDescription
step()NoneRun a single MD step.
run_steps(n)NoneRun n MD steps.
get_temperature()floatCurrent temperature (K).
get_potential_energy()floatPotential energy (kcal/mol).
get_kinetic_energy()floatKinetic energy (kcal/mol).
get_total_energy()floatTotal 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.

FunctionReturnsDescription
refresh()NoneForce a re-render.
highlight(indices, color='#00ffff')NoneHighlight atoms (highlights accumulate).
clear_highlight()NoneClear notebook highlights.
select(indices)NoneSelect atoms (drives the Effect menu).
clear_selection()NoneClear the current selection.
expose(indices=None, name=None, depth_margin=2.0)dictCreate 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)dictBuild 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.

FunctionReturnsDescription
get_atoms_with_label(label)list[int]Atom indices belonging to a label.
create_label(atoms, name=None)dictCreate 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.

FunctionReturnsDescription
sleep(ms=0) (async)Noneawait to yield to the UI. Raises StopExecution if stopped.
check_stop()NoneRaise StopExecution if a stop was requested.
is_stop_requested()boolNon-raising stop check.
StopExecutionexceptionRaised 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.

FunctionReturnsDescription
write_trajectory(positions, step_number=None, box_size=None)dictAppend a frame; returns {success, frame_index, error}.
get_trajectory_frame_count()intNumber of stored frames.
get_trajectory_frame(index)dict | NoneFrame data: positions, step_number, box_size.
clear_trajectory()NoneDelete all recorded frames.
get_current_trajectory_frame()dictData 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.

FunctionReturnsDescription
register_plot(window_name, analyze_fn)NoneRegister fn(positions, frame_index) -> dict for a plot window.
unregister_plot(window_name)NoneRemove 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:

KeyTypeDescription
namestrPhase name.
ensemblestr'NVE', 'NVT', or 'NPT'.
time_psfloatDuration in picoseconds.
temperaturefloat (opt)Target temperature (K).
pressurefloat (opt)Target pressure (bar).
output_frequency_psfloat (opt)Time between dumps (ps, default 1.0).
thermostat_taufloat (opt)Thermostat coupling (fs, default 100).
barostat_taufloat (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

NameDescription
info()Summary dict: atom_count, box_size, atom_types, molecule_types, labels.
describe()Human-readable summary str of the current system.
atomsAlias of get_atom_count.
positionsAlias of get_positions.
npThe numpy module.
_bridgeThe underlying JS bridge object (advanced/internal use).
import jungular

print(jungular.describe())
summary = jungular.info()
print(summary["atom_count"], "atoms,", len(summary["labels"]), "labels")