simudo.gui package

Subpackages

Submodules

simudo.gui.app module

Simudo GUI — main entry point.

Run with:

panel serve app.py –show –port 5006

from this directory (containing app.py), in an environment where panel is installed.

The working directory matters: the panels below are imported by bare name (from model import ...), so serving app.py from anywhere else fails on import.

class simudo.gui.app.SimudoApp[source]

Bases: object

autosave()[source]

Write to disk silently if a filepath is already set.

property bundled_lib_dir: str
get_effective_execution_profile() ExecutionProfile[source]

Return the active execution profile: per-project override if set, else user default.

get_execution_profile_for_type(type_str: str)[source]

Return the last-used ExecutionProfile for this type, or None.

get_library_dirs() list[str][source]

Return valid library directories: user dir first (higher precedence).

load_from_checkpoint(project_path: str, checkpoint_relpath: str)[source]

Start a new (already-open, already-saved) project resuming from checkpoint_relpath (e.g. “checkpoints/sim_V=0.4.yaml”) found under project_path’s configured output folder.

Loads the frozen device-definition YAML that the runner copies into the output folder for provenance – not project_path itself, which may have kept changing since that run – so the resumed project’s layers/materials/bands/etc. are guaranteed to match what actually produced the checkpoint. Saves in place inside that run folder (see gui/TODO.md “Checkpoint / Resume UX” for why).

load_yaml(path: str, add_to_recents: bool = True)[source]
new_project()[source]

Reset to a blank project (default bands, no layers, no filepath).

notify_run_output_dir(host_path: str)[source]

Store the output dir and update both panels that display it.

refresh_layers_missing_params()[source]

Recompute and redisplay the missing-params section and badge.

refresh_layers_panel()[source]

Full rebuild of the Layers panel (table, schematic, overlays, missing params).

Call this after any change that affects what layers can reference — e.g. when project materials are added or removed.

refresh_output_panel()[source]

Refresh the Output panel plot (called after a run completes).

save_execution_profile_for_type(type_str: str, profile: ExecutionProfile)[source]

Remember the profile for this type in memory and persist to config.

save_yaml(path: str = None)[source]

Save project. If no path given and no filepath set, prompt with dialog.

set_default_execution_profile(profile: ExecutionProfile)[source]

Persist profile as the user-level default in ~/.simudo_gui.yaml.

set_last_run_output_dir(host_path: str)[source]

Called by the simulation panel once the runner’s output directory is known.

set_user_lib_dir(path: str)[source]
show_error(message: str)[source]

Display a dismissable red banner below the header with message.

update_layers_badge()[source]

Update the yellow ● badge on the Layers nav button.

The badge is shown when any required parameter (from bands or EOPs) is unset in every region. Called after any property edit and on rebuild.

view() panel.Column[source]
simudo.gui.app.discover_material_classes(library_dirs: list) list[str][source]

AST-scan library dirs for top-level class names in .py files.

simudo.gui.batch module

Simudo GUI — batch parameter sweep.

Data model, sidecar I/O, sub-YAML generation, and parallel launcher for running a set of sub-simulations that vary one or more project parameters.

Sidecar file: <project_stem>.batch.yaml (next to the project YAML).

Sub-YAML layout:

<output_folder>/0/<project>.yaml   (output.folder = ".")
<output_folder>/1/<project>.yaml
...
class simudo.gui.batch.BatchLauncher[source]

Bases: object

Launches sub-runs in parallel, bounded by a semaphore.

launch_all(sub_yaml_paths: List[str], spec: BatchSpec, backend, runner_host: str, lib_dirs: List[str], log_cb: Callable[[str], None], status_cb: Callable[[int, str], None] | None = None) None[source]

Start a background supervisor thread that manages all sub-runs.

status_cb(idx, status) is called from background threads whenever a run changes state. status is one of: “queued”, “running”, “done”, “failed”, “skipped”.

stop() None[source]
class simudo.gui.batch.BatchSpec(mode: 'str' = 'zip', max_parallel: 'int' = 1, variations: 'List[BatchVariation]' = <factory>)[source]

Bases: object

classmethod from_dict(d: dict) BatchSpec[source]
max_parallel: int = 1
mode: str = 'zip'
run_count() int[source]

Number of sub-runs this spec will produce.

to_dict() dict[source]
variations: List[BatchVariation]
class simudo.gui.batch.BatchTarget(kind: str, container_name: str, key: str = '', unit: str = '')[source]

Bases: object

Identifies a single numeric parameter in the project YAML tree.

container_name: str
classmethod from_dict(d: dict) BatchTarget[source]
key: str = ''
kind: str
to_dict() dict[source]
unit: str = ''
class simudo.gui.batch.BatchVariation(label: str, target: BatchTarget, values: List[float])[source]

Bases: object

One dimension of the parameter sweep: which parameter and what values.

classmethod from_dict(d: dict) BatchVariation[source]
label: str
target: BatchTarget
to_dict() dict[source]
values: List[float]
simudo.gui.batch.build_combinations(spec: BatchSpec) List[List[Tuple[BatchTarget, float]]][source]

Return one list of (target, value) pairs per sub-run.

simudo.gui.batch.compact_param_label(target: BatchTarget, value: float) str[source]

Return a short ‘name=value’ string for a single (target, value) pair.

simudo.gui.batch.ensure_new_dir(cur_dir: str) str[source]

Return cur_dir (or an incremented variant) that does not yet exist.

simudo.gui.batch.enumerate_sweep_params(project) List[Tuple[str, BatchTarget]][source]

Return all numeric parameters in the project that can be swept.

Returns a list of (display_label, BatchTarget) pairs.

simudo.gui.batch.generate_sub_yamls(project_yaml_path: str, spec: BatchSpec) List[str][source]

Create numbered sub-directories and write a modified project YAML into each.

Sub-YAMLs use output.folder = “.” so runner output goes alongside them. Returns the list of absolute sub-YAML paths.

simudo.gui.batch.get_current_param_value(project, target: BatchTarget) str[source]

Return a human-readable ‘current value + unit’ string for a BatchTarget.

Returns an empty string if the parameter has no value set.

simudo.gui.batch.load_batch_spec(project_yaml_path: str) BatchSpec | None[source]
simudo.gui.batch.save_batch_spec(project_yaml_path: str, spec: BatchSpec) None[source]
simudo.gui.batch.sidecar_path(project_yaml_path: str) str[source]

simudo.gui.fourlayer_reference module

Reference run of the fourlayer IB solar cell using the original fourlayer.py driver. Output goes to gui/out/reference/ (relative to the working directory, which in Docker is /home/user/simudo/code).

Run from the Docker container:

python3 gui/fourlayer_reference.py

The output is used by gui/compare_jv.py to validate simudo_1d_runner.py against the original driver.

simudo.gui.fourlayer_reference.run_reference()[source]

simudo.gui.model module

Simudo GUI — project data model.

Plain Python dataclasses that map 1-to-1 with the project YAML schema. No Panel / param dependencies here; this module must be importable without any GUI framework installed.

class simudo.gui.model.Band(name: 'str' = 'CB', type: 'str' = 'nondegenerate', sign: 'int' = -1, extent: 'Any' = 'all', extent_boundary_bc_left: 'str' = 'zero_current', extent_boundary_bc_right: 'str' = 'zero_current')[source]

Bases: object

extent: Any = 'all'
extent_boundary_bc_left: str = 'zero_current'
extent_boundary_bc_right: str = 'zero_current'
name: str = 'CB'
sign: int = -1
type: str = 'nondegenerate'
class simudo.gui.model.BlackbodyIntensity(T_source: 'float' = 6000.0, T_source_unit: 'str' = 'K', concentration: 'float' = 1.0, E_min: 'float' = 1.42, E_min_unit: 'str' = 'eV', E_max: 'Optional[float]' = None, E_max_unit: 'str' = 'eV')[source]

Bases: object

E_max: float | None = None
E_max_unit: str = 'eV'
E_min: float = 1.42
E_min_unit: str = 'eV'
T_source: float = 6000.0
T_source_unit: str = 'K'
concentration: float = 1.0
class simudo.gui.model.CallableTopHat(region: str = 'domain', E_low: float = 0.0, E_low_unit: str = 'eV', E_high: float | None = None, E_high_unit: str = 'eV', value: float = 0.0, unit: str = '1/cm')[source]

Bases: object

One top-hat callable spec attached to a Process for a given region.

region is either 'domain' or a layer name. E_low/E_high are stored as floats with explicit units; E_high of None means +infinity. Multiple CallableTopHat rows for the same callable shorthand are allowed (one per region scope).

E_high: float | None = None
E_high_unit: str = 'eV'
E_low: float = 0.0
E_low_unit: str = 'eV'
region: str = 'domain'
unit: str = '1/cm'
value: float = 0.0
class simudo.gui.model.CheckpointStageSettings(checkpoint_at_end: 'bool' = True, at_values: 'List[float]' = <factory>)[source]

Bases: object

at_values: List[float]
checkpoint_at_end: bool = True
class simudo.gui.model.CheckpointsSettings(intensity_ramp: 'CheckpointStageSettings' = <factory>, voltage_sweep: 'CheckpointStageSettings' = <factory>, directory: 'str' = 'checkpoints')[source]

Bases: object

directory: str = 'checkpoints'
intensity_ramp: CheckpointStageSettings
voltage_sweep: CheckpointStageSettings
class simudo.gui.model.Contact(role: 'str' = 'reference', band_bcs: 'Dict[str, str]' = <factory>)[source]

Bases: object

band_bcs: Dict[str, str]
role: str = 'reference'
class simudo.gui.model.ExecutionProfile(type: str = 'docker', docker_container: str = '', docker_host_root: str = '', docker_container_root: str = '', docker_simudo_path: str = '', ssh_host: str = '', ssh_user: str = '', ssh_identity_file: str = '', ssh_remote_work_dir: str = '~/simudo_runs', ssh_simudo_path: str = '', ssh_delete_remote_after_sync: bool = False, python_cmd: str = 'python3')[source]

Bases: object

Describes how to launch simudo_1d_runner.py.

type: “local” | “docker” | “ssh” | “ssh+docker”

The user-level default lives in ~/.simudo_gui.yaml under the key “execution”. A per-project override can be stored in the project YAML under the same key; it is merged on top of the default at runtime.

docker_container: str = ''
docker_container_root: str = ''
docker_host_root: str = ''
docker_simudo_path: str = ''
python_cmd: str = 'python3'
ssh_delete_remote_after_sync: bool = False
ssh_host: str = ''
ssh_identity_file: str = ''
ssh_remote_work_dir: str = '~/simudo_runs'
ssh_simudo_path: str = ''
ssh_user: str = ''
type: str = 'docker'
class simudo.gui.model.ExplicitIntensity(value: 'float' = 100.0, unit: 'str' = 'mW/cm^2', input_type: 'str' = 'power_density')[source]

Bases: object

input_type: str = 'power_density'
unit: str = 'mW/cm^2'
value: float = 100.0
class simudo.gui.model.IntensityRampSettings(enabled: 'bool' = True, selfconsistent_optics: 'bool' = True, step_size: 'Optional[float]' = None)[source]

Bases: object

enabled: bool = True
selfconsistent_optics: bool = True
step_size: float | None = None
class simudo.gui.model.Interface(left: str = '', right: str = '', bcs: ~typing.List[~simudo.gui.model.InterfaceBCSpec] = <factory>)[source]

Bases: object

Internal interface between two adjacent layers.

left and right are layer names (Python identifiers) matching the Simudo region attribute names: facet = R.<left>.boundary(R.<right>).

bcs: List[InterfaceBCSpec]
left: str = ''
right: str = ''
class simudo.gui.model.InterfaceBCSpec(type: str = 'ThermionicHeterojunction', bands: ~typing.List[str] = <factory>, HJBC_enhancement: ~typing.Dict[str, float] = <factory>)[source]

Bases: object

One BC type applied at an internal interface.

HJBC_enhancement: Dict[str, float]
bands: List[str]
type: str = 'ThermionicHeterojunction'
class simudo.gui.model.Layer(name: 'str' = 'layer', thickness: 'float' = 1.0, thickness_unit: 'str' = 'um', material: 'str' = '', properties: 'Dict[str, Any]' = <factory>, mesh: 'MeshParams' = <factory>, color: 'str' = '#4a9eff')[source]

Bases: object

color: str = '#4a9eff'
material: str = ''
mesh: MeshParams
name: str = 'layer'
properties: Dict[str, Any]
thickness: float = 1.0
thickness_unit: str = 'um'
class simudo.gui.model.Material(name: 'str' = 'material', source: 'Optional[str]' = None, properties: 'Dict[str, Any]' = <factory>)[source]

Bases: object

name: str = 'material'
properties: Dict[str, Any]
source: str | None = None
class simudo.gui.model.MeshParams(start: 'Optional[float]' = None, factor: 'Optional[float]' = None)[source]

Bases: object

factor: float | None = None
start: float | None = None
class simudo.gui.model.OpticalField(name: 'str' = 'field', direction: 'str' = '+x', photon_energy: 'float' = 1.42, photon_energy_unit: 'str' = 'eV', intensity: 'Any' = <factory>)[source]

Bases: object

direction: str = '+x'
intensity: Any
name: str = 'field'
photon_energy: float = 1.42
photon_energy_unit: str = 'eV'
class simudo.gui.model.OpticalFieldSet(name_prefix: str = 'sun', direction: str = '+x', spectrum: str = 'am15g', N_bins: int = 5, E_min: float = 0.8, E_min_unit: str = 'eV', E_max: float = 4.0, E_max_unit: str = 'eV', concentration: float = 1.0, T_source: float = 6000.0, T_source_unit: str = 'K', bin_edges: str = 'equal_flux')[source]

Bases: object

A set of N optical fields generated from a spectrum.

Persisted to / from the YAML optical_field_sets: block; the runner expands it into N monochromatic fields at simulation time (item 7).

E_max: float = 4.0
E_max_unit: str = 'eV'
E_min: float = 0.8
E_min_unit: str = 'eV'
N_bins: int = 5
T_source: float = 6000.0
T_source_unit: str = 'K'
bin_edges: str = 'equal_flux'
concentration: float = 1.0
direction: str = '+x'
name_prefix: str = 'sun'
spectrum: str = 'am15g'
class simudo.gui.model.OutputSettings(xdmf_mesh: 'bool' = True, spatial_profiles: 'bool' = False)[source]

Bases: object

spatial_profiles: bool = False
xdmf_mesh: bool = True
class simudo.gui.model.OverlayRegion(name: 'str' = 'overlay', start: 'Optional[float]' = None, end: 'Optional[float]' = None, extent_unit: 'str' = 'um', layer_names: 'List[str]' = <factory>, properties: 'Dict[str, Any]' = <factory>, is_domain: 'bool' = False)[source]

Bases: object

end: float | None = None
extent_unit: str = 'um'
is_domain: bool = False
layer_names: List[str]
name: str = 'overlay'
properties: Dict[str, Any]
start: float | None = None
class simudo.gui.model.PhysicsSettings(temperature: 'float' = 300.0, temperature_unit: 'str' = 'K')[source]

Bases: object

temperature: float = 300.0
temperature_unit: str = 'K'
class simudo.gui.model.Process(name: 'str' = '', cls: 'str' = 'SRHRecombination', src_band: 'str' = 'VB', dst_band: 'str' = 'CB', trap_band: 'Optional[str]' = None, radiative_recombination: 'Optional[bool]' = None, svr_E_min: 'Optional[float]' = None, svr_E_min_unit: 'str' = 'eV', svr_E_max: 'Optional[float]' = None, svr_E_max_unit: 'str' = 'eV', callable_top_hats: 'Dict[str, List[CallableTopHat]]' = <factory>)[source]

Bases: object

callable_top_hats: Dict[str, List[CallableTopHat]]
cls: str = 'SRHRecombination'
dst_band: str = 'CB'
name: str = ''
radiative_recombination: bool | None = None
src_band: str = 'VB'
svr_E_max: float | None = None
svr_E_max_unit: str = 'eV'
svr_E_min: float | None = None
svr_E_min_unit: str = 'eV'
trap_band: str | None = None
class simudo.gui.model.Project(filepath: 'Optional[str]' = None, description: 'str' = '', layers: 'List[Layer]' = <factory>, overlay_regions: 'List[OverlayRegion]' = <factory>, bands: 'List[Band]' = <factory>, processes: 'List[Process]' = <factory>, optical_fields: 'List[OpticalField]' = <factory>, optical_field_sets: 'List[OpticalFieldSet]' = <factory>, contacts: 'Dict[str, Contact]' = <factory>, physics: 'PhysicsSettings' = <factory>, simulation: 'SimulationSettings' = <factory>, custom_eop_files: 'List[str]' = <factory>, output_folder: 'str' = <factory>, mesh_max_edge_length: 'float' = 0.02, mesh_dimension: 'int' = 1, materials: 'List[Material]' = <factory>, interfaces: 'List[Interface]' = <factory>, execution: 'Optional[ExecutionProfile]' = None)[source]

Bases: object

property band_names: List[str]
bands: List[Band]
contacts: Dict[str, Contact]
custom_eop_files: List[str]
description: str = ''
execution: ExecutionProfile | None = None
filepath: str | None = None
interfaces: List[Interface]
property layer_names: List[str]
layers: List[Layer]
materials: List[Material]
mesh_dimension: int = 1
mesh_max_edge_length: float = 0.02
optical_field_sets: List[OpticalFieldSet]
optical_fields: List[OpticalField]
output_folder: str
overlay_regions: List[OverlayRegion]
physics: PhysicsSettings
processes: List[Process]
simulation: SimulationSettings
class simudo.gui.model.SimulationSettings(intensity_ramp: 'IntensityRampSettings' = <factory>, voltage_sweep: 'VoltageSweepSettings' = <factory>, output: 'OutputSettings' = <factory>, checkpoints: 'CheckpointsSettings' = <factory>, resume_from: 'Optional[str]' = None)[source]

Bases: object

checkpoints: CheckpointsSettings
intensity_ramp: IntensityRampSettings
output: OutputSettings
resume_from: str | None = None
voltage_sweep: VoltageSweepSettings
class simudo.gui.model.ValueUnit(value: 'float' = 0.0, unit: 'str' = '')[source]

Bases: object

classmethod from_dict(d) ValueUnit[source]
to_dict() dict[source]
unit: str = ''
value: float = 0.0
class simudo.gui.model.VoltageSweepSettings(enabled: 'bool' = True, values: 'List[float]' = <factory>, selfconsistent_optics: 'bool' = True, step_size: 'Optional[float]' = None)[source]

Bases: object

enabled: bool = True
selfconsistent_optics: bool = True
step_size: float | None = None
values: List[float]
simudo.gui.model.default_bands() List[Band][source]

Standard two-band semiconductor starting point.

simudo.gui.model.default_contacts() Dict[str, Contact][source]
simudo.gui.model.default_layer_color(index: int) str[source]
simudo.gui.model.make_domain_overlay() OverlayRegion[source]

simudo.gui.runner_backends module

Runner backend abstraction.

Each backend knows how to launch simudo_1d_runner.py in a particular execution environment (local subprocess, Docker, SSH, SSH+Docker) and stream its stdout back as a line iterator.

Adding a new backend only requires subclassing RunnerBackend and implementing launch(). The Simulation panel is backend-agnostic.

class simudo.gui.runner_backends.DockerBackend(container: str, host_root: str, container_root: str, python_cmd: str = 'python3', container_simudo_path: str = '')[source]

Bases: RunnerBackend

Run the runner inside a local Docker container via docker exec.

A bind-mount must exist mapping host_root on the host to container_root inside the container. All host paths under host_root are translated to the equivalent container path automatically.

Works on macOS, Linux, and Windows (with Docker Desktop). On Windows the host_root may be given as a Windows path (C:\Users\...) or with forward slashes (C:/Users/...); both are normalised correctly. On Cygwin, use the native Cygwin path format (/cygdrive/c/...).

Example

host_root = “/Users/you/Simudo” # Mac/Linux host_root = “C:/Users/you/Simudo” # Windows / Cygwin container_root = “/home/user/simudo” container = “container_name” python_cmd = “python3”

get_core_count() int[source]

Return the number of available CPU cores in the execution environment.

launch(runner_host_path, project_yaml_host_path, lib_dirs_host, extra_args) DockerRunHandle[source]
launch_extractor(extractor_host_path, output_dir_host_path, extra_args=(), local_proj_dir=None) ExtractorHandle[source]
class simudo.gui.runner_backends.DockerRunHandle(proc, cmd_str, host_root: str, container_root: str)[source]

Bases: RunHandle

RunHandle that translates Docker container paths back to host paths.

translate_output_path(path: str) str[source]

Translate an execution-environment path to a host-local path. Default: identity (local backend needs no translation).

class simudo.gui.runner_backends.ExtractorHandle(proc: Popen, cmd_str: str)[source]

Bases: object

RunHandle variant for spatial_extractor: separate stdout and stderr.

property returncode: int | None
stderr_text() str[source]
stdout_lines() Iterator[str][source]
stop()[source]
class simudo.gui.runner_backends.LocalBackend[source]

Bases: RunnerBackend

Run the runner directly as a subprocess using the current Python interpreter.

get_core_count() int[source]

Return the number of available CPU cores in the execution environment.

launch(runner_host_path, project_yaml_host_path, lib_dirs_host, extra_args) RunHandle[source]
launch_extractor(extractor_host_path, output_dir_host_path, extra_args=(), local_proj_dir=None) ExtractorHandle[source]
class simudo.gui.runner_backends.RunHandle(proc: Popen, cmd_str: str)[source]

Bases: object

Wraps an active subprocess. Subclasses override path-translation and post-run hooks for environment-specific behaviour (Docker, SSH, etc.).

do_intermediate_sync() Iterator[str][source]

Perform one intermediate sync and yield log-line strings. Default: no-op (local and Docker runs need no file transfer).

finalize_msgs() Iterator[str][source]

Post-run actions. Yields log-lines while executing (e.g. rsync). Default: nothing to do.

initial_msgs() Iterator[str][source]

Messages to emit before stdout streaming starts (e.g. announce paths). Default: nothing.

periodic_sync_interval: int = 0

Seconds between automatic intermediate syncs while the run is active. 0 means no periodic sync (default for local and Docker runs).

property returncode: int | None
stdout_lines() Iterator[str][source]

Yield stdout+stderr lines until the process exits.

stop()[source]
translate_output_path(path: str) str[source]

Translate an execution-environment path to a host-local path. Default: identity (local backend needs no translation).

class simudo.gui.runner_backends.RunnerBackend[source]

Bases: object

Abstract base — subclasses implement launch().

get_core_count() int[source]

Return the number of available CPU cores in the execution environment.

launch(runner_host_path: str, project_yaml_host_path: str, lib_dirs_host: List[str], extra_args: List[str]) RunHandle[source]
launch_extractor(extractor_host_path: str, output_dir_host_path: str, extra_args: List[str] = (), local_proj_dir: str | None = None) ExtractorHandle[source]
class simudo.gui.runner_backends.SSHBackend(host: str, user: str = '', identity_file: str | None = None, remote_work_dir: str = '~/simudo_runs', python_cmd: str = 'python3', simudo_path: str = '', delete_remote: bool = False)[source]

Bases: RunnerBackend

Run the runner on a remote Linux machine via SSH.

Launch workflow:
  1. Create a unique run directory on the remote under remote_work_dir.

  2. rsync the project YAML to that directory.

  3. For each library dir: if it lives under the local simudo tree, translate to the corresponding remote path; otherwise rsync it to a .lib_cache sub-directory inside the run directory.

  4. Launch: ssh <host> <python_cmd> -u <runner_path> <remote_yaml> [flags]

  5. Stream stdout/stderr back in real time.

  6. After the process exits (finalize_msgs): rsync the whole remote run directory back to local_proj_dir so output files appear locally.

SSH authentication:
  • Preferred: configure the host in ~/.ssh/config (handles auth, port, etc.). Set ssh_host to the config alias; leave ssh_user and ssh_identity_file blank.

  • Alternative: set ssh_host to hostname/IP, ssh_user to your username, and optionally ssh_identity_file to a private key path.

  • Password authentication is not supported; set up key-based auth first.

Remote runner discovery:
Set ssh_simudo_path to the output of:

python3 -c “import simudo; print(simudo.__file__)”

run on the remote machine. The runner path is derived as:

<simudo_pkg_parent>/gui/simudo_1d_runner.py

get_core_count() int[source]

Return the number of available CPU cores in the execution environment.

launch(runner_host_path: str, project_yaml_host_path: str, lib_dirs_host: List[str], extra_args: List[str]) SSHRunHandle[source]
launch_extractor(extractor_host_path: str, output_dir_host_path: str, extra_args=(), local_proj_dir: str | None = None) SSHExtractorHandle[source]

Run the extractor on the remote host.

Always rsyncs the local output directory to a temporary directory on the remote before running the extractor, so the files are guaranteed to be present regardless of whether delete_remote was set or whether the run was done with a different backend. The temp directory is removed automatically when stdout_lines() drains.

class simudo.gui.runner_backends.SSHDockerBackend[source]

Bases: RunnerBackend

SSH to a remote host, then docker exec inside a container there. Not yet implemented.

launch(runner_host_path, project_yaml_host_path, lib_dirs_host, extra_args) RunHandle[source]
launch_extractor(extractor_host_path, output_dir_host_path, extra_args=()) ExtractorHandle[source]
class simudo.gui.runner_backends.SSHExtractorHandle(proc, cmd_str, cleanup_ssh_cmd: List[str])[source]

Bases: ExtractorHandle

ExtractorHandle that removes the remote temp directory after extraction.

stdout_lines() Iterator[str][source]
class simudo.gui.runner_backends.SSHRunHandle(proc: Popen, cmd_str: str, host_spec: str, rsync_ssh_opts: List[str], remote_out_dir: str, local_out_dir: str, staging_dir: str, delete_remote: bool = False)[source]

Bases: RunHandle

RunHandle for SSH execution.

Tracks the negotiated remote and local output directories separately. Path translation maps remote_out_dir → local_out_dir. Post-run: rsyncs remote_out_dir back to local_out_dir, removes the hidden staging directory, and optionally deletes the remote output copy.

Periodic sync: do_intermediate_sync() rsyncs the remote output directory to the local one without any cleanup, so partially-written output files (sim_V.csv, info.log, …) appear locally while the run is still active. periodic_sync_interval is set to 120 s.

do_intermediate_sync() Iterator[str][source]

Rsync remote output dir → local output dir without any cleanup. Called periodically while the run is active.

finalize_msgs() Iterator[str][source]

Post-run actions. Yields log-lines while executing (e.g. rsync). Default: nothing to do.

initial_msgs() Iterator[str][source]

Messages to emit before stdout streaming starts (e.g. announce paths). Default: nothing.

periodic_sync_interval: int = 120

Seconds between automatic intermediate syncs while the run is active. 0 means no periodic sync (default for local and Docker runs).

translate_output_path(remote_path: str) str[source]

Translate an execution-environment path to a host-local path. Default: identity (local backend needs no translation).

simudo.gui.runner_backends.backend_from_profile(profile) RunnerBackend[source]

Build the appropriate RunnerBackend from an ExecutionProfile dataclass.

simudo.gui.simudo_1d_runner module

simudo_1d_runner.py

Generic runner that reads a Simudo project YAML file and executes a 1D layered-structure Poisson / drift-diffusion simulation.

Usage (inside the Docker container or environment with Simudo installed):

cd ~/simudo/code
python gui/simudo_1d_runner.py gui/fourlayer_example.yaml

The project YAML format is documented and exemplified in gui/fourlayer_example.yaml.

The execution: section of the project YAML is consumed by the GUI launcher layer (which decides whether to run locally, via docker exec, or over SSH). This script always runs locally inside the target environment and never reads the execution: key.

Design notes

Priority levels for spatial rules (lower number = higher priority; wins via setdefault in get()):

PRIORITY_OVERLAY  = 0   named overlay-region properties — highest priority
PRIORITY_LAYER    = 5   layer-specific properties
PRIORITY_DOMAIN   = 7   the `domain` overlay — a device-wide default
PRIORITY_MATERIAL = 10  material default properties — lowest priority

domain is deliberately below layers. It is how a user says “unless I say otherwise, this holds everywhere”, so a value set on a single layer has to be able to beat it; before this it shared PRIORITY_OVERLAY with named overlays and was registered first, which made every layer-level override silently inert.

Material properties are registered by calling spatial.add_material_data directly with priority=PRIORITY_MATERIAL, so that layer / overlay rules always beat material defaults regardless of registration order.

Bidirectional voltage sweeps

Simudo’s Newton solver ramps the parameter (I or V) slowly from a known solution. Thermal equilibrium gives the solution at I = V = 0. A unidirectional sweep from 0 → +Voc is straightforward. voltage_sweep.values is split around an initial_v – 0 for a fresh run, or the resumed checkpoint’s own value (see below) – into an “up” group (values above initial_v) and a “down” group (values below initial_v), each run against its own problem instance sharing one seed state, so the (possibly expensive) state that produced that initial_v point is only computed once.

Checkpoint resumption

simulation.resume_from (a checkpoint .yaml path relative to the output folder) resumes a previous run instead of starting fresh. The stage the checkpoint belongs to is inferred from its filename (..._V=<v>.yaml or ..._I=<i>.yaml, written by write_xdmf_checkpoint()):

  • An optical-ramp (I) checkpoint continues the intensity ramp from that intensity (or, if it is already at I=1, is used directly) and then runs the voltage sweep(s) normally (initial_v = 0) from the resulting V=0 state — this is how a run can branch into a fresh voltage sweep without recomputing the optical ramp.

  • A voltage checkpoint (V of either sign) resumes with initial_v = that checkpoint’s own value, and sweeps both directions from it as needed – e.g. resuming from V=0.4 with targets [0.3, 0.4, 0.5] computes both 0.3 and 0.5, each from its own copy of the loaded V=0.4 state. The optical ramp is skipped either way (the checkpoint already reflects its outcome).

Either way, any requested value already present in this output folder’s sim_V.csv / sim_I.csv (from the run that produced the checkpoint) is dropped rather than recomputed – see _already_computed_values().

See _parse_checkpoint_stage() and the “Stage 1/2/3” comments in run() for the exact logic. Resuming still requires the project YAML to describe an identically-constructed problem (same layers, bands, processes, mesh) — the checkpoint only restores solution values, not the problem structure.

simudo.gui.simudo_1d_runner.DEFAULT_MESH_DIMENSION = 1

Default mesh dimension for the runner. 1 = true-1D interval mesh (new route, about half the degrees of freedom); 2 = the historical 2D strip (one cell layer of triangles between y=0 and y=1). Override with mesh: {dimension: 2} in the project YAML, e.g. to compare the two.

simudo.gui.simudo_1d_runner.DEFAULT_TEMPERATURE_K = 300.0

Temperature assumed when a project names none anywhere. Every material reads it during get_dict(), and an unset spatial key resolves to zero, so without a default a project that forgets it is solved at 0 K – which does not converge, and says nothing useful about why.

class simudo.gui.simudo_1d_runner.ProblemBuilder(config, library_dir=None, setup_dolfin=True)[source]

Bases: object

Everything a project config needs before solving: units, mesh, topology.

_create_problemdata takes (config, R, F, U, mesh_data, ..., dark_mode=...) on every call, and run() builds up to four problems from the same five objects. This class owns that bundle so it is constructed once, and so callers other than run() – notably the test suite – can build a problem without running a simulation.

Constructing a builder has no side effects outside dolfin’s global parameters: it creates no directories, writes no files, configures no logging, and solves nothing.

Parameters:
  • config (dict) – Parsed project YAML.

  • library_dir (str, Path, or list, optional) – Passed through to library:// material resolution.

  • setup_dolfin (bool, optional) – Call setup_dolfin_parameters(). On by default; pass False when the caller has already configured dolfin.

Examples

Check that a configuration assembles, without solving it:

builder = ProblemBuilder(config, library_dir=materials)
problem = builder.problem('full')
problem(goal, **kwargs)[source]

Build the ProblemData for goal. Does not solve.

kwargs are forwarded to _create_problemdataV_ext for the 'full' goal, phi_cn for 'thermal equilibrium'.

simudo.gui.simudo_1d_runner.build_problems(config, library_dir=None, goals=('full',), V_ext=None)[source]

Build the problems named in goals without solving any of them.

Returns (builder, {goal: problem}).

This is the entry point for tests that need to know whether a configuration assembles – which is where a large class of runner bugs lives. ThermionicHeterojunction was registered on every goal for months, and raised AttributeError on the 'local charge neutrality' problem long before any solve; a construction-only check would have caught it in about a second.

'thermal equilibrium' needs a phi_cn from the local-charge-neutrality problem. That is a dolfin Function which exists whether or not it has been solved for, so the dependency is satisfied here by building the LCN problem and handing over its (unsolved) phi. The resulting equilibrium problem is therefore well-formed but not physically meaningful – which is all a construction test needs.

simudo.gui.simudo_1d_runner.ensure_new_dir(cur_dir)[source]

Return cur_dir (or an incremented variant) that does not yet exist.

simudo.gui.simudo_1d_runner.parse_quantity(entry, U)[source]

Parse a YAML property value into a pint Quantity.

Accepted forms:

  • {value: X, unit: "cm^2"} — returns X * U(unit)

  • a bare scalar (int or float) — returned as-is (dimensionless)

  • True / False — returned as-is (Python bool)

Extra keys (such as mode) are silently ignored, so a full intensity dict can be passed directly.

simudo.gui.simudo_1d_runner.parse_spatial_quantity(entry, U)[source]

Like parse_quantity but wraps numeric magnitudes in dolfin.Constant.

Use for quantities going into spatial.add_rule / spatial.add_material_data. dolfin.Constant avoids FEniCS form recompilation when the value changes between runs.

simudo.gui.simudo_1d_runner.run(yaml_path, library_dir=None, new_experiment=True)[source]

Read the project YAML at yaml_path and execute the simulation.

Parameters:
  • yaml_path (str or Path) – Path to the project YAML file.

  • library_dir (str, Path, or list, optional) – Directory (or list of directories) containing Python Material subclasses for library:// material sources.

  • new_experiment (bool, optional) – When True (default), the output directory is checked for existence and incremented if necessary (ensure_new_dir), so that each new run gets its own folder. Set to False when continuing from a checkpoint so that output lands back in the same folder as the original run.

simudo.gui.simudo_1d_runner.standard_outdir(base_dir)[source]

Return <base_dir>/out/YYYY[Mon]DD/a with auto-increment if the dir exists.

simudo.gui.spatial_extractor module

Spatial data extractor for Simudo output.

Runs inside the execution environment (Docker container or local) where simudo and dolfin are installed. Delegates all XDMF reading and function evaluation to simudo.example.fourlayer.sweep_extraction.SpatialXdmf.

Usage:

python3 spatial_extractor.py <output_dir> [–n-points N]

stdout protocol (one block per sweep point):

# BEGIN_POINT V=<v> I=<i> coord_x,field1,field2,… val,val,val,… … # END_POINT V=<v> I=<i>

V= files use I=0; I= files use V=0. Dolfin log messages and warnings go to stderr only.

simudo.gui.spatial_extractor.main()[source]

simudo.gui.yaml_io module

Simudo GUI — YAML serialization / deserialization.

Converts between the Project data model and the YAML format consumed by simudo_1d_runner.py.

simudo.gui.yaml_io.load_project(path: str) Project[source]
simudo.gui.yaml_io.project_to_dict(proj: Project) dict[source]
simudo.gui.yaml_io.save_project(proj: Project, path: str | None = None) None[source]

Module contents

Panel-based graphical interface for setting up and running Simudo problems.

Intentionally empty of imports: the GUI modules depend on panel/bokeh, which are not required to use the rest of Simudo. Import the submodules directly (e.g. simudo.gui.app) if you need them.