恒星亮度的变化曲线被称为光变曲线(Light Curve)。当系外行星从恒星前方经过时,恒星亮度会出现微小的周期性下降——这就是**凌星法(Transit Method)**探测系外行星的基本原理。
本文是”Coursera系外行星探测”课程的课后作业,任务名称:Homework 1 — Light Curve Detective。通过分析一条神秘的光变曲线,学习如何清洗天文数据、识别异常点和估算凌星参数。目标不是证明发现了行星,而是仔细描述数据。
1. 作业说明与配置
1.1 作业目标
以下是 notebook 中的原始英文说明:
Homework 1 — Light Curve Detective
In Day One you learned how to use a Jupyter notebook, Python variables, lists, NumPy arrays, and plots to study a star’s brightness.
In this homework, you will repeat those skills and analyze a mystery light curve.
Your goal is not to prove that you found a planet. Your goal is to describe the data carefully.
本次作业的核心在于复现课堂上学到的技能(Jupyter Notebook、Python 变量、列表、NumPy 数组和 Matplotlib 绘图),并用它们来分析一条神秘的光变曲线。
1.2 作业格式说明
How to work with this notebook
This homework is written in a Coursera-style format.
Some cells have this pattern:
### START CODE HERE #### your code### END CODE HERE ###Replace
Nonewith your own code. After many exercises, there is a small test cell usingassert.If a test fails, read the error message, fix your code, and run the cell again.
作业采用 Coursera 风格,部分代码单元格内标记了 ### START CODE HERE ### 和 ### END CODE HERE ### 区间,需要将 None 替换为自己的代码。每个练习后面都有一个使用 assert(断言)的测试单元格,如果测试失败,需要阅读错误信息、修正代码后重新运行。
1.3 环境配置(Part 0 — Setup)
Part 0 — Setup
Run this cell first. It imports the tools we need today.
We only use public Python packages that are already available in Google Colab: NumPy and Matplotlib.
首先运行此单元格导入 NumPy(数值计算库)和 Matplotlib(绘图库),这两个是 Google Colab 中预装的公开 Python 包:
import numpy as npimport matplotlib.pyplot as plt
print("Setup complete.")输出:
Setup complete.2. 热身复习(Part 1 — Warm-up review)
Part 1 — Warm-up review from today’s class
Before we analyze a mystery star, let’s repeat the basic skills from today’s lesson.
2.1 练习 1:自我介绍
# Exercise 1 — Hello, Exoplanet Hunter# Replace None with your own name and team number.
### START CODE HERE ###student_name = 'Luquiescene'team_number = 1### END CODE HERE ###
print("Hello, Exoplanet Hunter!")print("Student:", student_name)print("Team:", team_number)输出:
Hello, Exoplanet Hunter!Student: LuquiesceneTeam: 1测试单元格使用 assert 验证变量类型是否正确:
# Test 1assert isinstance(student_name, str), "student_name should be a string, for example: 'Aigerim'"assert student_name.strip() != "", "student_name should not be empty."assert isinstance(team_number, int), "team_number should be an integer, for example: 1"print("Test 1 passed.")输出:
Test 1 passed.isinstance(student_name, str) 检查 student_name 是否为字符串类型,str.strip() != "" 确保字符串非空。
2.2 练习 2:亮度下降计算
当行星凌星时,恒星亮度会从正常值小幅下降。设正常亮度为 1.00,凌星期亮度为 0.985,计算下降量:
# Exercise 2 — brightness drop# A star has normal brightness 1.00.# During a possible transit, the brightness becomes 0.985.# Calculate the drop in brightness.
normal_brightness = 1.00during_transit = 0.985
### START CODE HERE ###dip = normal_brightness - during_transitdip_percent = dip * 100### END CODE HERE ###
print("Brightness drop:", dip)print("Brightness drop in percent:", dip_percent)输出:
Brightness drop: 0.015000000000000013Brightness drop in percent: 1.5000000000000013dip 是亮度下降的分数值,乘以 100 后得到百分比。注意浮点数运算存在微小精度误差(0.015 vs 0.015000000000000013),测试中允许了 1e-12 的容差:
# Test 2assert abs(dip - 0.015) < 1e-12, "Check: dip = normal_brightness - during_transit"assert abs(dip_percent - 1.5) < 1e-12, "Check: dip_percent = dip * 100"print("Test 2 passed.")输出:
Test 2 passed.2.3 练习 3:列表基本操作
用 Python 原生列表存储一条小型光变曲线的时间与亮度数据,练习基本的数据量测:
# Exercise 3 — lists and simple measurements# This is a very small fake light curve.
time_small = [1., 2., 3., 4., 5., 6., 7.]brightness_small = [1.00, 1.00, 0.99, 0.96, 0.95, 0.96, 0.99]
### START CODE HERE ###n_time_points = len(time_small)minimum_brightness = min(brightness_small)maximum_brightness = max(brightness_small)### END CODE HERE ###
print("Number of points:", n_time_points)print("Minimum brightness:", minimum_brightness)print("Maximum brightness:", maximum_brightness)输出:
Number of points: 7Minimum brightness: 0.95Maximum brightness: 1.0len() 返回列表中的元素个数,min() 和 max() 分别获取最小值和最大值。7个数据点中亮度的最低值为 0.95,对应可能的凌星信号:
# Test 3assert n_time_points == 7, "Use len(time_small)."assert abs(minimum_brightness - 0.95) < 1e-12, "Use min(brightness_small)."assert abs(maximum_brightness - 1.00) < 1e-12, "Use max(brightness_small)."print("Test 3 passed.")输出:
Test 3 passed.2.4 练习 4:绘制小型光变曲线
plt.plot(..., marker="o") 用带圆形标记的折线图可视化光变曲线:
# Exercise 4 — plot the small light curve# Use plt.plot(..., marker="o").
plt.figure(figsize=(7, 4))
### START CODE HERE ###plt.plot(time_small, brightness_small, marker="o")### END CODE HERE ###
plt.xlabel("Time [days]")plt.ylabel("Brightness")plt.title("Small light curve")plt.grid(True)plt.show()
Quick check
In a light curve, a possible transit appears as a small drop in brightness.
But real data are not perfectly smooth. They can contain noise, missing values, and outliers.
光变曲线中,潜在凌星表现为小幅亮度下降。但真实数据并不完美平滑——它们可能包含噪声、缺失值(NaN)和异常值(Outlier)。
2.5 练习 5:NumPy 时间网格与噪声模拟
真实观测数据不像上面的小型光变曲线那样平滑。需要使用 NumPy 创建高密度时间网格,并叠加随机噪声来模拟真实场景。
np.linspace(start, stop, number_of_points) 在起止区间内均匀生成指定数量的点。rng.normal(mean, std, size) 从正态分布中抽样:
# Exercise 5 — NumPy time grid and noisy brightness# Today you used NumPy to create many points.# np.linspace(start, stop, number_of_points) creates an evenly spaced grid.
rng = np.random.default_rng(42)
### START CODE HERE ###time_demo = np.linspace(0, 10, 300)noise = rng.normal(0, 0.01, len(time_demo))brightness_demo = 1 + noise### END CODE HERE ###
print("First 5 time values:", time_demo[:5])print("First 5 brightness values:", brightness_demo[:5])输出:
First 5 time values: [0. 0.03344482 0.06688963 0.10033445 0.13377926]First 5 brightness values: [1.00304717 0.98960016 1.00750451 1.00940565 0.98048965]np.random.default_rng(42) 创建了一个随机数生成器,种子值 42 保证每次运行生成相同的随机序列。rng.normal(0, 0.01, 300) 生成 300 个均值为 0、标准差为 0.01 的正态分布噪声值。正常亮度 1.0 叠加噪声后,亮度在 1.0 ± 0.03 范围内波动:
# Test 5assert isinstance(time_demo, np.ndarray), "time_demo should be a NumPy array."assert isinstance(brightness_demo, np.ndarray), "brightness_demo should be a NumPy array."assert len(time_demo) == 300, "time_demo should contain 300 points."assert len(brightness_demo) == 300, "brightness_demo should contain 300 points."print("Test 5 passed.")输出:
Test 5 passed.用 plt.scatter() 散点图绘制含噪声的光变曲线,散点图比折线图更适合展示离散观测数据:
# Plot your noisy flat light curve.
plt.figure(figsize=(8, 4))plt.scatter(time_demo, brightness_demo, s=8, alpha=0.7)plt.xlabel("Time [days]")plt.ylabel("Brightness")plt.title("Noisy brightness measurements")plt.grid(True)plt.show()
s=8 设置散点大小,alpha=0.7 设置透明度使重叠点可见。
2.6 练习 6:模拟人为凌星
利用布尔掩码在指定时间段内降低亮度,模拟一次凌星事件。(time_demo > 4) & (time_demo < 5) 是一个布尔数组,True 表示该数据点位于第 4 天到第 5 天之间:
# Exercise 6 — create a simple synthetic transit# We will make brightness lower between day 4 and day 5.
brightness_transit = brightness_demo.copy()
### START CODE HERE ###in_transit = (time_demo > 4) & (time_demo < 5)brightness_transit[in_transit] = 0### END CODE HERE ###
print("Number of points inside the transit:", np.sum(in_transit))输出:
Number of points inside the transit: 30.copy() 创建数组的独立副本,避免修改原数组。np.sum(in_transit) 对布尔数组求和,True 计为 1、False 计为 0,因此得到凌星区间内的数据点数(30 个):
# Test 6assert in_transit.dtype == bool, "in_transit should be a Boolean mask."assert np.sum(in_transit) > 0, "Your transit mask should select some points."assert np.nanmin(brightness_transit) < np.nanmin(brightness_demo), "The transit should make the brightness lower."print("Test 6 passed.")输出:
Test 6 passed.np.nanmin() 计算忽略 NaN 的最小值,验证凌星确实降低了亮度。绘制人为制造的凌星光变曲线:
# Plot the synthetic transit.
plt.figure(figsize=(8, 4))plt.scatter(time_demo, brightness_transit, s=8, alpha=0.7)plt.xlabel("Time [days]")plt.ylabel("Brightness")plt.title("Synthetic transit")plt.grid(True)plt.show()
图中在第 4 到第 5 天之间出现了一个清晰的”凹陷”,亮度从正常的 ∼1.0 下降到 0。
3. 神秘光变曲线(Part 2 — Your mystery star)
Part 2 — Your mystery star
Now you will receive a mystery light curve.
The data are synthetic, but they are designed to look a little like real telescope data:
- there is noise;
- there are a few missing values;
- there are a few outliers;
- there may be a repeating transit-like dip.
Do not worry if you do not understand every line in the generator function. Your job is to analyze the data it creates.
接下来我们将接收一条神秘的光变曲线。数据是人工合成的,但模拟了真实望远镜观测的特点:含有测量噪声、缺失值(NaN)、异常亮斑,以及可能存在的周期性凌星信号。
3.1 数据生成函数
以下函数根据队伍编号和学生编号生成可复现的光变曲线,内部原理包括:
- 用固定随机种子(
seed = 1000 + 100 * team + student_id)保证同一队伍同一学生始终得到相同的曲线 - 从 5 个预设周期(3.25、4.10、5.60、6.80、8.40 天)中分配一个周期
- 用
np.sin添加长周期亮度趋势(缓慢变化) - 用取模运算
((time - t0 + P/2) % P) - P/2计算每个时间点到最近凌星中心的距离,实现周期性凹陷 - 随机选择 8 个点设为 NaN、10 个点添加亮斑异常值
def make_mystery_light_curve(team, student_id, n_days=45, cadence_minutes=30): # This function creates one reproducible mystery light curve. # Same team + same student_id = same target. seed = 1000 + 100 * int(team) + int(student_id) rng = np.random.default_rng(seed)
time = np.arange(0, n_days, cadence_minutes / (60 * 24))
possible_periods = np.array([3.25, 4.10, 5.60, 6.80, 8.40]) period = possible_periods[(int(team) + int(student_id)) % len(possible_periods)] period = period + rng.normal(0, 0.04)
first_transit_time = rng.uniform(1.0, period + 0.5) duration = rng.uniform(0.14, 0.28) depth = rng.uniform(0.010, 0.026)
slow_trend = 1 + 0.005 * np.sin(2 * np.pi * time / 22 + rng.uniform(0, 2 * np.pi)) flux = slow_trend.copy()
# Distance from the nearest transit center. dt = ((time - first_transit_time + period / 2) % period) - period / 2 transit_mask = np.abs(dt) < duration / 2 flux[transit_mask] -= depth
# Add random measurement noise. flux += rng.normal(0, 0.0025, size=len(time))
# Add missing values. nan_index = rng.choice(len(time), size=8, replace=False) flux[nan_index] = np.nan
# Add a few bright outliers. finite_index = np.where(np.isfinite(flux))[0] outlier_index = rng.choice(finite_index, size=10, replace=False) flux[outlier_index] += rng.uniform(0.035, 0.070, size=len(outlier_index))
target_id = f"MYSTERY-{int(team):02d}-{int(student_id):02d}"
truth = { "period_days": period, "first_transit_time_days": first_transit_time, "duration_days": duration, "depth_fraction": depth, "depth_percent": depth * 100, }
return { "target_id": target_id, "time": time, "flux": flux, "truth": truth, }np.arange(0, n_days, step) 等价于 Python 的 range,但返回 NumPy 数组。cadence_minutes / (60 * 24) 将分钟间隔转换为天数步长(30分钟 = 0.02083 天)。np.where(np.isfinite(flux))[0] 返回所有有限值(非 NaN)的索引,rng.choice(..., replace=False) 从中无放回随机抽取异常值位置。
3.2 练习 7:选取目标
TEAM 应为 1、2、3 或 4;STUDENT_ID 为 1 到 99 的任意整数:
# Exercise 7 — choose your target# Use your real team number and your own student number.# TEAM must be 1, 2, 3, or 4.# STUDENT_ID can be any integer from 1 to 99.
### START CODE HERE ###TEAM = 1STUDENT_ID = 36### END CODE HERE ###
MYSTERY_TARGET = make_mystery_light_curve(TEAM, STUDENT_ID)
time_raw = MYSTERY_TARGET["time"]flux_raw = MYSTERY_TARGET["flux"]
print("Target ID:", MYSTERY_TARGET["target_id"])print("Number of measurements:", len(time_raw))输出:
Target ID: MYSTERY-01-36Number of measurements: 216045 天内以 30 分钟间隔采样,总共 2160 个数据点:
# Test 7assert TEAM in [1, 2, 3, 4], "TEAM must be 1, 2, 3, or 4."assert isinstance(STUDENT_ID, int), "STUDENT_ID should be an integer."assert 1 <= STUDENT_ID <= 99, "STUDENT_ID should be between 1 and 99."assert len(time_raw) == len(flux_raw), "time and flux should have the same length."print("Test 7 passed.")输出:
Test 7 passed.4. 绘制原始光变曲线(Part 3 — Look at the raw light curve)
Part 3 — Look at the raw light curve
First, plot the data exactly as the telescope gives it to us.
At this stage, do not clean anything yet. Just look.
在清洗之前,直接绘制望远镜接收到的原始数据,“看看数据长什么样”:
4.1 练习 8:原始光变曲线
# Exercise 8 — plot raw mystery light curve
plt.figure(figsize=(10, 4))
### START CODE HERE ###plt.scatter(time_raw, flux_raw, s=4, alpha=0.6)### END CODE HERE ###
plt.xlabel("Time [days]")plt.ylabel("Raw flux")plt.title("Raw mystery light curve")plt.grid(True)plt.show()
4.2 观察 1(Observation 1)
Observation 1
Write 2–3 sentences below:
- Does the star look constant or variable?
- Do you see any strange points?
- Do you see any possible dips?
Your answer:
The star’s brightness stays roughly steady at 1.00, making it a constant-brightness star.
There are outlier bright points visible in the plot.
Clusters of data points dip downward at regular time intervals, which are characteristic brightness dips caused by planetary transits.
恒星亮度大致稳定在 1.00 附近,属于亮度恒定型恒星。图像中存在若干异常亮斑(outlier),高出正常值很多。在多个位置可以看到成簇的数据点向下凹陷,这是行星凌星的典型亮度特征。
5. 数据清洗与归一化(Part 4 — Clean and normalize)
Part 4 — Clean and normalize
Real light curves often contain missing values and outliers.
We will do three simple steps:
- remove missing values (
NaN);- remove very strange bright outliers;
- normalize the flux so that the typical brightness is close to 1.
真实光变曲线常含有缺失值和异常值,需要进行三步清洗:去除 NaN → 去除异常亮斑 → 归一化流量。
5.1 练习 9:清洗与归一化
# Exercise 9 — remove NaNs, remove bright outliers, normalize
### START CODE HERE ###finite_mask = np.isfinite(flux_raw)
time_no_nan = time_raw[finite_mask]flux_no_nan = flux_raw[finite_mask]
median_flux = np.median(flux_no_nan)scatter_flux = 1.4826 * np.median(np.abs(flux_no_nan - median_flux))
outlier_mask = flux_no_nan > median_flux + 5 * scatter_flux
time_clean = time_no_nan[~outlier_mask]flux_clean = flux_no_nan[~outlier_mask]
flux_normalized = flux_clean / median_flux### END CODE HERE ###
print("Raw points:", len(time_raw))print("After cleaning:", len(time_clean))print("Median normalized flux:", np.nanmedian(flux_normalized))输出:
Raw points: 2160After cleaning: 2142Median normalized flux: 0.999955187521236逐步解析:
np.isfinite(flux_raw):判断每个值是否为有限值(排除NaN和inf),返回布尔掩码。用此掩码筛选出有效数据。np.median(flux_no_nan):计算中位数,作为典型亮度值。中位数比均值更稳健,不受异常值影响。- MAD 异常值检测:
scatter_flux = 1.4826 * np.median(np.abs(flux_no_nan - median_flux))是**中位数绝对偏差(MAD)**乘以缩放因子 1.4826,使其与正态分布的标准差等价。将超出中位数 5 倍 MAD 的点标记为异常值。 ~outlier_mask:~运算符对布尔数组取反,保留非异常值的数据点。flux_clean / median_flux:将整条光变曲线除以中位数,使典型亮度归一化到 1.0 附近。
原始 2160 个点 → 去除 8 个 NaN → 去除 10 个异常亮斑 → 剩余 2142 个有效数据点:
# Test 9assert np.all(np.isfinite(time_clean)), "time_clean should not contain NaNs."assert np.all(np.isfinite(flux_normalized)), "flux_normalized should not contain NaNs."assert len(time_clean) == len(flux_normalized), "time_clean and flux_normalized should have the same length."assert abs(np.nanmedian(flux_normalized) - 1.0) < 0.01, "The median normalized flux should be close to 1."print("Test 9 passed.")输出:
Test 9 passed.5.2 练习 10:绘制清洗后的光变曲线
# Exercise 10 — plot cleaned and normalized light curve
plt.figure(figsize=(10, 4))
### START CODE HERE ###plt.scatter(time_clean, flux_normalized, s=4, alpha=0.6)### END CODE HERE ###
plt.xlabel("Time [days]")plt.ylabel("Normalized flux")plt.title("Cleaned and normalized light curve")plt.grid(True)plt.show()
5.3 观察 2(Observation 2)
Observation 2
Compare the raw plot and the cleaned plot.
Write 2–3 sentences:
- What changed after cleaning?
- Is it easier to see possible dips now?
- Why is normalization useful?
Your answer:
After cleaning, the bright outliers and NaN are removed, making the light curve look smoother.
Now it is easier to see possible dips.
The normalization makes it much easier to identify dips below the baseline.
清洗后,亮斑和 NaN 被移除,光变曲线更平滑。凌星凹陷更加清晰可见,更容易识别低于基线(1.0)的数据点簇。归一化的价值在于将流量统一到 1.0 附近,使得不同目标的亮度变化可以直接比较。
6. 手动凌星探测(Part 5 — Manual transit detective)
Part 5 — Manual transit detective
Now we will estimate a possible transit by hand.
This is not a final discovery. It is a first human inspection.
A possible transit should look like a short drop in normalized flux.
这一步是人工初步检查,不是最终确认,而是用肉眼辅助代码来估算可能的凌星参数。一个可能的凌星信号应该在归一化流量中表现为短暂的凹陷。
6.1 练习 11:找到最深点
np.argmin(array) 返回数组中最小值的索引位置(而非最小值本身),用于定位光变曲线中亮度最低的时刻:
# Exercise 11 — find the deepest point# Hint: np.argmin(array) gives the index of the smallest value.
### START CODE HERE ###min_index = np.argmin(flux_normalized)transit_time_guess = time_clean[min_index]minimum_flux = flux_normalized[min_index]transit_depth_fraction = 1.0 - minimum_fluxtransit_depth_percent = transit_depth_fraction * 100### END CODE HERE ###
print("Deepest point time [days]:", transit_time_guess)print("Minimum normalized flux:", minimum_flux)print("Estimated depth [%]:", transit_depth_percent)输出:
Deepest point time [days]: 10.895833333333332Minimum normalized flux: 0.9753378892244227Estimated depth [%]: 2.466211077557734最深点出现在第 10.90 天,最小归一化流量约 0.975,估算凌星深度约 2.47%。凌星深度 = 1.0 − 最小流量,表示恒星亮度因凌星而下降的比例:
# Test 11assert isinstance(min_index, (int, np.integer)), "min_index should be an integer index."assert np.isfinite(transit_time_guess), "transit_time_guess should be a number."assert np.isfinite(transit_depth_percent), "transit_depth_percent should be a number."assert transit_depth_percent > 0, "The depth should be positive."print("Test 11 passed.")输出:
Test 11 passed.在整条光变曲线中用虚线标记最深点位置:
# Plot the cleaned light curve and mark the deepest point.
plt.figure(figsize=(10, 4))plt.scatter(time_clean, flux_normalized, s=4, alpha=0.6)plt.axvline(transit_time_guess, linestyle="--", label="deepest point")plt.xlabel("Time [days]")plt.ylabel("Normalized flux")plt.title("Where is the deepest point?")plt.legend()plt.grid(True)plt.show()
plt.axvline() 在 x 轴指定位置绘制一条垂直的参考线,linestyle="--" 设置为虚线。
6.2 练习 12:放大观察凌星区域
np.abs(time_clean - transit_time_guess) < window 创建一个布尔掩码,选取最深点前后各 1.5 天范围内的数据:
# Exercise 12 — zoom near the deepest point# We will look at a window of 1.5 days before and after the deepest point.
window = 1.5
### START CODE HERE ###zoom_mask = np.abs(time_clean - transit_time_guess) < window### END CODE HERE ###
plt.figure(figsize=(8, 4))plt.scatter(time_clean[zoom_mask], flux_normalized[zoom_mask], s=12, alpha=0.8)plt.axvline(transit_time_guess, linestyle="--")plt.xlabel("Time [days]")plt.ylabel("Normalized flux")plt.title("Zoom near the deepest point")plt.grid(True)plt.show()
将时间窗口外的数据过滤掉、仅保留窗口内的点,散点大小增大到 s=12:
# Test 12assert zoom_mask.dtype == bool, "zoom_mask should be a Boolean mask."assert np.sum(zoom_mask) > 5, "Your zoom window should contain several data points."print("Test 12 passed.")输出:
Test 12 passed.6.3 练习 13:估算凌星持续时间
使用简单阈值法:取正常流量(1.0)与最低流量之间的中点作为阈值,低于该阈值的点视为凌星期间。np.ptp()(peak-to-peak)计算数组的最大值减最小值,用于估算凌星持续时长:
# Exercise 13 — estimate a rough duration# We use a simple threshold halfway between normal flux (1.0) and the deepest point.# Points below this threshold near the deepest point are counted as part of the dip.
### START CODE HERE ###threshold = (1.0 + minimum_flux) / 2near_transit_mask = zoom_maskin_dip_mask = near_transit_mask & (flux_normalized < threshold)estimated_duration_days = np.ptp(time_clean[in_dip_mask])### END CODE HERE ###
print("Threshold:", threshold)print("Estimated duration [days]:", estimated_duration_days)print("Estimated duration [hours]:", estimated_duration_days * 24)输出:
Threshold: 0.9876689446122113Estimated duration [days]: 0.20833333333333393Estimated duration [hours]: 5.000000000000014& 运算符组合两个布尔掩码:时间窗口内 AND 流量低于阈值。估算凌星持续时间约 0.21 天(约 5 小时):
# Test 13assert np.isfinite(threshold), "threshold should be a number."assert in_dip_mask.dtype == bool, "in_dip_mask should be a Boolean mask."assert np.sum(in_dip_mask) >= 2, "You should have at least two points inside the possible dip."assert estimated_duration_days > 0, "estimated_duration_days should be positive."print("Test 13 passed.")输出:
Test 13 passed.6.4 观察 3(Observation 3)
Observation 3
Look at your zoom plot.
Write 2–4 sentences:
- Does the deepest point look like part of a real dip?
- Or does it look like one strange measurement?
- What makes you confident or uncertain?
Your answer:
Yes, the deepest point look like part of a real dip. Because many data points around it also lie below the baseline.
最深点周围有多个数据点也低于基线,这是一个真实的凌星凹陷而非孤立的噪声点。
7. 寻找重复凌星信号(Part 6 — Look for repeated dips by eye)
Part 6 — Look for repeated dips by eye
A real planet should usually produce more than one dip.
In this section, we will not use an automatic search algorithm. We will only make a helpful plot and inspect it.
真正的系外行星应产生多次凌星信号。本节不采用自动搜索算法,仅通过可视化来人工检查。
7.1 练习 14:标记低流量点
使用与前一步相同的阈值,标记整条光变曲线中所有低于阈值的点:
# Exercise 14 — highlight low-flux points# Create a mask for points that are below the threshold.
### START CODE HERE ###low_flux_mask = flux_normalized < thresholdnumber_of_low_points = np.sum(low_flux_mask)### END CODE HERE ###
print("Number of low-flux points:", number_of_low_points)输出:
Number of low-flux points: 6666 个低流量点分布在 45 天的观测中:
# Test 14assert low_flux_mask.dtype == bool, "low_flux_mask should be a Boolean mask."assert number_of_low_points == np.sum(low_flux_mask), "number_of_low_points should count the True values."assert number_of_low_points > 0, "There should be at least one low-flux point."print("Test 14 passed.")输出:
Test 14 passed.用两种颜色区分所有点和低流量点,并画出阈值水平线:
# Plot all points and highlight the low-flux points.
plt.figure(figsize=(10, 4))plt.scatter(time_clean, flux_normalized, s=4, alpha=0.35, label="all points")plt.scatter(time_clean[low_flux_mask], flux_normalized[low_flux_mask], s=14, alpha=0.9, label="low-flux points")plt.axhline(threshold, linestyle="--", label="threshold")plt.xlabel("Time [days]")plt.ylabel("Normalized flux")plt.title("Low-flux points in the light curve")plt.legend()plt.grid(True)plt.show()
plt.axhline() 在 y 轴指定位置绘制水平参考线。plt.legend() 显示图例。
7.2 观察 4(Observation 4)
Observation 4
Look at the highlighted points.
Write 2–4 sentences:
- Do low-flux points appear in several places?
- Are they grouped into short dips?
- Would you call this a possible transit-like signal? Why or why not?
Your answer:
Yes, low-flux points appear in several places.
Yes, they grouped into short dips.
I would call this a possible transit-like signal.Because they appear with a constant period.
低流量点确实出现在多个位置,以短簇形式聚集。它们以近乎固定的间隔重复出现,这符合周期性凌星信号的特征。因此可以将其称为可能的凌星信号(possible transit-like signal)。
8. 观测报告(Part 7 — Observation card)
Part 7 — Observation card
Now summarize your result like a scientist.
Use careful language. You are not confirming a planet. You are reporting a possible transit-like signal.
8.1 练习 15:填写观测卡片
将之前手动估算的结果整理为一张观测卡片。使用审慎的科学语言——报告的是”可能的凌星信号”,而非”确认的行星”:
# Exercise 15 — observation card# Fill this card using your measurements.
### START CODE HERE ###observation_card = { "target_id": MYSTERY_TARGET["target_id"], "estimated_transit_time_days": transit_time_guess, "estimated_depth_percent": transit_depth_percent, "estimated_duration_hours": estimated_duration_days * 24, "number_of_low_flux_points": number_of_low_points, "confidence": "medium", "conclusion": "A possible transit-like signal was detected in the light curve. " "The dips are short, grouped, and appear at several locations. " "But further period-folding analysis is needed to verify whether " "the signal repeats at regular intervals.",}### END CODE HERE ###
observation_card输出:
{'target_id': 'MYSTERY-01-36', 'estimated_transit_time_days': np.float64(10.895833333333332), 'estimated_depth_percent': np.float64(2.466211077557734), 'estimated_duration_hours': np.float64(5.000000000000014), 'number_of_low_flux_points': np.int64(66), 'confidence': 'medium', 'conclusion': 'A possible transit-like signal was detected in the light curve. ' 'The dips are short, grouped, and appear at several locations. ' 'But further period-folding analysis is needed to verify whether ' 'the signal repeats at regular intervals.'}confidence 设为 "medium"(中等置信度),结论中使用了 "possible transit-like signal" 而非 "confirmed planet",体现了科学报告的审慎原则:
# Test 15required_keys = [ "target_id", "estimated_transit_time_days", "estimated_depth_percent", "estimated_duration_hours", "number_of_low_flux_points", "confidence", "conclusion",]
for key in required_keys: assert key in observation_card, f"Missing key: {key}"
assert observation_card["target_id"] == MYSTERY_TARGET["target_id"], "Use the correct target_id."assert observation_card["confidence"] in ["low", "medium", "high"], "confidence should be low, medium, or high."assert "confirmed" in observation_card["conclusion"].lower() or "possible" in observation_card["conclusion"].lower(), "Use careful scientific language."print("Test 15 passed.")输出:
Test 15 passed.9. 结果验证(Final reveal)
Final reveal
Run this only after your observation card is complete.
This tells you the hidden parameters used to generate the synthetic light curve.
观测卡片填写完毕后,运行以下代码查看用于生成光变曲线的真实隐藏参数:
truth = MYSTERY_TARGET["truth"]
print("Hidden target information")print("-------------------------")print("True period [days]:", truth["period_days"])print("First transit time [days]:", truth["first_transit_time_days"])print("True duration [hours]:", truth["duration_days"] * 24)print("True depth [%]:", truth["depth_percent"])print()print("Compare your manual estimates with the hidden values.")print("Tomorrow, we will learn how computers search for repeating transit patterns automatically.")输出:
Hidden target information-------------------------True period [days]: 5.606855586815562First transit time [days]: 5.208084328426675True duration [hours]: 5.871704212206591True depth [%]: 1.548150177820702
Compare your manual estimates with the hidden values.Tomorrow, we will learn how computers search for repeating transit patterns automatically.手动估算 vs 真实值对比:
| 参数 | 手动估算 | 真实值 |
|---|---|---|
| 凌星深度 | ∼2.47% | 1.55% |
| 凌星时长 | ∼5.0小时 | 5.87小时 |
| 周期 | — | 5.61天 |
手动估算的凌星时长(5.0 小时)与真实值(5.87 小时)接近,但深度估算偏高(因为手动只取了最深点而真实深度是平均值)。周期无法通过手动方式可靠估计,这是下一步周期折叠分析(period-folding)的任务。
10. 提交清单(Submission checklist)
Submission checklist
Before you finish, make sure your notebook has:
- your name and team number;
- completed code exercises;
- all tests passed;
- raw light curve plot;
- cleaned and normalized light curve plot;
- zoom plot near the deepest point;
- highlighted low-flux points plot;
- completed observation card;
- careful conclusion: possible signal, not confirmed planet.
完成 notebook 前需确保:姓名和队伍编号已填写、代码练习已完成、所有测试通过、四张图均已绘制、观测卡片已填写、结论使用了审慎措辞。
11. 本节小结
本文完成了以下内容:
- 使用 NumPy 创建时间网格、叠加正态分布噪声,模拟真实望远镜观测数据。
- 通过
np.isfinite()去除缺失值(NaN),使用 MAD 方法(np.median+ 1.4826 缩放因子)检测并去除极端亮斑异常值。 - 用中位数归一化将流量统一到 1.0 附近。
- 利用
np.argmin()定位最深点,通过布尔掩码和np.ptp()手动估算凌星时间和持续时间。 - 使用阈值法标记全局低流量点,通过可视化检查信号是否以固定间隔重复出现。
- 学习了科学报告的审慎原则:报告的是”可能信号”而非”确认发现”,使用
confidence等级和possible措辞。 - 对比手动估算值与真实参数,理解自动化周期搜索(period-folding)的必要性。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)