aboutsummaryrefslogtreecommitdiff
path: root/src/physics/models.ts
blob: 8fb121ff9ff56780e493e4f06a3597cbb90b7b94 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { COULOMB_CONSTANT, electricField, type Charge, type Point } from './electricField';

export const VACUUM_PERMITTIVITY = 8.8541878128e-12;
export const ELEMENTARY_CHARGE = 1.602176634e-19;
const THERMAL_ENERGY_EV = 8.617333262e-5 * 300;

function positive(...values: number[]) {
  if (values.some(value => !Number.isFinite(value) || value <= 0)) {
    throw new RangeError('Physical dimensions and material constants must be finite and positive.');
  }
}

/** Vacuum potential in volts, referenced to infinity. Undefined inside the
 * same 0.18 m visualization exclusion used by electricField. */
export function potential(point: Point, charges: readonly Charge[]): number {
  let volts = 0;
  for (const charge of charges) {
    if (charge.coulombs === 0) continue;
    const r = Math.hypot(point.x - charge.x, point.y - charge.y, point.z - charge.z);
    if (r < 0.18) return NaN;
    volts += COULOMB_CONSTANT * charge.coulombs / r;
  }
  return volts;
}

/** Force on a test charge, excluding that charge from the supplied sources. */
export function forceOn(test: Charge, sources: readonly Charge[]): Point {
  const field = electricField(test, sources);
  return { x: field.x * test.coulombs, y: field.y * test.coulombs, z: field.z * test.coulombs };
}

export function pairEnergy(q1: number, q2: number, separation: number): number {
  positive(separation);
  return COULOMB_CONSTANT * q1 * q2 / separation;
}

/** Exact signed flux for an origin-centred spherical Gaussian surface.
 * A point charge exactly on the surface makes the integral undefined. */
export function gaussianFlux(charges: readonly Charge[], radius: number): number {
  positive(radius);
  let enclosed = 0;
  for (const charge of charges) {
    if (charge.coulombs === 0) continue;
    const r = Math.hypot(charge.x, charge.y, charge.z);
    if (Math.abs(r - radius) < 1e-9) return NaN;
    if (r < radius) enclosed += charge.coulombs;
  }
  return enclosed / VACUUM_PERMITTIVITY;
}

export interface CapacitorParameters {
  area: number;
  gap: number;
  relativePermittivity: number;
  voltage: number;
  /** If isolated, voltage means the initial vacuum charging voltage. */
  isolated?: boolean;
}
export function capacitor(p: CapacitorParameters) {
  positive(p.area, p.gap, p.relativePermittivity);
  const vacuumCapacitance = VACUUM_PERMITTIVITY * p.area / p.gap;
  const capacitance = vacuumCapacitance * p.relativePermittivity;
  const voltage = p.isolated ? p.voltage / p.relativePermittivity : p.voltage;
  const charge = capacitance * voltage;
  const field = voltage / p.gap;
  return {
    capacitance, voltage, charge, field,
    energy: 0.5 * capacitance * voltage ** 2,
    polarization: VACUUM_PERMITTIVITY * (p.relativePermittivity - 1) * field,
  };
}

export interface ConductorParameters {
  voltage: number;
  resistivity: number;
  length: number;
  area: number;
  carrierDensity: number;
}
export function conductor(p: ConductorParameters) {
  positive(p.resistivity, p.length, p.area, p.carrierDensity);
  const resistance = p.resistivity * p.length / p.area;
  const current = p.voltage / resistance;
  return {
    resistance, current,
    power: p.voltage * current,
    density: current / p.area,
    electronDrift: -current / (p.carrierDensity * ELEMENTARY_CHARGE * p.area),
  };
}

export interface SemiconductorParameters {
  intrinsicDensity: number;
  /** Fully ionized donors minus acceptors, in m^-3. */
  netDoping: number;
  electronMobility: number;
  holeMobility: number;
}
/** Non-degenerate thermal equilibrium at 300 K. Solving for the minority
 * through mass action avoids catastrophic subtraction at large doping. */
export function semiconductor(p: SemiconductorParameters) {
  positive(p.intrinsicDensity, p.electronMobility, p.holeMobility);
  const majority = (Math.abs(p.netDoping) + Math.hypot(p.netDoping, 2 * p.intrinsicDensity)) / 2;
  const minority = p.intrinsicDensity ** 2 / majority;
  const electrons = p.netDoping >= 0 ? majority : minority;
  const holes = p.netDoping >= 0 ? minority : majority;
  return {
    electrons, holes,
    fermiShift: THERMAL_ENERGY_EV * Math.log(electrons / p.intrinsicDensity),
    conductivity: ELEMENTARY_CHARGE * (electrons * p.electronMobility + holes * p.holeMobility),
  };
}