Thermal radiation¶
A hot cell's radiated energy carried out along its rays, each cell it reaches absorbing what it does not let through; energy conserved.
- Requires
- the
Radiatedenergy each cell sends this step E (J; zero where nothing is hot enough), each cell'sTransmittancet (1, 0..1: the share of what reaches it that it lets through), theReachr of a source (cells). - Provides
- the energy each cell
Absorbs(J), and the energy that left the reach, or the lattice, unabsorbed (J), the two together the energy sent, exactly as the sum of the parts. - Law
- a source's energy is split equally among the rays from it to its reach's rim (rays.fan
rim); along a ray, each cell absorbs (1 - t) of what arrives (the fold of what the cells before let on) and lets t of it on; what is left at the rim, or where the ray leaves the lattice, escapes. The rays' spreading is the shell's falloff, and an opaque cell (t = 0) shadows everything behind it. - Source
- Modest, Radiative Heat Transfer, 3rd ed., ch. 1 and ch. 21 (the discrete ordinates and ray tracing); Incropera et al., Fundamentals of Heat and Mass Transfer, ch. 12.
- Proof
- invariant, limit, curve.
No Trait form: waits on the middle: the rays are rays.fan's samples, a relation of ray and step, and the law a fold over `ordered(it.ray, it.step)` with `@ before` segmented by ray, which draft 5 does not write yet; until then a reference.
laws.radiate(emitted, transmittance, reach)A hot cell's energy carried out along its rays, each cell absorbing what it does not let through; every joule absorbed or leaving the reach.
import os, sys
root = os.getcwd()
while not os.path.isfile(os.path.join(root, 'docsite.json')):
root = os.path.dirname(root)
sys.path.insert(0, os.path.join(root, 'tools'))
import numpy as np
import matplotlib.pyplot as plt
from lusine import library
from lusine.kernels.laws.radiate import radiate
The reference¶
The kernel's reference, in numpy, over rays.fan's samples to the rim, what arrives folded with @ before; its Trait form is not written yet.
library.reference(radiate)
the reference, radiate.py
def radiate(emitted, transmittance, reach):
"""THERMAL RADIATION: a hot cell's radiated energy carried out along its
rays, each cell it reaches absorbing what it does not let through; energy
conserved.
Requires: the `Radiated` energy each cell sends this step E (J; zero where
nothing is hot enough), each cell's `Transmittance` t (1, 0..1: the share
of what reaches it that it lets through), the `Reach` r of a source
(cells).
Provides: the energy each cell `Absorbs` (J), and the energy that left
the reach, or the lattice, unabsorbed (J), the two together the energy
sent, exactly as the sum of the parts.
Law: a source's energy is split equally among the rays from it to its
reach's rim (rays.fan `rim`); along a ray, each cell absorbs (1 - t) of
what arrives (the fold of what the cells before let on) and
lets t of it on; what is left at the rim, or where the ray leaves the
lattice, escapes. The rays' spreading is the shell's falloff, and an
opaque cell (t = 0) shadows everything behind it.
Source: Modest, Radiative Heat Transfer, 3rd ed., ch. 1 and ch. 21 (the
discrete ordinates and ray tracing); Incropera et al., Fundamentals of Heat
and Mass Transfer, ch. 12.
Proof: invariant, limit, curve."""
E = np.asarray(emitted, dtype=np.float64)
t = np.asarray(transmittance, dtype=np.float64)
absorbed = np.zeros_like(E)
escaped = 0.0
n = len(rim(reach))
on = lambda left, tt: left - left * (1.0 - tt) # what a cell lets on of what reaches it
for sy, sx in sources(E):
share = E[sy, sx] / n
ray, step, cell = fan(E.shape, (sy, sx), reach, rays="rim")
tv = t[tuple(cell.T)]
arrives = before(tv, ray, step, on, share)
left = {} # each ray's energy past its last sample inside
for i in np.argsort(ray * (int(step.max()) + 1 if len(step) else 1) + step, kind="stable"):
y, x = int(cell[i, 0]), int(cell[i, 1])
absorbed[y, x] += arrives[i] * (1.0 - tv[i])
left[int(ray[i])] = on(arrives[i], tv[i])
for k in range(n):
escaped += left.get(k, share) # a ray with no cell inside leaves whole
return absorbed, escaped
Invariant: energy is conserved¶
Over any field of sources and any medium, what the cells absorb and what leaves the reach is what was sent, to rounding.
rng = np.random.default_rng(7)
for _ in range(5):
E = np.where(rng.random((30, 30)) < 0.04, rng.random((30, 30)) * 50, 0.0)
t = rng.random((30, 30))
a, out = radiate(E, t, 9)
assert abs(float(a.sum()) + out - float(E.sum())) < 1e-9 * float(E.sum())
print('absorbed + escaped = emitted')
absorbed + escaped = emitted
Limit: opaque and clear¶
Walled in by opaque cells a source's energy all lands in the wall; in a clear medium nothing is absorbed.
one = np.zeros((21, 21)); one[10, 10] = 1.0
box = np.ones((21, 21)); box[8:13, 8:13] = 0.0; box[9:12, 9:12] = 1.0
a, out = radiate(one, box, 9)
assert out == 0.0 and abs(float(a.sum()) - 1) < 1e-12
a, out = radiate(one, np.ones((21, 21)), 9)
assert float(a.sum()) == 0.0 and abs(out - 1) < 1e-12
print('opaque and clear right')
opaque and clear right
Curve: Beer-Lambert ring by ring¶
In a uniform absorber taking m of what reaches each cell, the k-th ring out takes m (1 - m)^(k-1) of the energy, until the rays reach the rim.
m = 0.2
one = np.zeros((25, 25)); one[12, 12] = 1.0
a, out = radiate(one, np.full((25, 25), 1 - m), 11)
ks = np.arange(1, 8)
rings = np.array([sum(a[12 + dy, 12 + dx] for dy in range(-k, k + 1) for dx in range(-k, k + 1) if max(abs(dy), abs(dx)) == k) for k in ks])
want = m * (1 - m) ** (ks - 1)
assert np.allclose(rings, want, rtol=1e-12, atol=0)
fig, ax = plt.subplots(figsize=(7, 3.2))
ax.semilogy(ks, rings, 'o', label='kernel, by ring')
ax.semilogy(ks, want, label='m (1 - m)^(k-1)')
ax.set_xlabel('ring')
ax.set_ylabel('share absorbed')
ax.legend()
plt.show()