Due Wednesday, October 7, 11:59 pm PT · pairs allowed, own code and own write-up
What you are building¶
How do you find an unknown that appears on both sides of an equation?
Build a root-finding library to recover Wien’s constant, compute orbital
positions, and locate transit contacts. Then use algebra and a Star class
to compare equilibrium-temperature zones for your fifteen Project 1 stars.
| Part | You build | Evidence |
|---|---|---|
| 1 | roots.py — bisection, Newton, secant | convergence measurements and behavior tests |
| 2 | Wien’s constant | the physically relevant root and agreement with tabulated |
| 3 | Kepler’s equation → the orbit → | small equation residuals and recovery of Project 1’s circular limit |
| 4 | Transit contact points and durations | agreement with the circular formula and comparison with grid estimates |
| 5 | Star class → simplified habitable zones | temperature round-trip checks and a fifteen-star comparison |
Answer each part’s interpretation questions in one or two short paragraphs
after its calculations and figures in analysis.ipynb. The Deliverables
callouts summarize the requirements; there is no separate report. They are
completion reminders, not a points rubric. Your work is assessed using the
course’s four project dimensions:
correct model and validation, scientific reasoning, reproducibility and code
design, and communication and figures.
Check each layer before combining them¶
| Layer | First check | If it fails, inspect |
|---|---|---|
| Root finder | known root, endpoint root, and failure cases | stopping rule, history, and input validation |
| Physical root | positive Wien root and units of | domain and bracket, even if the residual is small |
| Orbit | at , then Project 1’s circular separation | radians, quadrant, array shape, and projection |
| Contacts | circular analytic durations, then eccentric cases | front/back selection, brackets, ordering, and inner Kepler accuracy |
| Stellar model | recover the boundary temperatures by substitution | radius and distance units, albedo, and powers |
A passing check supports that layer; it does not establish that every later calculation is correct. The required tests and comparisons below implement this sequence.
Structure: classes start here¶
project-3/
├── README.md
├── roots.py # bisection, Newton, secant — plain functions
├── star.py # the Star class
├── orbit.py # Kepler's equation, z(t), contact points
├── transit.py # your existing transit functions
├── integrate.py # retained from Project 2
├── conftest.py # retain the Project 2 test layout
├── tests/
└── analysis.ipynbUse python -m pytest from the project directory. Reuse your tested Project 1–2
functions; you do not need to implement another quadrature method here.
| Component | Form | Reason |
|---|---|---|
| Root finders | functions | no persistent state between calls |
| Transit model and orbit | functions | outputs depend on explicit parameters; closures bind application parameters |
Star | class | radius and temperature belong to one object; properties derive consistent quantities from them |
Validate the star’s stored radius and temperature. Compute luminosity and peak wavelength from that state so the derived quantities stay consistent.
Documentation
Review the course’s functions/modules and testing material. For Star, follow
this reading route:
In Chapter 6: OOP Fundamentals, follow the introductory examples in §§6.1–6.4: construct an object with
__init__, useself, call a method, and compute a derived@property. Focus on these examples rather than every exercise or extended example.Read §§10.1 and 10.4 of Designing Small Scientific Types for state validation and tests of the object’s contract.
Inheritance, operator overloading, and other advanced class machinery are not required. Dataclasses are optional; a small ordinary class is sufficient.
Python: classes,
property,
dataclasses (optional).
NumPy: sign,
diff,
arctan2.
Sign changes help locate brackets on a sampled grid. Handle exact sampled
zeros separately so a contact is not counted twice.
Part 1 — The library¶
Implement these functions in roots.py:
def bisect(f, a, b, atol=1e-12, rtol=0.0, max_iter=200): ...
def newton(f, fprime, x0, atol=1e-12, rtol=0.0, max_iter=200): ...
def secant(f, x0, x1, atol=1e-12, rtol=0.0, max_iter=200): ...Each returns (root, n_iter, history):
root: the final numerical estimate of the root.n_iter: the integer iteration count, defined for each method below.history: the ordered sequence of root estimates recorded by the method.
Keep the Lecture 8 bisection contract and use these recording conventions:
Bisection: history contains evaluated midpoints;
n_iter == len(history). Return the last evaluated midpoint. An endpoint root returns that endpoint, zero iterations, and an empty history.Newton: history begins with
x0, followed by each updated iterate;n_iter == len(history) - 1.Secant: history begins with
x0, x1, followed by each updated iterate;n_iter == len(history) - 2. Check both initial residuals before dividing. An initial root returns with zero updates and the two initial entries.
Newton and secant may return immediately at a computed zero residual; otherwise use the stopping rule below. Bisection counts midpoint evaluations; the open methods count updates. These are not total function-evaluation costs.
Use ValueError for invalid inputs, an invalid bracket, nonfinite function or
derivative values, a zero Newton derivative at a nonroot, or a zero secant
denominator when neither input is a root. Require finite real scalar inputs and
function values, finite nonnegative tolerances with at least one positive, and a
positive integer max_iter excluding Booleans. Bisection requires a < b;
secant requires distinct initial inputs.
Use RuntimeError for iteration-budget exhaustion or a bisection interval
whose tolerance is unmet but has no representable interior midpoint. Compare
signs directly rather than multiplying function values. Continuity and reliable
signs are mathematical preconditions; input validation cannot establish them.
What a tolerance means¶
The allowance at a candidate is
atol is the absolute tolerance, in the same units as the root. A positive
value supplies an allowance near zero. rtol is the relative tolerance, a
dimensionless fraction that scales the allowance with the candidate root’s
magnitude. Near 1012, an absolute tolerance of 10-12 is below
double-precision spacing.
For bisection, evaluate the midpoint of the current bracket and use . Accept a computed zero residual or . For an exact midpoint, . Use the bracket before updating it to bound that midpoint’s error, and record the midpoint before returning.
For Newton and secant, accept an updated iterate if its computed residual is zero or its last step is no larger than . A small step does not certify a small residual or root error; check the defining equation in each application. A computed zero can also result from rounding or underflow.
Measure convergence¶
Use , with bisection on , Newton from , and secant from . Use as the double-precision reference; do not round it before computing errors.
Plot against history index on a log axis. State which entries are starting guesses. Report each solver’s returned iteration count and final error. Distinguish satisfying its stopping rule from the first recorded error below 10-12. Omit zero errors from the logarithmic plot and identify them in the caption rather than replacing them with invented values.
Newton and secant: estimate the order using
Use and report the last consecutive triple with for all three errors and a nonzero denominator. This band avoids the numerical floor here but does not establish an asymptotic regime. If no triple qualifies, report that. Compare with orders 2 and .
Bisection: reconstruct the first 20 brackets from the initial bracket and recorded midpoints without changing the return interface. Measure successive width ratios and the slope of against . Compare with and . Plot the bracket half-width alongside the actual errors. Actual midpoint errors need not decrease at every update.
Part 2 — Where Wien’s constant comes from¶
The wavelength form of Wien’s law is , with tabulated cm·K. Recover this value from the Planck spectrum:
Use the same CGS constants as Project 2:
Derivation: the wavelength peak
Project 2 integrated the Planck function per unit frequency. Per unit wavelength,
For finite positive and , differentiate with respect to wavelength and set the derivative to zero. Substituting gives
The nonzero root has no elementary inverse. The rearranged equation also holds at , outside the derivation’s finite-wavelength domain. Select the positive, nonzero root using the physics and the function’s behavior. The peaks of and are different density maxima; do not obtain one peak merely by substituting into the other.
Investigate root selection¶
Run your Newton method from and inspect its history.
Report the returned root and residual. Compute the implied and at 5772 K if the root is nonzero. If it is exactly zero, state why these quantities are undefined instead of dividing by zero.
Explain why does not identify a finite-wavelength spectral peak.
Recover the desired root using a bracket or starting guess justified by the function and its physical domain.
Plot and in two panels, marking both roots, the Newton history, and the derivative’s zero at . Run Newton from that point and report the outcome. The evaluated derivative may be zero or merely tiny in floating point; an enormous step or reported failure is evidence to explain.
The near-zero result depends on how you evaluate ; no particular sign or last digit is expected. A small derivative can send Newton outside the domain.
Part 3 — The orbit¶
Read the Science Background for diagrams and derivations of the orbital angles, sky projection, and transit contacts.
Project 1 used for a circular, edge-on orbit. Now compute the projected separation for an eccentric orbit.
For a bound Kepler orbit, . With at periastron,
Mean anomaly increases uniformly with time. Eccentric anomaly is a geometric parameter on the ellipse. Given and , solve for .
Geometry and assumptions
For an isolated Newtonian two-body orbit, recover position at a given time by inverting Kepler’s equation. Numerical ODE integration is also valid and becomes useful when additional forces prevent this reduction.
True anomaly is the angle from periastron measured at the focus. The orbital radius and sky-projected separation are
Inclination is measured relative to the sky plane; is edge-on. Fix , and choose the observer orientation so the planet is in front at periastron. For the required edge-on cases, mid-transit is then . Projected separation alone does not tell you whether the planet is in front.
What to build¶
In orbit.py:
def solve_kepler(M, e, atol=1e-12, rtol=0.0, max_iter=100): ... # (E, n_iter)
def true_anomaly(E, e): ... # radians
def separation(t, period, a_over_rstar, e, inc): ... # scalar or arrayUse your own Newton from Part 1, with closures:
resid = lambda E: E - e*np.sin(E) - M
dresid = lambda E: 1 - e*np.cos(E)Use the quadrant-aware expression
For , a stable equivalent projected separation is
This sum of squares avoids cancellation near an edge-on mid-transit. Explain its equivalence to the geometric expression above.
Conventions: angles in radians; t and period in the same units (days
for the examples); positive period and a_over_rstar; and
. solve_kepler takes scalar and returns the solution on
the same unwrapped branch as . If you reduce modulo internally,
restore that branch in the returned . separation returns a scalar for a
scalar input and preserves an array’s shape. Reject nonfinite or out-of-domain
inputs with ValueError.
Compare starting guesses and check the circular limit¶
Use M = np.linspace(0, 2*np.pi, 400) at
. Compare with on this interval,
using the same tolerances and max_iter=100. You may call newton directly
for this comparison; no extra argument in solve_kepler is required.
Report failures, mean and maximum iterations among successful runs, and their
residuals. Use this evidence to choose and document a solve_kepler starting
strategy. Failures on this grid do not establish a universal eccentricity
threshold. The high- cases are solver tests, not viable transit models at
.
For the circular-limit check, use , ,
t = np.linspace(-0.25, 0.25, 501), , and . Compare with
Project 1’s formula using rtol=0, atol=1e-11. Report the maximum absolute
error, not a prescribed round-off pattern. Relative error is undefined where
the reference separation is zero.
Part 4 — Contact points and duration¶
Let be the planet-to-star radius ratio. For a nongrazing transit with , four contacts mark tangency:
| Contact | Condition | Event |
|---|---|---|
| 1 | overlap begins | |
| 2 | planet becomes fully inside the stellar disc | |
| 3 | planet begins to leave the stellar disc | |
| 4 | overlap ends |
Solve . Return ordered times from
contact_points(period, a_over_rstar, p, e, inc), and report
and . is the full-overlap duration;
it is a flat-bottom duration for the uniform-brightness model used here.
Use days, , , , and , unless a check specifies otherwise. These required configurations have four distinct contacts. Report failure if four distinct contacts are not found; a general grazing/no-transit model is not required.
Bracket first, then solve. For these configurations, sample , locate sign changes for each contact equation, and pass each bracket to bisection. Retain exact sampled roots once. Refine the sampling if needed and check that the four times are distinct and correctly ordered.
Circular check and eccentric light curves¶
At , , compare against your Project 1 grid estimates for and 0.25, and against
| (days, rounded) | (days, rounded) | |
|---|---|---|
| 0.05 | 0.100453 | 0.090855 |
| 0.25 | 0.119679 | 0.071687 |
Compute the analytic reference; the table is rounded. Report the root-found duration error and the original grid spacing. Errors in both contact times and the nested Kepler solve affect duration: a 10-12 stopping tolerance on one time does not promise 10-13 duration accuracy.
Plot the three required eccentric light curves using your uniform-disk
flux(p, z) from transit.py. Mark all four contacts on each curve and report
and . The period, scaled semimajor axis, inclination, and
periastron orientation remain fixed as eccentricity changes.
Part 5 — Stars, and where the algebra wins¶
Implement the complete Star class in star.py with this interface:
class Star:
def __init__(self, name, radius, teff): ... # radius in R_sun; teff in K
@property
def luminosity(self): ... # L_sun
@property
def lambda_max(self): ... # cm, using your derived b
def habitable_zone(self, albedo=0.3): ... # (inner, outer) in AUStore readable name, radius, and teff attributes; radius is in solar radii
and temperature is in kelvin. Require finite, positive radius and temperature
at construction. If those values can change later, validate each new value; a
read-only design is also acceptable. Compute luminosity and lambda_max from
the current state rather than storing separate copies.
Reuse your tested Project 1 Stefan–Boltzmann calculation. You may copy its
luminosity(radius, teff, solar=False) function into star.py, import it from a
submitted module, or refactor it while preserving its units and behavior. The
Star.luminosity property must return from the object’s current
radius and temperature. Check that the Sun gives the same result as in
Project 1.
Use cm and cm.
For a planet with Bond albedo , uniform redistribution of absorbed energy, unit thermal emissivity, and no internal heating, radiative balance gives
Use the existing simplified criterion K at . Require . The inner boundary corresponds to 373 K and the outer boundary to 273 K. Rearrange this equation for by hand and implement the algebraic result. No root finder is needed.
Instantiate all fifteen Project 1 stars from the local data. Plot one horizontal interval per star, ordered and labelled by . Use a log distance axis in AU; mark Earth at 1 AU and identify the Sun and Proxima Centauri.
One last question¶
End the notebook with a markdown cell headed Looking back:
Which single number in this notebook do you trust least, and why?
Answer in two or three sentences. Any honest answer earns full credit.
Required tests¶
Include these eight test cases in tests/, retaining your Lecture 8 solver
contract tests. A case may use several assertions or parameterized inputs.
| # | Test | What it checks |
|---|---|---|
| 1 | All three methods find the positive Wien root and agree to 10-10 | solver agreement on the selected root |
| 2 | bisect raises ValueError for same-sign nonzero endpoint values | bracket precondition |
| 3 | Newton on , , from raises ValueError | exactly zero derivative at a nonroot |
| 4 | on the Part 3 sweep for your selected strategy | Kepler equation residual |
| 5 | solve_kepler(M, 0) returns | circular limit, including negative and multi-period |
| 6 | Circular edge-on separation matches Project 1 with rtol=0, atol=1e-11; scalar and array shapes are preserved | orbital geometry and interface |
| 7 | Reinsert the returned zone bounds into and recover 373 and 273 K | algebra and unit conversions |
| 8 | Bisection resolves a root near 1012 using rtol | scale-aware stopping |
Analytic-limit checks and defining-equation residuals catch different errors; keep both. Use explicit tolerances rather than relying on comparison defaults.
Required figures¶
Convergence: the three error histories and bisection’s bracket half-width; log error axis, with starting guesses and omitted zero errors identified.
Wien root selection: and in two panels, both roots, the history, and the derivative zero. Axis limits must contain the displayed iterates.
Transit timing: uniform-disk light curves at , with all four contacts marked on each.
Fifteen-star zones: labelled temperature ordering, log distance in AU, Earth at 1 AU, and the Sun and Proxima identified.
Use Project 1’s figure standards: labelled units, readable mathematical labels, legends clear of the data, and figures saved to files.
What you turn in¶
The repository with modules and tests, passing under
python -m pytest.analysis.ipynbwith calculations, the five interpretations, andLooking back.The four figures saved as files.
README.mdexplaining the contents and how to run the work.
Pairs are allowed, but each student writes their own code and answers and must be able to explain every submitted line.