solprop.backends.session#

The execution-session contract between solprop and a backend (SEP 47 section 5).

Python constructs and validates the scientific problem (solprop.translate.problem.PropagationProblem) and resolves every run policy into an immutable solprop.translate.run_request.RunRequest. A backend prepares them once (SessionBackend.prepare(), which every backend offers as Backend.prepare), and the prepared execution starts one RunSession per trajectory. The session is the only owner of that trajectory’s mutable numerical state: the field, the method’s history, the controller and monitor, the counters and cursors, and the pending events. Python’s service loop (solprop.driver.service) advances it, services the events it yields – files, callbacks, warnings, progress – and acknowledges them:

prepared = backend.prepare(problem, request)
with prepared.start(start, stop=token) as session:
    while not session.finished:
        batch = session.advance(work_limit=256, deadline=0.1)
        service(batch)
        session.acknowledge(batch.receipt)

Yields are exact transport no-ops (SEP 47 section 5.1): the attempt bound, the deadline, the event capacity and the timing of acknowledgments change when control returns, never the numerical state or the control trace.

Event storage is bounded (SEP 47 section 5.3): every event delivers, exactly once, the attempts and periodic rows made since the previous event – a yield’s Progress as much as an observation, a snapshot or the terminal event – and a row’s step indexes its own chunk’s attempts. A session therefore buffers no more than one advance’s attempts, and the service loop, not the session, assembles a plane’s or a checkpoint’s history from the chunks (solprop.backends.events.join_attempts()). The status counts every attempt once, as it is drained.

Control requests travel separately from the immutable request: a thread-safe StopToken asks for a stop at the next safe boundary, and RunSession.request_snapshot() latches a snapshot for the next eligible accepted endpoint (SEP 47 section 5.5).

A GroupSession advances compatible trajectories through one common fixed schedule, a window at a time, and reports each member’s own events and outcome (SEP 47 section 6.7). A backend prepares its own group the same way, from the group’s first member (PreparedGroup):

prepared = members[0].prepare_group(members, problems, requests)
with prepared.start(starts, stop=token) as group:
    ...

SessionLedger is the lifecycle bookkeeping every Python session shares: receipts, the pending barrier, closing and poisoning. It holds no numerical state.

Module Attributes

DEFAULT_WORK_LIMIT

Attempts one advance makes at most before yielding, as the native marches do.

DEFAULT_DEADLINE

Host seconds after which advance yields at its next safe boundary, s.

Functions

check_group(count, problems, requests, starts)

Refuse a group's inputs that no group implementation starts from.

check_member_ticket(ticket, member)

Refuse a group ticket presented for another member than the one it names.

positive_count(name, value)

Return an integral, positive budget or chunk as an int.

statistics_unavailable(problem, names)

Return why no executor computes some of a problem's statistics, or None.

Classes

ExecutionOptions([node_budget, node_chunk, ...])

Execution choices that are not part of the numerical problem.

GroupSession(*args, **kwargs)

Compatible trajectories advanced together through one common fixed schedule.

PreparedExecution(*args, **kwargs)

A backend's lowering of one problem and request, reusable across sessions.

PreparedGroup(*args, **kwargs)

A backend's preparation of a group of compatible members (SEP 47 section 6.7).

RunSession(*args, **kwargs)

One trajectory's numerical execution, owned by its backend.

SessionBackend(*args, **kwargs)

A backend that prepares executions (SEP 47 section 6.1).

SessionLedger()

The lifecycle bookkeeping of a Python session; no numerical state.

SessionState(*values)

Where a session is in its lifecycle.

SessionStatus(z, plane_index, attempts, ...)

A compact, read-only view of a session, for reporting.

SnapshotReceipt(request, coalesced)

Identifies a snapshot request until it is fulfilled or refused.

Start(field, z[, noise, plane_index, march, ...])

Where a session starts: an initial state, or a checkpoint being resumed.

StopToken()

A thread-safe request to stop a run at its next safe boundary.

Exceptions

SessionError(*args[, parameter, rule])

A session was used outside its lifecycle.

solprop.backends.session.DEFAULT_DEADLINE = 0.1#

Host seconds after which advance yields at its next safe boundary, s.

solprop.backends.session.DEFAULT_WORK_LIMIT = 256#

Attempts one advance makes at most before yielding, as the native marches do.

Python signal handlers run only between native calls, so a bounded return keeps a long run interruptible (SEP 47 section 5.5).

class solprop.backends.session.ExecutionOptions(node_budget=None, node_chunk=None, backend_options=<factory>)[source]#

Bases: object

Execution choices that are not part of the numerical problem.

Two runs may share their scientific description while differing in these (SEP 47 section 4.3). They are recorded as execution metadata, never compared by a restart.

Attributes:
node_budgetint or None

An explicit workspace budget for node chunks, bytes: any integral value (operator.index, a NumPy integer too), held as an int. None leaves the backend’s automatic choice. An explicit budget is authoritative: the backend plans its chunk from it and no machine rule overrides it (solprop.backends.node_chunk.plan_node_chunk()).

node_chunkint or None

An exact node chunk, integral and held as an int, which overrides both the budget and the automatic choice: a measurement’s or a test’s control, not a user setting. A backend takes at most every node, and on the planar kind whole transverse rows.

backend_optionscollections.abc.Mapping

Backend-specific execution options, passed through untouched (for example a native CPU execution record).

Raises:
solprop.exceptions.BackendError

If node_budget or node_chunk is given and is not a positive integer.

Examples

>>> from solprop.backends.session import ExecutionOptions
>>> ExecutionOptions(node_budget=2**26).node_budget
67108864
backend_options: Mapping[str, object]#
property budget: int | None[source]#

Return the explicit node budget as an int, or None.

Returns:
int or None

node_budget, which construction already made an int.

property chunk: int | None[source]#

Return the exact node chunk as an int, or None.

Returns:
int or None

node_chunk, which construction already made an int.

node_budget: int | SupportsIndex | None = None#
node_chunk: int | SupportsIndex | None = None#
class solprop.backends.session.GroupSession(*args, **kwargs)[source]#

Bases: Protocol

Compatible trajectories advanced together through one common fixed schedule.

A group request is a sequence of independently translated member problems that pass the compatibility predicate a batched scan groups its points by; the common schedule’s maximum error norm is a diagnostic only (SEP 47 section 6.7). Each advance() runs every member still running through the next common schedule window – to the next save plane – and reports each member’s own events (GroupBatch):

with group:
    while not group.finished:
        batch = group.advance()
        service(batch)                 # each member's planes and outcome
        group.acknowledge(batch.receipt)

Failure. A boundary error, or a boundary warning raised as an error, ends that member alone with a Terminal, and the others go on. A non-finite error norm, a step floor, a trial exception or an in-flight failure ends the group: every member still running ends with the same error. Members are stepped in turn, so earlier members may already have advanced the failed scheduled step: each terminal reports that member’s actual committed position and checkpoint eligibility, and no common cursor or rollback is invented. A group-wide stop request ends every running member at a common accepted endpoint.

acknowledge(receipt)[source]#

Release a window’s batch and allow advancing past it.

Parameters:
receiptsolprop.backends.events.Receipt

The batch’s receipt.

advance()[source]#

Run every running member through the next common window, and report it.

close()[source]#

Release every member’s resources; idempotent.

property closed: bool[source]#

Return whether the group was closed.

execution_record(member)[source]#

Return one member’s execution facts.

Parameters:
memberint

The member’s index, from zero.

Returns:
collections.abc.Mapping

The member’s execution record.

export(member, ticket, recipe, out=None)[source]#

Fill a destination with one member’s converted field at a pending ticket.

Parameters:
memberint

The member’s index, from zero.

ticketsolprop.backends.events.ExportTicket

The member’s pending ticket.

recipesolprop.backends.export.ExportRecipe

The conversion.

outnumpy.ndarray or None, optional

The destination; a new array by default.

Returns:
numpy.ndarray

The filled destination.

property finished: bool[source]#

Return whether every member has ended.

member_status(member)[source]#

Return one member’s committed position, counters and outcome.

Parameters:
memberint

The member’s index, from zero.

Returns:
SessionStatus

The member’s status.

monitor_record(member)[source]#

Return one member’s boundary-monitor record, None when unwatched.

Parameters:
memberint

The member’s index, from zero.

Returns:
collections.abc.Mapping or None

The record.

property n_members: int[source]#

Return the number of members.

noise_phase(member, ticket)[source]#

Return one member’s noise phase at a pending ticket.

Parameters:
memberint

The member’s index, from zero.

ticketsolprop.backends.events.ExportTicket

The member’s pending ticket.

Returns:
numpy.ndarray or None

The phase, or None without noise.

class solprop.backends.session.PreparedExecution(*args, **kwargs)[source]#

Bases: Protocol

A backend’s lowering of one problem and request, reusable across sessions.

property backend: str[source]#

Return the registered name of the backend that prepared it.

close()[source]#

Release the prepared resources; idempotent.

memory_ledger()[source]#

Return every retained buffer by name, bytes.

metadata()[source]#

Return the execution metadata recorded with the run.

start(start, *, listener=None, started=None, stop=None, owns=True)[source]#

Start a session from an initial state or a checkpoint.

Parameters:
startStart

The initial state or checkpoint the session starts from.

listenercallable or None, optional

Called with each attempt’s end, length and acceptance as it is made, for a progress reporter; it observes only.

startedcallable or None, optional

Called with the start, the end and the plane count when propagation begins.

stopsolprop.log.StopFlag or None, optional

The run’s stop token.

ownsbool, optional

Whether closing the session closes the backend.

class solprop.backends.session.PreparedGroup(*args, **kwargs)[source]#

Bases: Protocol

A backend’s preparation of a group of compatible members (SEP 47 section 6.7).

The group’s counterpart of PreparedExecution: a backend returns it from prepare_group(), and its start() opens the backend’s own GroupSession, so that solprop.backends.sessions.start_group() names no backend.

property backend: str[source]#

Return the registered name of the backend that prepared it.

start(starts, *, stop=None)[source]#

Start the group, each member from its entrance.

Parameters:
startscollections.abc.Sequence of Start

Each member’s entrance, in member order; the group takes their fields.

stopsolprop.log.StopFlag or None, optional

A group-wide stop token.

Returns:
GroupSession

The started group, which owns the members once it is constructed; a start that fails leaves them to the caller.

class solprop.backends.session.RunSession(*args, **kwargs)[source]#

Bases: Protocol

One trajectory’s numerical execution, owned by its backend.

See solprop.backends.session for the loop that drives it.

acknowledge(receipt)[source]#

Release a batch and allow advancing across its barrier.

Parameters:
receiptsolprop.backends.events.Receipt

The pending batch’s receipt.

advance(*, work_limit=256, deadline=0.1)[source]#

Run until a barrier, the attempt bound or the deadline, and report it.

Parameters:
work_limitint, optional

Attempts before a yield.

deadlinefloat, optional

Host seconds before a yield, s.

close()[source]#

Release the session’s resources; idempotent.

property closed: bool[source]#

Return whether the session was closed.

continue_monitor(continuation)[source]#

Continue a resumed run’s boundary-monitor record, before it watches the run.

As Start.monitor, for a service loop that reads the earlier execution’s record from its output after the session started.

Parameters:
continuationcollections.abc.Mapping

The continuation, as Start.monitor holds it.

execution_record()[source]#

Return the session’s execution facts for the run’s execution record.

export(ticket, recipe, out=None)[source]#

Fill a destination with a pending observation’s converted field.

Parameters:
ticketsolprop.backends.events.ExportTicket

The pending observation’s ticket.

recipesolprop.backends.export.ExportRecipe

The conversion.

outnumpy.ndarray or None, optional

The destination; a new array by default.

property finished: bool[source]#

Return whether a terminal event was yielded.

march_export_refusal()[source]#

Return why the method history cannot be exported, or None when it can.

march_snapshot(ticket)[source]#

Return the method history at a pending observation, when retained.

Parameters:
ticketsolprop.backends.events.ExportTicket

The pending observation’s ticket.

march_snapshot_nbytes()[source]#

Return the size a method-history snapshot would have, without making it.

monitor_record()[source]#

Return the boundary monitor’s record, or None without a monitor.

noise_phase(ticket)[source]#

Return the noise phase at a pending observation, when a noise model runs.

Parameters:
ticketsolprop.backends.events.ExportTicket

The pending observation’s ticket.

pending_ticket()[source]#

Return a ticket for the committed state after a failure outside the session.

None when nothing can be exported: the session is closed or poisoned, or no batch is pending to bind the ticket to.

request_snapshot()[source]#

Latch a snapshot for the next eligible accepted endpoint.

status()[source]#

Return a read-only summary of the session.

undelivered(ticket)[source]#

Deliver the attempts and rows made since the previous event, once.

For a terminal checkpoint after a failure outside the session, whose attempts no event delivered; empty when every attempt was delivered.

Parameters:
ticketsolprop.backends.events.ExportTicket

The checkpoint’s ticket.

Returns:
tuple

The attempts and the periodic rows.

class solprop.backends.session.SessionBackend(*args, **kwargs)[source]#

Bases: Protocol

A backend that prepares executions (SEP 47 section 6.1).

Every Backend is one: it is built for a problem and a request (solprop.backends.registry.select()), and prepare binds the problem and the request one run serves.

name: str#
prepare(problem, request)[source]#

Lower a problem and request; refuse an unsupported one before allocating.

Parameters:
problemsolprop.translate.problem.PropagationProblem

The translated problem.

requestsolprop.translate.run_request.RunRequest

What the run asks of the backend.

exception solprop.backends.session.SessionError(*args, parameter=None, rule=None)[source]#

Bases: BackendError

A session was used outside its lifecycle.

Raised for a stale or foreign receipt, an advance across an unacknowledged barrier, use after close or after a terminal outcome, or an export with an expired ticket.

class solprop.backends.session.SessionLedger[source]#

Bases: object

The lifecycle bookkeeping of a Python session; no numerical state.

It issues receipts, records the pending barrier, refuses stale or foreign receipts and advancing across an unacknowledged barrier, and remembers whether the session finished, failed or closed.

Examples

>>> from solprop.backends.session import SessionLedger
>>> ledger = SessionLedger()
>>> receipt = ledger.issue()
>>> ledger.ticket_valid(receipt)
True
>>> ledger.acknowledge(receipt)
>>> ledger.ticket_valid(receipt)
False
acknowledge(receipt)[source]#

Accept the pending batch’s receipt.

Parameters:
receiptsolprop.backends.events.Receipt

The receipt to acknowledge.

Raises:
SessionError

If the receipt is not the pending one: foreign, stale or repeated.

check_advance()[source]#

Refuse to advance a session that is pending, finished or closed.

Raises:
SessionError

Naming the state that forbids it.

check_open()[source]#

Refuse a closed session.

Raises:
SessionError

If the session was closed.

check_ticket(ticket)[source]#

Refuse an export with an expired or foreign ticket.

Parameters:
ticketsolprop.backends.events.ExportTicket

The ticket presented.

Raises:
SessionError

If its batch is no longer pending.

close()[source]#

Mark the session closed.

Returns:
bool

True the first time, False when it was already closed.

property finished: bool[source]#

Return whether a terminal batch was issued.

Returns:
bool

True once the session yielded its terminal event.

issue(*, outcome=None)[source]#

Issue the receipt of a new pending batch.

Parameters:
outcomesolprop.backends.events.Outcome or None, optional

The batch’s terminal outcome, when it ends the session.

Returns:
solprop.backends.events.Receipt

The batch’s receipt.

outcome: Outcome | None#
property pending: Receipt | None[source]#

Return the pending batch’s receipt.

Returns:
solprop.backends.events.Receipt or None

The receipt, or None when nothing is pending.

ticket_valid(receipt)[source]#

Return whether tickets of a batch can still be exported.

Parameters:
receiptsolprop.backends.events.Receipt

The batch’s receipt.

Returns:
bool

True while that batch is the pending one.

class solprop.backends.session.SessionState(*values)[source]#

Bases: Enum

Where a session is in its lifecycle.

Attributes:
READYstr

It can advance.

PENDINGstr

A batch is out; it advances again once that batch is acknowledged.

FINISHEDstr

It yielded a terminal event; nothing more can happen but close.

CLOSEDstr

Its resources are released.

CLOSED = 'closed'#
FINISHED = 'finished'#
PENDING = 'pending'#
READY = 'ready'#
class solprop.backends.session.SessionStatus(z, plane_index, attempts, rejected, state, outcome=None)[source]#

Bases: object

A compact, read-only view of a session, for reporting.

Attributes:
zfloat

The committed position, m.

plane_indexint

The next save plane to observe.

attemptsint

Attempts made, accepted and rejected.

rejectedint

Rejected attempts among them.

stateSessionState

Lifecycle state.

outcomesolprop.backends.events.Outcome or None

The terminal outcome, once finished.

attempts: int#
outcome: Outcome | None = None#
plane_index: int#
rejected: int#
state: SessionState#
z: float#
class solprop.backends.session.SnapshotReceipt(request, coalesced)[source]#

Bases: object

Identifies a snapshot request until it is fulfilled or refused.

Attributes:
requestint

Sequence number of the request in its session, from 1.

coalescedbool

Whether it joined an equivalent request still outstanding, so that one snapshot fulfils both (SEP 47 section 5.5 makes coalescing explicit).

coalesced: bool#
request: int#
class solprop.backends.session.Start(field, z, noise=None, plane_index=0, march=None, ledgers=None, monitor=None, plane_pending=True, resume=False)[source]#

Bases: object

Where a session starts: an initial state, or a checkpoint being resumed.

A start is handed to one session, which takes its field (take_field()): the session loads a copy made once at start, and the start forgets the array, so an input that nothing else holds – a runner’s caller’s array – is released before the first step rather than held through the run by every frame that passed the start along (SEP 47 section 5.4).

Attributes:
fieldnumpy.ndarray or None

The state in internal amplitude units and the FFT convention, complex, shape (n_modes, n_pol, n_omega); None once a session took it. Mutating the caller’s array after the start changes nothing.

zfloat

Its position, m.

noisesolprop.noise.NoiseRealisation or None

The run’s noise realisation, when a noise model is active.

plane_indexint

The first save plane strictly beyond z.

marchsolprop.output.schema.MarchSnapshot or None

A method history to restore, when resuming one that was retained.

ledgerscollections.abc.Mapping or None

Persisted call ledgers to restore (TDSE and budgeted sources).

monitorcollections.abc.Mapping or None

When resuming a monitored run, the monitor’s continuation: "record", its persisted record; "plane", whether the resumed position is a saved plane; "unwatched", the spans the earlier executions left unwatched.

plane_pendingbool

Whether the plane at z is owed (the launch plane of a fresh run, or a resumed position whose plane was not saved).

resumebool

Whether field was loaded from a plane the run already saved, so that an input noise realisation is not added a second time.

classmethod entrance(problem, *, noise=None)[source]#

Return the fresh start of a problem: its launch state, its launch plane owed.

Parameters:
problemsolprop.translate.problem.PropagationProblem

The translated problem.

noisesolprop.noise.NoiseRealisation or None, optional

A realisation to carry instead of the one the problem was assembled with.

Returns:
Start

The start at problem.initial.z, before the first save plane.

field: Complex[ndarray, 'n_modes n_pol n_omega'] | None#
ledgers: Mapping[str, object] | None = None#
march: MarchSnapshot | None = None#
monitor: Mapping[str, object] | None = None#
noise: NoiseRealisation | None = None#
plane_index: int = 0#
plane_pending: bool = True#
resume: bool = False#
take_field()[source]#

Hand the field to the session starting here, and forget it.

Returns:
numpy.ndarray

The field, shape (n_modes, n_pol, n_omega); the caller copies it.

Raises:
SessionError

If a session already took it: a start is used once.

z: float#
class solprop.backends.session.StopToken[source]#

Bases: object

A thread-safe request to stop a run at its next safe boundary.

Setting it from any thread – a GUI’s stop button, a signal handler on the main thread, a scan’s supervisor – asks every session attached to it to finish the attempt in hand, complete that endpoint’s mandatory accounting, and end with STOPPED (SEP 47 section 5.5). It implements solprop.log.StopFlag.

Examples

>>> from solprop.backends.session import StopToken
>>> token = StopToken()
>>> token.is_set()
False
>>> token.set()
>>> token.is_set()
True
attach(listener)[source]#

Call listener whenever the token is set, and now if it already is.

Native sessions attach the store of their own atomic flag, so that a stop set from another thread reaches a march that holds no Python lock.

Parameters:
listenercallable

Called with no arguments. It must not raise and must not block.

Returns:
callable

Detaches the listener.

clear()[source]#

Withdraw the request, for a token reused between runs.

is_set()[source]#

Return whether a stop was requested.

Returns:
bool

True once set() was called and not cleared.

set()[source]#

Request a stop, and forward it to every attached session.

solprop.backends.session.check_group(count, problems, requests, starts)[source]#

Refuse a group’s inputs that no group implementation starts from.

A group (GroupSession) takes one problem, request and start per member, and starts every member at its entrance, with no method history or monitor record to continue (SEP 47 section 6.7).

Parameters:
countint

The members given.

problemscollections.abc.Sequence of solprop.translate.problem.PropagationProblem

Each member’s problem.

requestscollections.abc.Sequence of solprop.translate.run_request.RunRequest

Each member’s run request.

startscollections.abc.Sequence of Start

Each member’s start.

Raises:
SessionError

If the sequences differ in length or are empty, or if a start is not a fresh entrance whose launch plane is owed.

solprop.exceptions.BackendError

If a member asks for statistics its problem does not carry (statistics_unavailable()).

solprop.backends.session.check_member_ticket(ticket, member)[source]#

Refuse a group ticket presented for another member than the one it names.

Parameters:
ticketsolprop.backends.events.ExportTicket

A ticket of a group’s pending batch.

memberint

The member it is presented for.

Raises:
SessionError

If the ticket names another member, or none.

solprop.backends.session.positive_count(name, value)[source]#

Return an integral, positive budget or chunk as an int.

Parameters:
namestr

"node_budget" or "node_chunk", for the message.

valueobject

Anything operator.index accepts, such as an int or a NumPy integer.

Returns:
int

The value.

Raises:
solprop.exceptions.BackendError

If value is a bool, not integral, or not positive.

Examples

>>> import numpy as np
>>> from solprop.backends.session import positive_count
>>> positive_count("node_budget", np.int64(2**26))
67108864
solprop.backends.session.statistics_unavailable(problem, names)[source]#

Return why no executor computes some of a problem’s statistics, or None.

What the problem does not carry, refused by name before any session starts, on every executor (SEP 47 section 6.3), rather than at the first plane: a modal statistic of a spatial route, a spatial statistic of a modal one, the plasma integration diagnostics of a problem without the field-grid plasma column (SEP 18), and the electron density or the peak ionisation rate of a problem with no compiled plasma response. The messages are the reference’s at its first plane.

Parameters:
problemsolprop.translate.problem.PropagationProblem

The translated problem.

namescollections.abc.Sequence of str

The statistics asked for, at planes or between them.

Returns:
str or None

The refusal, or None.

Examples

>>> from solprop.backends.session import statistics_unavailable
>>> from solprop.driver import Simulation
>>> simulation = Simulation.from_gnlse(
...     0.11, 0.01, [0.0, 0.0, -1.183e-26], wavelength=835e-9,
...     wavelength_limits=(500e-9, 2e-6), trange=1e-12,
...     width=100e-15, peak_power=9.8, shape="sech",
... )
>>> problem = simulation.translate()
>>> statistics_unavailable(problem, ["energy"]) is None
True
>>> statistics_unavailable(problem, ["beam_radius"])
"the 'beam_radius' statistic requires spatial geometry"