docs: bilingual comments for the integration examples
This commit is contained in:
parent
d911a4bb0a
commit
2190e1f81f
|
|
@ -121,7 +121,6 @@ const CENTRAL_ANGLES_DEGREES: &[f64] = &[0.0, 1.0, 5.0, 15.0, 30.0, 45.0, 60.0,
|
|||
fn main() {
|
||||
// The header names every column so that the Python side can read by name
|
||||
// rather than by position.
|
||||
//
|
||||
// 表头为每一列命名,使 Python 侧可以按名而非按位置读取。
|
||||
println!(
|
||||
"case,satellite_radius_km,body_radius_km,elevation_mask_rad,\
|
||||
|
|
@ -162,7 +161,6 @@ fn main() {
|
|||
|
||||
// The inverse relation, written with its own header so the Python side
|
||||
// can tell the two blocks apart by the first column.
|
||||
//
|
||||
// 反解关系,带上它自己的表头,使 Python 侧可以按第一列区分两个块。
|
||||
println!();
|
||||
println!("inverse_central_degrees,elevation_rad,round_trip_central_degrees");
|
||||
|
|
@ -171,7 +169,6 @@ fn main() {
|
|||
// Use the equatorial radius as the satellite radius for the inverse
|
||||
// check, so the geometry is the degenerate one where the elevation is
|
||||
// always zero. A more useful check uses a satellite altitude.
|
||||
//
|
||||
// 反解检查把赤道半径用作卫星半径,故几何是退化的那种,其中仰角恒为零。更
|
||||
// 有用的检查要用卫星高度。
|
||||
let satellite_radius = BODY_RADIUS_KM + 550.0;
|
||||
|
|
@ -181,7 +178,6 @@ fn main() {
|
|||
// Beyond the horizon the inverse has no solution, which is
|
||||
// reported as an empty row rather than skipped, so the count of
|
||||
// rows stays fixed.
|
||||
//
|
||||
// 在地平之外反解无解,故报告为一个空行而非跳过,使行数保持固定。
|
||||
println!("{degrees},,");
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ const TAI_MINUS_UTC: f64 = 37.0;
|
|||
/// 返回 UTC 时标上某约化儒略日对应的 TT 儒略日。
|
||||
fn tt_julian_date(utc_mjd: f64) -> f64 {
|
||||
// TAI = UTC + 37, TT = TAI + 32.184, so TT = UTC + 69.184 seconds.
|
||||
//
|
||||
// TAI = UTC + 37,TT = TAI + 32.184,故 TT = UTC + 69.184 秒。
|
||||
utc_mjd + 2_400_000.5 + (TT_MINUS_TAI + TAI_MINUS_UTC) / 86_400.0
|
||||
}
|
||||
|
|
@ -54,26 +53,22 @@ fn main() {
|
|||
let t = (tt - 2_451_545.0) / 36_525.0;
|
||||
|
||||
// The obliquity, in radians.
|
||||
//
|
||||
// 黄赤交角,单位为弧度。
|
||||
println!("{mjd},obliquity,0,{:.17e}", mean_obliquity(t));
|
||||
|
||||
// The nutation, from the IAU 2000A model.
|
||||
//
|
||||
// 章动,取自 IAU 2000A 模型。
|
||||
let nutation = nutation_2000a(t);
|
||||
println!("{mjd},nutation_longitude_a,0,{:.17e}", nutation.longitude);
|
||||
println!("{mjd},nutation_obliquity_a,0,{:.17e}", nutation.obliquity);
|
||||
|
||||
// The nutation with the IAU 2006 adjustment.
|
||||
//
|
||||
// 施加 IAU 2006 调整后的章动。
|
||||
let adjusted = nutation_2000a_with_2006_adjustment(t);
|
||||
println!("{mjd},nutation_longitude_06a,0,{:.17e}", adjusted.longitude);
|
||||
println!("{mjd},nutation_obliquity_06a,0,{:.17e}", adjusted.obliquity);
|
||||
|
||||
// The four precession angles.
|
||||
//
|
||||
// 四个岁差角。
|
||||
let angles = precession_angles(t);
|
||||
println!("{mjd},precession_gamma_bar,0,{:.17e}", angles.gamma_bar);
|
||||
|
|
@ -83,7 +78,6 @@ fn main() {
|
|||
|
||||
// The bias-precession-nutation matrix, which carries the celestial
|
||||
// reference frame to the true equator and equinox of date.
|
||||
//
|
||||
// 偏差-岁差-章动矩阵,它把天球参考系带到该日的真赤道与真春分点。
|
||||
let bpn = bias_precession_nutation_matrix(t);
|
||||
for row in 0..3 {
|
||||
|
|
@ -94,7 +88,6 @@ fn main() {
|
|||
}
|
||||
|
||||
// Greenwich apparent sidereal time, in radians.
|
||||
//
|
||||
// 格林尼治视恒星时,单位为弧度。
|
||||
println!(
|
||||
"{mjd},gast,0,{:.17e}",
|
||||
|
|
@ -102,7 +95,6 @@ fn main() {
|
|||
);
|
||||
|
||||
// The celestial-to-terrestrial matrix, with zero polar motion.
|
||||
//
|
||||
// 天球到地球坐标系矩阵,极移为零。
|
||||
let matrix = celestial_to_terrestrial_matrix(tt, tt, PolarMotion::NONE);
|
||||
for row in 0..3 {
|
||||
|
|
|
|||
|
|
@ -160,7 +160,6 @@ const GEO_RADIUS: f64 = 42_164.0;
|
|||
|
||||
fn main() {
|
||||
// The first table: elements to a Cartesian state.
|
||||
//
|
||||
// 第一张表:根数到直角坐标状态。
|
||||
println!(
|
||||
"case,semi_major_axis_km,eccentricity,inclination_rad,right_ascension_rad,\
|
||||
|
|
@ -175,7 +174,6 @@ fn main() {
|
|||
// The specific energy is reported as well, because it is the
|
||||
// quantity the Python side uses to check the speed independently of
|
||||
// the direction.
|
||||
//
|
||||
// 比能量也被报告,因为它是 Python 侧用来独立于方向核对速率的量。
|
||||
let radius = state.position.norm();
|
||||
let energy = state.velocity.norm_squared() / 2.0 - MU.value() / radius;
|
||||
|
|
@ -201,7 +199,6 @@ fn main() {
|
|||
|
||||
// A circular orbit at the geostationary radius, whose speed has a closed
|
||||
// form, is included in the same table under its own name.
|
||||
//
|
||||
// 地球静止半径处的圆轨道,其速率有闭式,故以它自己的名称纳入同一张表。
|
||||
let geo = KeplerianElements {
|
||||
semi_major_axis: GEO_RADIUS,
|
||||
|
|
@ -235,7 +232,6 @@ fn main() {
|
|||
// The second table: a state back to elements, over a grid of true
|
||||
// anomalies on one orbit. This exercises the vector algebra rather than
|
||||
// the perifocal formulae the first table uses.
|
||||
//
|
||||
// 第二张表:在一个轨道的真近点角网格上从状态转回根数。它检验矢量代数,而非
|
||||
// 第一张表所用的近焦点公式。
|
||||
println!();
|
||||
|
|
|
|||
|
|
@ -144,7 +144,6 @@ fn main() {
|
|||
let cn = budget.carrier_to_noise().map(|value| value.db());
|
||||
// The energy per bit ratio is reported at a fixed data rate equal to
|
||||
// the bandwidth, which makes it the carrier to noise ratio.
|
||||
//
|
||||
// 每比特能量比在等于带宽的固定数据速率下报告,这使它即为载噪比。
|
||||
let ebn0 = budget
|
||||
.energy_per_bit_over_noise(bandwidth)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ use apsis_tle::TwoLineElement;
|
|||
/// 中、高偏心率以及深空分支。
|
||||
const ELEMENT_SETS: &[&str] = &[
|
||||
// International Space Station, near circular low Earth orbit.
|
||||
//
|
||||
// 国际空间站,近圆形低轨。
|
||||
concat!(
|
||||
"1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927\r\n",
|
||||
|
|
@ -38,21 +37,18 @@ const ELEMENT_SETS: &[&str] = &[
|
|||
),
|
||||
// A highly eccentric orbit, which exercises the eccentric anomaly
|
||||
// iteration and the drag terms away from circular motion.
|
||||
//
|
||||
// 一个高度偏心的轨道,它检验偏近点角迭代以及远离圆运动时的阻力项。
|
||||
concat!(
|
||||
"1 00005U 58002B 00179.78495062 .00000023 00000-0 28098-4 0 4753\r\n",
|
||||
"2 00005 34.2682 348.7242 1859667 331.7664 19.3264 10.82419157413667\r\n",
|
||||
),
|
||||
// A geostationary transfer orbit with a low perigee.
|
||||
//
|
||||
// 一个近地点很低的地球静止转移轨道。
|
||||
concat!(
|
||||
"1 27651U 03004A 06185.99374200 .00000109 00000-0 19909-4 0 445\r\n",
|
||||
"2 27651 7.1743 182.9443 7867251 269.0386 18.6601 1.03461646 9884\r\n",
|
||||
),
|
||||
// A deep space orbit, whose period exceeds 225 minutes.
|
||||
//
|
||||
// 一个深空轨道,其周期超过 225 分钟。
|
||||
concat!(
|
||||
"1 09880U 77021A 06174.85710247 .00000090 00000-0 10000-3 0 7322\r\n",
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ fn main() {
|
|||
for &frequency in FREQUENCIES_GHZ {
|
||||
// A horizontal path, which removes the elevation dependence and
|
||||
// isolates the coefficient curves.
|
||||
//
|
||||
// 水平路径,它去除仰角依赖并分离出各系数曲线。
|
||||
for &(elevation_degrees, polarisation_degrees) in
|
||||
&[(90.0_f64, 0.0_f64), (90.0, 90.0), (30.0, 0.0), (30.0, 45.0)]
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ fn main() {
|
|||
// is used directly here and the check tolerates that difference.
|
||||
// The Julian date is split into a whole number of days and a
|
||||
// fraction, which preserves more of the day than a single value.
|
||||
//
|
||||
// 该变换需要 UT1 时间,因为是地球自转在带动坐标系。UT1 与 UTC 相差不到
|
||||
// 0.9 秒,即 0.0037 度的转动,故此处直接使用 UTC 值,而检查容忍该差异。儒略日
|
||||
// 被拆成整数天数与零头,这比单个值保留更多当日信息。
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ fn main() {
|
|||
|
||||
// Sample every leap second boundary at several times of day, so that
|
||||
// the step behaviour and the interior of each interval are covered.
|
||||
//
|
||||
// 在一天中的若干时刻对每个闰秒边界采样,使阶跃行为与每个区间的内部都被覆盖。
|
||||
for entry in LEAP_SECONDS {
|
||||
for (hour, minute, second) in [(0, 0, 0.0), (6, 0, 0.0), (12, 0, 0.0), (18, 0, 0.0)] {
|
||||
|
|
@ -74,7 +73,6 @@ fn main() {
|
|||
}
|
||||
|
||||
// The last second before a leap second and the first second after it.
|
||||
//
|
||||
// 闰秒前的最后一秒与其后的第一秒。
|
||||
for (year, month, day, hour, minute, second) in [
|
||||
(2016, 12, 31, 23, 59, 59.0),
|
||||
|
|
@ -91,7 +89,6 @@ fn main() {
|
|||
|
||||
// The Terrestrial Time offset is a defining constant, written here so
|
||||
// that the checker can verify it without recomputing the calendar.
|
||||
//
|
||||
// 地球时偏移是一个定义常量,写在这里使检查器无需重算日历即可核验它。
|
||||
println!("# tt_minus_tai_seconds={TT_MINUS_TAI:.3}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,6 @@ const SAMPLES: &[Sample] = &[
|
|||
sample(
|
||||
// A two digit year of 99 must expand to 1999, the last year of the
|
||||
// twentieth century branch of the convention.
|
||||
//
|
||||
// 两位年份 99 必须展开为 1999,即该约定二十世纪分支的最后一年。
|
||||
"two_digit_year_1999",
|
||||
"1 10000U 99001A 99365.00000000 .00000000 00000-0 00000-0 0 05",
|
||||
|
|
@ -151,7 +150,6 @@ fn main() {
|
|||
// The epoch is stored as a Modified Julian Date; the calendar form
|
||||
// is written so the reader can compare the year and the day of year
|
||||
// against the two digit year and fraction the line encodes.
|
||||
//
|
||||
// 历元存为约化儒略日;写出日历形式,使读者可以把年与一年中的第几天对照该行
|
||||
// 所编码的两位年份与零头。
|
||||
let calendar = element
|
||||
|
|
|
|||
|
|
@ -25,6 +25,22 @@
|
|||
//! ```text
|
||||
//! cargo run -p apsis-rs --features network --example integrate_network
|
||||
//! ```
|
||||
//!
|
||||
//! 跨层集成检查:星座到可见性窗口。
|
||||
//!
|
||||
//! 本示例走完本工作区所提供的整条链,并断言其中每一环都为下一环产生合理的输入。
|
||||
//! 它不是对照外部参考的数值交叉核对;逐层交叉核对做那件事。它的目的是在某层的
|
||||
//! 改动破坏另一层的假设时失败。
|
||||
//!
|
||||
//! 该链是:
|
||||
//!
|
||||
//! 1. 用 `apsis-constellation` 构建 Walker 星座。
|
||||
//! 2. 用 `apsis-sgp4` 传播一颗卫星,得到真赤道平春分点坐标系中的状态矢量。
|
||||
//! 3. 用 `apsis-coord` 与来自 `apsis-frames` 的地球自转角,把该状态转入地固系。
|
||||
//! 4. 用 `apsis-access` 从地面站搜索可见性窗口。
|
||||
//! 5. 用 `apsis-topology` 与 `apsis-isl` 构建星间链路的时间序列。
|
||||
//! 6. 用 `apsis-routing` 在两个终端之间路由,并用 `apsis-mesh` 度量网格的
|
||||
//! 冗余度。
|
||||
|
||||
use apsis_access::{AccessFinder, OrbitSample};
|
||||
use apsis_constellation::{Constellation, WalkerPattern};
|
||||
|
|
@ -43,16 +59,24 @@ use apsis_topology::{Topology, TopologySample, TopologySeries};
|
|||
/// The Earth equatorial radius, in kilometers, used for the link geometry.
|
||||
///
|
||||
/// The value comes from the shared constants rather than being repeated.
|
||||
///
|
||||
/// 链路几何所用的地球赤道半径,单位为千米。
|
||||
///
|
||||
/// 该值来自共享常量而非重复书写。
|
||||
const BODY_RADIUS_KM: f64 = apsis_core::constant::EARTH_EQUATORIAL_RADIUS;
|
||||
|
||||
/// The element set of a satellite in a 550 km orbit, used as the source of
|
||||
/// the propagated track.
|
||||
///
|
||||
/// 一个 550 km 轨道卫星的根数集,用作所传播轨迹的来源。
|
||||
const TLE: &str = concat!(
|
||||
"1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927\r\n",
|
||||
"2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537\r\n",
|
||||
);
|
||||
|
||||
/// Returns the time of the element set epoch as a Modified Julian Date.
|
||||
///
|
||||
/// 把根数集历元的时间返回为约化儒略日。
|
||||
fn epoch_mjd() -> f64 {
|
||||
let element = TwoLineElement::parse(TLE).expect("the sample element set parses");
|
||||
element
|
||||
|
|
@ -70,6 +94,12 @@ fn epoch_mjd() -> f64 {
|
|||
/// thousandth of a degree. It is adequate for an integration check, and
|
||||
/// replacing it with the exact transformation is the job of the coordinate
|
||||
/// milestone that adds precession and nutation.
|
||||
///
|
||||
/// 返回自根数集历元起若干秒处地固系中的卫星位置。
|
||||
///
|
||||
/// SGP4 状态在真赤道平春分点坐标系中,此处把它当作转动所用的惯性系。这是一个
|
||||
/// 近似:二者相差分点方程式,不到千分之一度。对集成检查它足够,而把它换成精确
|
||||
/// 变换是加入岁差与章动的坐标里程碑的事。
|
||||
fn position_ecef_km(satellite: &Satellite, seconds: f64) -> Vec3 {
|
||||
let state = satellite
|
||||
.propagate_minutes(seconds / 60.0)
|
||||
|
|
@ -78,6 +108,8 @@ fn position_ecef_km(satellite: &Satellite, seconds: f64) -> Vec3 {
|
|||
// The Earth rotation angle needs the UT1 time. The difference between
|
||||
// UT1 and TAI is under a second, which is under 0.005 degrees of
|
||||
// rotation, so the TAI epoch is used here.
|
||||
// 地球自转角需要 UT1 时间。UT1 与 TAI 之差不到一秒,即不到 0.005 度的转动,
|
||||
// 故此处使用 TAI 历元。
|
||||
let days_since_j2000 = (epoch_mjd() + seconds / 86_400.0) - 51_544.5;
|
||||
let angle = EarthRotationAngle::from_ut1_days(days_since_j2000);
|
||||
inertial.to_ecef(angle).vector()
|
||||
|
|
@ -85,6 +117,7 @@ fn position_ecef_km(satellite: &Satellite, seconds: f64) -> Vec3 {
|
|||
|
||||
fn main() {
|
||||
// Step 1: the constellation geometry.
|
||||
// 第 1 步:星座几何。
|
||||
let pattern = WalkerPattern {
|
||||
total_satellites: 24,
|
||||
planes: 3,
|
||||
|
|
@ -107,6 +140,7 @@ fn main() {
|
|||
|
||||
// Step 2 and 3: propagate the track and rotate it into the Earth-fixed
|
||||
// frame.
|
||||
// 第 2 与第 3 步:传播轨迹并把它转入地固系。
|
||||
let element = TwoLineElement::parse(TLE).expect("the sample element set parses");
|
||||
let satellite =
|
||||
Satellite::from_element(&element, GravityModel::WGS72).expect("the orbit propagates");
|
||||
|
|
@ -120,6 +154,7 @@ fn main() {
|
|||
}
|
||||
// Every sample must be a plausible Earth-fixed position: between the
|
||||
// surface and a few thousand kilometers above it.
|
||||
// 每个样本都必须是合理的地固位置:在地表与其上方数千米之间。
|
||||
for sample in &samples {
|
||||
let radius = sample.position_ecef_km.norm();
|
||||
assert!(
|
||||
|
|
@ -138,6 +173,9 @@ fn main() {
|
|||
// fixed location makes the example depend only on the chain working,
|
||||
// not on the particular ground track of this element set, which crosses
|
||||
// a given longitude only every few orbits.
|
||||
// 第 4 步:来自一个被放在第一个样本星下点的地面站的可见性窗口。把站放在那里
|
||||
// 而非固定位置,使本示例只依赖于该链正常工作,而不依赖于该根数集特定的地面轨迹
|
||||
// ——它每隔几个轨道才穿过某一给定经度。
|
||||
let sub_satellite =
|
||||
apsis_coverage::sub_satellite_point(samples[0].position_ecef_km, apsis_coord::WGS84)
|
||||
.expect("the satellite is off the polar axis");
|
||||
|
|
@ -156,12 +194,16 @@ fn main() {
|
|||
);
|
||||
// A 550 km orbit in a 53 degree inclination passes over the equator a
|
||||
// few times in a hundred minutes, so at least one pass must be found.
|
||||
// 一个 53 度倾角的 550 km 轨道在一百分钟里数次经过赤道上空,故必须找到至少
|
||||
// 一次过境。
|
||||
assert!(summary.count() >= 1, "no pass was found");
|
||||
|
||||
// Step 5: the inter-satellite links at one instant. The constellation
|
||||
// elements are used directly to place the satellites on a circle, which
|
||||
// is what the Walker construction guarantees, so the link geometry can
|
||||
// be tested without propagating every satellite.
|
||||
// 第 5 步:某一瞬时的星间链路。星座根数被直接用于把卫星放在一个圆上,这是
|
||||
// Walker 构建所保证的,故无需传播每颗卫星即可检验链路几何。
|
||||
let mut topology = Topology::new(constellation.len());
|
||||
for first in 0..constellation.len() {
|
||||
for second in (first + 1)..constellation.len() {
|
||||
|
|
@ -171,6 +213,7 @@ fn main() {
|
|||
let second_position = elements_position(&second_elements);
|
||||
if mutual_visibility(first_position, second_position, BODY_RADIUS_KM) == Some(true) {
|
||||
// The cost is the range, so a shorter link is cheaper.
|
||||
// 代价即距离,故更短的链路更便宜。
|
||||
let cost = first_position.distance(second_position);
|
||||
topology
|
||||
.add_bidirectional_edge(first, second, cost)
|
||||
|
|
@ -189,6 +232,7 @@ fn main() {
|
|||
);
|
||||
|
||||
// Step 6: routing and redundancy.
|
||||
// 第 6 步:路由与冗余。
|
||||
let mesh = Mesh::new(topology.clone());
|
||||
let reachable = mesh.reachable_from(0).len();
|
||||
println!(
|
||||
|
|
@ -199,6 +243,7 @@ fn main() {
|
|||
);
|
||||
|
||||
// Route between two satellites that are joined.
|
||||
// 在两个相连的卫星之间路由。
|
||||
let neighbour = topology
|
||||
.neighbours(0)
|
||||
.first()
|
||||
|
|
@ -215,6 +260,7 @@ fn main() {
|
|||
|
||||
// The same topology as a one sample series, which is what the series
|
||||
// type is for.
|
||||
// 把同一拓扑作为单样本序列,这正是序列类型的用途。
|
||||
let series = TopologySeries::new(vec![TopologySample::new(0.0, topology)])
|
||||
.expect("the series is not empty");
|
||||
assert_eq!(series.len(), 1);
|
||||
|
|
@ -227,6 +273,7 @@ fn main() {
|
|||
|
||||
// A cross-check of the frame conversion against the geodetic layer. Two
|
||||
// things must hold.
|
||||
// 把坐标架变换与大地层交叉核对。必须成立两件事。
|
||||
//
|
||||
// First, the rotation into the Earth-fixed frame preserves the radius,
|
||||
// so the geocentric radius of the converted position equals the radius
|
||||
|
|
@ -234,6 +281,9 @@ fn main() {
|
|||
// mistake: the orbit layer works in kilometers and the coordinate layer
|
||||
// in meters, so mixing the two silently places the satellite inside the
|
||||
// Earth.
|
||||
// 首先,转入地固系的转动保持半径,故转换后位置的地心半径等于传播器所报告的
|
||||
// 半径。这正是能捕获单位错误的性质:orbit 层以千米工作,coord 层以米工作,把
|
||||
// 二者混用会无声地把卫星放进地球内部。
|
||||
let propagated = position_ecef_km(&satellite, 0.0);
|
||||
let state = satellite
|
||||
.propagate_minutes(0.0)
|
||||
|
|
@ -253,6 +303,10 @@ fn main() {
|
|||
// equatorial radius, about twenty one kilometers. Bracketing the height
|
||||
// tests the conversion without assuming an altitude the element set does
|
||||
// not have.
|
||||
// 其次,大地高度与半径之差在扁率以内。地心半径为 `r` 的点,其大地高度介于
|
||||
// `r - a` 与 `r - b` 之间,其中 `a` 与 `b` 是赤道与极半径:差值至多为扁率乘以
|
||||
// 赤道半径,约二十一千米。用区间夹住高度即可检验换算,而不假定根数集所没有的
|
||||
// 高度。
|
||||
let ecef = Ecef::new(propagated * 1_000.0);
|
||||
let geodetic = ecef
|
||||
.to_geodetic(apsis_coord::WGS84)
|
||||
|
|
@ -270,6 +324,7 @@ fn main() {
|
|||
|
||||
// The sub-satellite point must lie at the same latitude and longitude
|
||||
// as the satellite itself.
|
||||
// 星下点必须位于与卫星本身相同的纬度与经度。
|
||||
assert!(
|
||||
(sub_satellite.position.latitude - geodetic.latitude()).abs() < 1e-9,
|
||||
"latitude {} against {}",
|
||||
|
|
@ -283,6 +338,7 @@ fn main() {
|
|||
geodetic.longitude()
|
||||
);
|
||||
// The epoch of the element set must round trip through the time layer.
|
||||
// 根数集的历元必须经时间层往返一致。
|
||||
let epoch: Epoch = element.epoch;
|
||||
let calendar = epoch
|
||||
.calendar(TimeScale::Utc)
|
||||
|
|
@ -301,10 +357,15 @@ fn main() {
|
|||
/// The Walker construction is circular, so the radius is the semi-major axis
|
||||
/// and the position follows from the three orientation angles and the
|
||||
/// anomaly.
|
||||
///
|
||||
/// 由卫星的开普勒根数、在其所携带的真近点角处返回惯性位置。
|
||||
///
|
||||
/// Walker 构建是圆形的,故半径即半长轴,位置由三个取向角与该近点角得出。
|
||||
fn elements_position(elements: &apsis_kepler::KeplerianElements) -> Vec3 {
|
||||
let radius = elements.semi_major_axis;
|
||||
let angle = elements.true_anomaly;
|
||||
// The position in the perifocal frame.
|
||||
// 近焦点系中的位置。
|
||||
let perifocal = Vec3::new(radius * angle.cos(), radius * angle.sin(), 0.0);
|
||||
let rotation = apsis_kepler::perifocal_to_inertial(
|
||||
elements.argument_of_perigee,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,21 @@
|
|||
//! ```text
|
||||
//! cargo run -p apsis-rs --features ops --example integrate_ops
|
||||
//! ```
|
||||
//!
|
||||
//! 跨层集成检查:卫星与地面站上的任务规划。
|
||||
//!
|
||||
//! 本示例端到端走完 ops 层,并断言每一步都为下一步产生合理的输入。它不是数值
|
||||
//! 交叉核对;逐层测试做那件事。它的目的是在某 crate 的改动破坏另一 crate 的假设
|
||||
//! 时失败。
|
||||
//!
|
||||
//! 该链是:
|
||||
//!
|
||||
//! 1. 把一颗卫星与一座地面站定义为资源,站的可用性取自可见性窗口。
|
||||
//! 2. 定义各自需要该卫星与该站的观测任务。
|
||||
//! 3. 用 `apsis-scheduler` 规划这些任务,用先后约束表达一个按序需要两种资源的
|
||||
//! 任务。
|
||||
//! 4. 用 `apsis-conflict` 检查结果。
|
||||
//! 5. 用 `apsis-schedule` 评估调度。
|
||||
|
||||
use apsis_conflict::detect;
|
||||
use apsis_resource::{Resource, ResourceId, ResourceSet, Window};
|
||||
|
|
@ -28,14 +43,22 @@ use apsis_scheduler::{ImprovementSettings, Planner};
|
|||
use apsis_task::{PrecedenceGraph, Task, TaskId};
|
||||
|
||||
/// The satellite resource.
|
||||
///
|
||||
/// 卫星资源。
|
||||
const SATELLITE: ResourceId = ResourceId::new(0);
|
||||
/// The ground station resource.
|
||||
///
|
||||
/// 地面站资源。
|
||||
const STATION: ResourceId = ResourceId::new(1);
|
||||
|
||||
/// Returns the station availability, as a list of access windows.
|
||||
///
|
||||
/// These would come from the access layer in a full scenario. They are
|
||||
/// written out here so that the example depends only on the ops layer.
|
||||
///
|
||||
/// 把地面站的可用性返回为可见性窗口列表。
|
||||
///
|
||||
/// 在完整场景中它们会来自可见性层。此处把它们写出,使本示例只依赖于 ops 层。
|
||||
fn station_windows() -> Vec<Window> {
|
||||
vec![
|
||||
Window::new(100.0, 400.0),
|
||||
|
|
@ -47,6 +70,8 @@ fn station_windows() -> Vec<Window> {
|
|||
fn main() {
|
||||
// Step 1: the resources. The station is available only during its access
|
||||
// windows, while the satellite is available throughout the scenario.
|
||||
// 第 1 步:资源。地面站只在其可见性窗口期间可用,而卫星在整个场景期间都可
|
||||
// 用。
|
||||
let mut satellite = Resource::new(SATELLITE, 1.0);
|
||||
satellite
|
||||
.set_windows(&[Window::new(0.0, 5_000.0)])
|
||||
|
|
@ -72,6 +97,8 @@ fn main() {
|
|||
|
||||
// Step 2: the tasks. Each observation needs both resources for its whole
|
||||
// duration, so it can only run inside an access window.
|
||||
// 第 2 步:任务。每次观测在其整个时长内都需要两种资源,故它只能在某个可见性
|
||||
// 窗口内运行。
|
||||
let tasks: Vec<Task> = (0..8)
|
||||
.map(|index| {
|
||||
Task::new(TaskId::new(index), 60.0)
|
||||
|
|
@ -89,12 +116,14 @@ fn main() {
|
|||
);
|
||||
|
||||
// Step 3: plan.
|
||||
// 第 3 步:规划。
|
||||
let result = Planner::new().schedule(&tasks, &resources);
|
||||
println!("plan: {} of {} placed", result.schedule.len(), tasks.len());
|
||||
assert!(result.schedule.len() <= tasks.len());
|
||||
|
||||
// Step 4: the plan must be free of conflicts. The planner checks the
|
||||
// capacity before each placement, so this is a check of that promise.
|
||||
// 第 4 步:计划必须无冲突。规划器在每次放置前检查容量,故这是对该承诺的检查。
|
||||
let report = detect(&result.schedule, &tasks, &resources);
|
||||
println!("conflicts: {}", report.summary());
|
||||
assert!(
|
||||
|
|
@ -105,12 +134,14 @@ fn main() {
|
|||
|
||||
// Every placement must fall inside an access window, which the conflict
|
||||
// detector confirms as an absence of availability conflicts.
|
||||
// 每处放置都必须落在某个可见性窗口内,冲突检测器以无可用性冲突来确认这一点。
|
||||
assert_eq!(
|
||||
report.count_of(apsis_conflict::ConflictKind::Availability),
|
||||
0
|
||||
);
|
||||
|
||||
// Step 5: evaluate.
|
||||
// 第 5 步:评估。
|
||||
let evaluation = result.schedule.evaluate(&tasks, &resources);
|
||||
println!(
|
||||
"evaluation: makespan {:.0} s, fill rate {:.0}%",
|
||||
|
|
@ -128,6 +159,8 @@ fn main() {
|
|||
|
||||
// A sanity check on the whole chain: the placed work cannot exceed the
|
||||
// station availability, because every task needs the station.
|
||||
// 对整条链的健全性检查:所放置的工作不能超过地面站的可用时长,因为每个任务
|
||||
// 都需要该站。
|
||||
assert!(
|
||||
evaluation.scheduled_seconds
|
||||
<= resources
|
||||
|
|
@ -140,6 +173,7 @@ fn main() {
|
|||
|
||||
// The schedule must be readable in time order, and the order must be
|
||||
// consistent.
|
||||
// 调度必须可按时间次序读取,且该次序必须一致。
|
||||
let ordered = result.schedule.in_time_order();
|
||||
for pair in ordered.windows(2) {
|
||||
assert!(
|
||||
|
|
@ -150,6 +184,7 @@ fn main() {
|
|||
|
||||
// A second plan over the same inputs must be identical, so that a
|
||||
// planning cycle is reproducible.
|
||||
// 对相同输入的第二次规划必须相同,使规划周期可复现。
|
||||
let again = Planner::new().schedule(&tasks, &resources);
|
||||
assert_eq!(result.schedule, again.schedule);
|
||||
let _ = Schedule::new();
|
||||
|
|
@ -159,6 +194,8 @@ fn main() {
|
|||
// uses the payload, and the downlink that follows uses the transmitter.
|
||||
// The two are separate tasks joined by a precedence constraint, which is
|
||||
// how a sequential resource use is expressed.
|
||||
// 第 6 步:一个按序需要两种资源的任务。一次观测使用载荷,随后的下行使用发射机。
|
||||
// 二者是分开的任务,由一条先后约束连接,这正是按序使用资源的表达方式。
|
||||
let payload = ResourceId::new(2);
|
||||
let transmitter = ResourceId::new(3);
|
||||
let mut extra = resources.clone();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,19 @@
|
|||
//! ```text
|
||||
//! cargo run -p apsis-rs --features sim --example integrate_sim
|
||||
//! ```
|
||||
//!
|
||||
//! 跨层集成检查:把一个场景作为仿真运行。
|
||||
//!
|
||||
//! 本示例端到端走完仿真层,并断言每一步都为下一步产生合理的输入。它不是数值交叉
|
||||
//! 核对;逐层测试做那件事。它的目的是在某 crate 的改动破坏另一 crate 的假设时
|
||||
//! 失败。
|
||||
//!
|
||||
//! 该链是:
|
||||
//!
|
||||
//! 1. 从文本加载一个场景,它描述资源、任务以及它们之间的先后约束。
|
||||
//! 2. 用 `apsis-sim` 准备它,后者规划任务并构建时间线。
|
||||
//! 3. 启动一个会话并运行它,在每个活动处记录状态。
|
||||
//! 4. 读回运行报告,其中含有引擎结果与轨迹。
|
||||
|
||||
use apsis_sim::{RecordKind, Scenario, Simulation, StopReason};
|
||||
|
||||
|
|
@ -29,6 +42,11 @@ use apsis_sim::{RecordKind, Scenario, Simulation, StopReason};
|
|||
/// and the downlink that follows uses the transmitter, so the two are joined
|
||||
/// by a precedence constraint. A third task of low priority may or may not
|
||||
/// fit, which is what makes the plan interesting to report.
|
||||
///
|
||||
/// 本示例所运行的场景。
|
||||
///
|
||||
/// 地面站在两次过境中可用。一次观测使用载荷,随后的下行使用发射机,故二者由一条
|
||||
/// 先后约束连接。第三个低优先级任务或许放得下、或许放不下,这正使该计划值得报告。
|
||||
const SCENARIO: &str = "\
|
||||
# A short downlink scenario.
|
||||
scenario leo downlink
|
||||
|
|
@ -59,6 +77,7 @@ precedence 0 1
|
|||
fn main() {
|
||||
// Step 1: load the scenario. A failure here is a problem with the text,
|
||||
// so the example reports it rather than unwrapping.
|
||||
// 第 1 步:加载场景。此处失败是文本有问题,故本示例报告它而非 unwrap。
|
||||
let scenario = match Scenario::parse(SCENARIO) {
|
||||
Ok(scenario) => scenario,
|
||||
Err(error) => {
|
||||
|
|
@ -77,6 +96,7 @@ fn main() {
|
|||
|
||||
// Step 2: prepare the simulation. This plans the tasks and builds the
|
||||
// timeline that the plan implies.
|
||||
// 第 2 步:准备仿真。它规划各任务并构建该计划所蕴含的时间线。
|
||||
let simulation = match Simulation::prepare(scenario) {
|
||||
Ok(simulation) => simulation,
|
||||
Err(error) => {
|
||||
|
|
@ -103,6 +123,8 @@ fn main() {
|
|||
|
||||
// Step 3: run the timeline. The handler records what it sees, which is
|
||||
// what a real model would do with the physics it has.
|
||||
// 第 3 步:运行时间线。处理器记录它所看到的,这正是真实模型会用它手头的物理
|
||||
// 所做的。
|
||||
let mut session = simulation.start();
|
||||
let mut starts = 0_u32;
|
||||
let mut ends = 0_u32;
|
||||
|
|
@ -117,6 +139,7 @@ fn main() {
|
|||
});
|
||||
|
||||
// Step 4: read the report back.
|
||||
// 第 4 步:读回报告。
|
||||
println!(
|
||||
"run: {} activity(s) executed, {} start(s), {} end(s)",
|
||||
report.executed(),
|
||||
|
|
@ -136,6 +159,7 @@ fn main() {
|
|||
|
||||
// Two tasks are joined by precedence, so their ends and starts must line
|
||||
// up in the timeline the handler saw.
|
||||
// 两个任务由先后约束连接,故它们的结束与开始在处理器所见的时间线中必须对齐。
|
||||
let trace = &report.trace;
|
||||
let collection_end = trace
|
||||
.with_label("finished")
|
||||
|
|
@ -157,6 +181,7 @@ fn main() {
|
|||
}
|
||||
|
||||
// A run of the same prepared simulation must reproduce the trace.
|
||||
// 对同一已准备仿真的运行必须复现该轨迹。
|
||||
let mut again = simulation.start();
|
||||
let repeated = again.run(|_engine, recorder, event| {
|
||||
if event.payload.is_start() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue