// Figures for Part II: the square's modes, the anisotropy of the grid, the
// three stencils of Chapter 5, and the two rims of Chapter 6.
import type { ReactElement } from 'react'
import { Figure, Label, Node } from './svg.tsx'
import { faded, familyColor, ink, method } from '../plot/palette.ts'
const pts = (xy: [number, number][]) => xy.map(([x, y], i) => `${i ? 'L' : 'M'}${x.toFixed(1)} ${y.toFixed(1)}`).join('')
/** Six modes of the square membrane, with their frequencies relative to the lowest. */
export function Modes() {
const modes: [number, number][] = [
[1, 1],
[2, 1],
[2, 2],
[3, 1],
[3, 2],
[3, 3],
]
const s = 92
const gap = 22
const x0 = (716 - (6 * s + 5 * gap)) / 2
const y0 = 18
const n = 18
return (
{modes.map(([m, k], q) => {
const ox = x0 + q * (s + gap)
const cells: ReactElement[] = []
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
const v = Math.sin((m * Math.PI * (j + 0.5)) / n) * Math.sin((k * Math.PI * (i + 0.5)) / n)
const c = v > 0 ? method.boundary : method.fd2
cells.push(
,
)
}
}
return (
{cells}
)
})}
)
}
/** Where a circular wavefront ends up on the grid, for a short wavelength. */
export function Anisotropy() {
const cx = 250
const cy = 200
const R = 170
const P = 3
const kh = (2 * Math.PI) / P
const speed = (th: number, lam: number) => {
const s2 = Math.sin((kh * Math.cos(th)) / 2) ** 2 + Math.sin((kh * Math.sin(th)) / 2) ** 2
return (2 * Math.asin(lam * Math.sqrt(s2))) / (lam * kh)
}
const lams = [0.3, 0.5, Math.SQRT1_2]
const front = (lam: number) => {
const out: [number, number][] = []
for (let i = 0; i <= 90; i++) {
const th = (Math.PI / 2) * (i / 90)
const r = R * speed(th, lam)
out.push([cx + r * Math.cos(th), cy - r * Math.sin(th)])
}
return pts(out)
}
return (
{lams.map((l, k) => (
))}
{lams.map((l, k) => (
))}
)
}
/** The nodes each of Chapter 5's schemes reads to update one node. */
export function Stencils() {
const g = 22
const panel = (ox: number, title: string, sub: string, set: [number, number, string][]) => {
const cy = 100
const grid: ReactElement[] = []
for (let i = -2; i <= 2; i++) {
for (let j = -2; j <= 2; j++) {
grid.push()
}
}
return (
{grid}
{set.map(([i, j, c]) => (
))}
)
}
const five: [number, number, string][] = [
[0, 0, method.fd2],
[0, 1, method.fd2],
[0, -1, method.fd2],
[1, 0, method.fd2],
[-1, 0, method.fd2],
]
const cross = (c: string): [number, number, string][] => [
[0, 0, c],
[0, 1, c],
[0, -1, c],
[1, 0, c],
[-1, 0, c],
[0, 2, c],
[0, -2, c],
[2, 0, c],
[-2, 0, c],
]
const both: [number, number, string][] = [
...cross(method.fd4),
[1, 1, faded(method.fd4, 0.35)],
[1, -1, faded(method.fd4, 0.35)],
[-1, 1, faded(method.fd4, 0.35)],
[-1, -1, faded(method.fd4, 0.35)],
]
return (
{panel(130, 'second order', 'five-point cross', five)}
{panel(358, 'fourth order in space', 'nine-point cross', cross(method.waveguide))}
{panel(586, 'fourth order in both', 'thirteen nodes', both)}
)
}
/** A curved rim on a square grid: staircase against Shortley-Weller. */
export function Rim() {
const g = 34
const panel = (ox: number, sw: boolean) => {
// a circle of radius R centred below-left of the panel, so that its arc
// runs diagonally across it
const R = 7.3
const cxg = -2.2
const cyg = -2.6
const inside = (i: number, j: number) => (j - cxg) ** 2 + (i - cyg) ** 2 < R * R
const X = (j: number) => ox + j * g
const Y = (i: number) => 206 - i * g
const arc: [number, number][] = []
for (let k = 0; k <= 60; k++) {
const t = (Math.PI / 2) * (k / 60)
const j = cxg + R * Math.cos(t)
const i = cyg + R * Math.sin(t)
if (j >= -0.4 && j <= 6.4 && i >= -0.4 && i <= 5.4) arc.push([X(j), Y(i)])
}
const els: ReactElement[] = []
for (let i = 0; i <= 5; i++) {
for (let j = 0; j <= 6; j++) {
const inn = inside(i, j)
els.push(
,
)
if (inn && sw) {
// where the rim crosses the grid line to an outside neighbour
if (!inside(i, j + 1) && j + 1 <= 6.4) {
const jc = cxg + Math.sqrt(R * R - (i - cyg) ** 2)
els.push()
els.push()
}
if (!inside(i + 1, j) && i + 1 <= 5.4) {
const ic = cyg + Math.sqrt(R * R - (j - cxg) ** 2)
els.push()
els.push()
}
}
}
}
// the staircase: the stencil reaches outside neighbours, which are held
// at zero as if the rim ran through them
const stair: ReactElement[] = []
if (!sw) {
for (let i = 0; i <= 5; i++) {
for (let j = 0; j <= 6; j++) {
if (!inside(i, j)) continue
const nb: [number, number][] = [
[i, j + 1],
[i + 1, j],
]
for (const [a, b] of nb) {
if (a > 5 || b > 6 || inside(a, b)) continue
stair.push()
stair.push()
}
}
}
}
return (
{stair}
{els}
)
}
return (
{panel(70, false)}
{panel(430, true)}
)
}