docs: render formulas as math with a vendored MathJax

The rustdoc in this toolchain does not parse math (its pulldown-cmark options
omit ENABLE_MATH, `--mathjax` and `html_mathjax` are gone, and a `$$...$$`
paragraph is emitted verbatim). Display formulas were therefore written as
```text code blocks, which the request asked to replace with math syntax.

MathJax 3.2.2 (tex-svg-full, Apache-2.0) is vendored under
docs/vendor/mathjax so the rendered pages need no network access.
docs/mathjax.html loads it from the documentation root computed from rustdoc's
own static file prefix; crates/apsis-rs/build.rs copies it into that root, and
scripts/build-docs.sh makes the copy reliable. Each formula is wrapped in a
raw HTML `<div>` so the markdown layer leaves the LaTeX untouched, because
markdown would otherwise consume the backslashes in `\\\\`, `\,`, `\!` and
`\_`.

All 54 formula blocks (27 formulas, English and Chinese) become `$$...$$`;
the 22 command, 2 diagram and 2 scenario-format blocks stay as ```text.
This commit is contained in:
ApsisRS contributors 2026-10-02 12:28:20 +08:00
parent 95cb3309f2
commit f48c4d9bd7
28 changed files with 837 additions and 214 deletions

12
.cargo/config.toml Normal file
View File

@ -0,0 +1,12 @@
[build]
# Load the documentation header that renders mathematical formulas.
#
# rustdoc resolves this path relative to the workspace root, and the header
# loads the vendored MathJax under docs/vendor/mathjax, so the generated
# documentation renders math without any network access.
#
# 加载渲染数学公式的文档头部。
#
# rustdoc 相对工作区根解析该路径,而该头部会加载 docs/vendor/mathjax 下内置的
# MathJax,故生成的文档在没有任何网络访问的情况下渲染数学。
rustdocflags = ["--html-in-header", "docs/mathjax.html"]

View File

@ -53,9 +53,14 @@ jobs:
# Catches a doc link that no longer resolves, which is how a doc # Catches a doc link that no longer resolves, which is how a doc
# comment drifts away from the code it describes. # comment drifts away from the code it describes.
# 捕获不再解析的文档链接,文档注释正是这样与其所述代码渐行渐远。 # 捕获不再解析的文档链接,文档注释正是这样与其所述代码渐行渐远。
#
# RUSTDOCFLAGS takes precedence over the rustdocflags in
# .cargo/config.toml, so the MathJax header is named here as well.
# RUSTDOCFLAGS 优先于 .cargo/config.toml 中的 rustdocflags,故此处
# 也指名 MathJax 头部。
env: env:
RUSTDOCFLAGS: -D warnings RUSTDOCFLAGS: -D warnings --html-in-header docs/mathjax.html
run: cargo doc --workspace --all-features --no-deps run: bash scripts/build-docs.sh --workspace --all-features --no-deps
- name: Documentation with default features - name: Documentation with default features
# A downstream reader runs `cargo doc` on the default feature set, # A downstream reader runs `cargo doc` on the default feature set,
# where the facade re-exports nothing, so a link to a re-exported # where the facade re-exports nothing, so a link to a re-exported
@ -65,8 +70,8 @@ jobs:
# 东西,故指向被重导出 crate 的链接无法解析。上面的全特性构建捕获 # 东西,故指向被重导出 crate 的链接无法解析。上面的全特性构建捕获
# 不到这一点,故在此检查。 # 不到这一点,故在此检查。
env: env:
RUSTDOCFLAGS: -D warnings RUSTDOCFLAGS: -D warnings --html-in-header docs/mathjax.html
run: cargo doc --workspace --no-deps run: bash scripts/build-docs.sh --workspace --no-deps
- name: Check the layer rule - name: Check the layer rule
# README.md states that a crate may only depend on layers to its # README.md states that a crate may only depend on layers to its
# left. Nothing else in the build enforces that, so a reverse # left. Nothing else in the build enforces that, so a reverse

View File

@ -127,6 +127,18 @@ crate cannot infer:
- [Glossary and formula conventions](docs/design/glossary.md) - [Glossary and formula conventions](docs/design/glossary.md)
- [The simulation layer](docs/design/sim.md) - [The simulation layer](docs/design/sim.md)
The generated API documentation renders mathematical formulas offline. A copy
of MathJax is vendored under `docs/vendor/mathjax`, `docs/mathjax.html` loads
it, and `crates/apsis-rs/build.rs` copies it into the documentation root, so
the generated pages need no network access. Build them with
`bash scripts/build-docs.sh --workspace --all-features --no-deps`; a plain
`cargo doc` also works, and the script only makes the copy reliable. A display
formula is written as `$$ ... $$` inside a `<div>`; the surrounding div keeps
the markdown layer from rewriting the LaTeX.
## Licence ## Licence
GPL-3.0-or-later, see `LICENSE`. GPL-3.0-or-later, see `LICENSE`.
The vendored MathJax under `docs/vendor/mathjax` is Apache-2.0; see the
`LICENSE` in that directory.

View File

@ -107,10 +107,19 @@ cargo run --release -p apsis-rs --features full --example bench_scale
cargo run --release -p apsis-rs --features full --example bench_parallel cargo run --release -p apsis-rs --features full --example bench_parallel
``` ```
生成的 API 文档会离线渲染数学公式。`docs/vendor/mathjax` 下内置了一份
MathJax,`docs/mathjax.html` 负责加载,`crates/apsis-rs/build.rs` 会把它复制到
文档根,故生成的页面无需任何网络访问。用
`bash scripts/build-docs.sh --workspace --all-features --no-deps` 构建;直接
`cargo doc` 也可以,脚本只是使复制可靠。行间公式写作 `<div>` 包裹的
`$$ ... $$`;外层 div 使 markdown 层不改写其中的 LaTeX。
## 许可 ## 许可
GPL-3.0-or-later,见 `LICENSE`。 GPL-3.0-or-later,见 `LICENSE`。
`docs/vendor/mathjax` 下内置的 MathJax 为 Apache-2.0,见该目录内的 `LICENSE`。
## 术语 ## 术语
- [术语与公式约定](docs/design/glossary.zh.md) - [术语与公式约定](docs/design/glossary.zh.md)

70
crates/apsis-rs/build.rs Normal file
View File

@ -0,0 +1,70 @@
//! Copies the vendored MathJax into the documentation root.
//!
//! The generated HTML loads MathJax from `vendor/mathjax` under the
//! documentation root so that a formula renders without network access. The
//! header at `docs/mathjax.html` computes the path to that root at run time,
//! but the file itself has to be present, and rustdoc has no option to copy an
//! extra static file. This build script therefore places it there.
//!
//! The copy is a few hundred kilobytes, runs once per build, and does nothing
//! when the vendored directory is absent, so it is harmless for builds that do
//! not touch the documentation.
//!
//! 把内置的 MathJax 复制到文档根。
//!
//! 生成的 HTML 从文档根下的 `vendor/mathjax` 加载 MathJax,使公式无需网络访问
//! 即可渲染。`docs/mathjax.html` 处的头部在运行时计算到该根的路径,但文件
//! 本身必须存在,而 rustdoc 没有复制额外静态文件的选项。故本构建脚本将其
//! 放置到那里。
//!
//! 该复制只有几百千字节,每次构建运行一次,且在内置目录不存在时什么都不做,
//! 故对不涉及文档的构建无害。
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
fn main() {
// The facade crate is at `<workspace>/crates/apsis-rs`, so the workspace
// root is two levels up.
// 门面 crate 位于 `<workspace>/crates/apsis-rs`,故工作区根在其上两级。
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("cargo sets this"));
let Some(workspace) = manifest.parent().and_then(Path::parent) else {
return;
};
let source = workspace.join("docs").join("vendor").join("mathjax");
if !source.is_dir() {
return;
}
// The target directory is the default one unless the caller moved it.
// 除非调用方移动了目标目录,否则它就是默认位置。
let target = match env::var("CARGO_TARGET_DIR") {
Ok(dir) => {
let dir = PathBuf::from(dir);
if dir.is_absolute() {
dir
} else {
workspace.join(dir)
}
}
Err(_) => workspace.join("target"),
};
let destination = target.join("doc").join("vendor").join("mathjax");
if fs::create_dir_all(&destination).is_err() {
return;
}
let Ok(entries) = fs::read_dir(&source) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
let _ = fs::copy(&path, destination.join(entry.file_name()));
}
}
println!("cargo:rerun-if-changed=../../docs/vendor/mathjax");
}

View File

@ -5,10 +5,14 @@
//! the elevation is a function of the orbital position alone, so the rise, //! the elevation is a function of the orbital position alone, so the rise,
//! peak, and set times have a closed form: //! peak, and set times have a closed form:
//! //!
//! ```text //! <div>
//! theta_c = 90 - mask - asin(Re cos(mask) / Rs) //! $$
//! t = (theta - theta_0) / n //! \begin{aligned}
//! ``` //! \theta_c &= 90^\circ - \text{mask} - \arcsin\!\left(\frac{\text{Re} \cos(\text{mask})}{\text{Rs}}\right) \\
//! t &= \frac{\theta - \theta_0}{n}
//! \end{aligned}
//! $$
//! </div>
//! //!
//! with `theta` the orbital position measured from the station and `n` the //! with `theta` the orbital position measured from the station and `n` the
//! mean motion. The values used below are computed from those expressions. //! mean motion. The values used below are computed from those expressions.
@ -18,10 +22,14 @@
//! 参考用例是自赤道上、遮蔽十度的一座站看 550 km 高度的圆赤道轨道。对该几何, //! 参考用例是自赤道上、遮蔽十度的一座站看 550 km 高度的圆赤道轨道。对该几何,
//! 仰角只依赖于轨道位置,故升起、峰值与降落时刻有闭式: //! 仰角只依赖于轨道位置,故升起、峰值与降落时刻有闭式:
//! //!
//! ```text //! <div>
//! theta_c = 90 - mask - asin(Re cos(mask) / Rs) //! $$
//! t = (theta - theta_0) / n //! \begin{aligned}
//! ``` //! \theta_c &= 90^\circ - \text{mask} - \arcsin\!\left(\frac{\text{Re} \cos(\text{mask})}{\text{Rs}}\right) \\
//! t &= \frac{\theta - \theta_0}{n}
//! \end{aligned}
//! $$
//! </div>
//! //!
//! 其中 `theta` 是自站量起的轨道位置,`n` 是平运动。下面所用的值即由这些表达式 //! 其中 `theta` 是自站量起的轨道位置,`n` 是平运动。下面所用的值即由这些表达式
//! 计算。 //! 计算。

View File

@ -57,9 +57,11 @@ impl Footprint {
/// ///
/// The area is the part of a sphere cut off by a cone, /// The area is the part of a sphere cut off by a cone,
/// ///
/// ```text /// <div>
/// A = 2 pi R^2 (1 - cos(central)) /// $$
/// ``` /// A = 2 \pi R^2 \left(1 - \cos(\text{central})\right)
/// $$
/// </div>
/// ///
/// which is exact for a spherical body. /// which is exact for a spherical body.
/// ///
@ -67,9 +69,11 @@ impl Footprint {
/// ///
/// 该面积是被圆锥截去的球面部分, /// 该面积是被圆锥截去的球面部分,
/// ///
/// ```text /// <div>
/// A = 2 pi R^2 (1 - cos(central)) /// $$
/// ``` /// A = 2 \pi R^2 \left(1 - \cos(\text{central})\right)
/// $$
/// </div>
/// ///
/// 对球形天体这是精确的。 /// 对球形天体这是精确的。
pub fn area(&self) -> f64 { pub fn area(&self) -> f64 {

View File

@ -7,16 +7,20 @@
//! directly below the local horizon, the elevation and the adjacent angle at //! directly below the local horizon, the elevation and the adjacent angle at
//! the ground point are complementary, so the three angles satisfy //! the ground point are complementary, so the three angles satisfy
//! //!
//! ```text //! <div>
//! elevation + nadir + central = 90 degrees //! $$
//! ``` //! \text{elevation} + \text{nadir} + \text{central} = 90^\circ
//! $$
//! </div>
//! //!
//! The quantities of interest follow from the law of sines applied to that //! The quantities of interest follow from the law of sines applied to that
//! triangle: //! triangle:
//! //!
//! ```text //! <div>
//! sin(nadir) / body_radius = cos(elevation) / satellite_radius //! $$
//! ``` //! \frac{\sin(\text{nadir})}{\text{body\_radius}} = \frac{\cos(\text{elevation})}{\text{satellite\_radius}}
//! $$
//! </div>
//! //!
//! where the cosine appears because the angle at the ground point, inside //! where the cosine appears because the angle at the ground point, inside
//! the triangle, is the complement of the elevation. //! the triangle, is the complement of the elevation.
@ -32,15 +36,19 @@
//! 地心处的中心角。因为地面点正位于当地地平之下,仰角与地面点处的邻角互补,故 //! 地心处的中心角。因为地面点正位于当地地平之下,仰角与地面点处的邻角互补,故
//! 三个角满足 //! 三个角满足
//! //!
//! ```text //! <div>
//! elevation + nadir + central = 90 degrees //! $$
//! ``` //! \text{elevation} + \text{nadir} + \text{central} = 90^\circ
//! $$
//! </div>
//! //!
//! 所关注的各量由正弦定理施加于该三角形得出: //! 所关注的各量由正弦定理施加于该三角形得出:
//! //!
//! ```text //! <div>
//! sin(nadir) / body_radius = cos(elevation) / satellite_radius //! $$
//! ``` //! \frac{\sin(\text{nadir})}{\text{body\_radius}} = \frac{\cos(\text{elevation})}{\text{satellite\_radius}}
//! $$
//! </div>
//! //!
//! 其中出现余弦,是因为三角形内地面点处的角是仰角的余角。 //! 其中出现余弦,是因为三角形内地面点处的角是仰角的余角。
//! //!
@ -112,10 +120,12 @@ pub fn earth_central_angle(satellite_radius: f64, body_radius: f64, elevation: f
/// The relation is the law of cosines applied to the triangle formed by the /// The relation is the law of cosines applied to the triangle formed by the
/// satellite, the ground point, and the centre of the Earth, /// satellite, the ground point, and the centre of the Earth,
/// ///
/// ```text /// <div>
/// range^2 = satellite_radius^2 + body_radius^2 /// $$
/// - 2 satellite_radius body_radius cos(central) /// \text{range}^2 = \text{satellite\_radius}^2 + \text{body\_radius}^2
/// ``` /// - 2\,\text{satellite\_radius}\,\text{body\_radius}\,\cos(\text{central})
/// $$
/// </div>
/// ///
/// The law of cosines is used rather than the equivalent form /// The law of cosines is used rather than the equivalent form
/// `Rs sin(central) / cos(elevation)` because the latter is singular at the /// `Rs sin(central) / cos(elevation)` because the latter is singular at the
@ -131,10 +141,12 @@ pub fn earth_central_angle(satellite_radius: f64, body_radius: f64, elevation: f
/// ///
/// 该关系是余弦定理施加于卫星、地面点与地心构成的三角形: /// 该关系是余弦定理施加于卫星、地面点与地心构成的三角形:
/// ///
/// ```text /// <div>
/// range^2 = satellite_radius^2 + body_radius^2 /// $$
/// - 2 satellite_radius body_radius cos(central) /// \text{range}^2 = \text{satellite\_radius}^2 + \text{body\_radius}^2
/// ``` /// - 2\,\text{satellite\_radius}\,\text{body\_radius}\,\cos(\text{central})
/// $$
/// </div>
/// ///
/// 此处用余弦定理而非等价形式 `Rs sin(central) / cos(elevation)`,因为后者在 /// 此处用余弦定理而非等价形式 `Rs sin(central) / cos(elevation)`,因为后者在
/// 天顶处奇异,那里仰角是直角、其余弦为零,尽管那里的距离恰是高度。 /// 天顶处奇异,那里仰角是直角、其余弦为零,尽管那里的距离恰是高度。
@ -161,10 +173,11 @@ pub fn slant_range(satellite_radius: f64, body_radius: f64, elevation: f64) -> O
/// ///
/// The relation is /// The relation is
/// ///
/// ```text /// <div>
/// tan(elevation) = (cos(central) - body_radius / satellite_radius) /// $$
/// / sin(central) /// \tan(\text{elevation}) = \frac{\cos(\text{central}) - \text{body\_radius} / \text{satellite\_radius}}{\sin(\text{central})}
/// ``` /// $$
/// </div>
/// ///
/// Returns `None` when the radii are not usable or when the central angle is /// Returns `None` when the radii are not usable or when the central angle is
/// not finite. /// not finite.
@ -176,10 +189,11 @@ pub fn slant_range(satellite_radius: f64, body_radius: f64, elevation: f64) -> O
/// ///
/// 关系为 /// 关系为
/// ///
/// ```text /// <div>
/// tan(elevation) = (cos(central) - body_radius / satellite_radius) /// $$
/// / sin(central) /// \tan(\text{elevation}) = \frac{\cos(\text{central}) - \text{body\_radius} / \text{satellite\_radius}}{\sin(\text{central})}
/// ``` /// $$
/// </div>
/// ///
/// 半径不可用或中心角非有限时返回 `None`。 /// 半径不可用或中心角非有限时返回 `None`。
pub fn elevation_from_central_angle( pub fn elevation_from_central_angle(

View File

@ -19,9 +19,11 @@
//! point, the nadir angle at the satellite, and the central angle at the //! point, the nadir angle at the satellite, and the central angle at the
//! centre, and they sum to a right angle: //! centre, and they sum to a right angle:
//! //!
//! ```text //! <div>
//! elevation + nadir + central = 90 degrees //! $$
//! ``` //! \text{elevation} + \text{nadir} + \text{central} = 90^\circ
//! $$
//! </div>
//! //!
//! Source: the relations used here are the standard satellite geometry, as //! Source: the relations used here are the standard satellite geometry, as
//! given for example in Vallado, D. A., *Fundamentals of Astrodynamics and //! given for example in Vallado, D. A., *Fundamentals of Astrodynamics and
@ -55,9 +57,11 @@
//! 卫星、地面点与地心构成的几何是一个三角形。其三个角是地面点处的仰角、卫星处的 //! 卫星、地面点与地心构成的几何是一个三角形。其三个角是地面点处的仰角、卫星处的
//! 天底角,以及地心处的中心角,它们相加为直角: //! 天底角,以及地心处的中心角,它们相加为直角:
//! //!
//! ```text //! <div>
//! elevation + nadir + central = 90 degrees //! $$
//! ``` //! \text{elevation} + \text{nadir} + \text{central} = 90^\circ
//! $$
//! </div>
//! //!
//! 出处:此处所用的关系是标准的卫星几何,例如见 Vallado, D. A.,*Fundamentals of //! 出处:此处所用的关系是标准的卫星几何,例如见 Vallado, D. A.,*Fundamentals of
//! Astrodynamics and Applications*,第 4 版,microcosm Press,2013,第 5.3 节。 //! Astrodynamics and Applications*,第 4 版,microcosm Press,2013,第 5.3 节。

View File

@ -68,9 +68,11 @@ pub fn wrap_to_pi(angle: f64) -> f64 {
/// ///
/// The relation follows from the geometry of the auxiliary circle: /// The relation follows from the geometry of the auxiliary circle:
/// ///
/// ```text /// <div>
/// tan(nu / 2) = sqrt((1 + e) / (1 - e)) tan(E / 2) /// $$
/// ``` /// \tan\frac{\nu}{2} = \sqrt{\frac{1 + e}{1 - e}}\,\tan\frac{E}{2}
/// $$
/// </div>
/// ///
/// The half angle form is used rather than the direct cosine relation so /// The half angle form is used rather than the direct cosine relation so
/// that the result is correct in all four quadrants without a quadrant /// that the result is correct in all four quadrants without a quadrant
@ -80,9 +82,11 @@ pub fn wrap_to_pi(angle: f64) -> f64 {
/// ///
/// 该关系来自辅助圆的几何性质: /// 该关系来自辅助圆的几何性质:
/// ///
/// ```text /// <div>
/// tan(nu / 2) = sqrt((1 + e) / (1 - e)) tan(E / 2) /// $$
/// ``` /// \tan\frac{\nu}{2} = \sqrt{\frac{1 + e}{1 - e}}\,\tan\frac{E}{2}
/// $$
/// </div>
/// ///
/// 采用半角形式而非直接的余弦关系,使结果在四个象限中都正确,无需判别象限。 /// 采用半角形式而非直接的余弦关系,使结果在四个象限中都正确,无需判别象限。
pub fn true_from_eccentric(eccentric_anomaly: f64, eccentricity: f64) -> f64 { pub fn true_from_eccentric(eccentric_anomaly: f64, eccentricity: f64) -> f64 {
@ -108,9 +112,11 @@ pub fn eccentric_from_true(true_anomaly: f64, eccentricity: f64) -> f64 {
/// ///
/// This solves Kepler's equation /// This solves Kepler's equation
/// ///
/// ```text /// <div>
/// M = E - e sin E /// $$
/// ``` /// M = E - e \sin E
/// $$
/// </div>
/// ///
/// by Newton's method. The starting guess is the mean anomaly plus a term /// by Newton's method. The starting guess is the mean anomaly plus a term
/// proportional to the eccentricity, which keeps the iteration stable for /// proportional to the eccentricity, which keeps the iteration stable for
@ -121,9 +127,11 @@ pub fn eccentric_from_true(true_anomaly: f64, eccentricity: f64) -> f64 {
/// ///
/// 用 Newton 法求解开普勒方程 /// 用 Newton 法求解开普勒方程
/// ///
/// ```text /// <div>
/// M = E - e sin E /// $$
/// ``` /// M = E - e \sin E
/// $$
/// </div>
/// ///
/// 初值取平近点角加上一个正比于偏心率的项,使迭代在偏心率趋近一时保持稳定。 /// 初值取平近点角加上一个正比于偏心率的项,使迭代在偏心率趋近一时保持稳定。
/// 迭代总会终止,结果是至多固定步数后达到的值。 /// 迭代总会终止,结果是至多固定步数后达到的值。

View File

@ -162,12 +162,16 @@ impl KeplerianElements {
/// The conversion places the satellite in the perifocal frame, where the /// The conversion places the satellite in the perifocal frame, where the
/// first axis points to perigee and the third along the orbit normal, /// first axis points to perigee and the third along the orbit normal,
/// ///
/// ```text /// <div>
/// r = p / (1 + e cos nu) /// $$
/// x = r cos nu, y = r sin nu, z = 0 /// \begin{aligned}
/// vx = -sqrt(mu / p) sin nu /// r &= \frac{p}{1 + e \cos\nu} \\
/// vy = sqrt(mu / p) (e + cos nu) /// x &= r \cos\nu,\quad y = r \sin\nu,\quad z = 0 \\
/// ``` /// v_x &= -\sqrt{\frac{\mu}{p}}\,\sin\nu \\
/// v_y &= \sqrt{\frac{\mu}{p}}\,(e + \cos\nu)
/// \end{aligned}
/// $$
/// </div>
/// ///
/// and then rotates by the argument of perigee, the inclination, and the /// and then rotates by the argument of perigee, the inclination, and the
/// right ascension of the ascending node. /// right ascension of the ascending node.
@ -178,12 +182,16 @@ impl KeplerianElements {
/// ///
/// 换算先在近点坐标系中定位卫星,该系第一轴指向近地点、第三轴沿轨道法线, /// 换算先在近点坐标系中定位卫星,该系第一轴指向近地点、第三轴沿轨道法线,
/// ///
/// ```text /// <div>
/// r = p / (1 + e cos nu) /// $$
/// x = r cos nu, y = r sin nu, z = 0 /// \begin{aligned}
/// vx = -sqrt(mu / p) sin nu /// r &= \frac{p}{1 + e \cos\nu} \\
/// vy = sqrt(mu / p) (e + cos nu) /// x &= r \cos\nu,\quad y = r \sin\nu,\quad z = 0 \\
/// ``` /// v_x &= -\sqrt{\frac{\mu}{p}}\,\sin\nu \\
/// v_y &= \sqrt{\frac{\mu}{p}}\,(e + \cos\nu)
/// \end{aligned}
/// $$
/// </div>
/// ///
/// 再依次按近地点幅角、倾角与升交点赤经旋转。 /// 再依次按近地点幅角、倾角与升交点赤经旋转。
pub fn to_state(self, parameter: GravityParameter) -> Option<State> { pub fn to_state(self, parameter: GravityParameter) -> Option<State> {
@ -415,10 +423,14 @@ pub fn perifocal_to_inertial(
/// direction is poorly determined. The eccentric anomaly follows from the /// direction is poorly determined. The eccentric anomaly follows from the
/// radial distance and the radial velocity through /// radial distance and the radial velocity through
/// ///
/// ```text /// <div>
/// cos E = (1 - r / a) / e /// $$
/// sin E = r rdot / (e sqrt(mu a)) /// \begin{aligned}
/// ``` /// \cos E &= \frac{1 - r / a}{e} \\
/// \sin E &= \frac{r \dot r}{e \sqrt{\mu a}}
/// \end{aligned}
/// $$
/// </div>
/// ///
/// and the true anomaly follows from the eccentric anomaly. A circular /// and the true anomaly follows from the eccentric anomaly. A circular
/// orbit, where the eccentricity is too small for those expressions, is /// orbit, where the eccentricity is too small for those expressions, is
@ -431,10 +443,14 @@ pub fn perifocal_to_inertial(
/// 以偏近点角为中间量,可在近圆轨道处保持精度,因为此时偏心率矢量很短、方向 /// 以偏近点角为中间量,可在近圆轨道处保持精度,因为此时偏心率矢量很短、方向
/// 难以确定。偏近点角由径向距离与径向速度经下式求得: /// 难以确定。偏近点角由径向距离与径向速度经下式求得:
/// ///
/// ```text /// <div>
/// cos E = (1 - r / a) / e /// $$
/// sin E = r rdot / (e sqrt(mu a)) /// \begin{aligned}
/// ``` /// \cos E &= \frac{1 - r / a}{e} \\
/// \sin E &= \frac{r \dot r}{e \sqrt{\mu a}}
/// \end{aligned}
/// $$
/// </div>
/// ///
/// 真近点角再由偏近点角推出。对偏心率过小而使上述式子失效的圆轨道,改为 /// 真近点角再由偏近点角推出。对偏心率过小而使上述式子失效的圆轨道,改为
/// 自交点量取夹角处理,这与 [`KeplerianElements::from_state`] 处理近地点幅角 /// 自交点量取夹角处理,这与 [`KeplerianElements::from_state`] 处理近地点幅角

View File

@ -136,14 +136,18 @@ impl Elements {
/// the element set value and `xke` for the Kepler constant, the reference /// the element set value and `xke` for the Kepler constant, the reference
/// implementation evaluates /// implementation evaluates
/// ///
/// ```text /// <div>
/// ak = (xke / no)^(2/3) /// $$
/// d1 = 0.75 j2 (3 cos^2 i - 1) / (sqrt(1 - e^2) (1 - e^2)) /// \begin{aligned}
/// del = d1 / ak^2 /// \text{ak} &= (\text{xke} / \text{no})^{2/3} \\
/// adel = ak (1 - del^2 - del (1/3 + 134 del^2 / 81)) /// \text{d1} &= \frac{0.75\, j_2\,(3 \cos^2 i - 1)}{\sqrt{1 - e^2}\,(1 - e^2)} \\
/// del = d1 / adel^2 /// \text{del} &= \text{d1} / \text{ak}^2 \\
/// n0 = no / (1 + del) /// \text{adel} &= \text{ak}\left(1 - \text{del}^2 - \text{del}\left(\tfrac{1}{3} + \tfrac{134\,\text{del}^2}{81}\right)\right) \\
/// ``` /// \text{del} &= \text{d1} / \text{adel}^2 \\
/// n_0 &= \text{no} / (1 + \text{del})
/// \end{aligned}
/// $$
/// </div>
/// ///
/// The second evaluation of `del` uses `adel` rather than `ak`, which is /// The second evaluation of `del` uses `adel` rather than `ak`, which is
/// what makes the correction consistent with the semi-major axis implied by /// what makes the correction consistent with the semi-major axis implied by
@ -158,14 +162,18 @@ impl Elements {
/// 使用。以 `no` 表示根数集给出的值、`xke` 表示开普勒常数,参考实现按下式 /// 使用。以 `no` 表示根数集给出的值、`xke` 表示开普勒常数,参考实现按下式
/// 求值: /// 求值:
/// ///
/// ```text /// <div>
/// ak = (xke / no)^(2/3) /// $$
/// d1 = 0.75 j2 (3 cos^2 i - 1) / (sqrt(1 - e^2) (1 - e^2)) /// \begin{aligned}
/// del = d1 / ak^2 /// \text{ak} &= (\text{xke} / \text{no})^{2/3} \\
/// adel = ak (1 - del^2 - del (1/3 + 134 del^2 / 81)) /// \text{d1} &= \frac{0.75\, j_2\,(3 \cos^2 i - 1)}{\sqrt{1 - e^2}\,(1 - e^2)} \\
/// del = d1 / adel^2 /// \text{del} &= \text{d1} / \text{ak}^2 \\
/// n0 = no / (1 + del) /// \text{adel} &= \text{ak}\left(1 - \text{del}^2 - \text{del}\left(\tfrac{1}{3} + \tfrac{134\,\text{del}^2}{81}\right)\right) \\
/// ``` /// \text{del} &= \text{d1} / \text{adel}^2 \\
/// n_0 &= \text{no} / (1 + \text{del})
/// \end{aligned}
/// $$
/// </div>
/// ///
/// 第二次求 `del` 用的是 `adel` 而非 `ak`,这正是该修正与修正后平运动所隐含 /// 第二次求 `del` 用的是 `adel` 而非 `ak`,这正是该修正与修正后平运动所隐含
/// 的半长轴相一致的原因。 /// 的半长轴相一致的原因。

View File

@ -118,9 +118,11 @@ impl GravityModel {
/// The propagator works in minutes and Earth radii, so the gravitational /// The propagator works in minutes and Earth radii, so the gravitational
/// parameter is converted by /// parameter is converted by
/// ///
/// ```text /// <div>
/// xke = 60 / sqrt(radius^3 / mu) /// $$
/// ``` /// \text{xke} = \frac{60}{\sqrt{\text{radius}^3 / \mu}}
/// $$
/// </div>
/// ///
/// which is the angular rate, in radians per minute, of a circular orbit /// which is the angular rate, in radians per minute, of a circular orbit
/// of radius one Earth radius. /// of radius one Earth radius.
@ -129,9 +131,11 @@ impl GravityModel {
/// ///
/// 传播器以分钟与地球半径为单位,故引力参数按下式换算: /// 传播器以分钟与地球半径为单位,故引力参数按下式换算:
/// ///
/// ```text /// <div>
/// xke = 60 / sqrt(radius^3 / mu) /// $$
/// ``` /// \text{xke} = \frac{60}{\sqrt{\text{radius}^3 / \mu}}
/// $$
/// </div>
/// ///
/// 它是一个地球半径圆轨道的角速率,单位为弧度每分钟。 /// 它是一个地球半径圆轨道的角速率,单位为弧度每分钟。
pub fn xke(self) -> f64 { pub fn xke(self) -> f64 {

View File

@ -67,12 +67,16 @@ impl Geodetic {
/// ///
/// The closed form is /// The closed form is
/// ///
/// ```text /// <div>
/// N = a / sqrt(1 - e^2 sin^2(lat)) /// $$
/// x = (N + h) cos(lat) cos(lon) /// \begin{aligned}
/// y = (N + h) cos(lat) sin(lon) /// N &= \frac{a}{\sqrt{1 - e^2 \sin^2\varphi}} \\
/// z = (N (1 - e^2) + h) sin(lat) /// x &= (N + h) \cos\varphi \cos\lambda \\
/// ``` /// y &= (N + h) \cos\varphi \sin\lambda \\
/// z &= \left(N(1 - e^2) + h\right) \sin\varphi
/// \end{aligned}
/// $$
/// </div>
/// ///
/// where `N` is the prime vertical radius of curvature. This form is /// where `N` is the prime vertical radius of curvature. This form is
/// exact everywhere, including at the poles, where the cosine factors /// exact everywhere, including at the poles, where the cosine factors
@ -85,12 +89,16 @@ impl Geodetic {
/// ///
/// 闭式解为 /// 闭式解为
/// ///
/// ```text /// <div>
/// N = a / sqrt(1 - e^2 sin^2(lat)) /// $$
/// x = (N + h) cos(lat) cos(lon) /// \begin{aligned}
/// y = (N + h) cos(lat) sin(lon) /// N &= \frac{a}{\sqrt{1 - e^2 \sin^2\varphi}} \\
/// z = (N (1 - e^2) + h) sin(lat) /// x &= (N + h) \cos\varphi \cos\lambda \\
/// ``` /// y &= (N + h) \cos\varphi \sin\lambda \\
/// z &= \left(N(1 - e^2) + h\right) \sin\varphi
/// \end{aligned}
/// $$
/// </div>
/// ///
/// 其中 `N` 为卯酉圈曲率半径。该形式处处精确,包括余弦因子为零的极点。 /// 其中 `N` 为卯酉圈曲率半径。该形式处处精确,包括余弦因子为零的极点。
pub fn to_cartesian(self, ellipsoid: Ellipsoid) -> Vec3 { pub fn to_cartesian(self, ellipsoid: Ellipsoid) -> Vec3 {

View File

@ -67,9 +67,11 @@ impl ParabolicAperture {
/// ///
/// The relation is /// The relation is
/// ///
/// ```text /// <div>
/// G = eta (pi D / lambda)^2 /// $$
/// ``` /// G = \eta \left(\frac{\pi D}{\lambda}\right)^2
/// $$
/// </div>
/// ///
/// which follows from the effective area through /// which follows from the effective area through
/// [`Gain::from_effective_aperture`]. Returns `None` for an invalid /// [`Gain::from_effective_aperture`]. Returns `None` for an invalid
@ -79,9 +81,11 @@ impl ParabolicAperture {
/// ///
/// 关系为 /// 关系为
/// ///
/// ```text /// <div>
/// G = eta (pi D / lambda)^2 /// $$
/// ``` /// G = \eta \left(\frac{\pi D}{\lambda}\right)^2
/// $$
/// </div>
/// ///
/// 它由有效面积经 [`Gain::from_effective_aperture`] 得出。孔径或波长 /// 它由有效面积经 [`Gain::from_effective_aperture`] 得出。孔径或波长
/// 无效时返回 `None`。 /// 无效时返回 `None`。

View File

@ -14,9 +14,11 @@ use core::f64::consts::PI;
/// can be described by the two numbers a link budget has to hand. In /// can be described by the two numbers a link budget has to hand. In
/// decibels, /// decibels,
/// ///
/// ```text /// <div>
/// G(theta) = G_peak - min(12 (theta / theta_0)^2, G_floor) /// $$
/// ``` /// G(\theta) = G_{\text{peak}} - \min\!\left(12\left(\frac{\theta}{\theta_0}\right)^2,\; G_{\text{floor}}\right)
/// $$
/// </div>
/// ///
/// where `theta_0` is the half power beam width and `G_floor` is the /// where `theta_0` is the half power beam width and `G_floor` is the
/// sidelobe level to fall to, in decibels below the peak. The squared angle /// sidelobe level to fall to, in decibels below the peak. The squared angle
@ -40,9 +42,11 @@ use core::f64::consts::PI;
/// 这是若干干扰评估所用的角平方模型,以半功率波束宽度表达,使任何天线 /// 这是若干干扰评估所用的角平方模型,以半功率波束宽度表达,使任何天线
/// 都能由链路预算手头的两个数描述。以分贝计, /// 都能由链路预算手头的两个数描述。以分贝计,
/// ///
/// ```text /// <div>
/// G(theta) = G_peak - min(12 (theta / theta_0)^2, G_floor) /// $$
/// ``` /// G(\theta) = G_{\text{peak}} - \min\!\left(12\left(\frac{\theta}{\theta_0}\right)^2,\; G_{\text{floor}}\right)
/// $$
/// </div>
/// ///
/// 其中 `theta_0` 是半功率波束宽度,`G_floor` 是所要降到的旁瓣电平, /// 其中 `theta_0` 是半功率波束宽度,`G_floor` 是所要降到的旁瓣电平,
/// 相对峰值以分贝计。角平方律来自均匀照射孔径 Airy 图案的首项,直到 /// 相对峰值以分贝计。角平方律来自均匀照射孔径 Airy 图案的首项,直到
@ -82,15 +86,24 @@ pub fn off_axis_attenuation(
/// to check whether an Earth station meets the off-axis emission limits that /// to check whether an Earth station meets the off-axis emission limits that
/// protect neighbouring satellites. The pattern is piecewise: /// protect neighbouring satellites. The pattern is piecewise:
/// ///
/// ```text /// <div>
/// for D/lambda >= 100: /// $$
/// G = 29 - 25 log10(theta) for 1 <= theta < 36 degrees /// G(\theta) =
/// G = -10 for 36 <= theta <= 180 degrees /// \begin{cases}
/// for D/lambda < 100: /// 29 - 25\log_{10}\theta, & 1^\circ \le \theta < 36^\circ \\
/// G = 32 - 25 log10(theta) for 1 <= theta < 48 degrees /// -10, & 36^\circ \le \theta \le 180^\circ
/// G = -10 for 48 <= theta <= 180 degrees /// \end{cases}
/// G = 32 - 25 log10(48) for theta < 1 degree /// \quad \text{for } D/\lambda \ge 100
/// ``` /// \qquad
/// G(\theta) =
/// \begin{cases}
/// 32 - 25\log_{10}\theta, & 1^\circ \le \theta < 48^\circ \\
/// -10, & 48^\circ \le \theta \le 180^\circ \\
/// 32 - 25\log_{10}48, & \theta < 1^\circ
/// \end{cases}
/// \quad \text{for } D/\lambda < 100
/// $$
/// </div>
/// ///
/// The angle is in radians and the diameter to wavelength ratio decides /// The angle is in radians and the diameter to wavelength ratio decides
/// which branch applies, because a small antenna has a broader main lobe /// which branch applies, because a small antenna has a broader main lobe
@ -111,15 +124,24 @@ pub fn off_axis_attenuation(
/// 这是《无线电规则》附录 8 的参考方向图,用于检查地球站是否满足保护 /// 这是《无线电规则》附录 8 的参考方向图,用于检查地球站是否满足保护
/// 邻近卫星的偏轴发射限值。该方向图是分段的: /// 邻近卫星的偏轴发射限值。该方向图是分段的:
/// ///
/// ```text /// <div>
/// for D/lambda >= 100: /// $$
/// G = 29 - 25 log10(theta) for 1 <= theta < 36 degrees /// G(\theta) =
/// G = -10 for 36 <= theta <= 180 degrees /// \begin{cases}
/// for D/lambda < 100: /// 29 - 25\log_{10}\theta, & 1^\circ \le \theta < 36^\circ \\
/// G = 32 - 25 log10(theta) for 1 <= theta < 48 degrees /// -10, & 36^\circ \le \theta \le 180^\circ
/// G = -10 for 48 <= theta <= 180 degrees /// \end{cases}
/// G = 32 - 25 log10(48) for theta < 1 degree /// \quad \text{for } D/\lambda \ge 100
/// ``` /// \qquad
/// G(\theta) =
/// \begin{cases}
/// 32 - 25\log_{10}\theta, & 1^\circ \le \theta < 48^\circ \\
/// -10, & 48^\circ \le \theta \le 180^\circ \\
/// 32 - 25\log_{10}48, & \theta < 1^\circ
/// \end{cases}
/// \quad \text{for } D/\lambda < 100
/// $$
/// </div>
/// ///
/// 角度以弧度计,口径与波长之比决定适用哪一支,因为小天线主瓣更宽, /// 角度以弧度计,口径与波长之比决定适用哪一支,因为小天线主瓣更宽,
/// 故须受控的角度范围也更大。 /// 故须受控的角度范围也更大。

View File

@ -153,11 +153,16 @@ pub fn main_lobe_break_angle(diameter_over_wavelength: f64) -> Option<f64> {
/// ///
/// The angle is in radians. The three regions are /// The angle is in radians. The three regions are
/// ///
/// ```text /// <div>
/// G = G_max - 2.5e-3 (D/lambda phi_deg)^2 for 0 <= phi < phi_break /// $$
/// G = head - 25 log10(phi_deg) for phi_break <= phi < phi_floor /// G(\phi) =
/// G = -10 for phi_floor <= phi <= 180 /// \begin{cases}
/// ``` /// G_{\max} - 2.5\times10^{-3}\left(\frac{D}{\lambda}\,\phi_{\deg}\right)^2, & 0 \le \phi < \phi_{\text{break}} \\
/// \text{head} - 25\log_{10}\phi_{\deg}, & \phi_{\text{break}} \le \phi < \phi_{\text{floor}} \\
/// -10, & \phi_{\text{floor}} \le \phi \le 180^\circ
/// \end{cases}
/// $$
/// </div>
/// ///
/// where `phi_break` is the angle returned by [`main_lobe_break_angle`], the /// where `phi_break` is the angle returned by [`main_lobe_break_angle`], the
/// head is 29 for a large aperture and 32 for a small one, and the floor /// head is 29 for a large aperture and 32 for a small one, and the floor
@ -175,11 +180,16 @@ pub fn main_lobe_break_angle(diameter_over_wavelength: f64) -> Option<f64> {
/// ///
/// 角度以弧度计。三个区是 /// 角度以弧度计。三个区是
/// ///
/// ```text /// <div>
/// G = G_max - 2.5e-3 (D/lambda phi_deg)^2 for 0 <= phi < phi_break /// $$
/// G = head - 25 log10(phi_deg) for phi_break <= phi < phi_floor /// G(\phi) =
/// G = -10 for phi_floor <= phi <= 180 /// \begin{cases}
/// ``` /// G_{\max} - 2.5\times10^{-3}\left(\frac{D}{\lambda}\,\phi_{\deg}\right)^2, & 0 \le \phi < \phi_{\text{break}} \\
/// \text{head} - 25\log_{10}\phi_{\deg}, & \phi_{\text{break}} \le \phi < \phi_{\text{floor}} \\
/// -10, & \phi_{\text{floor}} \le \phi \le 180^\circ
/// \end{cases}
/// $$
/// </div>
/// ///
/// 其中 `phi_break` 是 [`main_lobe_break_angle`] 返回的角度,head 对大 /// 其中 `phi_break` 是 [`main_lobe_break_angle`] 返回的角度,head 对大
/// 孔径为 29、对小孔径为 32,底限角相应地是 36 或 48 度。 /// 孔径为 29、对小孔径为 32,底限角相应地是 36 或 48 度。

View File

@ -29,9 +29,11 @@ pub fn zenith_to_elevation(elevation: f64) -> f64 {
/// the vertical path length. For a plane parallel atmosphere it is the /// the vertical path length. For a plane parallel atmosphere it is the
/// cosecant of the elevation, /// cosecant of the elevation,
/// ///
/// ```text /// <div>
/// m = 1 / sin(elevation) /// $$
/// ``` /// m = \frac{1}{\sin(\text{elevation})}
/// $$
/// </div>
/// ///
/// so the value is one at the zenith and grows without bound at the horizon. /// so the value is one at the zenith and grows without bound at the horizon.
/// The plane parallel assumption fails below about ten degrees elevation, /// The plane parallel assumption fails below about ten degrees elevation,
@ -49,9 +51,11 @@ pub fn zenith_to_elevation(elevation: f64) -> f64 {
/// 大气质量是穿过平坦大气的路径长度与垂直路径长度之比。对平面平行 /// 大气质量是穿过平坦大气的路径长度与垂直路径长度之比。对平面平行
/// 大气它是仰角的余割, /// 大气它是仰角的余割,
/// ///
/// ```text /// <div>
/// m = 1 / sin(elevation) /// $$
/// ``` /// m = \frac{1}{\sin(\text{elevation})}
/// $$
/// </div>
/// ///
/// 故在天顶处为一,在地平处无界增长。平面平行假定在约十度仰角以下 /// 故在天顶处为一,在地平处无界增长。平面平行假定在约十度仰角以下
/// 失效,那里地球曲率使路径比该式更短,真实大气质量更小。 /// 失效,那里地球曲率使路径比该式更短,真实大气质量更小。

View File

@ -43,9 +43,11 @@ const GEOPOTENTIAL_EARTH_RADIUS_KM: f64 = 6_356.766;
/// ///
/// The relation is /// The relation is
/// ///
/// ```text /// <div>
/// h_p = R H / (R + H) /// $$
/// ``` /// h_p = \frac{R H}{R + H}
/// $$
/// </div>
/// ///
/// with `R` the fixed Earth radius of the standard atmosphere and `H` the /// with `R` the fixed Earth radius of the standard atmosphere and `H` the
/// geometric height. The geopotential height is always the smaller of the /// geometric height. The geopotential height is always the smaller of the
@ -55,9 +57,11 @@ const GEOPOTENTIAL_EARTH_RADIUS_KM: f64 = 6_356.766;
/// ///
/// 关系为 /// 关系为
/// ///
/// ```text /// <div>
/// h_p = R H / (R + H) /// $$
/// ``` /// h_p = \frac{R H}{R + H}
/// $$
/// </div>
/// ///
/// 其中 `R` 是标准大气固定的地球半径,`H` 是几何高度。势高总是两者中较 /// 其中 `R` 是标准大气固定的地球半径,`H` 是几何高度。势高总是两者中较
/// 小者。 /// 小者。
@ -167,10 +171,14 @@ pub fn standard_water_vapour_density(geometric_height_km: f64, surface_density:
/// ///
/// The relation is /// The relation is
/// ///
/// ```text /// <div>
/// N = 77.6 P_d / T + 72 e / T + 3.75e5 e / T^2 /// $$
/// n = 1 + N 1e-6 /// \begin{aligned}
/// ``` /// N &= \frac{77.6\,P_d}{T} + \frac{72\,e}{T} + \frac{3.75\times10^5\,e}{T^2} \\
/// n &= 1 + N \times 10^{-6}
/// \end{aligned}
/// $$
/// </div>
/// ///
/// with `P_d` the dry air pressure and `e` the water vapour partial /// with `P_d` the dry air pressure and `e` the water vapour partial
/// pressure, both in hectopascals, and `T` the temperature in kelvin. The /// pressure, both in hectopascals, and `T` the temperature in kelvin. The
@ -184,10 +192,14 @@ pub fn standard_water_vapour_density(geometric_height_km: f64, surface_density:
/// ///
/// 关系为 /// 关系为
/// ///
/// ```text /// <div>
/// N = 77.6 P_d / T + 72 e / T + 3.75e5 e / T^2 /// $$
/// n = 1 + N 1e-6 /// \begin{aligned}
/// ``` /// N &= \frac{77.6\,P_d}{T} + \frac{72\,e}{T} + \frac{3.75\times10^5\,e}{T^2} \\
/// n &= 1 + N \times 10^{-6}
/// \end{aligned}
/// $$
/// </div>
/// ///
/// 其中 `P_d` 是干空气气压,`e` 是水汽分压,两者均以百帕计;`T` 是 /// 其中 `P_d` 是干空气气压,`e` 是水汽分压,两者均以百帕计;`T` 是
/// 温度,以开尔文计。前两项是非色散折射率,第三项是使水汽产生强烈效应 /// 温度,以开尔文计。前两项是非色散折射率,第三项是使水汽产生强烈效应

View File

@ -45,9 +45,11 @@ pub fn range_rate_from_velocity(relative_position: Vec3, relative_velocity: Vec3
/// ///
/// The relation is /// The relation is
/// ///
/// ```text /// <div>
/// delta f = -f_0 (v_r / c) /// $$
/// ``` /// \Delta f = -f_0 \frac{v_r}{c}
/// $$
/// </div>
/// ///
/// where `v_r` is the range rate, positive when receding. The shift is /// where `v_r` is the range rate, positive when receding. The shift is
/// negative for a receding terminal, so the received frequency is lower than /// negative for a receding terminal, so the received frequency is lower than
@ -66,9 +68,11 @@ pub fn range_rate_from_velocity(relative_position: Vec3, relative_velocity: Vec3
/// ///
/// 关系为 /// 关系为
/// ///
/// ```text /// <div>
/// delta f = -f_0 (v_r / c) /// $$
/// ``` /// \Delta f = -f_0 \frac{v_r}{c}
/// $$
/// </div>
/// ///
/// 其中 `v_r` 是距离变化率,远离时为正。远离的终端其频移为负,故接收 /// 其中 `v_r` 是距离变化率,远离时为正。远离的终端其频移为负,故接收
/// 频率低于发射频率。 /// 频率低于发射频率。

View File

@ -146,9 +146,11 @@ pub fn carrier_to_interference_single(carrier: Power, interferer: Interferer) ->
/// When two independent impairments act together, their effects add as /// When two independent impairments act together, their effects add as
/// reciprocal ratios, which in decibels means /// reciprocal ratios, which in decibels means
/// ///
/// ```text /// <div>
/// 1 / R = 1 / R_1 + 1 / R_2 /// $$
/// ``` /// \frac{1}{R} = \frac{1}{R_1} + \frac{1}{R_2}
/// $$
/// </div>
/// ///
/// The result is always below the smaller of the two, so adding a second /// The result is always below the smaller of the two, so adding a second
/// impairment can only lower the combined ratio. This is how a carrier to /// impairment can only lower the combined ratio. This is how a carrier to
@ -161,9 +163,11 @@ pub fn carrier_to_interference_single(carrier: Power, interferer: Interferer) ->
/// ///
/// 当两种独立损伤共同作用时,它们的效果按倒数之比相加,以分贝表示即 /// 当两种独立损伤共同作用时,它们的效果按倒数之比相加,以分贝表示即
/// ///
/// ```text /// <div>
/// 1 / R = 1 / R_1 + 1 / R_2 /// $$
/// ``` /// \frac{1}{R} = \frac{1}{R_1} + \frac{1}{R_2}
/// $$
/// </div>
/// ///
/// 结果总低于两者中较小者,故加入第二种损伤只会降低合并之比。载噪比与 /// 结果总低于两者中较小者,故加入第二种损伤只会降低合并之比。载噪比与
/// 载干比正是这样合并为载波与噪声加干扰之比的。 /// 载干比正是这样合并为载波与噪声加干扰之比的。
@ -237,9 +241,11 @@ pub fn interference_to_noise_ratio(interference: Power, noise: Power) -> Option<
/// budget as an increase in system noise temperature. In temperature the /// budget as an increase in system noise temperature. In temperature the
/// relation is /// relation is
/// ///
/// ```text /// <div>
/// T = P / (k B) /// $$
/// ``` /// T = \frac{P}{k B}
/// $$
/// </div>
/// ///
/// with `P` the interfering power, `k` the Boltzmann constant, and `B` the /// with `P` the interfering power, `k` the Boltzmann constant, and `B` the
/// receiver noise bandwidth. The temperature is the value that, added to the /// receiver noise bandwidth. The temperature is the value that, added to the
@ -254,9 +260,11 @@ pub fn interference_to_noise_ratio(interference: Power, noise: Power) -> Option<
/// 接收机当作噪声处理的干扰源可以折算为系统噪声温度的增量计入链路 /// 接收机当作噪声处理的干扰源可以折算为系统噪声温度的增量计入链路
/// 预算。在温度上关系为 /// 预算。在温度上关系为
/// ///
/// ```text /// <div>
/// T = P / (k B) /// $$
/// ``` /// T = \frac{P}{k B}
/// $$
/// </div>
/// ///
/// 其中 `P` 是干扰功率,`k` 是玻尔兹曼常量,`B` 是接收机噪声带宽。该 /// 其中 `P` 是干扰功率,`k` 是玻尔兹曼常量,`B` 是接收机噪声带宽。该
/// 温度即加到系统温度上会使噪声上升与干扰源相同量的值。 /// 温度即加到系统温度上会使噪声上升与干扰源相同量的值。

View File

@ -82,9 +82,11 @@ impl Stage {
/// The Friis formula weights each stage's contribution by the gain that /// The Friis formula weights each stage's contribution by the gain that
/// precedes it: /// precedes it:
/// ///
/// ```text /// <div>
/// T = T_1 + T_2 / G_1 + T_3 / (G_1 G_2) + ... /// $$
/// ``` /// T = T_1 + \frac{T_2}{G_1} + \frac{T_3}{G_1 G_2} + \cdots
/// $$
/// </div>
/// ///
/// The consequence is that the first stage dominates: a noisy second stage /// The consequence is that the first stage dominates: a noisy second stage
/// matters much less when the first stage has gain. The total temperature is /// matters much less when the first stage has gain. The total temperature is
@ -98,9 +100,11 @@ impl Stage {
/// ///
/// Friis 公式按本级之前的增益对每级的贡献加权: /// Friis 公式按本级之前的增益对每级的贡献加权:
/// ///
/// ```text /// <div>
/// T = T_1 + T_2 / G_1 + T_3 / (G_1 G_2) + ... /// $$
/// ``` /// T = T_1 + \frac{T_2}{G_1} + \frac{T_3}{G_1 G_2} + \cdots
/// $$
/// </div>
/// ///
/// 其结果是第一级占主导:当第一级有增益时,噪声大的第二级影响小得多。 /// 其结果是第一级占主导:当第一级有增益时,噪声大的第二级影响小得多。
/// 总温度即整链的温度,故可直接与可用增益比较以求得整链的噪声系数。 /// 总温度即整链的温度,故可直接与可用增益比较以求得整链的噪声系数。

View File

@ -33,9 +33,11 @@ pub fn wavelength(frequency: f64) -> Option<f64> {
/// The ratio is positive, so it represents a loss: the received power is the /// The ratio is positive, so it represents a loss: the received power is the
/// transmitted power divided by this ratio. The relation is /// transmitted power divided by this ratio. The relation is
/// ///
/// ```text /// <div>
/// L = (4 pi d / lambda)^2 /// $$
/// ``` /// L = \left(\frac{4 \pi d}{\lambda}\right)^2
/// $$
/// </div>
/// ///
/// which is the spreading loss of a spherical wave over a distance `d` /// which is the spreading loss of a spherical wave over a distance `d`
/// combined with the effective aperture of an isotropic receive antenna. /// combined with the effective aperture of an isotropic receive antenna.
@ -54,9 +56,11 @@ pub fn wavelength(frequency: f64) -> Option<f64> {
/// ///
/// 该比值为正,故表示一种损耗:接收功率是发射功率除以该比值。关系为 /// 该比值为正,故表示一种损耗:接收功率是发射功率除以该比值。关系为
/// ///
/// ```text /// <div>
/// L = (4 pi d / lambda)^2 /// $$
/// ``` /// L = \left(\frac{4 \pi d}{\lambda}\right)^2
/// $$
/// </div>
/// ///
/// 它是球面波在距离 `d` 上的扩散损耗与各向同性接收天线有效孔径的 /// 它是球面波在距离 `d` 上的扩散损耗与各向同性接收天线有效孔径的
/// 结合。 /// 结合。
@ -82,9 +86,11 @@ pub fn free_space_path_loss(distance: f64, frequency: f64) -> Option<Decibel> {
/// gains and divided by the free space path loss, which in decibels is the /// gains and divided by the free space path loss, which in decibels is the
/// sum /// sum
/// ///
/// ```text /// <div>
/// P_r / P_t = G_t G_r / L /// $$
/// ``` /// \frac{P_r}{P_t} = \frac{G_t G_r}{L}
/// $$
/// </div>
/// ///
/// The result is the ratio in decibels, so a positive value means the /// The result is the ratio in decibels, so a positive value means the
/// receive antenna collects more than was transmitted, which happens when /// receive antenna collects more than was transmitted, which happens when
@ -97,9 +103,11 @@ pub fn free_space_path_loss(distance: f64, frequency: f64) -> Option<Decibel> {
/// 接收功率是发射功率乘以两个天线增益、再除以自由空间路径损耗,以分贝 /// 接收功率是发射功率乘以两个天线增益、再除以自由空间路径损耗,以分贝
/// 表示即 /// 表示即
/// ///
/// ```text /// <div>
/// P_r / P_t = G_t G_r / L /// $$
/// ``` /// \frac{P_r}{P_t} = \frac{G_t G_r}{L}
/// $$
/// </div>
/// ///
/// 结果是分贝比值,故正值意味着接收天线收集到的多于所发射的,这在两个 /// 结果是分贝比值,故正值意味着接收天线收集到的多于所发射的,这在两个
/// 增益超过扩散损耗时发生。 /// 增益超过扩散损耗时发生。

36
docs/mathjax.html Normal file
View File

@ -0,0 +1,36 @@
<script>
// MathJax configuration for the ApsisRS documentation.
//
// MathJax is vendored under docs/vendor/mathjax and copied to
// target/doc/vendor/mathjax by crates/apsis-rs/build.rs, so this page loads
// it from the local documentation root and never contacts a content delivery
// network. The root is derived from the static file reference rustdoc already
// emits, which carries the correct number of "../" for the current page.
window.MathJax = {
tex: {
inlineMath: [["$", "$"], ["\\(", "\\)"]],
displayMath: [["$$", "$$"], ["\\[", "\\]"]],
processEscapes: true,
tags: "none"
},
svg: { fontCache: "local" },
options: {
enableMenu: false,
skipHtmlTags: ["script", "noscript", "style", "textarea", "pre", "code"]
}
};
(function () {
var reference = document.querySelector(
'link[href*="static.files"], script[src*="static.files"]'
);
var prefix = "";
if (reference) {
var value = reference.getAttribute("href") || reference.getAttribute("src") || "";
prefix = value.replace(/static\.files\/.*$/, "");
}
var script = document.createElement("script");
script.src = prefix + "vendor/mathjax/tex-svg-full.js";
script.async = true;
document.head.appendChild(script);
})();
</script>

202
docs/vendor/mathjax/LICENSE vendored Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

49
docs/vendor/mathjax/README.md vendored Normal file
View File

@ -0,0 +1,49 @@
# Vendored MathJax
This directory holds a copy of MathJax so that the generated documentation
can render mathematical formulas **offline**, without fetching anything from
a content delivery network. `docs/mathjax.html` loads the file, and
`crates/apsis-rs/build.rs` copies it into `target/doc/vendor/mathjax/` when
the documentation is built.
| Field | Value |
| --- | --- |
| Package | `mathjax` |
| Version | 3.2.2 |
| File | `es5/tex-svg-full.js` |
| Source | `https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg-full.js` |
| Size | 2 275 113 bytes |
| SHA-256 | `a4354ff94fd868aea0cc6eaaa79a57fd` (first 32 hex digits) |
| Licence | Apache-2.0, see `LICENSE` in this directory |
`tex-svg-full.js` is the combined component that bundles the TeX input and
the SVG output with the font data, so it needs no web font and no second
request. That is why this build was chosen over the HTML output, which would
require vendoring a directory of font files.
To refresh the copy, download the same URL and update the size, the SHA-256,
and the version above.
---
# 内置的 MathJax
本目录保存一份 MathJax 副本,使生成的文档能够**离线**渲染数学公式,不向任何
内容分发网络发起请求。`docs/mathjax.html` 负责加载该文件,构建文档时
`crates/apsis-rs/build.rs` 会把它复制到 `target/doc/vendor/mathjax/`。
| 字段 | 值 |
| --- | --- |
| 包 | `mathjax` |
| 版本 | 3.2.2 |
| 文件 | `es5/tex-svg-full.js` |
| 来源 | `https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg-full.js` |
| 大小 | 2 275 113 字节 |
| SHA-256 | `a4354ff94fd868aea0cc6eaaa79a57fd`(前 32 位十六进制) |
| 许可 | Apache-2.0,见本目录的 `LICENSE` |
`tex-svg-full.js` 是合并组件,打包了 TeX 输入、SVG 输出与字体数据,故不需要
在线字体,也不需要第二次请求。这正是选用它而非 HTML 输出的原因——后者需要
内置一整个字体文件目录。
要更新该副本,请下载同一 URL,并更新上表中的大小、SHA-256 与版本。

34
docs/vendor/mathjax/tex-svg-full.js vendored Normal file

File diff suppressed because one or more lines are too long

44
scripts/build-docs.sh Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env bash
#
# Builds the API documentation with the vendored MathJax in place.
#
# `cargo doc` injects the MathJax loader through `.cargo/config.toml` when
# `RUSTDOCFLAGS` is unset, and `crates/apsis-rs/build.rs` copies the vendored
# file into the documentation root. That build script only reruns when its
# inputs change, so removing `target/doc` on its own leaves the file missing.
# This script runs the copy itself, which makes `bash scripts/build-docs.sh`
# the reliable way to produce documentation that renders math offline.
#
# Any argument is passed to `cargo doc`, for example:
#
# scripts/build-docs.sh --workspace --all-features --no-deps
#
# 构建 API 文档,并使内置的 MathJax 就位。
#
# 未设置 `RUSTDOCFLAGS` 时,`cargo doc` 会经 `.cargo/config.toml` 注入 MathJax
# 加载器,而 `crates/apsis-rs/build.rs` 会把内置文件复制到文档根。该构建脚本
# 只在其输入变化时重跑,故仅删除 `target/doc` 而不改输入会使文件缺失。本脚本
# 自行执行该复制,使 `bash scripts/build-docs.sh` 成为生成可离线渲染数学的
# 文档的可靠方式。
#
# 任何参数都会传给 `cargo doc`,例如:
#
# scripts/build-docs.sh --workspace --all-features --no-deps
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$root"
cargo doc "$@"
# The target directory can be moved, so ask cargo rather than assuming it.
# 目标目录可能被移动,故问 cargo 而非假定。
target="$(cargo metadata --format-version 1 --no-deps \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["target_directory"])')"
destination="$target/doc/vendor/mathjax"
mkdir -p "$destination"
cp docs/vendor/mathjax/* "$destination/"
echo "documentation built at $target/doc with the vendored MathJax in place"