diff --git a/crates/apsis-rs/examples/dump_coverage.rs b/crates/apsis-rs/examples/dump_coverage.rs index a8ce8ca..b863817 100644 --- a/crates/apsis-rs/examples/dump_coverage.rs +++ b/crates/apsis-rs/examples/dump_coverage.rs @@ -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; diff --git a/crates/apsis-rs/examples/dump_iau2000.rs b/crates/apsis-rs/examples/dump_iau2000.rs index 5235783..d338e79 100644 --- a/crates/apsis-rs/examples/dump_iau2000.rs +++ b/crates/apsis-rs/examples/dump_iau2000.rs @@ -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 { diff --git a/crates/apsis-rs/examples/dump_kepler.rs b/crates/apsis-rs/examples/dump_kepler.rs index 39005f8..29b3d50 100644 --- a/crates/apsis-rs/examples/dump_kepler.rs +++ b/crates/apsis-rs/examples/dump_kepler.rs @@ -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!(); diff --git a/crates/apsis-rs/examples/dump_link.rs b/crates/apsis-rs/examples/dump_link.rs index 389b97d..5613880 100644 --- a/crates/apsis-rs/examples/dump_link.rs +++ b/crates/apsis-rs/examples/dump_link.rs @@ -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) diff --git a/crates/apsis-rs/examples/dump_orbit.rs b/crates/apsis-rs/examples/dump_orbit.rs index 5d2abfa..d3c175d 100644 --- a/crates/apsis-rs/examples/dump_orbit.rs +++ b/crates/apsis-rs/examples/dump_orbit.rs @@ -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", diff --git a/crates/apsis-rs/examples/dump_rain.rs b/crates/apsis-rs/examples/dump_rain.rs index d948d25..bfa6cf9 100644 --- a/crates/apsis-rs/examples/dump_rain.rs +++ b/crates/apsis-rs/examples/dump_rain.rs @@ -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)] diff --git a/crates/apsis-rs/examples/dump_teme.rs b/crates/apsis-rs/examples/dump_teme.rs index bd52901..d8dea46 100644 --- a/crates/apsis-rs/examples/dump_teme.rs +++ b/crates/apsis-rs/examples/dump_teme.rs @@ -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 值,而检查容忍该差异。儒略日 // 被拆成整数天数与零头,这比单个值保留更多当日信息。 diff --git a/crates/apsis-rs/examples/dump_time.rs b/crates/apsis-rs/examples/dump_time.rs index dd0a49e..8bac44a 100644 --- a/crates/apsis-rs/examples/dump_time.rs +++ b/crates/apsis-rs/examples/dump_time.rs @@ -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}"); } diff --git a/crates/apsis-rs/examples/dump_tle.rs b/crates/apsis-rs/examples/dump_tle.rs index 5958067..f6a597d 100644 --- a/crates/apsis-rs/examples/dump_tle.rs +++ b/crates/apsis-rs/examples/dump_tle.rs @@ -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 diff --git a/crates/apsis-rs/examples/integrate_network.rs b/crates/apsis-rs/examples/integrate_network.rs index 2ab4a92..40431e3 100644 --- a/crates/apsis-rs/examples/integrate_network.rs +++ b/crates/apsis-rs/examples/integrate_network.rs @@ -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, diff --git a/crates/apsis-rs/examples/integrate_ops.rs b/crates/apsis-rs/examples/integrate_ops.rs index 8c4c336..335c52a 100644 --- a/crates/apsis-rs/examples/integrate_ops.rs +++ b/crates/apsis-rs/examples/integrate_ops.rs @@ -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 { vec![ Window::new(100.0, 400.0), @@ -47,6 +70,8 @@ fn station_windows() -> Vec { 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 = (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(); diff --git a/crates/apsis-rs/examples/integrate_sim.rs b/crates/apsis-rs/examples/integrate_sim.rs index a581cba..a8baffd 100644 --- a/crates/apsis-rs/examples/integrate_sim.rs +++ b/crates/apsis-rs/examples/integrate_sim.rs @@ -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() {