Generated from the full canonical file for this source snapshot. Line numbers match the library source.
Source SHA256: c24e73db8083ab9b6887e9ef4c460affc306f7f131a458a6eba22cea46f2c87e
1"""Spectral primary signal and explicit first-order products.23Paths are material-major (M,P); coefficients are (M,K); weights/response are4shared (K) or energy-major (K,P). FP64 registers hold depths, products and sums.5There is no pixel-by-energy retained tensor. Global parameter gradients use6bounded split reductions; only diagnostic flags use atomics.7"""89# Warp annotations are executable DSL expressions; host interfaces remain strict.10# The optional GPU import is resolved only when an operator is prepared.11# pyright: reportInvalidTypeForm=false, reportUnknownParameterType=false12# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false13# pyright: reportUnknownVariableType=false, reportUntypedFunctionDecorator=false14# pyright: reportMissingImports=false, reportUntypedClassDecorator=false1516from functools import cache1718import warp as wp1920STRICT = {"fast_math": False, "fuse_fp": True, "enable_backward": False}21wp.set_module_options(STRICT)22TILE = 256232425@wp.func26def checked_store(value: wp.float64, status: wp.array(dtype=wp.int32)) -> wp.float32:27rounded = wp.float32(value)28if not wp.isfinite(rounded):29wp.atomic_or(status, 0, 2)30return rounded313233@cache34def get_value_check(wide: bool, nonnegative: bool):35dtype = wp.float64 if wide else wp.float323637@wp.kernel(module="unique", module_options=STRICT)38def check(values: wp.array(dtype=dtype), status: wp.array(dtype=wp.int32)):39index = wp.tid()40invalid = not wp.isfinite(values[index])41if wp.static(nonnegative):42invalid = invalid or values[index] < dtype(0.0)43if invalid:44wp.atomic_or(status, 0, 1)4546return check474849check_nonnegative = get_value_check(False, True)50check_finite = get_value_check(False, False)515253@cache54def get_signal_store(wide: bool):55dtype = wp.float64 if wide else wp.float325657@wp.func58def store(value: wp.float64, status: wp.array(dtype=wp.int32)) -> dtype:59result = dtype(value)60if not wp.isfinite(result):61wp.atomic_or(status, 0, 2)62return result6364return store656667@cache68def get_optical_depth(wide: bool):69dtype = wp.float64 if wide else wp.float327071@wp.func72def depth_at(73paths: wp.array(dtype=dtype),74coefficients: wp.array(dtype=wp.float32),75pixel: int,76energy: int,77pixels: int,78materials: int,79energies: int,80) -> wp.float64:81depth = wp.float64(0.0)82for material in range(materials):83depth = depth + wp.float64(paths[material * pixels + pixel]) * wp.float64(84coefficients[material * energies + energy]85)86return depth8788return depth_at899091@cache92def get_attenuation(wide: bool):93@wp.func94def attenuate(depth: wp.float64, status: wp.array(dtype=wp.int32)) -> wp.float64:95factor = wp.exp(-depth)96if wp.static(wide):97# A zero exponential may hide a representable weighted tail. This98# explicit range boundary avoids silently disagreeing with the VJP.99if factor == wp.float64(0.0):100wp.atomic_or(status, 0, 4)101return factor102103return attenuate104105106@cache107def get_product(wide: bool):108@wp.func109def product(a: wp.float64, b: wp.float64, status: wp.array(dtype=wp.int32)) -> wp.float64:110result = a * b111if wp.static(wide):112# A later large factor could restore a representable value. Reject113# loss of an intermediate instead of silently returning a zero VJP.114if a != wp.float64(0.0) and b != wp.float64(0.0) and result == wp.float64(0.0):115wp.atomic_or(status, 0, 4)116return result117118return product119120121# region book:spectral-primary-sum122@cache123def get_forward(124materials: int, energies: int, shared_weights: bool, shared_response: bool, wide: bool = False125):126dtype = wp.float64 if wide else wp.float32127optical_depth = get_optical_depth(wide)128attenuate = get_attenuation(wide)129product = get_product(wide)130store_signal = get_signal_store(wide)131132@wp.kernel(module="unique", module_options=STRICT)133def forward(134paths: wp.array(dtype=dtype),135coefficients: wp.array(dtype=wp.float32),136weights: wp.array(dtype=wp.float32),137response: wp.array(dtype=wp.float32),138pixels: int,139mean: wp.array(dtype=dtype),140status: wp.array(dtype=wp.int32),141):142pixel = wp.tid()143total = wp.float64(0.0)144compensation = wp.float64(0.0)145for energy in range(energies):146wi = energy * pixels + pixel147ri = wi148if wp.static(shared_weights):149wi = energy150if wp.static(shared_response):151ri = energy152depth = optical_depth(paths, coefficients, pixel, energy, pixels, materials, energies)153contribution = product(154wp.float64(weights[wi]) * wp.float64(response[ri]), attenuate(depth, status), status155)156# All terms are non-negative, but compensation retains small bins157# when a broad response places many decades in the same sum.158corrected = contribution - compensation159updated = total + corrected160compensation = (updated - total) - corrected161total = updated162mean[pixel] = store_signal(total, status)163164return forward165166167# endregion book:spectral-primary-sum168169170# region book:spectral-recomputed-adjoint171@cache172def get_pixel_vjp(173materials: int,174energies: int,175shared_weights: bool,176shared_response: bool,177write_paths: bool,178write_weights: bool,179write_response: bool,180wide: bool = False,181):182dtype = wp.float64 if wide else wp.float32183optical_depth = get_optical_depth(wide)184attenuate = get_attenuation(wide)185product = get_product(wide)186store_signal = get_signal_store(wide)187gradient_vector = wp.types.vector(length=materials, dtype=wp.float64)188189@wp.kernel(module="unique", module_options=STRICT)190def vjp(191paths: wp.array(dtype=dtype),192coefficients: wp.array(dtype=wp.float32),193weights: wp.array(dtype=wp.float32),194response: wp.array(dtype=wp.float32),195seed: wp.array(dtype=dtype),196pixels: int,197grad_paths: wp.array(dtype=dtype),198grad_weights: wp.array(dtype=wp.float32),199grad_response: wp.array(dtype=wp.float32),200status: wp.array(dtype=wp.int32),201):202pixel = wp.tid()203path_gradient = gradient_vector()204path_compensation = gradient_vector()205for energy in range(energies):206wi = energy * pixels + pixel207ri = wi208if wp.static(shared_weights):209wi = energy210if wp.static(shared_response):211ri = energy212depth = optical_depth(paths, coefficients, pixel, energy, pixels, materials, energies)213weighted_seed = product(wp.float64(seed[pixel]), attenuate(depth, status), status)214if wp.static(write_weights):215grad_weights[wi] = checked_store(216product(weighted_seed, wp.float64(response[ri]), status), status217)218if wp.static(write_response):219grad_response[ri] = checked_store(220product(weighted_seed, wp.float64(weights[wi]), status), status221)222if wp.static(write_paths):223common = product(224product(weighted_seed, wp.float64(weights[wi]), status),225wp.float64(response[ri]),226status,227)228for material in range(materials):229term = product(230-common, wp.float64(coefficients[material * energies + energy]), status231)232corrected = term - path_compensation[material]233updated = path_gradient[material] + corrected234path_compensation[material] = (updated - path_gradient[material]) - corrected235path_gradient[material] = updated236if wp.static(write_paths):237for material in range(materials):238grad_paths[material * pixels + pixel] = store_signal(239path_gradient[material], status240)241242return vjp243244245# endregion book:spectral-recomputed-adjoint246247248@cache249def get_shared_partials(250materials: int,251energies: int,252shared_weights: bool,253shared_response: bool,254kind: int,255wide: bool = False,256):257"""kind=0 weights or 1 response; coefficients have their own shared kernel."""258dtype = wp.float64 if wide else wp.float32259optical_depth = get_optical_depth(wide)260attenuate = get_attenuation(wide)261product = get_product(wide)262if kind not in (0, 1):263raise ValueError("shared partial kind must select weights or response")264265@wp.kernel(module="unique", module_options=STRICT)266def partials(267paths: wp.array(dtype=dtype),268coefficients: wp.array(dtype=wp.float32),269weights: wp.array(dtype=wp.float32),270response: wp.array(dtype=wp.float32),271seed: wp.array(dtype=dtype),272pixels: int,273groups: int,274output: wp.array(dtype=wp.float64),275status: wp.array(dtype=wp.int32),276):277group, parameter, lane = wp.tid()278energy = parameter279total = wp.float64(0.0)280compensation = wp.float64(0.0)281pixel = group * TILE + lane282while pixel < pixels:283wi = energy * pixels + pixel284ri = wi285if wp.static(shared_weights):286wi = energy287if wp.static(shared_response):288ri = energy289depth = optical_depth(paths, coefficients, pixel, energy, pixels, materials, energies)290term = product(wp.float64(seed[pixel]), attenuate(depth, status), status)291if wp.static(kind == 0):292term = product(term, wp.float64(response[ri]), status)293else:294term = product(term, wp.float64(weights[wi]), status)295corrected = term - compensation296updated = total + corrected297compensation = (updated - total) - corrected298total = updated299# The final stride may exceed int32 even when every input index fits.300# Stop before adding it; pixels - pixel is non-negative and bounded.301if pixels - pixel <= groups * TILE:302break303pixel = pixel + groups * TILE304values = wp.tile(total)305result = wp.tile_sum(values)306wp.tile_store(output, result, offset=parameter * groups + group)307308return partials309310311@cache312def get_coefficient_partials(313materials: int, energies: int, shared_weights: bool, shared_response: bool, wide: bool = False314):315"""Reuse each energy's depth across its material cotangents, without a P*K tensor."""316dtype = wp.float64 if wide else wp.float32317optical_depth = get_optical_depth(wide)318attenuate = get_attenuation(wide)319product = get_product(wide)320accumulator = wp.types.vector(length=materials, dtype=wp.float64)321322@wp.kernel(module="unique", module_options=STRICT)323def coefficient_partials(324paths: wp.array(dtype=dtype),325coefficients: wp.array(dtype=wp.float32),326weights: wp.array(dtype=wp.float32),327response: wp.array(dtype=wp.float32),328seed: wp.array(dtype=dtype),329pixels: int,330groups: int,331output: wp.array(dtype=wp.float64),332status: wp.array(dtype=wp.int32),333):334group, energy, lane = wp.tid()335totals = accumulator()336compensations = accumulator()337pixel = group * TILE + lane338while pixel < pixels:339wi = energy * pixels + pixel340ri = wi341if wp.static(shared_weights):342wi = energy343if wp.static(shared_response):344ri = energy345depth = optical_depth(paths, coefficients, pixel, energy, pixels, materials, energies)346# Keep the same product order as the scalar-parameter reduction.347common = product(-wp.float64(seed[pixel]), attenuate(depth, status), status)348common = product(349product(common, wp.float64(weights[wi]), status), wp.float64(response[ri]), status350)351for material in range(materials):352term = product(common, wp.float64(paths[material * pixels + pixel]), status)353corrected = term - compensations[material]354updated = totals[material] + corrected355compensations[material] = (updated - totals[material]) - corrected356totals[material] = updated357# The final stride may exceed int32 even when every input index fits.358# Stop before adding it; pixels - pixel is non-negative and bounded.359if pixels - pixel <= groups * TILE:360break361pixel = pixel + groups * TILE362# Each parameter retains the original lane tree and split-sum order.363for material in range(materials):364values = wp.tile(totals[material])365result = wp.tile_sum(values)366wp.tile_store(output, result, offset=(material * energies + energy) * groups + group)367368return coefficient_partials369370371@cache372def get_finish_shared(output64: bool):373"""One reduction implementation with explicitly chosen scalar destination precision."""374output_dtype = wp.float64 if output64 else wp.float32375376@wp.kernel(module="unique", module_options=STRICT)377def finish_shared(378partials: wp.array(dtype=wp.float64),379groups: int,380output: wp.array(dtype=output_dtype),381status: wp.array(dtype=wp.int32),382):383parameter = wp.tid()384total = wp.float64(0.0)385compensation = wp.float64(0.0)386for group in range(groups):387corrected = partials[parameter * groups + group] - compensation388updated = total + corrected389compensation = (updated - total) - corrected390total = updated391if wp.static(output64):392output[parameter] = total393if not wp.isfinite(total):394wp.atomic_or(status, 0, 2)395else:396output[parameter] = checked_store(total, status)397398return finish_shared399400401finish_shared = get_finish_shared(False)402403404@wp.kernel405def check_probability(values: wp.array(dtype=wp.float32), status: wp.array(dtype=wp.int32)):406p = wp.tid()407if not wp.isfinite(values[p]) or values[p] < wp.float32(0.0) or values[p] > wp.float32(1.0):408wp.atomic_or(status, 0, 1)409410411@cache412def get_bin_counts(413materials: int, energies: int, shared_weights: bool, shared_efficiency: bool, wide: bool = False414):415dtype = wp.float64 if wide else wp.float32416optical_depth = get_optical_depth(wide)417attenuate = get_attenuation(wide)418product = get_product(wide)419store_signal = get_signal_store(wide)420421@wp.kernel(module="unique", module_options=STRICT)422def counts(423paths: wp.array(dtype=dtype),424coefficients: wp.array(dtype=wp.float32),425weights: wp.array(dtype=wp.float32),426efficiency: wp.array(dtype=wp.float32),427pixels: int,428output: wp.array(dtype=dtype),429status: wp.array(dtype=wp.int32),430):431energy, p = wp.tid()432wi = energy * pixels + p433ri = wi434if wp.static(shared_weights):435wi = energy436if wp.static(shared_efficiency):437ri = energy438depth = optical_depth(paths, coefficients, p, energy, pixels, materials, energies)439rate = product(440wp.float64(weights[wi]) * wp.float64(efficiency[ri]), attenuate(depth, status), status441)442output[energy * pixels + p] = store_signal(rate, status)443444return counts445