Special relativity · Electrodynamics §7

Frequency and direction transform together from the invariance of the phase.

How do the frequency and propagation direction of light transform between frames?

Doppler and aberration

Doppler principle and aberration

Static worked example

Model note
  • Frame speed (frameSpeed), Propagation angle (stationary system) (propagationAngleStationary), Propagation angle (moving system) (propagationAngleMoving), Wave frequency (stationary system) (waveFrequencyStationary), Wave frequency (moving system) (waveFrequencyMoving), Doppler factor (dopplerFactor), Wave phase (wavePhase), Lorentz factor (lorentzFactor), Classical observer Doppler factor (classicalObserverDopplerFactor), Classical source Doppler factor (classicalSourceDopplerFactor), Receding line-of-sight Doppler factor (recedingDopplerFactor) and Approaching line-of-sight Doppler factor (approachingDopplerFactor): Host calculation, by the site's own reference code. Computed in waves.
  • Computed from the settings last applied (settings revision 1, result 1).
  • Not modeled: media and dispersion; sound in a medium; gravitational redshift; finite packets (the finite light-complex laboratory); telescope optics and atmospheric refraction; photon picture; canal-ray apparatus beyond published values.

Predict before the numbers

When observing a light wave traveling in the same direction as the observer at 0.6c, how does the observed frequency compare to the source frequency?

Three relations the model could have

Predict before the numbers

When observing a light wave traveling toward the observer at 0.6c, how does the observed frequency compare to the source frequency?

Three relations the model could have

The result appears when you choose, say you have one in mind, or skip.

Set the observer and the ray

Choose a named ray, or type a speed and an angle.

The transverse case (θ = 90°) is the purely relativistic shift: the medium formulae give no change there.

Experiment settings the source frequency

Changes here apply with Apply settings.

Worked example: a ray at 0° and 500 THz in the stationary frame K arrives in the frame moving at 0.6c at 0° and 250 THz, a Doppler factor of 0.5.

Stationary frame KStationary Frame KSource frameν = 500.0 THz · θ = 0.0°xyθ = 0.0°Moving frame kMoving Frame k (β = 0.6c)Observer frameν′ = 250.0 THz · θ′ = 0.0°x′y′v = 0.60cθ′ = 0.0°
Doppler factor ν′/ν
0.5
γ(1 − β cos θ)
Aberration cos θ′
1
(cos θ − β) / (1 − β cos θ)

Values at these settings

Relativistic factors from one wave-vector transform, beside the two medium formulae that are right for sound.
Relativistic ν′/ν0.5
Lorentz factor γ1.25
Medium, moving observer0.4
Medium, moving source0.625
Receding line of sight0.5
Approaching line of sight2
Angle in k0 °

At θ = 90° the two medium formulae both give 1. The paper's factor is γ. That is the transverse Doppler shift, which has no classical counterpart. The medium formulae are not declared refuted: they are right for sound and agree with the paper to first order in v/c.

Light looks redder to an observer moving away from its source and bluer to one moving toward it, and it arrives from a slightly different direction. Einstein derived both effects, for any speed below that of light, from one transformation.

§7 places a source of plane waves very far away in K and asks what an observer at rest in the moving system k finds. Transforming the fields by §6 and the coordinates and time by §3 gives a wave of the same form in k, with a new frequency and a new direction. If φ is the angle between the line from source to observer and the observer's velocity, measured in K, the observed frequency is ν′ = ν(1 − cos φ · v/V)/√(1 − (v/V)²), which §7 calls Doppler's principle for arbitrary speeds, and the direction obeys cos φ′ = (cos φ − v/V)/(1 − (v/V) cos φ), the law of aberration in its most general form. The lab's default is 500 THz light and an observer receding at 0.6c along the line to the source, φ = 0: the factor is √((1 − 0.6)/(1 + 0.6)) = 0.5, so the observer measures 250 THz. Reverse the motion, φ = 180°, and the factor is 2, giving 1000 THz. At φ = 90° the frequency rises by γ = 1.25, to 625 THz, although the observer moves across the line of sight, and the ray arrives at 126.87°, where cos φ′ = −0.6, as §7 prints for this case. For comparison the lab shows the two older formulas, one for a moving observer (0.4 at φ = 0) and one for a moving source (0.625). They differ because each assumes light moves at a fixed speed through a medium, which makes it matter who moves; the relativistic 0.5 depends only on the relative speed. §7 also transforms the amplitude, A′² = A²(1 − (v/V) cos φ)²/(1 − (v/V)²), and concludes that a source approached at the speed of light would appear infinitely intense.

Modern qualifications

Einstein's φ is the angle between the line from source to observer and the observer's velocity, measured in the source's frame; his β is the modern γ and V the speed of light. The formulas are written for a source at rest in K and an observer at rest in k, and because only the relative velocity enters, the result is the same when the source moves. §7 remarks that, contrary to the usual view, the frequency grows without bound as the speed of approach nears V. The shift by γ at 90° follows from the formula, but the paper does not single it out; Ives and Stilwell measured the second-order shift in canal rays in 1938, although they read it within Lorentz's theory. Treating frequency and wave vector as one four-vector is Minkowski's later language, not the paper's.

Not modeled: media and dispersion; sound in a medium; gravitational redshift; finite packets (the finite light-complex laboratory); telescope optics and atmospheric refraction; photon picture; canal-ray apparatus beyond published values.

Show the code

The site’s own reference code for this law, in TypeScript. It computes the numbers above when the page runs it, and it is what the page uses wherever a FrankenSim result is not available.

evaluateSr09 · src/physics/reference/waves.ts · this site’s source, as built · sha256:ef623131c3e8ea8ac949e8079db833adab903b09b5bb016549d9ecbd18d959b4

This function computes the listed outputs when it runs.

In words

Paper 3's § 7 in one evaluation. It takes the boost, the angle the wave travels at in the resting system and its frequency, and returns the transformed angle, the transformed frequency, the Doppler factor, the amplitude factor, the line-of-sight factors for a source receding and approaching, and the aberration in arcseconds for the Earth's orbital speed. It also returns the second-order shift at a stated small speed, which is the part that distinguishes this prediction from the classical one.

Implementation

export function evaluateSr09(input: Sr09Input): Sr09EvaluationResult {
  const beta = input.beta;
  const thetaDeg = input.propagationAngleDeg;
  const thetaRad = (thetaDeg * Math.PI) / 180;
  const nuK = input.frequencyHz;
  const omegaK = 2 * Math.PI * nuK;
  const secondOrderSpeed = input.secondOrderSpeed ?? 0.005;

  if (!Number.isFinite(beta) || Math.abs(beta) >= 1) {
    return Object.freeze({
      beta,
      gamma: Number.NaN,
      propagationAngleStationaryDeg: thetaDeg,
      propagationAngleStationaryRad: thetaRad,
      propagationAngleMovingDeg: Number.NaN,
      propagationAngleMovingRad: Number.NaN,
      cosThetaStationary: Number.NaN,
      sinThetaStationary: Number.NaN,
      cosThetaMoving: Number.NaN,
      sinThetaMoving: Number.NaN,
      waveFrequencyStationaryHz: nuK,
      waveFrequencyMovingHz: Number.NaN,
      waveAngularFrequencyStationary: omegaK,
      waveAngularFrequencyMoving: Number.NaN,
      dopplerFactor: Number.NaN,
      amplitudeFactor: Number.NaN,
      lineOfSightRecedingFactor: Number.NaN,
      lineOfSightApproachingFactor: Number.NaN,
      detectorCrossings: Number.NaN,
      detectorProperRateHz: Number.NaN,
      earthOrbitAberrationArcsec: Number.NaN,
      earthOrbitAberrationFormatted: "outside-domain",
      secondOrderShift: Number.NaN,
      secondOrderComparisonSpeed: secondOrderSpeed,
      events: [],
      results: [
        sr09Outside("frameSpeed", "c", "speed"),
        sr09Outside("propagationAngleStationary", "deg", "angle"),
        sr09Outside("propagationAngleMoving", "deg", "angle"),
        sr09Outside("waveFrequencyStationary", "Hz", "frequency"),
        sr09Outside("waveFrequencyMoving", "Hz", "frequency"),
        sr09Outside("dopplerFactor", "1", "ratio"),
        sr09Outside("wavePhase", "rad", "angle"),
        sr09Outside("lorentzFactor", "1", "lorentz-factor"),
        sr09Outside("classicalObserverDopplerFactor", "1", "ratio"),
        sr09Outside("classicalSourceDopplerFactor", "1", "ratio"),
        sr09Outside("recedingDopplerFactor", "1", "ratio"),
        sr09Outside("approachingDopplerFactor", "1", "ratio"),
      ],
      status: "outside-domain",
    });
  }

  const gResult = gamma(beta);
  const g = gResult.status === "value" ? gResult.value : Number.NaN;
  const cosThetaK = Math.cos(thetaRad);
  const sinThetaK = Math.sin(thetaRad);
  const kMag = omegaK / C_SI;
  const boosted = transformWaveVector(
    omegaK,
    { x: kMag * cosThetaK, y: kMag * sinThetaK, z: 0 },
    beta,
    C_SI,
  );
  const omega_k = boosted.omegaPrime;
  const nu_k = omega_k / (2 * Math.PI);
  const doppler = omega_k / omegaK;
  const ab = aberration(beta, thetaRad);
  let theta_k_deg = (ab.thetaPrimeRad * 180) / Math.PI;
  if (theta_k_deg < 0) theta_k_deg += 360;
  const classicalObserver = classicalObserverDopplerFactor(beta, thetaRad);
  const classicalSource = classicalSourceDopplerFactor(beta, thetaRad);

  const recedingFactor = Math.sqrt((1 - beta) / (1 + beta));
  const approachingFactor = Math.sqrt((1 + beta) / (1 - beta));

  const detectorSpeed = input.detectorSpeed ?? beta;
  const detectorWindow = input.countingWindow ?? 10 / nuK;
  const detectorCrossings = detectorCrossingCount({
    omega: omegaK,
    k: { x: (omegaK / C_SI) * cosThetaK, y: (omegaK / C_SI) * sinThetaK, z: 0 },
    detectorVelocity: { x: detectorSpeed * C_SI, y: 0, z: 0 },
    window: detectorWindow,
  });
  const detectorProperRateHz = detectorCrossings / detectorWindow;

  const modernAberration = modernEarthOrbitAberration();
  const earthOrbitAberrationArcsec = modernAberration.arcsec;
  const earthOrbitAberrationFormatted = `${earthOrbitAberrationArcsec.toFixed(4)}" (${modernAberration.label})`;

  const secondOrderShiftVal = secondOrderShift(secondOrderSpeed);

  // Discrete named events for Phase Invariance verification
  const kxK = (omegaK / C_SI) * cosThetaK;
  const kyK = (omegaK / C_SI) * sinThetaK;
  const kzK = 0;
  const wave1_K = { omega: omegaK, kx: kxK, ky: kyK, kz: kzK };

  const transW = transformWaveVector(omegaK, { x: kxK, y: kyK, z: kzK }, beta, C_SI);
  const wave1_k = {
    omega: transW.omegaPrime,
    kx: transW.kPrime.x,
    ky: transW.kPrime.y,
    kz: transW.kPrime.z,
  };

  // Event 1: tick at t = 1e-15 s, x = 0, y = 0, z = 0
  const event1_K = { t: 1e-15, x: 0, y: 0, z: 0 };
  const event1_k = {
    t: g * (event1_K.t - (beta * event1_K.x) / C_SI),
    x: g * (event1_K.x - beta * C_SI * event1_K.t),
    y: 0,
    z: 0,
  };
  const p1_K = phaseAtEvent(event1_K, wave1_K);
  const p1_k = phaseAtEvent(event1_k, wave1_k);
  const val1_K = p1_K.status === "value" && typeof p1_K.value === "number" ? p1_K.value : 0;
  const val1_k = p1_k.status === "value" && typeof p1_k.value === "number" ? p1_k.value : 0;

  // Adversarial Phase: uses Galilean wave transformation where kx does not mix with omega
  const adv_kx_k = g * (kxK + (beta * omegaK) / C_SI);
  const adv_wave1_k = { omega: omega_k, kx: adv_kx_k, ky: kyK, kz: kzK };
  const adv_p1_k = phaseAtEvent(event1_k, adv_wave1_k);
  const adv_val1_k =
    adv_p1_k.status === "value" && typeof adv_p1_k.value === "number" ? adv_p1_k.value : 0;

  // Event 2: offset event (t = 2e-15 s, x = 3e-7 m, y = 0, z = 0)
  const event2_K = { t: 2e-15, x: 3e-7, y: 0, z: 0 };
  const event2_k = {
    t: g * (event2_K.t - (beta * event2_K.x) / C_SI),
    x: g * (event2_K.x - beta * C_SI * event2_K.t),
    y: 0,
    z: 0,
  };
  const p2_K = phaseAtEvent(event2_K, wave1_K);
  const p2_k = phaseAtEvent(event2_k, wave1_k);
  const val2_K = p2_K.status === "value" && typeof p2_K.value === "number" ? p2_K.value : 0;
  const val2_k = p2_k.status === "value" && typeof p2_k.value === "number" ? p2_k.value : 0;
  const adv_p2_k = phaseAtEvent(event2_k, adv_wave1_k);
  const adv_val2_k =
    adv_p2_k.status === "value" && typeof adv_p2_k.value === "number" ? adv_p2_k.value : 0;

  const events: readonly Sr09NamedEventRecord[] = [
    Object.freeze({
      id: "sr-09-event-origin-tick",
      label: "Origin cycle tick (t = 1 fs, x = 0)",
      eventK: event1_K,
      event_k: event1_k,
      waveK: wave1_K,
      wave_k: wave1_k,
      phaseK: val1_K,
      phase_k: val1_k,
      phasesAgree:
        classifyWithTolerance(val1_K - val1_k, { absolute: 1e-12 }).sign === "indeterminate" ||
        val1_K === val1_k,
      adversarialPhase_k: adv_val1_k,
    }),
    Object.freeze({
      id: "sr-09-event-offset",
      label: "Offset downstream event (t = 2 fs, x = 300 nm)",
      eventK: event2_K,
      event_k: event2_k,
      waveK: wave1_K,
      wave_k: wave1_k,
      phaseK: val2_K,
      phase_k: val2_k,
      phasesAgree:
        classifyWithTolerance(val2_K - val2_k, { absolute: 1e-12 }).sign === "indeterminate" ||
        val2_K === val2_k,
      adversarialPhase_k: adv_val2_k,
    }),
  ];

  const results: readonly ScientificResult[] = [
    Object.freeze({
      quantityId: "frameSpeed",
      unit: "c",
      semanticKind: "speed",
      ownerId: OWNER_ID,
      status: "value",
      value: beta,
    }),
    Object.freeze({
      quantityId: "propagationAngleStationary",
      unit: "deg",
      semanticKind: "angle",
      ownerId: OWNER_ID,
      status: "value",
      value: thetaDeg,
    }),
    Object.freeze({
      quantityId: "propagationAngleMoving",
      unit: "deg",
      semanticKind: "angle",
      ownerId: OWNER_ID,
      status: "value",
      value: theta_k_deg,
    }),
    Object.freeze({
      quantityId: "waveFrequencyStationary",
      unit: "Hz",
      semanticKind: "frequency",
      ownerId: OWNER_ID,
      status: "value",
      value: nuK,
    }),
    Object.freeze({
      quantityId: "waveFrequencyMoving",
      unit: "Hz",
      semanticKind: "frequency",
      ownerId: OWNER_ID,
      status: "value",
      value: nu_k,
    }),
    Object.freeze({
      quantityId: "dopplerFactor",
      unit: "1",
      semanticKind: "ratio",
      ownerId: OWNER_ID,
      status: "value",
      value: doppler,
    }),
    Object.freeze({
      quantityId: "wavePhase",
      unit: "rad",
      semanticKind: "angle",
      ownerId: OWNER_ID,
      status: "value",
      value: val1_K,
    }),
    Object.freeze({
      quantityId: "lorentzFactor",
      unit: "1",
      semanticKind: "lorentz-factor",
      ownerId: OWNER_ID,
      status: "value",
      value: g,
    }),
    Object.freeze({
      quantityId: "classicalObserverDopplerFactor",
      unit: "1",
      semanticKind: "ratio",
      ownerId: OWNER_ID,
      status: "value",
      value: classicalObserver,
    }),
    Object.freeze({
      quantityId: "classicalSourceDopplerFactor",
      unit: "1",
      semanticKind: "ratio",
      ownerId: OWNER_ID,
      status: "value",
      value: classicalSource,
    }),
    Object.freeze({
      quantityId: "recedingDopplerFactor",
      unit: "1",
      semanticKind: "ratio",
      ownerId: OWNER_ID,
      status: "value",
      value: recedingFactor,
    }),
    Object.freeze({
      quantityId: "approachingDopplerFactor",
      unit: "1",
      semanticKind: "ratio",
      ownerId: OWNER_ID,
      status: "value",
      value: approachingFactor,
    }),
  ];

  return Object.freeze({
    beta,
    gamma: g,
    propagationAngleStationaryDeg: thetaDeg,
    propagationAngleStationaryRad: thetaRad,
    propagationAngleMovingDeg: theta_k_deg,
    propagationAngleMovingRad: ab.thetaPrimeRad,
    cosThetaStationary: cosThetaK,
    sinThetaStationary: sinThetaK,
    cosThetaMoving: ab.cosThetaPrime,
    sinThetaMoving: ab.sinThetaPrime,
    waveFrequencyStationaryHz: nuK,
    waveFrequencyMovingHz: nu_k,
    waveAngularFrequencyStationary: omegaK,
    waveAngularFrequencyMoving: omega_k,
    dopplerFactor: doppler,
    amplitudeFactor: doppler,
    lineOfSightRecedingFactor: recedingFactor,
    lineOfSightApproachingFactor: approachingFactor,
    detectorCrossings,
    detectorProperRateHz,
    earthOrbitAberrationArcsec,
    earthOrbitAberrationFormatted,
    secondOrderShift: secondOrderShiftVal,
    secondOrderComparisonSpeed: secondOrderSpeed,
    events,
    results,
    status: "value",
  });
}

The site’s own reference code for this law, in TypeScript. It computes the numbers above when the page runs it, and it is what the page uses wherever a FrankenSim result is not available.

phaseAtEvent · src/physics/reference/waves.ts · this site’s source, as built · sha256:f28ee2f8d5ebf08961325ceeae3e003e27155b8fb8e75e8b79852f0aa2340adb

This function computes the listed outputs when it runs.

In words

The phase of a wave at one event: the wave vector dotted into the position, less the angular frequency times the time. It is the quantity that must come out the same in every frame, which is why it is computed here rather than assumed, and a nonfinite coordinate or component is refused by name before the arithmetic.

Implementation

/**
 * Evaluates the plane wave phase phi = k . x - omega * t at a given 4-event.
 *
 * INDEPENDENCE RULE (am-ref-waves-r53 / am-sr-09-doppler-aberration-rabd):
 * This function computes the inner product and time product from its arguments alone.
 * It MUST NOT call dopplerFactor, aberration, transformWaveVector, or lightComplexFactors,
 * directly or indirectly.
 */
export function phaseAtEvent(
  coordinates: Event4,
  waveComponents: Wave4Vector,
  frame: "stationary" | "moving" | "frame-independent" | string = "frame-independent",
): ScientificResult & { readonly frame: string } {
  const nonfiniteField = checkNonfinitePhaseField(coordinates, waveComponents);
  if (nonfiniteField !== null) {
    return Object.freeze({
      quantityId: "wavePhase",
      unit: "rad",
      semanticKind: "angle",
      ownerId: OWNER_ID,
      status: "outside-domain",
      condition: "nonfinite-input",
      domainKind: "physical",
      reason: `phaseAtEvent requires finite coordinates and wave components; ${nonfiniteField} is not finite.`,
      boundary: { parameterId: nonfiniteField, value: 0 },
      frame,
    });
  }

  const kDotX =
    waveComponents.kx * coordinates.x +
    waveComponents.ky * coordinates.y +
    waveComponents.kz * coordinates.z;
  const omegaT = waveComponents.omega * coordinates.t;
  const phase = kDotX - omegaT;

  return Object.freeze({
    quantityId: "wavePhase",
    unit: "rad",
    semanticKind: "angle",
    ownerId: OWNER_ID,
    status: "value",
    value: phase,
    frame,
  });
}

Worked case (readable without JavaScript)

Consider a light wave with frequency ν = 500 THz propagating at angle θ = 0° along the x-axis in the stationary system K, viewed by an observer moving along the x-axis at speed v = 0.6c (β = 0.6, γ = 1.25).

ν′=νγ(1−βcos⁡θ)=ν1−β1+β=0.5ν=250 THz\begin{aligned}\nu' &= \nu\gamma(1 - \beta\cos\theta) \\ &= \nu\sqrt{\frac{1-\beta}{1+\beta}} \\ &= 0.5\nu \\ &= 250\text{ THz}\end{aligned}
Explain this equation

For a ray at right angles in the stationary system (θ = 90°):

ν′=γν=1.25ν=625 THz,cos⁡θ′=−β=−0.6  ⟹  θ′≈126.87∘\begin{gathered}\nu' = \gamma\nu = 1.25\nu = 625\text{ THz}, \\ \cos\theta' = -\beta = -0.6 \\ \implies \theta' \approx 126.87^\circ\end{gathered}

The phase φ = k·x − ωt is an invariant scalar that takes the exact same numerical value in both reference frames at every spacetime event.

Embed this laboratory on another page. It starts from its worked defaults, not your current settings.