1#![warn(missing_docs)]
2mod block;
30
31#[cfg(feature = "cli")]
33#[allow(missing_docs)]
34pub mod cli;
35mod constants;
36mod file_operations;
37mod input;
38mod node;
39mod open;
40
41#[cfg(feature = "plot")]
42mod plot;
43
44mod amplifier_model;
45
46pub use amplifier_model::{AmplifierModel, AmplifierModelBuilder, AmplifierPoint};
47pub use block::{Block, Imd3Point};
48pub use input::Input;
49pub use node::{DynamicRange, SignalNode};
50
51#[doc(alias = "cascade")]
80#[doc(alias = "signal chain")]
81#[doc(alias = "gain lineup")]
82#[must_use]
83pub fn cascade_vector_return_output(input: Input, blocks: Vec<Block>) -> SignalNode {
84 let mut cascading_signal: SignalNode = SignalNode::default(); for (i, block) in blocks.iter().enumerate() {
87 if i == 0 {
88 cascading_signal = input.cascade_block(block);
89 } else {
90 cascading_signal = cascading_signal.cascade_block(block);
91 }
92 }
93
94 cascading_signal
95}
96
97#[doc(alias = "cascade")]
127#[doc(alias = "signal chain")]
128#[must_use]
129pub fn cascade_vector_return_vector(input: Input, blocks: Vec<Block>) -> Vec<SignalNode> {
130 let mut cascading_signal: SignalNode = SignalNode::default(); let mut node_vector: Vec<SignalNode> = vec![];
134 for (i, block) in blocks.iter().enumerate() {
135 if i == 0 {
136 cascading_signal = input.cascade_block(block);
137 } else {
138 cascading_signal = cascading_signal.cascade_block(block);
139 }
140 node_vector.push(cascading_signal.clone());
141 }
142 node_vector
143}
144
145#[doc(alias = "P1dB")]
169#[doc(alias = "compression")]
170#[must_use]
171pub fn cascade_am_am_sweep(
172 blocks: &[Block],
173 start_dbm: f64,
174 stop_dbm: f64,
175 step_db: f64,
176) -> Vec<(f64, f64)> {
177 let mut powers = vec![];
178 let mut pin = start_dbm;
179 while pin <= stop_dbm + step_db * 0.01 {
180 powers.push(pin);
181 pin += step_db;
182 }
183 powers
184 .iter()
185 .map(|&pin| {
186 let mut power = pin;
187 for block in blocks {
188 power = block.output_power(power);
189 }
190 (pin, power)
191 })
192 .collect()
193}
194
195#[doc(alias = "gain compression")]
218#[must_use]
219pub fn cascade_gain_compression_sweep(
220 blocks: &[Block],
221 start_dbm: f64,
222 stop_dbm: f64,
223 step_db: f64,
224) -> Vec<(f64, f64)> {
225 cascade_am_am_sweep(blocks, start_dbm, stop_dbm, step_db)
226 .iter()
227 .map(|&(pin, pout)| (pin, pout - pin))
228 .collect()
229}
230
231#[cfg(test)]
232mod tests {
233 #[test]
234 fn two_part_node_cascade_vector_return_output() {
235 let input_power: f64 = -30.0;
236 let input = super::Input {
237 power_dbm: input_power,
238 frequency_hz: 1.0e9, bandwidth_hz: 0.0, noise_temperature_k: Some(270.0),
241 };
242 let amplifier = super::Block {
243 name: "Low Noise Amplifier".to_string(),
244 gain_db: 30.0,
245 noise_figure_db: 3.0,
246 output_p1db_dbm: None,
247 output_ip3_dbm: None,
248 };
249 let attenuator = super::Block {
250 name: "Attenuator".to_string(),
251 gain_db: -6.0,
252 noise_figure_db: 6.0,
253 output_p1db_dbm: None,
254 output_ip3_dbm: None,
255 };
256 let blocks = vec![amplifier, attenuator];
257 let output_node = super::cascade_vector_return_output(input, blocks);
258
259 assert_eq!(output_node.signal_power_dbm, -6.0);
260 assert_eq!(output_node.cumulative_gain_db, 24.0);
261
262 assert_eq!(output_node.name, "Attenuator Output");
263
264 let rounded_noise_figure = (output_node.cumulative_noise_figure_db * 1e3).round() / 1e3;
266 assert_eq!(rounded_noise_figure, 3.006);
267 }
268
269 #[test]
270 fn two_part_node_cascade_vector_return_vector() {
271 let input_power: f64 = -30.0;
272 let input = super::Input {
273 power_dbm: input_power,
274 frequency_hz: 1.0e9, bandwidth_hz: 0.0, noise_temperature_k: Some(270.0),
277 };
278 let amplifier = super::Block {
279 name: "Low Noise Amplifier".to_string(),
280 gain_db: 30.0,
281 noise_figure_db: 3.0,
282 output_p1db_dbm: None,
283 output_ip3_dbm: None,
284 };
285 let attenuator = super::Block {
286 name: "Attenuator".to_string(),
287 gain_db: -6.0,
288 noise_figure_db: 6.0,
289 output_p1db_dbm: None,
290 output_ip3_dbm: None,
291 };
292 let blocks = vec![amplifier, attenuator];
293 let cascade_vector = super::cascade_vector_return_vector(input, blocks);
294
295 let output_node = cascade_vector.last().unwrap();
296 assert_eq!(output_node.signal_power_dbm, -6.0);
297 assert_eq!(output_node.cumulative_gain_db, 24.0);
298
299 assert_eq!(output_node.name, "Attenuator Output");
300
301 let rounded_noise_figure = (output_node.cumulative_noise_figure_db * 1e3).round() / 1e3;
303 assert_eq!(rounded_noise_figure, 3.006);
304 }
305
306 #[test]
307 fn cascade_am_am_linear() {
308 let blocks = vec![
309 super::Block {
310 name: "LNA".to_string(),
311 gain_db: 20.0,
312 noise_figure_db: 3.0,
313 output_p1db_dbm: None,
314 output_ip3_dbm: None,
315 },
316 super::Block {
317 name: "Atten".to_string(),
318 gain_db: -6.0,
319 noise_figure_db: 6.0,
320 output_p1db_dbm: None,
321 output_ip3_dbm: None,
322 },
323 ];
324 let sweep = super::cascade_am_am_sweep(&blocks, -40.0, -20.0, 10.0);
325 assert_eq!(sweep.len(), 3);
326 assert!((sweep[0].1 - (-26.0)).abs() < 0.01); assert!((sweep[1].1 - (-16.0)).abs() < 0.01); assert!((sweep[2].1 - (-6.0)).abs() < 0.01); }
331
332 #[test]
333 fn cascade_am_am_with_compression() {
334 let blocks = vec![
335 super::Block {
336 name: "LNA".to_string(),
337 gain_db: 30.0,
338 noise_figure_db: 3.0,
339 output_p1db_dbm: Some(5.0),
340 output_ip3_dbm: None,
341 },
342 super::Block {
343 name: "Driver".to_string(),
344 gain_db: 10.0,
345 noise_figure_db: 5.0,
346 output_p1db_dbm: Some(15.0),
347 output_ip3_dbm: None,
348 },
349 ];
350 let sweep = super::cascade_am_am_sweep(&blocks, -50.0, 0.0, 10.0);
351 assert!((sweep[0].1 - (-10.0)).abs() < 0.01);
353 let last = sweep.last().unwrap();
355 assert!(last.1 <= 16.0, "Should compress at high input");
356 }
357
358 #[test]
359 fn cascade_gain_compression() {
360 let blocks = vec![super::Block {
361 name: "Amp".to_string(),
362 gain_db: 20.0,
363 noise_figure_db: 3.0,
364 output_p1db_dbm: Some(10.0),
365 output_ip3_dbm: None,
366 }];
367 let sweep = super::cascade_gain_compression_sweep(&blocks, -40.0, 0.0, 10.0);
368 assert!((sweep[0].1 - 20.0).abs() < 0.01);
370 let last = sweep.last().unwrap();
372 assert!(last.1 < 20.0, "Gain should compress at high input");
373 }
374
375 #[test]
376 fn two_part_node_cascade_vector_return_vector_with_compression() {
377 let input_power: f64 = -30.0;
378 let input = super::Input {
379 power_dbm: input_power,
380 frequency_hz: 1.0e9, bandwidth_hz: 0.0, noise_temperature_k: Some(270.0),
383 };
384 let low_noise_amplifier = super::Block {
385 name: "Low Noise Amplifier".to_string(),
386 gain_db: 30.0,
387 noise_figure_db: 3.0,
388 output_p1db_dbm: Some(5.0),
389 output_ip3_dbm: None,
390 };
391 let attenuator = super::Block {
392 name: "Attenuator".to_string(),
393 gain_db: -6.0,
394 noise_figure_db: 6.0,
395 output_p1db_dbm: None,
396 output_ip3_dbm: None,
397 };
398 let high_power_amplifier = super::Block {
399 name: "High Power Amplifier".to_string(),
400 gain_db: 30.0,
401 noise_figure_db: 3.0,
402 output_p1db_dbm: Some(20.0),
403 output_ip3_dbm: None,
404 };
405 let blocks = vec![low_noise_amplifier, attenuator, high_power_amplifier];
406 let cascade_vector = super::cascade_vector_return_vector(input, blocks);
407
408 let output_node = cascade_vector.last().unwrap();
409 assert_eq!(output_node.signal_power_dbm, 21.0);
410 assert_eq!(output_node.cumulative_gain_db, 51.0);
411
412 assert_eq!(output_node.name, "High Power Amplifier Output");
413
414 let rounded_noise_figure = (output_node.cumulative_noise_figure_db * 1e3).round() / 1e3;
416 assert_eq!(rounded_noise_figure, 3.015);
417 }
418}