aboutsummaryrefslogtreecommitdiff
path: root/src/test4.ts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/test4.ts232
1 files changed, 232 insertions, 0 deletions
diff --git a/src/test4.ts b/src/test4.ts
new file mode 100644
index 0000000..9bd32d8
--- /dev/null
+++ b/src/test4.ts
@@ -0,0 +1,232 @@
+import { format, number, type Choice, type Experiment, type Measurement, type Parameters, type Slider, type TestId } from './experiments';
+import { type Charge } from './physics/electricField';
+import { pairEnergy } from './physics/models';
+import { assembledEnergy, centralWork, dipoleState, energyTransfer, uniformWork } from './physics/test4';
+import questionUrl from '../docs/Test_4.md?url';
+import answerUrl from '../docs/Test_Answers.md?url';
+
+export { answerUrl };
+export interface AnswerPart { title: string; steps: { why: string; math?: string }[] }
+export interface TestLesson extends Experiment {
+ id: TestId;
+ points: number;
+ section: '4A' | '4B';
+ questions: string[];
+ answers: AnswerPart[];
+ prediction: string;
+ predictionAnswer: string;
+ observe: (p: Parameters) => string;
+}
+const slider = (id: string, label: string, min: number, max: number, step: number, value: number, unit: string): Slider => ({ kind: 'range', id, label, min, max, step, value, unit });
+const choice = (id: string, label: string, value: string, options: [string, string][]): Choice => ({ kind: 'select', id, label, value, options: options.map(([value, label]) => ({ value, label })) });
+const metric = (label: string, value: number, unit = 'nJ'): Measurement => ({ label, value: format(Math.abs(value) < 1e-10 ? 0 : value), unit });
+const energy = (label: string, joules: number) => metric(label, joules * 1e9);
+const radians = (degrees: number) => degrees * Math.PI / 180;
+export const progress = (p: Parameters) => number(p, 'progress') / 100;
+export const movingCharge = (p: Parameters) => number(p, 'q0') * 1e-9;
+export function motionState(p: Parameters) {
+ const field = number(p, 'field');
+ const s = number(p, 'distance');
+ const angle = radians(number(p, 'angle') || 0);
+ const work = uniformWork(movingCharge(p), field, s, angle);
+ return { field, s, angle, work, dx: s * Math.cos(angle), dy: s * Math.sin(angle) };
+}
+export function centralState(p: Parameters) {
+ const q = number(p, 'source') * 1e-9, q0 = movingCharge(p);
+ const ra = number(p, 'ra');
+ const rb = p.destination === 'infinity' ? Infinity : number(p, 'rb');
+ return { q, q0, ra, rb, ua: pairEnergy(q, q0, ra), ub: rb === Infinity ? 0 : pairEnergy(q, q0, rb), work: centralWork(q, q0, ra, rb) };
+}
+export function conservationState(p: Parameters) {
+ const r = 1.2 + progress(p) * 4.3;
+ const q0 = movingCharge(p);
+ const ua = pairEnergy(3e-9, q0, 1.2), u = pairEnergy(3e-9, q0, r);
+ return { r, ua, u, ...energyTransfer(ua, u, number(p, 'kinetic') * 1e-9, 1e-6) };
+}
+export function assemblyCharges(p: Parameters): Charge[] {
+ const s = number(p, 'spacing');
+ return [
+ { x: -s / 2, y: 0, z: -s / 3, coulombs: 3e-9 },
+ { x: s / 2, y: 0, z: -s / 3, coulombs: -2e-9 },
+ { x: 0, y: 0, z: s * 0.6, coulombs: 2e-9 },
+ { x: 0, y: s * 0.8, z: 0, coulombs: movingCharge(p) },
+ ];
+}
+export function assemblyState(p: Parameters) {
+ const charges = assemblyCharges(p), stage = Number(p.stage);
+ const base = assembledEnergy(charges.slice(0, 3));
+ const total = assembledEnergy(charges);
+ return { charges, stage, base, extra: total - base, current: assembledEnergy(charges.slice(0, stage)) };
+}
+export function rotationState(p: Parameters) {
+ const a = radians(number(p, 'thetaA')), b = radians(number(p, 'thetaB'));
+ const theta = a + (b - a) * progress(p);
+ const moment = number(p, 'charge') * 1e-9 * number(p, 'length');
+ const field = number(p, 'field');
+ return { a, b, theta, moment, field, ...dipoleState(moment, field, theta, a), endpointWork: dipoleState(moment, field, b, a).work };
+}
+const common = {
+ source: { title: 'Original question · Test_4.md', url: questionUrl },
+ legend: [['#ff967b', 'Positive charge'], ['#73d7ff', 'Negative charge'], ['#91e2cb', 'Electric field E'], ['#f5d58b', 'Path / force / energy']] as [string, string][],
+};
+const motionControls = [slider('q0', 'Moving charge q₀', -4, 4, 0.5, 2, 'nC'), slider('field', 'Electric field E', 0, 80, 5, 30, 'V/m'), slider('distance', 'Displacement s', 1, 6, 0.25, 4, 'm')];
+const journey = () => slider('progress', 'Journey progress', 0, 100, 1, 35, '%');
+
+export const testLessons: TestLesson[] = [
+ {
+ ...common, id: 'test-work', title: 'Work along a path', shortTitle: 'Work & paths', points: 30, section: '4A',
+ description: 'Follow a charge from a to b. Only the component of force along each tiny step contributes to work.',
+ questions: [String.raw`What is the work done by the electric force, when $q_0$ is moved from a to b?`],
+ controls: [...motionControls, choice('path', 'Path shape', 'straight', [['straight', 'Straight path'], ['curved', 'Curved path · same endpoints']]), journey()],
+ equations: [String.raw`W_{ab} = \int_a^b \vec F_e\cdot\mathrm d\vec r = q_0\int_a^b \vec E\cdot\mathrm d\vec r`, String.raw`W_{ab} = U_a-U_b = q_0(V_a-V_b)`],
+ explanation: 'The dot product measures how much the electric force helps or opposes each step. Electrostatic work depends on the endpoints. Positive field work means a drop in electric potential energy.',
+ challenge: 'Switch between the two paths. Predict the final work before changing the moving charge from positive to negative.',
+ prediction: 'Does the longer, curved route require more work by the electric field?',
+ predictionAnswer: String.raw`No. In an electrostatic field, $W=U_a-U_b$. Both routes start and finish at the same positions. Sideways steps are perpendicular to $E$ and contribute no work. Any extra motion along $E$ cancels the motion back against $E$.`,
+ sceneNote: String.raw`Example: uniform $E$ along +x. Green arrows are $E$; the gold arrow is electric force. Both paths share a and b.`,
+ limits: String.raw`Electrostatic vacuum model; prescribed path, not a free-particle trajectory. Geometry is in metres; field and force arrows show direction with compressed lengths. The gold probe is scrubbed by journey progress; dotted endpoint markers stay fixed. Choose $U_a=0$.`,
+ measure: p => { const m = motionState(p); return [energy('Field work a → b', m.work), energy('Work to current position', m.work * progress(p)), energy('Potential energy Uᵦ', -m.work)]; },
+ observe: p => String.raw`${p.path === 'curved' ? 'The curved route adds sideways motion, but the final work stays the same.' : 'The straight route makes the force–displacement dot product easy to see.'} At b, the field does ${format(motionState(p).work * 1e9)} nJ of work; $U$ drops by that amount.`,
+ answers: [{ title: 'Question 1 · Work by the electric force · 30p', steps: [
+ { why: 'Start with the definition of work: add the component of force along every small displacement.', math: String.raw`W_{ab} = \int_a^b\vec F_e\cdot\mathrm d\vec r` },
+ { why: String.raw`Substitute the electric force on the moving charge, $\vec F_e=q_0\vec E$.`, math: String.raw`W_{ab} = q_0\int_a^b\vec E\cdot\mathrm d\vec r` },
+ { why: 'The electrostatic force is conservative, so field work is the decrease in potential energy.', math: String.raw`W_{ab} = U_a-U_b = q_0(V_a-V_b)` },
+ ] }],
+ },
+ {
+ ...common, id: 'test-conservation', title: 'Where does the energy go?', shortTitle: 'Conservation', points: 15, section: '4A',
+ description: 'A positive charge moves away from a fixed positive source. Watch potential energy become kinetic energy.',
+ questions: ['Please write the condition for conservation of the total energy.'],
+ controls: [slider('q0', 'Moving charge q₀', 0.5, 4, 0.5, 2, 'nC'), slider('kinetic', 'Initial kinetic energy Kₐ', 0, 40, 1, 0, 'nJ'), journey()],
+ equations: [String.raw`\Delta E_{\mathrm{total}} = 0\quad\text{(isolated system)}`, String.raw`K_a+U_a = K_b+U_b`, String.raw`\Delta K = W_{ab} = -\Delta U`],
+ explanation: 'Total energy is conserved for an isolated system. If only the electric force does work on the moving charge, kinetic plus electric potential energy stays constant. The source is fixed; its support does no work.',
+ challenge: String.raw`Move the journey slider from 0% to 100%. Check that the total bar stays the same height as $K$ rises and $U$ falls.`,
+ prediction: 'If the particle starts at rest, can it gain speed without an energy supply?',
+ predictionAnswer: String.raw`Yes. Its initial electric potential energy supplies the increase in kinetic energy. Energy changes form while $K+U$ stays constant. The particle and source together are the electrostatic system; the moving particle alone does not have constant kinetic energy.`,
+ sceneNote: String.raw`Free radial motion away from +3 nC. Bars show $U$, $K$ and their constant sum; mass = 1 mg.`,
+ limits: String.raw`Source fixed, vacuum, nonrelativistic point particle of mass 1 mg. $U(\infty)=0$. Journey progress selects a position, not elapsed time; the speed is computed from energy, not from slider speed. Bar heights are normalized to the total energy; readouts are in nJ.`,
+ measure: p => { const s = conservationState(p); return [energy('Potential energy U', s.u), energy('Kinetic energy K', s.kinetic), energy('Total K + U', s.total), metric('Particle speed', s.speed, 'm/s')]; },
+ observe: p => { const s = conservationState(p); return String.raw`At $r$ = ${format(s.r)} m, $U$ = ${format(s.u * 1e9)} nJ and $K$ = ${format(s.kinetic * 1e9)} nJ. Their sum stays ${format(s.total * 1e9)} nJ. The work done so far is the increase in $K$.`; },
+ answers: [{ title: 'Question 2 · Conservation condition · 15p', steps: [
+ { why: 'For an isolated system, no net energy crosses the system boundary.', math: String.raw`\Delta E_{\mathrm{total}} = 0` },
+ { why: 'If the electric force is the only force doing work, the mechanical energy is conserved.', math: String.raw`K_a+U_a = K_b+U_b` },
+ { why: String.raw`For a particle of mass $m$, substitute kinetic energy. The work–energy theorem connects the two changes.`, math: String.raw`\begin{aligned}\frac12 mv_a^2+U_a &= \frac12 mv_b^2+U_b\\\Delta K &= W_{ab} = -\Delta U\end{aligned}` },
+ ] }],
+ },
+ {
+ ...common, id: 'test-central', title: 'The field of a point charge', shortTitle: 'Central field', points: 55, section: '4A',
+ description: String.raw`Move $q_0$ radially through a central field. Connect the inverse-square force to an inverse-distance potential energy.`,
+ questions: [String.raw`When $q_0$ moves from a to b in an $E$-field from a point charge $q$ (“Central field”).`, 'a. (30p) Please derive the work done by the electric force.', String.raw`b. (25p) The work above is equal to the drop $U_a$ − $U_b$. What are $U_a$ and $U_b$ if $b\to\infty$?`],
+ controls: [slider('source', 'Source charge q', -4, 4, 0.5, 3, 'nC'), slider('q0', 'Moving charge q₀', -4, 4, 0.5, 2, 'nC'), slider('ra', 'Initial distance rₐ', 0.75, 4, 0.25, 1.5, 'm'), slider('rb', 'Final distance rᵦ', 0.75, 6, 0.25, 4.5, 'm'), choice('destination', 'Destination b', 'finite', [['finite', 'Finite final distance'], ['infinity', 'Infinity · final energy is zero']]), journey()],
+ equations: [String.raw`\vec E(r) = \frac{kq}{r^2}\hat r`, String.raw`W_{ab} = kqq_0\left(\frac1{r_a}-\frac1{r_b}\right)`, String.raw`U(r) = \frac{kqq_0}{r},\qquad U(\infty) = 0`],
+ explanation: String.raw`Only radial displacement contributes to work in a central field. Integrating $\frac1{r^2}$ gives $-\frac1r$. The zero at infinity fixes the energy reference; it is not an additional force law.`,
+ challenge: String.raw`Compare like and opposite charges. Send b to infinity and check that $W_{a\infty}=U_a$, including its sign.`,
+ prediction: String.raw`Is $U$ always positive when we choose $U(\infty)=0$?`,
+ predictionAnswer: String.raw`No. $U=\frac{kqq_0}{r}$ is positive for like charges and negative for opposite charges. A negative value means the configuration has less energy than the separated charges at infinity. Work by the field in separating opposite charges is negative.`,
+ sceneNote: String.raw`Radial path above; $U(r)$ curve below. Infinity mode shows an outward continuation, not a finite stand-in for infinity.`,
+ limits: String.raw`Fixed point source; vacuum Coulomb constant $k=\frac1{4\pi\varepsilon_0}$. Distances in metres. $U$ graph: 30 nJ per vertical scene unit, clipped outside ±2.5 units. Infinity has exactly $U_b=0$ analytically; the visible path stops at the edge of the diagram and does not locate b at a finite radius.`,
+ measure: p => { const s = centralState(p); return [energy('Potential energy Uₐ', s.ua), energy('Potential energy Uᵦ', s.ub), energy('Field work a → b', s.work)]; },
+ observe: p => { const s = centralState(p); return s.rb === Infinity ? String.raw`At infinity, $U_b=0$ exactly. $W_{a\infty}=U_a$ = ${format(s.work * 1e9)} nJ. The diagram’s edge is only a visual continuation toward infinity.` : String.raw`The field does ${format(s.work * 1e9)} nJ of work from $r_a$ = ${s.ra} m to $r_b$ = ${s.rb} m. Switching the sign of either charge reverses $U$ and $W$.`; },
+ answers: [
+ { title: '3a · Derive the radial work · 30p', steps: [
+ { why: 'Write the radial field and force of the fixed point charge.', math: String.raw`\vec E = \frac{kq}{r^2}\hat r,\qquad \vec F_e = \frac{kqq_0}{r^2}\hat r` },
+ { why: String.raw`The radial component of the displacement is $\hat r\cdot\mathrm d\vec r=\mathrm dr$. Tangential steps do no work.`, math: String.raw`W_{ab} = kqq_0\int_{r_a}^{r_b}\frac1{r^2}\,\mathrm dr` },
+ { why: 'Integrate and evaluate at the endpoints. The result depends only on their distances.', math: String.raw`W_{ab} = kqq_0\left[-\frac1r\right]_{r_a}^{r_b} = kqq_0\left(\frac1{r_a}-\frac1{r_b}\right)` },
+ ] },
+ { title: '3b · Set the zero at infinity · 25p', steps: [
+ { why: String.raw`Choose $U(\infty)=0$. As $r_b\to\infty$, the reciprocal distance goes to zero.`, math: String.raw`W_{a\infty} = \frac{kqq_0}{r_a}` },
+ { why: String.raw`Use $W_{a\infty}=U_a-U_\infty$ to identify the two endpoint energies.`, math: String.raw`U_a = \frac{kqq_0}{r_a},\qquad U_b = U_\infty = 0` },
+ { why: String.raw`This gives the general interaction energy. Its sign is the sign of $qq_0$.`, math: String.raw`U(r) = \frac{kqq_0}{r}` },
+ ] },
+ ],
+ },
+ {
+ ...common, id: 'test-assembly', title: 'Build a system of charges', shortTitle: 'Charge assembly', points: 25, section: '4B',
+ description: 'Bring charges in one at a time. Every connecting edge is one interaction energy, counted exactly once.',
+ questions: ['Bringing charges together.', String.raw`a. (15p) What is the total electric potential energy if $q_1$, $q_2$, $q_3$ are brought together from infinity?`, String.raw`b. (10p) What is the additional electric potential energy if $q_0$ is brought together with $q_1$, $q_2$, $q_3$?`],
+ controls: [choice('stage', 'Assembly stage', '3', [['1', '1 · Place first charge'], ['2', '2 · Add second charge'], ['3', '3 · Add third charge'], ['4', '4 · Add test charge']]), slider('spacing', 'Configuration size', 2, 5, 0.25, 3.5, 'm'), slider('q0', 'Added charge q₀', -4, 4, 0.5, 2, 'nC')],
+ equations: [String.raw`U_{123} = k\left(\frac{q_1q_2}{r_{12}}+\frac{q_1q_3}{r_{13}}+\frac{q_2q_3}{r_{23}}\right)`, String.raw`\Delta U_0 = k\left(\frac{q_0q_1}{r_{01}}+\frac{q_0q_2}{r_{02}}+\frac{q_0q_3}{r_{03}}\right)`, String.raw`\Delta U_0 = q_0V_{123}`],
+ explanation: String.raw`Place $q_1$ for free. Adding $q_2$ creates one pair; adding $q_3$ creates two more. The fourth charge creates three new pairs without changing the energy of the existing three. For quasistatic assembly, external work equals the increase in $U$.`,
+ challenge: String.raw`Step through all four assembly stages. Then reverse $q_0$ and watch the sign of its three new interactions reverse.`,
+ prediction: String.raw`Should the total energy contain both $\frac{q_1q_2}{r_{12}}$ and $\frac{q_2q_1}{r_{21}}$?`,
+ predictionAnswer: String.raw`No. Those describe the same pair. Sum only over $i<j$. Three charges have three distinct pairs; four charges have six. The additional energy from $q_0$ includes just its three new pairs, $\Delta U_0=q_0V_{123}$.`,
+ sceneNote: 'A tetrahedral arrangement. Mint edges: existing pairs. Gold edges: pairs introduced by the newest charge. Dashed markers: not yet assembled.',
+ limits: String.raw`Point charges fixed at distinct positions. $q_1$ = +3 nC, $q_2$ = −2 nC, $q_3$ = +2 nC. All coordinates scale with the configuration size in metres. Reference: all charges mutually separated at infinity. Self-energies are excluded. Later-stage totals are shown in readouts even before those charges are placed.`,
+ measure: p => { const s = assemblyState(p); return [energy('Energy of current assembly', s.current), energy('Three-charge energy U₁₂₃', s.base), energy('Additional energy ΔU₀', s.extra), metric('Pairs currently assembled', s.stage * (s.stage - 1) / 2, 'pairs')]; },
+ observe: p => { const s = assemblyState(p); return String.raw`Stage ${s.stage}: ${s.stage * (s.stage - 1) / 2} distinct pairs are present. $U_{123}$ = ${format(s.base * 1e9)} nJ; adding $q_0$ changes it by ${format(s.extra * 1e9)} nJ. Negative pair energy indicates opposite signs.`; },
+ answers: [
+ { title: '4a · Assemble q₁, q₂, q₃ · 15p', steps: [
+ { why: String.raw`Bring $q_1$ from infinity first. With no other charges present, its interaction energy is zero.`, math: String.raw`U_1 = 0` },
+ { why: String.raw`Bring in $q_2$ against the potential due to $q_1$; then bring $q_3$ against the potential due to $q_1$ and $q_2$.`, math: String.raw`\begin{aligned}\Delta U_2 &= \frac{kq_1q_2}{r_{12}}\\\Delta U_3 &= \frac{kq_1q_3}{r_{13}}+\frac{kq_2q_3}{r_{23}}\end{aligned}` },
+ { why: 'Add the contributions, counting each pair once.', math: String.raw`U_{123} = k\left(\frac{q_1q_2}{r_{12}}+\frac{q_1q_3}{r_{13}}+\frac{q_2q_3}{r_{23}}\right)` },
+ ] },
+ { title: '4b · Add q₀ to the fixed system · 10p', steps: [
+ { why: String.raw`The existing three charges are held fixed. Add only the three new interactions involving $q_0$.`, math: String.raw`\Delta U_0 = k\left(\frac{q_0q_1}{r_{01}}+\frac{q_0q_2}{r_{02}}+\frac{q_0q_3}{r_{03}}\right)` },
+ { why: String.raw`Factor out $q_0$. The remaining sum is the potential of the original three charges at the new position.`, math: String.raw`\begin{aligned}V_{123} &= k\left(\frac{q_1}{r_{01}}+\frac{q_2}{r_{02}}+\frac{q_3}{r_{03}}\right)\\\Delta U_0 &= q_0V_{123}\end{aligned}` },
+ ] },
+ ],
+ },
+ {
+ ...common, id: 'test-uniform', title: 'Work in a uniform field', shortTitle: 'Uniform field', points: 25, section: '4B',
+ description: String.raw`Rotate the displacement relative to the field. The projection along $E$ sets the work, not the full path length.`,
+ questions: [String.raw`When $q_0$ moves from a to b in a uniform $E$-field.`, 'a. (15p) Please derive the work done by the electric force.', String.raw`b. (10p) What are $U_{\mathrm{pot},a}$ and $U_{\mathrm{pot},b}$?`],
+ controls: [...motionControls, slider('angle', 'Displacement angle α', 0, 180, 5, 35, '°'), journey()],
+ equations: [String.raw`W_{ab} = q_0\vec E\cdot(\vec r_b-\vec r_a) = q_0Es\cos\alpha`, String.raw`W_{ab} = q_0E(x_b-x_a)`, String.raw`U(x) = -q_0Ex+C`],
+ explanation: String.raw`A constant field can be taken outside the work integral. With x along $E$, the potential energy changes only with x. The energy zero is arbitrary; here a is the reference, so $U_a=0$.`,
+ challenge: String.raw`Set $\alpha$ to $90^\circ$. The charge travels a nonzero distance, but $E$ is perpendicular to every step: work is zero.`,
+ prediction: String.raw`If $\alpha$ exceeds $90^\circ$ for a positive charge, does the field give energy to the charge?`,
+ predictionAnswer: String.raw`The field does negative work because $\cos\alpha<0$. $U$ increases as the charge moves against the field. An external agent would need to supply that energy to move it slowly, or a freely moving particle would lose kinetic energy.`,
+ sceneNote: String.raw`Displacement is in the xy plane; $E$ points along +x. The blue segment is $\Delta x=s\cos\alpha$. Choose $U_a=0$.`,
+ limits: String.raw`Ideal constant field with no edge effects. Geometry in metres; arrows show direction with compressed lengths. Prescribed motion, not a free-particle trajectory. $U_a=0$ fixes $C=q_0Ex_a$; $x_a$ is shown explicitly by the a marker.`,
+ measure: p => { const m = motionState(p); return [energy('Field work a → b', m.work), metric('Projection Δx', m.dx, 'm'), energy('Potential energy Uₐ', 0), energy('Potential energy Uᵦ', -m.work)]; },
+ observe: p => { const m = motionState(p); return String.raw`$\alpha = ${number(p, 'angle')}^\circ$ projects $s$ = ${m.s} m onto $\Delta x$ = ${format(m.dx)} m. $W$ = ${format(m.work * 1e9)} nJ and $U_b$ = ${format(-m.work * 1e9)} nJ with $U_a=0$.`; },
+ answers: [
+ { title: '5a · Derive uniform-field work · 15p', steps: [
+ { why: String.raw`Start with the work integral. The field is constant, so take $\vec E$ outside the integral.`, math: String.raw`W_{ab} = q_0\int_a^b\vec E\cdot\mathrm d\vec r = q_0\vec E\cdot(\vec r_b-\vec r_a)` },
+ { why: String.raw`Write the dot product using the displacement length and angle $\alpha$.`, math: String.raw`W_{ab} = q_0Es\cos\alpha` },
+ { why: 'Choose the x-axis along the field. Only the x-displacement contributes.', math: String.raw`W_{ab} = q_0E(x_b-x_a)` },
+ ] },
+ { title: '5b · Endpoint potential energies · 10p', steps: [
+ { why: 'The change in potential energy is the negative of the field work.', math: String.raw`U_b-U_a = -q_0E(x_b-x_a)` },
+ { why: String.raw`Integrating with respect to x leaves an arbitrary reference constant $C$.`, math: String.raw`U_a = -q_0Ex_a+C,\qquad U_b = -q_0Ex_b+C` },
+ { why: String.raw`A convenient choice is $U_a=0$. It changes no forces or energy differences.`, math: String.raw`U_a = 0,\qquad U_b = -q_0E(x_b-x_a)` },
+ ] },
+ ],
+ },
+ {
+ ...common, id: 'test-dipole', title: 'Turn a dipole in a field', shortTitle: 'Dipole rotation', points: 50, section: '4B',
+ description: 'Two equal and opposite forces cancel in translation, yet form a torque. Rotate the dipole to explore work and stability.',
+ questions: [String.raw`The electric dipole in a uniform $E$-field.`, String.raw`a. (10p) Derive the torque vector and prove $\tau=p_eE\sin\theta$, where $\theta=\angle(\vec p_e,\vec E)$.`, String.raw`b. (30p) Derive $W_{ab}$ as the dipole rotates from $\theta_a$ to $\theta_b$. What are $U_a$ and $U_b$? You do not have to derive $\mathrm dW=-\tau\,\mathrm d\theta$.`, String.raw`c. (10p) Explain why $\theta_a$ = $90^\circ$ can be a reference, give $U_a$, and find the general $U$ also as a scalar product.`],
+ controls: [slider('charge', 'Dipole charge magnitude q', 0.5, 4, 0.5, 2, 'nC'), slider('length', 'Charge separation d', 0.5, 3, 0.25, 2, 'm'), slider('field', 'Electric field E', 0, 80, 5, 30, 'V/m'), slider('thetaA', 'Initial angle θₐ', 0, 180, 5, 90, '°'), slider('thetaB', 'Final angle θᵦ', 0, 180, 5, 15, '°'), slider('progress', 'Rotation progress', 0, 100, 1, 50, '%')],
+ equations: [String.raw`\vec p_e = q\vec d,\qquad \vec\tau = \vec p_e\times\vec E,\qquad |\vec\tau| = p_e E\sin\theta`, String.raw`W_{ab} = p_e E(\cos\theta_b-\cos\theta_a)`, String.raw`U(\theta) = -p_e E\cos\theta = -\vec p_e\cdot\vec E`],
+ explanation: String.raw`The dipole moment points from $-q$ to $+q$. The equal and opposite electric forces create a couple that tends to align $\vec p_e$ with $\vec E$. We use $U(90^\circ)=0$; alignment is the minimum energy state.`,
+ challenge: String.raw`Set the final angle to $0^\circ$, then $180^\circ$, with rotation progress at 100%. Both have zero torque. Compare their energy and stability.`,
+ prediction: String.raw`If the torque is zero at both $0^\circ$ and $180^\circ$, are both orientations stable?`,
+ predictionAnswer: String.raw`No. At $0^\circ$, $U=-p_eE$ is a minimum: a small angular displacement produces a restoring torque. At $180^\circ$, $U=+p_eE$ is a maximum: a small displacement drives the dipole farther away. At $90^\circ$, $U=0$ by convention but torque is largest, so this is not equilibrium. Without damping, a freely rotating dipole can oscillate about alignment.`,
+ sceneNote: String.raw`$E$ along $+x$; $\vec p_e$ points from − to +. Gold arrows show forces; the blue z-arrow shows signed torque. $U(\theta)$ below.`,
+ limits: String.raw`Ideal rigid dipole with fixed centre in a uniform field. Geometry in metres. No net force. Angle measured counterclockwise in xy, so $\tau_z=-p_eE\sin\theta$. The slider prescribes orientation, not a damped dynamical trajectory. Torque arrow length and graph height are normalized; readouts give magnitudes in SI-derived units. $U(90^\circ)=0$.`,
+ measure: p => { const s = rotationState(p); return [metric('Current angle θ', s.theta * 180 / Math.PI, '°'), metric('Torque τz', s.torqueZ * 1e9, 'nN·m'), energy('Potential energy U', s.energy), energy('Field work a → b', s.endpointWork), energy('Work to current angle', s.work)]; },
+ observe: p => { const s = rotationState(p); const degrees = s.theta * 180 / Math.PI; return s.field === 0 ? String.raw`$E=0$: both forces, torque, rotation work and dipole interaction energy vanish. There is no preferred orientation.` : degrees < 0.01 ? String.raw`Aligned at $0^\circ$: $U$ is minimum and torque is zero. This is stable equilibrium; a small rotation produces a restoring torque.` : degrees > 179.99 ? String.raw`Opposed at $180^\circ$: $U$ is maximum and torque is zero. This is unstable equilibrium; a small rotation takes it away from this position.` : String.raw`At $\theta = ${format(degrees)}^\circ$, $\tau_z$ = ${format(s.torqueZ * 1e9)} nN·m. The negative z direction reduces $\theta$ toward alignment. ${Math.abs(degrees - 90) < 0.01 ? String.raw`At $90^\circ$, $U=0$ by choice, but the torque is maximum: this is not equilibrium.` : 'Follow the point on the energy curve as you rotate.'}`; },
+ answers: [
+ { title: '6a · Torque vector and magnitude · 10p', steps: [
+ { why: String.raw`Let $\vec d$ point from the negative charge to the positive charge. Define the dipole moment.`, math: String.raw`\begin{aligned}\vec p_e &= q\vec d\\\vec F_+ &= q\vec E,\quad \vec F_- = -q\vec E\\\vec F_{\mathrm{net}} &= 0\end{aligned}` },
+ { why: String.raw`Take moments about the midpoint. The two lever arms are $\pm\frac{\vec d}{2}$.`, math: String.raw`\vec\tau = \frac{\vec d}{2}\times q\vec E+\left(-\frac{\vec d}{2}\right)\times(-q\vec E) = q\vec d\times\vec E` },
+ { why: 'The cross product gives the vector direction and magnitude. The two forces form a couple.', math: String.raw`\vec\tau = \vec p_e\times\vec E,\qquad |\vec\tau| = p_e E\sin\theta` },
+ ] },
+ { title: '6b · Rotation work and endpoint energies · 30p', steps: [
+ { why: String.raw`The torque acts toward decreasing $\theta$. With $\tau$ as its positive magnitude, use the supplied relation.`, math: String.raw`\mathrm dW = -\tau\,\mathrm d\theta = -p_e E\sin\theta\,\mathrm d\theta` },
+ { why: 'Integrate from the initial angle to the final angle.', math: String.raw`W_{ab} = -p_e E\int_{\theta_a}^{\theta_b}\sin\theta\,\mathrm d\theta = p_e E(\cos\theta_b-\cos\theta_a)` },
+ { why: String.raw`Use $W_{ab}=U_a-U_b$. The reference constant is the same at both endpoints.`, math: String.raw`\begin{aligned}U_a &= -p_e E\cos\theta_a+C\\U_b &= -p_e E\cos\theta_b+C\end{aligned}` },
+ ] },
+ { title: '6c · Reference at 90° and scalar product · 10p', steps: [
+ { why: String.raw`The zero of potential energy is arbitrary. At $90^\circ$ the two charges are at the same electric potential, so $+qV$ and $-qV$ cancel.`, math: String.raw`U_a = U(90^\circ) = 0` },
+ { why: String.raw`Since $\cos90^\circ=0$, this reference sets $C=0$.`, math: String.raw`U(\theta) = -p_e E\cos\theta = -\vec p_e\cdot\vec E` },
+ { why: 'Aligned is a stable energy minimum; opposed is an unstable maximum. The perpendicular reference is not an equilibrium.', math: String.raw`\begin{aligned}U(0^\circ) &= -p_e E\\U(90^\circ) &= 0\\U(180^\circ) &= +p_e E\end{aligned}` },
+ ] },
+ ],
+ },
+];
+
+export function getTestLesson(experiment: Experiment): TestLesson | undefined {
+ return testLessons.find(lesson => lesson.id === experiment.id);
+}