# Run isolated Linux sweep workers with explicit memory limits

Compare a complete six-case serial and parallel study, inspect worker admission and recover from rejected or interrupted runs.

## Compatibility

2.9.0 release target; maintained Linux CLI and Windows native settings candidate checks

Published guidance with release-applicability limits. Confirm package applicability before following this guide.

**KB-SETTINGS-001 · verified workaround**: Create only the missing .josim-pro directory under the dialog's resolved project root, then reopen the dialog and retry Save. Do not assume the working directory is the project root.

[Download Complete Linux serial and isolated-parallel six-case comparison (ZIP)](/_functions/kbExample?name=josim-parallel.zip)

Includes: sweep.cir, run_sweep.py, README.txt.

## Check platform and analysis eligibility

JoSIM 2.9 isolated parallel analysis jobs are available on Linux for buffered, fixed-step transient .STEP plans. Select 1–4 workers. Serial remains the default and executes cases with fresh state in source order. Adaptive stepping, AC/OP/DC/IV plans and streamed output are outside this parallel route.

Windows offers Serial only in the actual Run settings dialog. Its disabled worker/budget fields do not indicate a missing licence. The image below documents that platform restriction; the complete parallel execution checks in this guide use the maintained Linux CLI candidate.

![Actual Windows Run settings offers Serial execution; plan and worker memory controls are disabled. This is not a Linux parallel-run screenshot.](https://static.wixstatic.com/media/31de30_2bb51762aeb847438c64096f5db179bc~mv2.png)

Windows Run settings: Serial is the available execution policy. Linux worker execution was checked separately through the CLI.

## Set two explicit memory controls

Set both the total admission budget and per-worker address-space limit in MiB, where 1 MiB = 1,048,576 bytes. The worker limit must be at least 64 MiB. A selected worker count is a maximum; plan/case reservations can admit fewer workers. These controls do not cap the whole GUI’s resident memory.

For this complete sweep, two requested workers with a 512 MiB budget and 128 MiB worker limit admit two actual numerical workers. Reducing the budget to 256 MiB admits one. A 128 MiB budget is insufficient for even the first reservation despite matching the 128 MiB worker limit. A budget greater than a worker limit is not, by itself, proof of admission.

Keep interactive plots retains arrays. Files only—buffered omits retained arrays while preserving case records and scalars; solver state and buffered output still consume memory. Streamed text is a separate serial route with explicit CSV/DAT, optional gzip and no transient measurements. Full writer queues wait rather than dropping frames.

## Compare serial and parallel cases on Linux

Extract both files into a new writable folder on Linux. Select the actual compatible JoSIM 2.9 CLI and your existing issued JoSIM licence path in the command below. The illustrated licence path is a selection placeholder. Use a new result directory every time; run the command from the extracted folder.

The complete standard-library Python client runs a serial reference and then isolated parallel cases in separate fresh subdirectories. It references the selected licence in an isolated child configuration and preserves normal user settings. It validates the current files and writes a receipt; it does not modify licence contents or activate a new grant.

```text
set -eu
python=python3
josim=/usr/local/bin/josim-pro-cli
licence=/path/to/your/issued/sm_license_jpro.txt
"$python" -B ./run_sweep.py --josim "$josim" --licence "$licence" \
    --results ./sweep-results --workers 2 --budget-mib 512 --worker-mib 128
```

## Read the six current case results

The first .STEP dimension selects bias 1 mA then 2 mA; the last dimension selects resistance 3, 2 then 1 ohm and advances fastest. Each case has fresh state. Expected steady resistor voltages are 3, 2, 1, 6, 4 and 2 mV. I(R1) is 1 mA for the first three and 2 mA for the last three.

Each mode must publish exactly result.tran-001.case-1.csv through result.tran-006.case-6.csv. The headers are time, V(N), I(R1), with seconds, volts and amperes. Each file has 1,001 finite samples from 0 to 100 ns at 100 ps output cadence. The client’s Ohm-law check applies after the initial sample. In the independently inspected files, the initial row already carries the DC resistor values; inspect stored initial samples rather than assuming they are zero.

The checked serial and parallel CSV data agree exactly after parsing, at both admitted concurrency levels. All post-initial values agree with this ideal resistor reference within 1e-12 V/A. That threshold belongs to this simple example; it is not a general solver accuracy tolerance or physical-device qualification.

Use receipt.json status, current exit codes and fresh case files together. Exit 0 means this complete client comparison passed; exit 2 reports an input/execution/check failure. The client’s /proc observation counts only actual native analysis-worker commands, excluding licence helpers. A brief process can be missed by sampling, so observation is evidence for this run rather than a general scheduling guarantee.

## Reject invalid runs and recover without mixing generations

The checked core rejects an undersized worker limit, insufficient admission budget, a plan without .STEP and a streaming parallel request without publishing numerical CSV. An output path that aliases sweep.cir is also rejected and the source remains unchanged. Read the first current diagnostic; an old CSV in another folder is not evidence of recovery.

Correct the eligibility, limits or destination, then choose a new results directory. The client refuses an existing result directory and refuses non-Linux execution before starting a solve. It never overwrites a completed study generation.

Ctrl+C interrupts this Python client. It stops its own newly created CLI process group, waits for exit and records cancelled with exit 130. Completed case files can remain, but that receipt is not a completed serial/parallel comparison. The exercised interruption observed two workers, no active owned worker after exit and all six completed serial files retained.

That client process-group termination is distinct from the GUI’s cooperative Cancel contract. In the GUI, request Cancel and wait for the active worker to finish before starting another execution; inspect separate failed, cancelled and unstarted case records. The Linux GUI cancellation route is not qualified by this CLI interruption check.

## Configure the Linux GUI deliberately

These Linux GUI steps follow the maintained manual/source. The actual native checks here exercise Windows Serial controls, tab options and preview; Linux execution is CLI-only. Full Linux GUI/operator and final-package review remain open. No universal speed improvement is claimed from the small teaching circuit.

1. Open the saved .STEP deck in a compatible Linux 2.9 GUI and open Run > Run settings.... Choose isolated parallel execution, a maximum worker count, buffered result storage and both explicit MiB controls.
2. Set a native output destination and retain the intended trace selectors. Save source before Preview plan. Inspect eligibility, reservations, case destinations, resolved settings and seed. Preview neither simulates nor publishes files.
3. Run the saved deck and inspect each current case status, committed path and Run configuration snapshot. Open a committed file later if arrays were not retained. Change settings deliberately and rerun when previous results are marked stale.

## Save sweep.cir

```text
* Public six-case current/resistance sweep; every case has fresh state
.PARAM bias=1m
.PARAM resistance=1
I1 0 N DC bias
R1 N 0 resistance
.OPTION ANALYSIS=VOLTAGE INTEGRATION=BDF SEED=27
.STEP PARAM bias LIST 1m 2m
.STEP PARAM resistance LIST 3 2 1
.TRAN 1p 100n 0 100p
.PRINT V(N) I(R1)
.END
```

## Save run_sweep.py

This is the complete client, also included in the ZIP. Its private run logs and isolated configuration can contain local paths; review them before sharing. Each CLI phase has a 120-second client bound.

```text
"""Compare the supplied serial and isolated-parallel teaching sweep on Linux.

The worker budget is admission/accounting, not a whole-process RSS guarantee.
This client checks the complete supplied six-case output and an independent
resistor reference; it is not a general device-accuracy or speed benchmark.
"""
import argparse
import csv
import hashlib
import json
import math
import os
from pathlib import Path
import shutil
import signal
import subprocess
import sys
import time


def sha(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def table(path):
    with path.open(encoding='utf-8-sig',newline='') as stream:
        rows=list(csv.reader(stream))
    names=[n.strip('"') for n in rows[0]]
    values=[[float(v) for v in row] for row in rows[1:] if row]
    if len(names)!=3 or not values or any(len(row)!=3 or not all(math.isfinite(v) for v in row) for row in values):
        raise ValueError('Missing, malformed or nonfinite current output.')
    return names,values


def main():
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--josim',type=Path,required=True)
    parser.add_argument('--licence',type=Path,required=True)
    parser.add_argument('--results',type=Path,required=True)
    parser.add_argument('--workers',type=int,choices=range(1,5),default=2)
    parser.add_argument('--budget-mib',type=int,default=512)
    parser.add_argument('--worker-mib',type=int,default=128)
    args=parser.parse_args();source=Path(__file__).resolve().with_name('sweep.cir');work=args.results.absolute()
    try:
        if not sys.platform.startswith('linux'):raise ValueError('Isolated parallel analysis jobs require Linux; use serial execution on this platform.')
        binary,licence=[p.resolve(strict=True) for p in (args.josim,args.licence)]
        if not binary.is_file() or not licence.is_file():raise ValueError('Select existing compatible JoSIM CLI and issued licence files.')
        if args.worker_mib<64 or args.budget_mib<args.worker_mib:raise ValueError('Use at least 64 MiB per worker and an explicit plan budget no smaller than one worker.')
        if work.exists() or not work.parent.is_dir():raise ValueError('Select a new result directory under an existing parent.')
        original=sha(source);work.mkdir()
        home=work/'private-home/.josimpro';home.mkdir(parents=True)
        (home/'config.ini').write_text('[General]\nLicenseLocation='+str(licence)+'\n',encoding='utf-8')
        env=os.environ.copy();env['HOME']=str(home.parent)
        env['LD_LIBRARY_PATH']=str(binary.parent)+':'+env.get('LD_LIBRARY_PATH','')
        receipt={'status':'running','source_sha256':original,'josim_sha256':sha(binary),'workers_requested':args.workers,
                 'budget_mib':args.budget_mib,'worker_mib':args.worker_mib,'runs':{},
                 'scope':'Supplied fixed-step six-case resistor sweep; no general accuracy, universal speedup or whole-GUI memory bound.'}
        def save():(work/'receipt.json').write_text(json.dumps(receipt,indent=2)+'\n',encoding='utf-8')
        save();results={}
        for name,flags in [('serial',[]),('parallel',['--jobs',str(args.workers),'--memory-budget',str(args.budget_mib),'--job-memory',str(args.worker_mib)])]:
            folder=work/name;folder.mkdir();shutil.copyfile(source,folder/'sweep.cir')
            with (folder/'private-run.log').open('wb') as stream:
                process=subprocess.Popen([str(binary),'--minimal','-P','17',*flags,'-o','result.csv','sweep.cir'],cwd=folder,env=env,stdout=stream,stderr=subprocess.STDOUT,start_new_session=True)
                start=time.monotonic();worker_pids=set();peak=0
                try:
                    while process.poll() is None:
                        running=0
                        try:children=(Path('/proc')/str(process.pid)/'task'/str(process.pid)/'children').read_text().split()
                        except FileNotFoundError:children=[]
                        for pid in children:
                            try:arguments=(Path('/proc')/pid/'cmdline').read_bytes().split(b'\0')
                            except (FileNotFoundError,ProcessLookupError):continue
                            if b'--native-analysis-worker' in arguments:
                                worker_pids.add(int(pid));running+=1
                        peak=max(peak,running)
                        if time.monotonic()-start>120:raise TimeoutError('This teaching run exceeded its 120 second bound.')
                        time.sleep(.005)
                except BaseException:
                    # The new session belongs only to this invocation and its workers.
                    try:os.killpg(process.pid,signal.SIGTERM)
                    except ProcessLookupError:pass
                    try:process.wait(timeout=6)
                    except subprocess.TimeoutExpired:
                        try:os.killpg(process.pid,signal.SIGKILL)
                        except ProcessLookupError:pass
                        process.wait()
                    raise
            receipt['runs'][name]={'exit_code':process.returncode,'observed_native_workers':len(worker_pids),'peak_observed_native_workers':peak,
                                  'elapsed_seconds':time.monotonic()-start}
            if process.returncode!=0:raise RuntimeError('The current '+name+' run failed; retain its private diagnostic.')
            paths=[folder/f'result.tran-{i:03d}.case-{i}.csv' for i in range(1,7)]
            if set(folder.glob('*.csv'))!=set(paths):raise ValueError('Expected exactly six freshly published case CSVs.')
            results[name]=[table(path) for path in paths]
            receipt['runs'][name]['outputs']={p.name:sha(p) for p in paths}
            save()
        if results['serial']!=results['parallel']:raise ValueError('Current serial and parallel parsed outputs differ.')
        expected=[(.001,3),(.001,2),(.001,1),(.002,3),(.002,2),(.002,1)]
        for (names,rows),(current,resistance) in zip(results['parallel'],expected):
            if names!=['time','V(N)','I(R1)'] or len(rows)!=1001 or rows[0][0]!=0 or abs(rows[-1][0]/1e-7-1)>1e-12:
                raise ValueError('The checked trace/sample/time contract changed.')
            if not all(b[0]>a[0] for a,b in zip(rows,rows[1:])):raise ValueError('The output time axis is not strictly increasing.')
            if not all(abs(row[1]-current*resistance)<1e-12 and abs(row[2]-current)<1e-12 for row in rows[1:]):
                raise ValueError('A case failed its independent Ohm-law/current reference after the initial sample.')
        if sha(source)!=original or any(sha(work/n/'sweep.cir')!=original for n in ('serial','parallel')):raise ValueError('A source input changed.')
        receipt.update(status='passed',samples_per_case=1001,cases=6,parsed_outputs_identical=True,inputs_preserved=True)
        save();print(json.dumps({'status':'passed','cases':6,'samples_per_case':1001,'peak_observed_native_workers':receipt['runs']['parallel']['peak_observed_native_workers']}));return 0
    except KeyboardInterrupt:
        if 'receipt' in locals():receipt.update(status='cancelled',reason='Client interrupted; its owned CLI process group was terminated.');save()
        print('Interrupted. Completed case files may remain; this generation is not a completed comparison.',file=sys.stderr);return 130
    except (OSError,ValueError,RuntimeError,TimeoutError) as error:
        if 'receipt' in locals():receipt.update(status='failed',reason=str(error));save()
        print(str(error),file=sys.stderr);return 2


if __name__=='__main__':raise SystemExit(main())
```

## Sources and verification details

Recorded inputs/builds only. Complete walkthrough and final-package acceptance remain separate.

- VAL-049: Maintained Linux JoSIM-Pro 2.9 candidate CLI. Licensed maintained Linux JoSIM-Pro 2.9 candidate CLI, complete public fixed-step resistor sweep. Independent saved-CSV review, real worker admission, bounded rejection and client-owned process-group interruption. This is not a Linux GUI run, cooperative GUI cancellation, a whole-process memory bound, general speedup or final-package qualification Actual browser-downloaded two-input ZIP and exact Bash article/README command with only Python, CLI and issued-licence selections changed. Fresh Linux folder with spaces; twelve current CSV hashes match independent review. Existing-generation and small-worker client refusal checked, plus Windows platform preflight. No GUI or final-package qualification Linux CLI and Windows native controls are distinct routes. No physical picker, complete restart, Linux GUI, whole-process memory guarantee, product repair or final-package qualification.
- VAL-050: Frozen maintained Windows JoSIM-Pro 2.9 GUI. Frozen maintained Windows JoSIM 2.9 GUI with normal licensed runtime: actual Run settings Cancel/OK, per-tab options, saved-plan preview, serial run/stale state and actual editor-scope Save/Cancel/remove. First project save reproduces path-not-found; explicit settings-directory creation recovers without product changes. Typed/hash guards use production owners. Images are exact native regions. No physical menu/picker, complete restart, Linux GUI or final-package qualification Linux CLI and Windows native controls are distinct routes. No physical picker, complete restart, Linux GUI, whole-process memory guarantee, product repair or final-package qualification.

- josim-windows-serial-policy: Windows current-source JoSIM 2.9 native settings scenario, reviewed 4 November 2026. Actual Windows settings controls and bounded native actions. Linux CLI execution is checked separately; no physical picker or final-package acceptance.

- josim-pro:docs/manual_source/sections/gui_settings.tex: Choose defaults and setting scopes; project resolution, typed JSON and safe-save limits
- josim-pro:docs/manual_source/sections/gui_simulation_results.tex: Simulation lifecycle and Run settings; platform eligibility, active-tab precedence and preview
- validation:josim-policies-20261104: VAL-049 Linux CLI admission/output/interruption; VAL-050 native Windows settings and first-save recovery

- 38 scoped checks keep Windows native controls and Linux CLI execution distinct. KB-SETTINGS-001 is a reproduced first-save issue with explicit directory-creation recovery; no product repair. Final distributed packages and full operator routes remain open.

## Related guides

- [Choose JoSIM settings by scope and recover an override](/knowledge-base/josim-settings-scopes)
- [Sweep parameters and keep every case separate](/knowledge-base/josim-parameter-sweeps)
- [Compare cases and create derived traces or measurements](/knowledge-base/josim-derived-traces)
