Astrology Engine

Read a position from a curve

A prepared curve can answer a position question without reading the original observations again. We choose the stored series, locate the segment containing the requested instant, and combine its coefficients. The result is already an angular quantity prepared for the chart; the runtime does not repeat the earlier light-time and reference-plane calculations.

Before this lesson

Read “Read the chart inputs” and recall the angular fitting lesson. A coefficient is a stored weight for a polynomial. We will introduce the few polynomial terms needed for the worked example.

Read the preceding lesson.

What you will learn

You will be able to select a segment, map time into its local coordinate, evaluate a small Chebyshev series and preserve the distinction between circular longitude and signed declination.

Dotted-underlined terms open a definition beside the text. Select one to read more, then close it to continue.

Each step has its own check. Pass every step to complete the lesson. Your answers and checked steps are saved in this browser, so you can continue after leaving or reloading.

1. Find the interval containing the instant

First choose the body and quantity, such as Moon longitude. The evaluator finds the first table entry with that pair. If none exists, it returns MissingSeries. Longitude and declination need separate entries, so finding one does not prove the other is present.

Consider invented metadata with start time t₀ = 1000 s, segment length D = 100 s, and three segments. They span 1000–1100, 1100–1200 and 1200–1300 s. At t = 1175 s, subtraction gives 175 s from the start. Division by 100 gives 1.75; rounding downward gives index 1. Indices start at zero, so this is the second segment.

relative = (t − t₀) / D; index = floor(relative)

At a shared boundary such as 1200 s, index 2 selects the segment on the right. At the final endpoint 1300 s, the initial index equals the segment count, 3. The evaluator subtracts one in this special case and uses index 2. Dates before the start or beyond the final endpoint are rejected; it does not extrapolate a position.

The selected segment begins at 1100 s. Our time is 75 s into a 100-second interval, or three quarters of the way across. Mapping the start to −1 and the end to +1 turns that fraction into x = 2 × 0.75 − 1 = 0.5. This dimensionless local coordinate is the input to the stored polynomial.

segment_start = t₀ + index × D; x = 2 × (t − segment_start) / D − 1

If this series begins at byte offset 208 and stores four eight-byte coefficients per segment, index 1 starts at 208 + 1 × 4 × 8 = 240. The evaluator reads those four little-endian floating-point values. “Little-endian” means the least significant byte is stored first.

Why interval records are familiar in ephemerides

The Jet Propulsion Laboratory’s SPK documentation describes Chebyshev records over time intervals. It supplies a precedent for storing local curves. This engine’s index rule and HDCHEB01 addresses are its own implementation, not SPK records.

See the teaching TypeScript
const localCoordinate = (t: number, start: number, duration: number): number => 2 * (t - start) / duration - 1;

Finite, correctly labeled inputs are assumed. This demonstrates the arithmetic; it does not fetch data or replace the engine.

Connect this step to the source

src/cheb/eval.rs

Ephemeris::series; Ephemeris::value_at

Paths refer to the astrology-engine repository. Examples use invented inputs; a successful exercise is not an astronomical-accuracy test.

Sources for this section

Apply this step

Answer every part, then check. You can retry as often as you like.

Give at least six decimal places if needed; tolerance ±0.00000001. Accepted tolerance: ±1e-8 index. Omit units and commas.

Give at least six decimal places if needed; tolerance ±0.00000001. Accepted tolerance: ±1e-8 dimensionless. Omit units and commas.

Give at least six decimal places if needed; tolerance ±0.00000001. Accepted tolerance: ±1e-8 bytes. Omit units and commas.

4. Which index is used at exactly 1300 s in this example?

2. Combine the stored polynomial weights

A polynomial can be built from familiar pieces. For a Chebyshev series, the first pieces are T₀(x) = 1, T₁(x) = x and T₂(x) = 2x² − 1, where x² means x multiplied by itself. With invented degree-valued coefficients [10, 2, 3] and x = 0.5, the pieces are 1, 0.5 and −0.5. Multiplying each by its weight and adding gives 10 + 1 − 1.5 = 9.5°.

p(x) = c₀T₀(x) + c₁T₁(x) + c₂T₂(x) + …

For a longer series we need not build every piece separately. The engine uses a backward calculation named Clenshaw summation. Begin with two temporary values b₁ = 0 and b₂ = 0, and visit coefficients from the last down to c₁. For each coefficient compute a new value b₀, then replace the old pair with b₁ = b₀ and b₂ = the previous b₁. The subscripts here identify temporary slots, not coefficient indices.

b₀ = 2xb₁ − b₂ + c; after the loop, p(x) = c₀ + xb₁ − b₂

In our example the c₂ = 3 step produces b₀ = 3, leaving (b₁, b₂) = (3, 0). The c₁ = 2 step produces 2 × 0.5 × 3 − 0 + 2 = 5, leaving (5, 3). The final expression is 10 + 0.5 × 5 − 3 = 9.5°. This is the same polynomial as before, evaluated by a reusable sequence of multiplication and subtraction. A one-coefficient series simply returns c₀.

The engine stores c₀ with its full weight. Some references use a half-weight convention for the constant coefficient; their final expression must be interpreted with their coefficient definition. Halving this file’s c₀ would change the answer.

A named numerical method

The National Institute of Standards and Technology’s mathematical reference calls the method Clenshaw’s algorithm and documents its backward recurrence. The name identifies the method used here; no separate invention is claimed for the engine’s short implementation.

See the teaching TypeScript
const clenshaw = (coefficients: readonly number[], x: number): number => {
  const [b1, b2] = coefficients.slice(1).reduceRight<[number, number]>(([a, b], c) => [2 * x * a - b + c, a], [0, 0]);
  return coefficients[0]! + x * b1 - b2;
};

Finite, correctly labeled inputs are assumed. This demonstrates the arithmetic; it does not fetch data or replace the engine.

Connect this step to the source

src/cheb/fit.rs

clenshaw

Paths refer to the astrology-engine repository. Examples use invented inputs; a successful exercise is not an astronomical-accuracy test.

Sources for this section

Apply this step

Answer every part, then check. You can retry as often as you like.

Give at least six decimal places if needed; tolerance ±0.00000001. Accepted tolerance: ±1e-8 degrees. Omit units and commas.

Give at least six decimal places if needed; tolerance ±0.00000001. Accepted tolerance: ±1e-8 degrees. Omit units and commas.

3. For a one-coefficient series [10], what does the evaluator return?

3. Give the two angular quantities their meaning

The fitter kept longitude continuous across complete turns so that a curve could pass smoothly through the zero-degree seam. Suppose evaluation returns 361.25°. On a chart circle, subtracting one full turn gives 1.25°, the same direction. Likewise −2° becomes 358°. The longitude accessor takes a nonnegative remainder after division by 360.

longitude = nonnegative remainder of p(x) divided by 360°

Declination has a different meaning: it is the angle north or south of the celestial equator, the plane through Earth’s equator extended into the sky. Its sign carries that distinction. A fitted value of −12.5° remains −12.5°; turning it into 347.5° would discard its meaning. The declination accessor returns the evaluated value unchanged. Both quantities are degrees, but only longitude is wrapped around a full circle.

These accessors recover quantities already prepared by the builder. Their use of remainder and a signed angle is a numerical convention in the current runtime; there is no separately sourced origin story for these wrapper functions. Mathematical wrap formulas describe the ideal [0, 360) interval. At extremely small negative floating-point inputs, rounding can produce exactly 360, as the source-level limitation recorded in earlier lessons explains.

See the teaching TypeScript
const longitude = (angle: number): number => { const remainder = angle % 360; return remainder < 0 ? remainder + 360 : remainder; };
const declination = (angle: number): number => angle;

Finite, correctly labeled inputs are assumed. This demonstrates the arithmetic; it does not fetch data or replace the engine.

Connect this step to the source

src/cheb/eval.rs

Ephemeris::longitude; Ephemeris::declination

Paths refer to the astrology-engine repository. Examples use invented inputs; a successful exercise is not an astronomical-accuracy test.

Sources for this section

Apply this step

Answer every part, then check. You can retry as often as you like.

Give at least six decimal places if needed; tolerance ±0.00000001. Accepted tolerance: ±1e-8 degrees. Omit units and commas.

Give at least six decimal places if needed; tolerance ±0.00000001. Accepted tolerance: ±1e-8 degrees. Omit units and commas.

3. Does this evaluation repeat the builder’s light-time corrections?

Your lesson checks

0 of 3 steps passed.

Use the feedback beside each check to retry any unfinished step.