aboutsummaryrefslogtreecommitdiff
path: root/src/physics
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/physics/test4.test.ts42
-rw-r--r--src/physics/test4.ts36
2 files changed, 78 insertions, 0 deletions
diff --git a/src/physics/test4.test.ts b/src/physics/test4.test.ts
new file mode 100644
index 0000000..08dd868
--- /dev/null
+++ b/src/physics/test4.test.ts
@@ -0,0 +1,42 @@
+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
new file mode 100644
index 0000000..a4d4be5
--- /dev/null
+++ b/src/physics/test4.ts
@@ -0,0 +1,36 @@
+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)),
+ };
+}