aboutsummaryrefslogtreecommitdiff
path: root/src/physics
diff options
context:
space:
mode:
Diffstat (limited to 'src/physics')
-rw-r--r--src/physics/electricField.test.ts21
-rw-r--r--src/physics/electricField.ts22
-rw-r--r--src/physics/models.test.ts108
-rw-r--r--src/physics/models.ts112
-rw-r--r--src/physics/test4.test.ts42
-rw-r--r--src/physics/test4.ts36
6 files changed, 0 insertions, 341 deletions
diff --git a/src/physics/electricField.test.ts b/src/physics/electricField.test.ts
deleted file mode 100644
index 2e01309..0000000
--- a/src/physics/electricField.test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { describe, expect, it } from 'vitest';
-import { electricField, COULOMB_CONSTANT } from './electricField';
-
-describe('Coulomb electric field', () => {
- it('points away from a positive charge with inverse-square magnitude', () => {
- const field = electricField({ x: 2, y: 0, z: 0 }, [{ x: 0, y: 0, z: 0, coulombs: 1e-9 }]);
- expect(field.x).toBeCloseTo(COULOMB_CONSTANT * 1e-9 / 4);
- expect(field.y).toBe(0);
- expect(field.z).toBe(0);
- });
- it('superposes opposite charges in the same direction at the midpoint', () => {
- const field = electricField({ x: 0, y: 0, z: 0 }, [
- { x: -1, y: 0, z: 0, coulombs: 1e-9 },
- { x: 1, y: 0, z: 0, coulombs: -1e-9 },
- ]);
- expect(field.x).toBeCloseTo(2 * COULOMB_CONSTANT * 1e-9);
- });
- it('omits singular samples within the visualization exclusion radius', () => {
- expect(electricField({ x: 0, y: 0, z: 0 }, [{ x: 0, y: 0, z: 0, coulombs: 1e-9 }])).toEqual({ x: 0, y: 0, z: 0 });
- });
-});
diff --git a/src/physics/electricField.ts b/src/physics/electricField.ts
deleted file mode 100644
index 26975e3..0000000
--- a/src/physics/electricField.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-export interface Point { x: number; y: number; z: number }
-export interface Charge extends Point { coulombs: number }
-export const COULOMB_CONSTANT = 8.9875517923e9;
-
-/** SI units: positions in metres, charges in coulombs, output in N/C.
- * Samples inside 0.18 m of a charge are omitted to avoid visual singularities.
- */
-export function electricField(point: Point, charges: readonly Charge[]): Point {
- const field = { x: 0, y: 0, z: 0 };
- for (const charge of charges) {
- const dx = point.x - charge.x;
- const dy = point.y - charge.y;
- const dz = point.z - charge.z;
- const r2 = dx * dx + dy * dy + dz * dz;
- if (r2 < 0.18 ** 2) continue;
- const factor = COULOMB_CONSTANT * charge.coulombs / (r2 * Math.sqrt(r2));
- field.x += factor * dx;
- field.y += factor * dy;
- field.z += factor * dz;
- }
- return field;
-}
diff --git a/src/physics/models.test.ts b/src/physics/models.test.ts
deleted file mode 100644
index 3ff6487..0000000
--- a/src/physics/models.test.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { describe, expect, it } from 'vitest';
-import { electricField } from './electricField';
-import { potential, forceOn, pairEnergy, gaussianFlux, capacitor, conductor, semiconductor } from './models';
-
-// Fixtures use hand-calculated SI results, independent of model implementations.
-describe('charge, potential and energy', () => {
- const source = [{ x: 0, y: 0, z: 0, coulombs: 2e-9 }];
- it('gives scalar potential tending to zero at infinity', () => {
- expect(potential({ x: 2, y: 0, z: 0 }, source)).toBeCloseTo(8.98755, 4);
- expect(potential({ x: 1e9, y: 0, z: 0 }, source)).toBeCloseTo(0, 6);
- });
- it('makes the field the negative spatial gradient of potential', () => {
- const h = 1e-4;
- const numerical = -(potential({ x: 2 + h, y: 0, z: 0 }, source) - potential({ x: 2 - h, y: 0, z: 0 }, source)) / (2 * h);
- expect(electricField({ x: 2, y: 0, z: 0 }, source).x).toBeCloseTo(numerical, 5);
- });
- it('reverses force for a negative test charge', () => {
- expect(forceOn({ x: 2, y: 0, z: 0, coulombs: -3e-9 }, source).x).toBeCloseTo(-13.48132768845e-9, 16);
- });
- it('gives negative interaction energy for opposite charges', () => {
- expect(pairEnergy(2e-9, -3e-9, 2)).toBeCloseTo(-26.9626553769e-9, 16);
- });
- it('marks potential at a charge as undefined instead of inventing a finite value', () => {
- expect(potential({ x: 0, y: 0, z: 0 }, source)).toBeNaN();
- expect(() => pairEnergy(1, 1, 0)).toThrow(RangeError);
- });
-});
-
-describe('Gauss law', () => {
- it('counts only enclosed charge, including its sign', () => {
- const charges = [{ x: 0, y: 0, z: 0, coulombs: -2e-9 }, { x: 3, y: 0, z: 0, coulombs: 5e-9 }];
- expect(gaussianFlux(charges, 2)).toBeCloseTo(-225.8818, 3);
- expect(gaussianFlux(charges, 4)).toBeCloseTo(338.8227, 3);
- });
- it('does not depend on surface size when enclosed charge is unchanged', () => {
- const charges = [{ x: 0, y: 0, z: 0, coulombs: 1e-9 }];
- expect(gaussianFlux(charges, 1)).toBe(gaussianFlux(charges, 2));
- });
- it('marks charge on the Gaussian surface as undefined', () => {
- expect(gaussianFlux([{ x: 2, y: 0, z: 0, coulombs: 1e-9 }], 2)).toBeNaN();
- });
-});
-
-describe('parallel plate capacitor', () => {
- it('converts physical geometry into C, Q, E and stored energy', () => {
- const c = capacitor({ area: 0.01, gap: 0.001, relativePermittivity: 1, voltage: 10 });
- expect(c.capacitance).toBeCloseTo(8.8541878128e-11, 18);
- expect(c.charge).toBeCloseTo(8.8541878128e-10, 18);
- expect(c.field).toBe(10000);
- expect(c.energy).toBeCloseTo(4.4270939064e-9, 17);
- });
- it('increases free charge at fixed voltage when dielectric is inserted', () => {
- const c = capacitor({ area: 0.01, gap: 0.001, relativePermittivity: 4, voltage: 10 });
- expect(c.field).toBe(10000);
- expect(c.charge).toBeCloseTo(3.54167512512e-9, 17);
- expect(c.polarization).toBeCloseTo(2.65625634384e-7, 15);
- });
- it('reduces voltage and energy at fixed free charge', () => {
- const c = capacitor({ area: 0.01, gap: 0.001, relativePermittivity: 4, voltage: 10, isolated: true });
- expect(c.charge).toBeCloseTo(8.8541878128e-10, 18);
- expect(c.voltage).toBeCloseTo(2.5);
- expect(c.field).toBeCloseTo(2500);
- expect(c.energy).toBeCloseTo(1.1067734766e-9, 17);
- });
- it('rejects impossible geometry', () => {
- expect(() => capacitor({ area: 0.01, gap: 0, relativePermittivity: 1, voltage: 10 })).toThrow(RangeError);
- });
-});
-
-describe('current and drift', () => {
- it('follows Ohm law and sends electrons opposite conventional current', () => {
- const wire = conductor({ voltage: 2, resistivity: 1.68e-8, length: 1, area: 1e-6, carrierDensity: 8.5e28 });
- expect(wire.resistance).toBeCloseTo(0.0168, 6);
- expect(wire.current).toBeCloseTo(119.047619, 5);
- expect(wire.electronDrift).toBeCloseTo(-0.00874160935, 10);
- expect(wire.power).toBeCloseTo(238.095238, 5);
- });
- it('reverses current but keeps dissipated power positive with reversed voltage', () => {
- const wire = conductor({ voltage: -2, resistivity: 1.68e-8, length: 1, area: 1e-6, carrierDensity: 8.5e28 });
- expect(wire.current).toBeLessThan(0);
- expect(wire.electronDrift).toBeGreaterThan(0);
- expect(wire.power).toBeGreaterThan(0);
- });
-});
-
-describe('semiconductor equilibrium at 300 K', () => {
- it('has equal intrinsic electrons and holes', () => {
- const s = semiconductor({ intrinsicDensity: 1e16, netDoping: 0, electronMobility: 0.135, holeMobility: 0.048 });
- expect(s.electrons).toBe(1e16);
- expect(s.holes).toBe(1e16);
- expect(s.fermiShift).toBe(0);
- expect(s.conductivity).toBeCloseTo(0.0002931983, 9);
- });
- it('conserves mass action and charge neutrality even at heavy donor doping', () => {
- const s = semiconductor({ intrinsicDensity: 1e16, netDoping: 1e22, electronMobility: 0.135, holeMobility: 0.048 });
- expect(s.electrons / 1e22).toBeCloseTo(1, 10);
- expect(s.holes).toBeCloseTo(1e10, 0);
- expect(s.electrons * s.holes / 1e32).toBeCloseTo(1, 12);
- expect((s.electrons - s.holes) / 1e22).toBeCloseTo(1, 12);
- expect(s.fermiShift).toBeCloseTo(0.3571586, 5);
- });
- it('makes holes majority carriers for acceptor doping', () => {
- const s = semiconductor({ intrinsicDensity: 1e16, netDoping: -1e22, electronMobility: 0.135, holeMobility: 0.048 });
- expect(s.holes / 1e22).toBeCloseTo(1, 10);
- expect(s.electrons).toBeCloseTo(1e10, 0);
- expect(s.fermiShift).toBeLessThan(0);
- });
-});
diff --git a/src/physics/models.ts b/src/physics/models.ts
deleted file mode 100644
index 8fb121f..0000000
--- a/src/physics/models.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-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),
- };
-}
diff --git a/src/physics/test4.test.ts b/src/physics/test4.test.ts
deleted file mode 100644
index 08dd868..0000000
--- a/src/physics/test4.test.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { describe, expect, it } from 'vitest';
-import { assembledEnergy, centralWork, dipoleState, energyTransfer, uniformWork } from './test4';
-
-describe('Test 4 physical answers', () => {
- it('uses only displacement along the uniform field and preserves the charge sign', () => {
- expect(uniformWork(2e-9, 100, 3, Math.PI / 3)).toBeCloseTo(3e-7, 14);
- expect(uniformWork(-2e-9, 100, 3, 0)).toBeCloseTo(-6e-7, 14);
- expect(uniformWork(2e-9, 100, 3, Math.PI / 2)).toBeCloseTo(0, 14);
- });
- it('computes point-charge work including the analytic infinity reference', () => {
- expect(centralWork(3e-9, 2e-9, 2, 4)).toBeCloseTo(1.348132768845e-8, 16);
- expect(centralWork(3e-9, -2e-9, 2, Infinity)).toBeCloseTo(-2.69626553769e-8, 16);
- expect(() => centralWork(1, 1, 0, 2)).toThrow(RangeError);
- });
- it('converts the potential drop to kinetic energy without changing total energy', () => {
- const state = energyTransfer(3e-8, 1e-8, 5e-9, 1e-6);
- expect(state.kinetic).toBeCloseTo(2.5e-8, 16);
- expect(state.total).toBeCloseTo(3.5e-8, 16);
- expect(state.speed).toBeCloseTo(0.22360679775, 10);
- expect(() => energyTransfer(0, 1, 0, 1)).toThrow(RangeError);
- });
- it('counts each pair once when building a charge configuration', () => {
- const charges = [
- { x: 0, y: 0, z: 0, coulombs: 1e-9 },
- { x: 1, y: 0, z: 0, coulombs: 1e-9 },
- { x: 0, y: 0, z: 0.75, coulombs: -1e-9 },
- ];
- expect(assembledEnergy(charges.slice(0, 1))).toBe(0);
- expect(assembledEnergy(charges)).toBeCloseTo(-1.0185892031273e-8, 16);
- expect(() => assembledEnergy([charges[0], charges[0]])).toThrow(RangeError);
- });
- it('distinguishes signed restoring torque, rotation work and zero at 90 degrees', () => {
- const state = dipoleState(2e-9, 100, Math.PI / 2, 0);
- expect(state.energy).toBeCloseTo(0, 14);
- expect(state.torqueZ).toBeCloseTo(-2e-7, 14);
- expect(state.work).toBeCloseTo(-2e-7, 14);
- const aligned = dipoleState(2e-9, 100, 0, Math.PI / 2);
- expect(aligned.energy).toBeCloseTo(-2e-7, 14);
- expect(aligned.work).toBeCloseTo(2e-7, 14);
- expect(dipoleState(2e-9, 100, Math.PI, 0).energy).toBeCloseTo(2e-7, 14);
- });
-});
diff --git a/src/physics/test4.ts b/src/physics/test4.ts
deleted file mode 100644
index a4d4be5..0000000
--- a/src/physics/test4.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { COULOMB_CONSTANT, type Charge } from './electricField';
-
-export function uniformWork(q: number, field: number, distance: number, angle: number): number {
- return q * field * distance * Math.cos(angle);
-}
-
-export function centralWork(q: number, q0: number, ra: number, rb: number): number {
- if (!Number.isFinite(ra) || ra <= 0 || rb <= 0 || Number.isNaN(rb)) throw new RangeError('Distances must be positive; only the final distance may be infinity.');
- return COULOMB_CONSTANT * q * q0 * (1 / ra - 1 / rb);
-}
-
-export function energyTransfer(initialPotential: number, potential: number, initialKinetic: number, mass: number) {
- const kinetic = initialKinetic + initialPotential - potential;
- if (kinetic < -1e-20 || mass <= 0) throw new RangeError('This position is energetically inaccessible or the mass is invalid.');
- return { kinetic: Math.max(0, kinetic), total: initialKinetic + initialPotential, speed: Math.sqrt(2 * Math.max(0, kinetic) / mass) };
-}
-
-export function assembledEnergy(charges: readonly Charge[]): number {
- let energy = 0;
- for (let i = 0; i < charges.length; i++) for (let j = i + 1; j < charges.length; j++) {
- const a = charges[i], b = charges[j];
- const distance = Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
- if (distance === 0) throw new RangeError('Point charges must have distinct positions.');
- energy += COULOMB_CONSTANT * a.coulombs * b.coulombs / distance;
- }
- return energy;
-}
-
-/** Dipole in the xy plane, E along +x; positive theta is counterclockwise. */
-export function dipoleState(moment: number, field: number, theta: number, thetaA: number) {
- return {
- energy: -moment * field * Math.cos(theta),
- torqueZ: -moment * field * Math.sin(theta),
- work: moment * field * (Math.cos(theta) - Math.cos(thetaA)),
- };
-}