bf.subpixel and bf.timing¶
subpixel ¶
Material permittivity tensors and their subpixel averages for :mod:bifrost.stack.
Pure matrix routines, independent of the stack's geometry:
- the material tensor contract: every material's relative permittivity as a
real, symmetric, positive-definite 3 x 3 matrix in simulation coordinates
(fdtdx's row-major 9-tuple), checked once (:func:
material_tensors); - the planar (laminate) average of anisotropic materials of Kottke,
Farjadpour and Johnson, Phys. Rev. E 77, 036611 (2008), Section VI
(:func:
laminate), written with the projectors onto the interface normal and onto its plane, so that it needs no tangent frame; - the junction closure: the effective tensor of a voxel's own arrangement of
materials from the periodic cell problem
div(A (e_i + grad phi_i)) = 0on a grid of sub-cells (:func:periodic_cell_tensor), and the weight that blends it with the laminate where a voxel's interfaces are close to one plane (:func:junction_weight).
Tensors are (..., 3, 3) arrays. Rows and columns are x, y, z of the
simulation; nothing here rotates a material: a crystal whose principal axes are
rotated by R relative to the simulation axes has the tensor R @ eps @ R.T.
check_permittivity ¶
Raises unless a permittivity tensor is finite, symmetric and positive definite.
The tolerances are relative to the tensor's own scale (SYMMETRY_RTOL,
DEFINITE_RTOL). Invalid input is rejected, never repaired: eigenvalues
are not clipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eps
|
ndarray
|
(3, 3) tensor. |
required |
name
|
str
|
the material's name, for the message. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
with the reason and how to fix it. |
Source code in bifrost/subpixel.py
contrast_matrix ¶
(M, M) pairwise contrasts :func:tensor_contrast, scaled by the largest Frobenius norm of the materials.
Zero exactly between materials with equal tensors, so that an interface between them is not an interface.
Source code in bifrost/subpixel.py
from_six ¶
(..., 6) xx, yy, zz, xy, xz, yz -> (..., 3, 3) symmetric.
Source code in bifrost/subpixel.py
is_diagonal ¶
Whether a (3, 3) tensor has no off-diagonal entry (exactly, as fdtdx decides its array layout).
junction_weight ¶
Weight of the junction closure for a voxel with interface direction tensor d, and its planar normal.
d = sum_faces w n n^T of a voxel's interfaces; its spread off its leading
direction, r = 1 - lambda_1 / tr d (0 where all faces are parallel, up
to 2/3), sets the weight omega: 0 for r <= low (the laminate along the
leading direction is used), 1 for r >= high (the closure), a smooth step
in between. Only where omega < 1 is the direction used, and there the
leading eigenvalue is separated (r < high <= 1/3), so it is well defined
and differentiable; where r >= high its derivative is cut.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
d
|
Array
|
(..., 3, 3) direction tensors. |
required |
low
|
float
|
r below which the voxel is planar. |
required |
high
|
float
|
r above which it is a junction (at most 1/3). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
omega |
Array
|
(...,) in [0, 1]. |
normal |
Array
|
(..., 3) the leading direction. |
Source code in bifrost/subpixel.py
laminate ¶
Effective permittivity of parallel layers of anisotropic materials (Kottke's planar average).
In a frame Q whose first axis is the interface normal n, the continuous
components (normal D, tangential E) and the averaged ones (normal E,
tangential D) are related by tau(Q^T eps Q), so for layers of volume
fractions f_m::
eps_eff = Q tau^-1(sum_m f_m tau(Q^T eps_m Q)) Q^T
with, for a local tensor [[a, b^T], [b, C]] (a = n.eps n, b its normal /
tangential coupling, C its tangential block), tau = [[-1/a, b^T/a],
[b/a, C - b b^T/a]] and its inverse [[-1/t, -s^T/t], [-s/t, G - s
s^T/t]] for tau = [[t, s^T], [s, G]]. Here a, b and C are taken with
the projectors n n^T and I - n n^T directly in simulation
coordinates, so the result does not depend on a choice of tangent axes,
nor on the sign of n. For isotropic materials it is the scalar formula,
eps_eff^-1 = <1/eps> n n^T + (I - n n^T) / <eps>. The order of the
layers does not enter, so it is exact for any number of parallel layers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eps
|
Any
|
(M, 3, 3) symmetric positive-definite tensors (static numpy or traced). |
required |
fractions
|
Array
|
(..., M) volume fractions; normalized to sum to one. |
required |
normal
|
Array
|
(..., 3) interface normal (any length; sign does not matter). |
required |
Returns:
| Type | Description |
|---|---|
Array
|
(..., 3, 3) the effective permittivity, symmetric positive definite. |
Source code in bifrost/subpixel.py
material_tensor ¶
The relative permittivity of an fdtdx Material as a (3, 3) matrix in simulation coordinates.
fdtdx stores a scalar, a 3-tuple (the diagonal) and a nested 3 x 3 tuple alike as one row-major 9-tuple (xx, xy, xz, yx, yy, yz, zx, zy, zz).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
material
|
Any
|
an |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
(3, 3) float64 array. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if the permittivity is not 9 numbers. |
Source code in bifrost/subpixel.py
material_tensors ¶
material_tensors(materials: Mapping[str, Any], names: Sequence[str], ignore: Sequence[str] = ()) -> np.ndarray
The (M, 3, 3) permittivity tensors of materials in a given order, each checked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
materials
|
Mapping[str, Any]
|
fdtdx Materials by name. |
required |
names
|
Sequence[str]
|
the order (e.g. fdtdx's |
required |
ignore
|
Sequence[str]
|
names not checked (internal placeholders), returned as the identity. |
()
|
Returns:
| Type | Description |
|---|---|
ndarray
|
(M, 3, 3) float64 array. |
Raises:
| Type | Description |
|---|---|
ValueError
|
see :func: |
Source code in bifrost/subpixel.py
periodic_cell_tensor ¶
Effective tensor of a periodic cell made of constant-tensor sub-cells.
Solves, for the three unit fields e_i, the cell problem div(A (e_i +
grad phi_i)) = 0 with periodic phi, by trilinear (Q1) finite elements on
the grid of sub-cells (every element has its sub-cell's constant tensor):
one stiffness matrix K per cell, factorized once (Cholesky) for the three
right-hand sides. It returns the energy bilinear form of the computed
fields, A_eff_ij = <(e_i + grad phi_i) . A (e_j + grad phi_j)>,
integrated exactly (2 x 2 x 2 Gauss points per element): a sum of the
elements' positive semidefinite contributions, so symmetric and positive
semidefinite by construction, and, as the energy of admissible fields, not
below the exact cell tensor and second order in the solve's error (the
equal <A> - F^T K^-1 F would be first order, and lose about the
condition number of K in float32). The mean field is e_i (phi is
periodic), so this is the cell's homogenized tensor, between the harmonic
and arithmetic means of A in the matrix sense. Its derivative is the one
of the linear solve (implicit), not of an iteration.
A box with sides D = diag(sides) (a voxel of a graded grid) is the unit
cube for D^-1 A D^-1 (the coordinates scaled by D), whose effective
tensor gives the box's as D A_eff D; the scale of D cancels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coefficients
|
Array
|
(..., n1, n2, n3, 3, 3) symmetric positive-definite tensor of every sub-cell; the cell is the unit cube, sub-cells 1/n1 x 1/n2 x 1/n3 (an axis of one sub-cell is constant along it). |
required |
sides
|
Array | None
|
(..., 3) the cell's sides (any common scale), or None: a cube. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
tensor |
Array
|
(..., 3, 3) the effective tensor. |
residual |
Array
|
(...,) the solve's normwise backward error |
Source code in bifrost/subpixel.py
329 330 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 404 | |
principal_direction ¶
The leading eigenvector and eigenvalue of (..., 3, 3) positive semidefinite matrices.
By repeated squaring (the columns of d^(2^squarings)), which unlike an
eigendecomposition has finite derivatives at repeated eigenvalues. The
error of the direction is of order (lambda_2 / lambda_1)^(2^squarings):
exact for rank one, and below 1e-9 where lambda_2 <= lambda_1 / 4.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
d
|
Array
|
(..., 3, 3) symmetric positive semidefinite. |
required |
squarings
|
int
|
number of squarings. |
4
|
Returns:
| Name | Type | Description |
|---|---|---|
direction |
Array
|
(..., 3) unit vector (sign arbitrary); e_z where d vanishes (entries below 1e-15). |
value |
Array
|
(...,) its Rayleigh quotient. |
Source code in bifrost/subpixel.py
symmetric_inverse ¶
Inverse of (..., 3, 3) symmetric matrices from their adjugate (exact symmetry, cheap derivatives).
Source code in bifrost/subpixel.py
tensor_contrast ¶
Rotation-invariant contrast of two tensors: |a - b|_F / scale.
to_six ¶
(..., 3, 3) symmetric -> (..., 6): xx, yy, zz, xy, xz, yz.
timing ¶
Opt-in wall-clock timing of the steps that build a :class:~bifrost.stack.StackDevice.
Off by default. When off, every timed call costs one global lookup and adds
nothing to traced code. Turn it on with :func:record_timings::
with record_timings() as timings:
device = StackDevice.from_layer_stack(...)
objects, arrays, *_ = fdtdx.place_objects(...)
arrays, *_ = apply_stack(arrays, objects, params, key)
print(timings.table())
Steps nest (write/weights/source_sdfs). Each record is classified:
- traced: the step ran while JAX traced a function for
jax.jit(orlax.scanand the like): its wall time is tracing time, which is part of compiling. - eager: the step ran on concrete values, also under eager autodiff
(
jax.gradorjax.value_and_gradwithoutjit, whose tracers carry concrete primals). Its outputs (their primals) are waited for (jax.block_until_ready) at the step's end, outside any trace, so the wall time includes the device work. JAX compile events (tracing, lowering and XLA compilation, fromjax.monitoring) that happen while the step is open are its compile time; the rest is run time. The backward pass of an eagerjax.gradruns after the steps returned and belongs to no step: time the whole call with :func:timed_stepto see it.
Work outside every step is not lost: compile events that fire while no step is
open are summed in :attr:StepTimings.outside_compile (lowering and XLA
compilation of jitted callers), and the timing plugin reports each test's time
outside the steps.
Timing never changes results or gradients: it only reads the clock, waits for eager outputs, and listens to JAX's monitoring events.
StepRecord
dataclass
¶
Accumulated time of one step (one key of :attr:StepTimings.steps).
Source code in bifrost/timing.py
StepTimings ¶
Records the wall, compile and run time of named steps (see the module docstring).
Source code in bifrost/timing.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | |
outside_compile
property
¶
Compile time (s) outside every step: lowering and XLA compilation of jitted callers (merged events).
__init__ ¶
Creates an empty recorder.
Source code in bifrost/timing.py
as_dict ¶
by_leaf ¶
Records summed per step name, whatever outer steps called it.
apply_stack/write/weights and write/weights both count as
weights; a lap name of step step is step.name.
Source code in bifrost/timing.py
close ¶
Ends a step: waits for its eager value and records the time.
Source code in bifrost/timing.py
lap ¶
Records the time since the innermost step's start or last lap as its sub-step <step>.<name>.
Source code in bifrost/timing.py
open ¶
Starts a step nested in the innermost open one.
Source code in bifrost/timing.py
table ¶
A text table of the steps (by leaf name with leaf), slowest first.
active ¶
format_table ¶
A text table of records: calls, traced calls, wall, traced, compile, run (s), slowest first.
Source code in bifrost/timing.py
lap ¶
Records the time since the enclosing step's start or last lap as sub-step name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
sub-step name. |
required |
value
|
Any
|
the sub-step's outputs, waited for when eager. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in bifrost/timing.py
record_timings ¶
Turns step timing on inside the block.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timings
|
StepTimings | None
|
recorder to add to (default: a new one). |
None
|
Yields:
| Type | Description |
|---|---|
StepTimings
|
The recorder; its :attr: |
Source code in bifrost/timing.py
timed ¶
Decorator timing every call of a function as the step name (its output is waited for when eager).
Source code in bifrost/timing.py
timed_step ¶
A context manager timing the step name (a no-op when timing is off).