solprop.backends.node_chunk#

How many nodes a response pass holds at once: the backend’s node-chunk plan.

A response pass synthesises the field at a chunk of the transform’s nodes, evaluates the responses there and projects back; the chunk bounds the space-domain buffers. It is an execution choice, not part of the scientific problem (SEP 47 sections 4.3 and 6.5): two runs that differ only in their chunks compute the same fields to rounding, which the chunked rows of tests/data/convergence/spatial_accuracy.json assert. So the translation records none, and each backend plans its chunk when it is prepared, from the problem and its ExecutionOptions (plan_node_chunk()).

The rule is the one the translation applied before SEP 47 moved it here, so that every default and every explicit budget chooses the chunk it chose then:

  • The budget. An explicit node_budget is authoritative; without one a free-space, line or planar problem takes DEFAULT_SPATIAL_NODE_BUDGET and every other DEFAULT_NODE_BUDGET.

  • The base chunk. A modal or radial node holds BUFFERS_PER_NODE complex temporal columns per component; a Cartesian or planar column holds FREE_COLUMN_BUFFERS. A mode-averaged run has one node, and a general modal guide’s static matrices are a placeholder of one (its route sizes its own chunk from general_node_chunk() at the same budget).

  • The response admissions then shrink the chunk until each node’s envelope plasma, Raman, dispersive and held-current buffers fit the same budget, refusing a budget that cannot hold one node (or one planar row), and the last step spreads the nodes evenly over the passes the admitted chunk needs.

The counts are conservative inventories, not the live buffers of each route: SEP 28’s phase 0 found the twenty Cartesian column-equivalents about three times the live group buffers (solprop issue #349). They stay, so that a user’s budget keeps buying the chunk it bought before; counting a route’s live buffers changes the chunk of every explicit budget, which is the maintainer’s decision under #349 and #89, not this move’s.

A device backend may choose differently when no budget is given (solprop issue #361); the native backend’s rule is solprop_native.backend.device_node_chunk, and an explicit budget or chunk is never overridden by it.

Module Attributes

DEFAULT_NODE_BUDGET

Bytes of node-chunk buffers a problem may hold, before the chunk size is reduced.

DEFAULT_SPATIAL_NODE_BUDGET

Node-chunk bytes a free-space, line or planar problem may hold by default (SEP 28).

BUFFERS_PER_NODE

Node-chunk buffers the fused pass holds: E, P and the Raman drive.

BUFFERS_PER_GENERAL_NODE

Chunk buffers the general source holds per node, each n_components wide.

PAIR_BUFFERS_PER_GENERAL_NODE

Chunk buffers the general source holds per node, each n_components squared.

FREE_COLUMN_BUFFERS

Complex-equivalent buffers per component a Cartesian or planar column holds.

ChunkSource

Where a plan's chunk came from: the default budget, an explicit one, or a chunk.

Functions

default_node_budget(transform)

Return the node budget a problem takes when none is given.

dispersive_node_buffers(responses, *, spatial)

Return the buffers per node and component SEP 6's dispersive response adds.

general_dispersive_buffers(leaf)

Return the buffers per node and component SEP 6's response adds to a chunk.

general_node_chunk(n_components, n_to, ...)

Return the nodes the general source may hold at once within a byte budget.

general_planned_chunk(problem, plan)

Return the general route's node chunk under a backend's node-chunk plan.

plan_node_chunk(problem[, options])

Plan a problem's node chunk on the host rule, before anything is allocated.

Classes

NodeChunkPlan(chunk, budget, source[, requested])

A backend's node chunk and what chose it, recorded as execution metadata.

solprop.backends.node_chunk.BUFFERS_PER_GENERAL_NODE = 4#

Chunk buffers the general source holds per node, each n_components wide.

The synthesised fine spectrum, the analytic time field, the polarisation in time and its one-sided spectrum.

solprop.backends.node_chunk.BUFFERS_PER_NODE = 3#

Node-chunk buffers the fused pass holds: E, P and the Raman drive.

solprop.backends.node_chunk.ChunkSource#

Where a plan’s chunk came from: the default budget, an explicit one, or a chunk.

alias of Literal[‘default’, ‘explicit’, ‘forced’]

solprop.backends.node_chunk.DEFAULT_NODE_BUDGET = 67108864#

Bytes of node-chunk buffers a problem may hold, before the chunk size is reduced.

Architecture section 13 chooses the whole node set when it fits a per-problem budget and the largest chunk that fits otherwise. Sixty-four mebibytes reproduces the chunk of about sixteen nodes that section 8 tabulates for the comb-to-continuum target (32768 time samples, six modes, Raman) while leaving a small problem un-chunked. It is an estimate until the benchmark suite measures the crossover.

The free-space, line and planar grids default to DEFAULT_SPATIAL_NODE_BUDGET.

solprop.backends.node_chunk.DEFAULT_SPATIAL_NODE_BUDGET = 1073741824#

Node-chunk bytes a free-space, line or planar problem may hold by default (SEP 28).

A spatial evaluation pays a fixed cost per node chunk: its launches, its temporal transforms’ thread wake-ups, and its gather and scatter. A field volume is gigabytes, so a larger group is cheap in memory. With the per-chunk copies and clears removed, one gibibyte was the fastest of 64, 256 and 1024 MiB for the 384- and 768-square Kerr beams on Zen 5 and on Apple silicon, 2.3 to 2.4 times faster than 64 MiB on the native backend’s march; on an H200, IP-DOPRI5 was 1.3 times faster at one gibibyte than at 64 MiB.

solprop.backends.node_chunk.FREE_COLUMN_BUFFERS = 20#

Complex-equivalent buffers per component a Cartesian or planar column holds.

Eight response and FFT buffers, three doubled Raman buffers, 3.5 device plasma buffers, two split-step buffers and 2.5 statistic buffers: 19, and one more column for the real FFT’s endpoint bins. The transverse scratch and the opaque FFT plans are named separately in the memory ledgers. A conservative inventory, about three times the live group buffers (solprop issue #349); the dispersive admission adds its own to it.

class solprop.backends.node_chunk.NodeChunkPlan(chunk, budget, source, requested=None)[source]#

Bases: object

A backend’s node chunk and what chose it, recorded as execution metadata.

Attributes:
chunkint

Nodes a response pass holds at once, between 1 and the transform’s node count; one on a general modal guide, whose static matrices are a placeholder.

budgetint

The budget the chunk was planned within, bytes: the explicit one, or the problem’s default (default_node_budget()).

source{‘default’, ‘explicit’, ‘forced’}

"default" without an explicit budget (the route’s default budget), "explicit" with one, and "forced" for an exact chunk (solprop.backends.session.ExecutionOptions.node_chunk).

requestedint or None

The exact chunk asked for, before it was bounded by the node count; None unless source is "forced". A general modal guide’s route takes it in place of its own budgeted chunk.

Examples

>>> from solprop.backends.node_chunk import NodeChunkPlan
>>> NodeChunkPlan(16, 2**26, "default").record()
{'node_chunk': 16, 'node_budget': 67108864, 'node_chunk_source': 'default'}
property automatic: bool[source]#

Whether no budget or chunk was given, so a machine rule may apply.

True when source is "default".

budget: int#
chunk: int#
record()[source]#

Return the plan as execution metadata, outside every identity.

Returns:
dict

node_chunk, node_budget and node_chunk_source.

requested: int | None = None#
source: Literal['default', 'explicit', 'forced']#
solprop.backends.node_chunk.PAIR_BUFFERS_PER_GENERAL_NODE = 2#

Chunk buffers the general source holds per node, each n_components squared.

The tensor contraction is written in two steps rather than as one four-operand einsum, so that every intermediate it allocates is a buffer this module names: the pair history E_k conj(E_l) and the tensor contracted against it. A single einsum with optimize=True allocates the same intermediates and names none of them, which a memory ledger cannot report.

The static maps S and Q, the per-node tensor, the dense E/H reference, its reciprocal partners and the coefficient dual are whole-problem allocations, counted row by row by solprop.backends.numpy_ref.general_modal.general_memory(); they do not shrink with the chunk, which is why the counts here and BUFFERS_PER_NODE are different and neither reuses the other.

solprop.backends.node_chunk.default_node_budget(transform)[source]#

Return the node budget a problem takes when none is given.

Parameters:
transformsolprop.translate.modal.Transform

The problem’s transform.

Returns:
int

DEFAULT_SPATIAL_NODE_BUDGET on a Cartesian (free-space or line) or planar transform, DEFAULT_NODE_BUDGET otherwise.

solprop.backends.node_chunk.dispersive_node_buffers(responses, *, spatial)[source]#

Return the buffers per node and component SEP 6’s dispersive response adds.

On the fused node path a backend forms the response’s products at the nodes: one node buffer beside other responses for the symmetric evaluator, which a lone one shares with the bare field, and two for the polynomial, whose cube and derivative products take them in turn. A Cartesian or planar column also holds its filtered fields and products in time and their spectra: two buffers per product, the symmetric evaluator’s one (a mixture’s under its mixture filter) and the polynomial’s 1 + 2 G for G groups of equal leg bound (groups()), and the planar nodes’ one or two besides.

Parameters:
responsessolprop.translate.responses.CompiledResponses

The compiled responses, with or without the dispersive leaf.

spatialbool

Whether the route’s nodes are Cartesian or planar columns.

Returns:
int

Complex-equivalent buffers per node and component; zero without the leaf.

solprop.backends.node_chunk.general_dispersive_buffers(leaf)[source]#

Return the buffers per node and component SEP 6’s response adds to a chunk.

The compiled general workspace holds, for each node of a chunk and each of its components, every filtered field’s spectrum and time series and every product and its spectrum: two complex-equivalent buffers per field and two per product. The symmetric evaluator has one field and one product per region with a gas fill (a mixture’s under its mixture filter); the polynomial two fields and two products per group of equal leg bound, and one cube per region (groups()).

Parameters:
leafsolprop.translate.responses.DispersiveKerrTerms or None

The route’s dispersive channels, or None.

Returns:
int

Complex-equivalent buffers per node and component; zero without the leaf.

solprop.backends.node_chunk.general_node_chunk(n_components, n_to, n_omega_fine, budget, *, dispersive=0)[source]#

Return the nodes the general source may hold at once within a byte budget.

The chunk bounds the space-domain work alone: BUFFERS_PER_GENERAL_NODE complex buffers per node and component, PAIR_BUFFERS_PER_GENERAL_NODE per node and component squared, and SEP 6’s dispersive response’s own (general_dispersive_buffers()), over the longer of the fine time axis and the fine spectrum. The maps, the reference and the dual are whole-problem allocations that no chunk shrinks (solprop.backends.numpy_ref.general_modal.general_memory()).

Parameters:
n_componentsint

Cartesian components of the local field, positive.

n_toint

Fine time samples, positive.

n_omega_fineint

Fine spectral bins, positive.

budgetint

Bytes the chunk buffers may occupy in total, positive.

dispersiveint, optional

The dispersive response’s complex buffers per node and component; zero, the default, without one.

Returns:
int

The chunk size, at least one.

Raises:
BackendError

If the budget is not positive.

Examples

>>> from solprop.backends.node_chunk import general_node_chunk
>>> general_node_chunk(3, 1024, 513, 64 * 2**20)
136
solprop.backends.node_chunk.general_planned_chunk(problem, plan)[source]#

Return the general route’s node chunk under a backend’s node-chunk plan.

The route’s own rule (general_node_chunk()) within the plan’s budget – the explicit one, or DEFAULT_NODE_BUDGET – or the plan’s exact chunk when one was forced, bounded by the node count. Both compiled backends take it, so that they execute the same chunks.

Parameters:
problemsolprop.translate.problem.PropagationProblem

A problem that carries transform.modal_tables.

plansolprop.backends.node_chunk.NodeChunkPlan

The backend’s plan (solprop.backends.node_chunk.plan_node_chunk()).

Returns:
int

Nodes the general source holds at once, between one and the node count.

solprop.backends.node_chunk.plan_node_chunk(problem, options=None)[source]#

Plan a problem’s node chunk on the host rule, before anything is allocated.

Parameters:
problemsolprop.translate.problem.PropagationProblem

The validated problem.

optionssolprop.backends.session.ExecutionOptions or None, optional

The run’s execution options; None for the defaults.

Returns:
NodeChunkPlan

The chunk, its budget and its source.

Raises:
solprop.exceptions.BackendError

If the budget cannot hold one node’s buffers (one complete temporal column, or one planar transverse row, with every response’s own).

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.pulses import SechPulse
>>> from solprop.stepper import PIController
>>> from solprop.translate.problem import assemble
>>> from solprop.backends.node_chunk import plan_node_chunk
>>> grid = EnvelopeGrid.from_limits(0.01, 835e-9, (500e-9, 2e-6), 1e-12)
>>> gnlse = problem_from([0.0, 0.0, -1.2e-26], omega_ref=grid.omega0, gamma=0.11)
>>> start = SechPulse(wavelength=835e-9, width=1e-13, peak_power=1.0).build(grid)
>>> problem, request = assemble(grid, gnlse.mode, [gnlse.kerr], start,
...                    controller=PIController(rtol=1e-8, atol=1e-14),
...                    save_planes=[0.01], dz_max=0.01, dz_init=1e-3,
...                    density=1.0, shock=False, prefactor_index=1.0)
>>> plan_node_chunk(problem).chunk
1