1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Declarative simulation specifications.
//!
//! A specification describes a whole Monte Carlo campaign in a YAML, JSON or TOML file:
//! the grid of conditions, the link as a list of blocks, the metrics, the stopping
//! rule and the outputs. Every number of a study then comes from a file that can be
//! read, versioned and rerun (`comnum run spec.yaml`, [`run`] in Rust,
//! `comnum.run_spec` in Python), and a simulation that cannot be written as a
//! specification shows that a block is missing from the [registry](blocks::registry).
//!
//! # Format
//!
//! ```yaml
//! name: qam16_awgn # the name of the campaign (it enters the seeds)
//! description: BER of 16-QAM over AWGN
//! seed: 1 # base seed (default 0)
//! batch: 64 # trials between two checks of the stopping rule
//! threads: 0 # worker threads (0: all cores)
//! confidence: 0.95 # level of the reported intervals
//!
//! grid: # axes, the last one varying fastest
//! ebn0_db: {from: 0, to: 12, step: 2} # also: a list, or {linspace: [a, b, n]}
//! cases: # optional explicit conditions, crossed with the axes
//! - {m: 4}
//! - {m: 16}
//!
//! fiber: {n_spans: 10} # defaults of every fibre block (FiberParams)
//!
//! link: # blocks, in order; `$name` is a grid variable
//! - {type: source, constellation: {kind: qam, m: $m}, n_symbols: 4096}
//! - {type: awgn_ebn0, ebn0_db: $ebn0_db}
//!
//! metrics: {report: [ber, ser, snr_db]}
//!
//! stop: # (targets and min_trials) or max_trials or max_time_s
//! min_errors: {ber: 200}
//! precision: {snr_db: 0.01} # relative half-width of the interval
//! min_trials: 8
//! max_trials: 10000
//! max_time_s: 600
//!
//! callbacks:
//! progress: true
//! skip_below: {metric: ber, threshold: 1e-6, along: ebn0_db}
//!
//! profile: true # the time of every block, as the metrics <label>.time_ms
//! output: {csv: results/qam16_awgn.csv, json: results/qam16_awgn.json}
//!
//! search: # instead of the sweep: the operating point
//! {along: ebn0_db, range: [4, 16], metric: ber, target: 1.0e-3}
//!
//! expect: # expected results, checked by `comnum verify`
//! - {metric: ber, theory: {function: ber, kind: qam, m: $m, ebn0_db: $ebn0_db}, rtol: 0.05}
//! ```
//!
//! - The variables of the grid are substituted in the link and the metrics before the
//! blocks are built: a string `$name` (or `${name}`) becomes the value with its type,
//! and `${name}` inside a longer string is replaced by its text.
//! - The stopping rule is `(all targets and min_trials) or max_trials or max_time_s`;
//! without any field it is `max_trials: 100`, and a rule with targets but no cap is
//! rejected (it could run forever). A target on a metric that the link does not
//! record, or `min_errors` on a mean (`snr_db`: use `precision`), is an error at the
//! first batch (`comnum check` reports it); `max_time_s` is a finite number of seconds
//! at least 0, a `precision` a positive number. A run of `max_trials` runs exactly
//! that many trials, whatever the batch.
//! - The accepted values of a parameter (`reference: signal`, `kind: rrc`) are compared
//! without case, and the block receives the declared spelling.
//! - The blocks are listed with their parameters by [`blocks::registry`] (and
//! `comnum blocks`); the metrics are those of [`metrics`].
//! - `search` replaces the sweep by the search of the value of a variable at which a
//! metric reaches a target, for every combination of the other axes ([`search`]).
//! - `expect` lists results that the campaign must reproduce (closed forms, published
//! curves), with their sources and tolerances; see [`verify`].
//! - A block trained once per condition ([`Block::needs_training`](blocks::Block::needs_training),
//! a learned receiver of another crate, say) is trained on an independent block drawn
//! from the stream `"<name>/train"` before the trials.
//! - Blocks defined in other crates join the registry by [`blocks::register`].
//!
//! # Modules
//!
//! | Module | Content |
//! |---|---|
//! | [`blocks`] | the blocks of a link, the frame they act on, their registry and its extension point |
//! | [`metrics`] | the metrics of a declarative link |
//! | [`verify`] | the expected results of a specification and their checks |
//! | [`search`] | the search of operating points |
//! | [`io`] | recordings: SigMF, raw IQ files, NPY arrays |
//! | [`cli`] | the command line `comnum` (`run`, `check`, `verify`, `conditions`, `blocks`, `schema`) |
//!
//! The specification itself ([`Spec`]), the link ([`Link`]) and the runs ([`run`],
//! [`profile`]) are at the root.
//!
//! # Examples
//! ```
//! use comnum_spec::{run, RunOptions, Spec};
//! let spec = Spec::from_yaml(r#"
//! name: qpsk_doc
//! seed: 3
//! grid: {ebn0_db: [2, 4]}
//! link:
//! - {type: source, constellation: QPSK, n_symbols: 2000}
//! - {type: awgn_ebn0, ebn0_db: $ebn0_db}
//! metrics: {report: [ber]}
//! stop: {min_errors: {ber: 300}, max_trials: 500}
//! "#).unwrap();
//! let results = run(&spec, &RunOptions::default()).unwrap();
//! let ber = results[1].metric("ber").unwrap();
//! let theory = comnum_metrics::theory_ber(comnum_mapping::constellation::ConstellationKind::Qam, 4, 4.0).unwrap();
//! let (lo, hi) = ber.interval(0.999);
//! assert!(lo < theory && theory < hi);
//! ```
pub use ;
pub use ;
pub use ;
pub use ;
/// The error of a name that is not among the known ones (a block, a code, a metric): one
/// wording for every list of names.
pub
/// The paths the exported macros expand to, so that a crate that uses them needs no
/// dependency of its own on `comnum-core` or `serde_json`.