Skip to content

electrostatics._pescado_build — problem assembly

tempura/electrostatics/_pescado_build.py assembles a pescado ProblemBuilder from a finalized device and its mesh plan. It is the bridge between Tempura's geometry model and the backend's meshed Poisson problem, and it backs the public build_problem entry point.

Assembly stages

build_problem_components(...) runs the build in explicit, named stages:

  1. Validate the device is finalized and every layer has a realized shape.
  2. Resolve the mesh plan (master lattice, simulation box, zones) via _mesh_plan.
  3. Initialize the base simulation mesh on the coarse simulation lattice, anchored at the shared global_point_anchor(...).
  4. Refine each automatic zone with one rectangular pattern at that same anchor, so the zone lattice is a sublattice of the master with no phase to choose (see _mesh_plan).
  5. Assign regions — give every layer its material and boundary model.
  6. Add the grounded boundary shell, when requested.

Region assignment and the Poisson model

Tempura solves the Poisson equation for the electrostatic potential \(\phi(\mathbf r)\),

\[ -\nabla\cdot\bigl(\varepsilon(\mathbf r)\,\nabla\phi(\mathbf r)\bigr) = \rho(\mathbf r), \]

where the layer stack defines the permittivity map \(\varepsilon(\mathbf r)\). Region assignment is what writes that map into the backend. Each layer claims an owned region (its realized shape, with the shared lower face excluded — within a band relative to the master \(d_z\), not a fixed absolute tolerance — so adjacent layers do not double-count interface sites) and sets:

  • Gates → metal at a very large relative permittivity plus a Dirichlet setup, so each gate is an equipotential electrode held at a chosen voltage. Gates have no refinement zone of their own: they are realized on the device-halo lattice (the finest non-quantum spacing), and a build-time warning fires if a stencil feature is finer than that mesh.
  • Dielectrics → their permittivity plus a Neumann model, \(\hat{\mathbf n}\cdot(\varepsilon\nabla\phi) = g\).
  • Quantum region → its permittivity plus the chosen response model: Helmholtz \(\hat{\mathbf n}\cdot(\varepsilon\nabla\phi) + \alpha\phi = \beta\), Neumann, or flexible.

The grounded boundary

With dirichlet_boundary=True the assembler adds a thin grounded shell at \(0\,\mathrm V\) around the simulation box. In outer_vacuum mode this is a shell just inside the vacuum faces, checked so it never overlaps the protected fine interior; in tight_stack_bbox mode it is placed only on the top and bottom faces, outside the realized stack. The shell is what makes the open-boundary Poisson problem well posed.

API

_pescado_build

ProblemBuilder assembly for Tempura's active electrostatics pipeline.

build_problem_components(device, vacuum_scale=8.0, *, vacuum_resolution_scale=8.0, dirichlet_boundary=True, verbose=False)

Build the internal components used by the public problem API.

Parameters:

Name Type Description Default
device Device

Finalized device whose electrostatic stack should be meshed.

required
vacuum_scale float

Outer vacuum size multiplier relative to the device.

8.0
vacuum_resolution_scale float

Coarsening factor applied to the automatic outer-vacuum lattice spacing.

8.0
dirichlet_boundary bool

Whether to add a thin outer shell of zero-volt Dirichlet sites around the simulation box. When False, the outer shell is omitted.

True
verbose bool

Whether to emit progress logging during mesh construction.

False

Returns:

Type Description
ProblemBuilder

Tuple of (problem_builder, region_shapes, gate_names, mesh_plan).

RegionMap

problem_builder backs the public build_problem() API, while

list[str]

the remaining items stay internal so later solve stages can derive

MeshPlan

their state from finalized-problem metadata instead of public tuples.

Source code in tempura/electrostatics/_pescado_build.py
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
def build_problem_components(
    device: Device,
    vacuum_scale: float = 8.0,
    *,
    vacuum_resolution_scale: float = 8.0,
    dirichlet_boundary: bool = True,
    verbose: bool = False,
) -> tuple[ProblemBuilder, RegionMap, list[str], MeshPlan]:
    """Build the internal components used by the public problem API.

    Args:
        device: Finalized device whose electrostatic stack should be meshed.
        vacuum_scale: Outer vacuum size multiplier relative to the device.
        vacuum_resolution_scale: Coarsening factor applied to the automatic
            outer-vacuum lattice spacing.
        dirichlet_boundary: Whether to add a thin outer shell of zero-volt
            Dirichlet sites around the simulation box. When ``False``, the
            outer shell is omitted.
        verbose: Whether to emit progress logging during mesh construction.

    Returns:
        Tuple of ``(problem_builder, region_shapes, gate_names, mesh_plan)``.
        ``problem_builder`` backs the public ``build_problem()`` API, while
        the remaining items stay internal so later solve stages can derive
        their state from finalized-problem metadata instead of public tuples.
    """
    layers = _validate_build_problem_inputs(device, verbose=verbose)
    mesh_plan = resolve_mesh_plan(
        device,
        layers,
        vacuum_scale=vacuum_scale,
        vacuum_resolution_scale=vacuum_resolution_scale,
        dirichlet_boundary=dirichlet_boundary,
    )
    pb = _initialize_problem_builder(
        mesh_plan["simulation_box"],
        mesh_plan["simulation_resolution"],
        global_point_anchor(device, mesh_plan["master_lattice"]),
        verbose=verbose,
    )
    _log_if_verbose(
        verbose,
        f"[build_problem] Automatic meshing | mode={mesh_plan['domain_mode']} "
        f"master={mesh_plan['master_lattice'].tolist()} "
        f"simulation={mesh_plan['simulation_resolution'].tolist()}",
    )
    _refine_auto_mesh_zones(pb, device, mesh_plan, verbose=verbose)
    region_shapes, gate_names = _assign_layer_regions(
        pb,
        device,
        layers,
        mesh_plan,
        verbose=verbose,
    )
    if dirichlet_boundary:
        if mesh_plan["domain_mode"] == "tight_stack_bbox":
            _add_top_bottom_dirichlet_boundary_region(
                pb,
                mesh_plan,
                region_shapes,
                verbose=verbose,
            )
        else:
            _add_outer_dirichlet_boundary_region(
                pb,
                mesh_plan,
                region_shapes,
                verbose=verbose,
            )
    else:
        _log_if_verbose(verbose, "[build_problem] Boundary skipped.")
    _log_if_verbose(verbose, "[build_problem] Done.")
    return pb, region_shapes, gate_names, mesh_plan