1use std::fs;
2use std::path::Path;
3
4use touchstone::Network;
5
6use serde::Deserialize;
7use toml;
8
9pub mod cli;
10mod file_operations;
11mod open;
12mod plot;
13
14#[derive(Clone, Debug)]
16pub struct Block {
17 pub name: String,
18 pub gain: f64, pub noise_figure: f64, pub output_1db_compression_point: Option<f64>, }
22
23impl Block {
24 pub fn new(
25 name: String,
26 gain: f64,
27 noise_figure: f64,
28 output_1db_compression_point: Option<f64>,
29 ) -> Block {
30 Block {
31 name,
32 gain,
33 noise_figure,
34 output_1db_compression_point,
35 }
36 }
37}
38
39#[derive(Clone, Debug)]
40pub struct SignalNode {
41 pub name: String,
42 pub power: f64, pub noise_temperature: f64, pub cumulative_gain: f64, }
46
47#[derive(Debug)]
52pub struct Config {
53 pub input_power: f64,
54 pub frequency: f64,
55 pub blocks: Vec<Block>,
56}
57
58#[derive(Deserialize, Debug)]
59struct IncludedConfig {
60 blocks: Vec<BlockConfig>,
61}
62
63#[derive(Deserialize, Debug)]
64#[serde(tag = "type", rename_all = "snake_case")]
65enum BlockConfig {
66 Explicit {
67 name: String,
68 gain: f64,
69 noise_figure: f64,
70 output_1db_compression_point: Option<f64>,
71 },
72 Touchstone {
73 file_path: String,
74 name: String,
75 noise_figure: Option<f64>,
76 output_1db_compression_point: Option<f64>,
77 },
78 Include {
79 path: String,
80 },
81}
82
83pub fn load_config(path: &str) -> Result<Config, Box<dyn std::error::Error>> {
84 let config_content = fs::read_to_string(path)?;
87 #[derive(Deserialize)]
92 struct IntermediateConfig {
93 input_power: f64,
94 frequency: f64,
95 blocks: Vec<BlockConfig>,
96 }
97
98 let intermediate_config: IntermediateConfig = toml::from_str(&config_content)?;
99 let mut blocks = Vec::new();
102 let config_path = Path::new(path);
103 let base_dir = config_path.parent().unwrap_or_else(|| Path::new("."));
104
105 load_blocks_recursive(
106 intermediate_config.blocks,
107 intermediate_config.frequency,
108 &mut blocks,
109 base_dir,
110 )?;
111
112 Ok(Config {
115 input_power: intermediate_config.input_power,
116 frequency: intermediate_config.frequency,
117 blocks,
118 })
119}
120
121fn load_blocks_recursive(
122 block_configs: Vec<BlockConfig>,
123 frequency: f64,
124 blocks: &mut Vec<Block>,
125 base_dir: &Path,
126) -> Result<(), Box<dyn std::error::Error>> {
127 for block_config in block_configs {
128 match block_config {
129 BlockConfig::Explicit {
130 name,
131 gain,
132 noise_figure,
133 output_1db_compression_point,
134 } => {
135 blocks.push(Block {
136 name,
137 gain,
138 noise_figure,
139 output_1db_compression_point,
140 });
141 }
142 BlockConfig::Touchstone {
143 file_path,
144 name,
145 noise_figure,
146 output_1db_compression_point,
147 } => {
148 let full_path = base_dir.join(&file_path);
150 let gain = touchstone_file_path_and_frequency_to_gain(
151 full_path.to_string_lossy().to_string(),
152 frequency,
153 );
154
155 let noise_figure_default = gain * -1.0; let output_1db_compression_point_default = 99.0; let final_noise_figure = noise_figure.unwrap_or(noise_figure_default);
159 let final_output_1db_compression_point =
160 output_1db_compression_point.or(Some(output_1db_compression_point_default));
161
162 blocks.push(Block {
163 name,
164 gain,
165 noise_figure: final_noise_figure,
166 output_1db_compression_point: final_output_1db_compression_point,
167 });
168 }
169 BlockConfig::Include { path } => {
170 let included_path = base_dir.join(&path);
171 let content = fs::read_to_string(&included_path)?;
173 let included: IncludedConfig = toml::from_str(&content)?;
174
175 let new_base_dir = included_path.parent().unwrap_or_else(|| Path::new("."));
176 load_blocks_recursive(included.blocks, frequency, blocks, new_base_dir)?;
177 }
178 }
179 }
180 Ok(())
181}
182
183pub fn touchstone_file_path_and_frequency_to_gain(file_path: String, frequency_in_hz: f64) -> f64 {
184 let s2p = Network::new(file_path.clone());
185
186 let gain_vector = s2p.s_db(2, 1); let gain = gain_vector
189 .iter()
190 .find(|frequency_db| frequency_db.frequency == frequency_in_hz)
191 .unwrap()
192 .s_db
193 .decibel();
194
195 gain
196}
197
198pub fn cascade(input_power: f64, block1: Block) -> f64 {
200 let output_power_without_compression = input_power + block1.gain;
201 if let Some(op1db) = block1.output_1db_compression_point {
202 if output_power_without_compression > op1db + 1.0 {
203 return op1db + 1.0;
204 }
205 }
206 output_power_without_compression
207}
208
209pub fn cascade_node(signal: SignalNode, block1: Block) -> SignalNode {
211 let output_node_name = block1.name + " Output";
212 let block_noise_temperature =
213 rfconversions::noise::noise_temperature_from_noise_figure(block1.noise_figure);
214 let cumulative_gain_linear = rfconversions::power::db_to_linear(signal.cumulative_gain)
215 + rfconversions::power::db_to_linear(block1.gain);
216
217 let output_power_without_compression = signal.power + block1.gain;
219 let output_power = if let Some(op1db) = block1.output_1db_compression_point {
220 if output_power_without_compression > op1db + 1.0 {
221 op1db + 1.0
222 } else {
223 output_power_without_compression
224 }
225 } else {
226 output_power_without_compression
227 };
228
229 let stage_gain = output_power - signal.power;
230
231 SignalNode {
232 name: output_node_name,
233 power: output_power,
234 noise_temperature: signal.noise_temperature
235 + block_noise_temperature / cumulative_gain_linear,
236 cumulative_gain: signal.cumulative_gain + stage_gain,
237 }
238}
239
240pub fn cascade_vector_return_output(input_signal: SignalNode, blocks: Vec<Block>) -> SignalNode {
242 let mut cascading_signal = input_signal;
243
244 for block in blocks {
245 cascading_signal = cascade_node(cascading_signal, block);
246 }
247 cascading_signal
248}
249
250pub fn cascade_vector_return_vector(
252 input_signal: SignalNode,
253 blocks: Vec<Block>,
254) -> Vec<SignalNode> {
255 let mut cascading_signal = input_signal;
256 let mut node_vector: Vec<SignalNode> = vec![cascading_signal.clone()];
257 for block in blocks.iter() {
258 cascading_signal = cascade_node(cascading_signal, block.clone());
259 node_vector.push(cascading_signal.clone());
260 }
261 node_vector
262}
263
264#[cfg(test)]
267mod tests {
268
269 use super::*;
270
271 #[test]
272 fn one_part() {
273 let input_power: f64 = -30.0;
274 let amplifier = super::Block {
275 name: "Simple Amplifier".to_string(),
276 gain: 10.0,
277 noise_figure: 3.0,
278 output_1db_compression_point: None,
279 };
280 let output_power = super::cascade(input_power, amplifier);
281
282 assert_eq!(output_power, -20.0);
283 }
284
285 #[test]
286 fn one_part_new() {
287 let input_power: f64 = -30.0;
288 let name = "Simple Amplifier".to_string();
289 let gain = 10.0;
290 let noise_figure = 3.0;
291 let amplifier = super::Block::new(name, gain, noise_figure, None);
292 let output_power = super::cascade(input_power, amplifier);
293
294 assert_eq!(output_power, -20.0);
295 }
296
297 #[test]
298 fn one_part_node() {
299 let input_power: f64 = -30.0;
300 let input_node = super::SignalNode {
301 name: "Input".to_string(),
302 power: input_power,
303 noise_temperature: 290.0,
304 cumulative_gain: 0.0, };
306 let amplifier = super::Block {
307 name: "Simple Amplifier".to_string(),
308 gain: 10.0,
309 noise_figure: 3.0,
310 output_1db_compression_point: None,
311 };
312 let output_node = super::cascade_node(input_node, amplifier);
313
314 assert_eq!(output_node.power, -20.0);
315 assert_eq!(output_node.name, "Simple Amplifier Output");
316 let output_noise_figure = rfconversions::noise::noise_figure_from_noise_temperature(
318 output_node.noise_temperature,
319 );
320 assert_eq!(output_noise_figure, 3.202456829285537);
321 }
322
323 #[test]
324 fn one_part_lna_node() {
325 let input_power: f64 = -30.0;
326 let input_node = super::SignalNode {
327 name: "Input".to_string(),
328 power: input_power,
329 noise_temperature: 290.0,
330 cumulative_gain: 0.0, };
332 let amplifier = super::Block {
333 name: "Low Noise Amplifier".to_string(),
334 gain: 30.0,
335 noise_figure: 3.0,
336 output_1db_compression_point: None,
337 };
338
339 let output_node = super::cascade_node(input_node, amplifier);
340
341 assert_eq!(output_node.power, 0.0);
342 assert_eq!(output_node.name, "Low Noise Amplifier Output");
343 let output_noise_figure = rfconversions::noise::noise_figure_from_noise_temperature(
345 output_node.noise_temperature,
346 );
347 assert_eq!(output_noise_figure, 3.0124584457866126);
348 }
349
350 #[test]
351 fn two_part_node() {
352 let input_power: f64 = -30.0;
353 let input_node = super::SignalNode {
354 name: "Input".to_string(),
355 power: input_power,
356 noise_temperature: 290.0,
357 cumulative_gain: 0.0, };
359 let amplifier = super::Block {
360 name: "Low Noise Amplifier".to_string(),
361 gain: 30.0,
362 noise_figure: 3.0,
363 output_1db_compression_point: None,
364 };
365 let attenuator = super::Block {
366 name: "Attenuator".to_string(),
367 gain: -6.0,
368 noise_figure: 6.0,
369 output_1db_compression_point: None,
370 };
371 let intermediate_node = super::cascade_node(input_node, amplifier);
372
373 assert_eq!(intermediate_node.cumulative_gain, 30.0);
374
375 let output_node = super::cascade_node(intermediate_node, attenuator);
376
377 assert_eq!(output_node.power, -6.0);
378 assert_eq!(output_node.cumulative_gain, 24.0);
379
380 assert_eq!(output_node.name, "Attenuator Output");
381 let output_noise_figure = rfconversions::noise::noise_figure_from_noise_temperature(
383 output_node.noise_temperature,
384 );
385 assert_eq!(output_noise_figure, 3.018922107070044);
386 }
387
388 #[test]
389 fn two_part_node_cascade_vector_return_output() {
390 let input_power: f64 = -30.0;
391 let input_node = super::SignalNode {
392 name: "Input".to_string(),
393 power: input_power,
394 noise_temperature: 290.0,
395 cumulative_gain: 0.0, };
397 let amplifier = super::Block {
398 name: "Low Noise Amplifier".to_string(),
399 gain: 30.0,
400 noise_figure: 3.0,
401 output_1db_compression_point: None,
402 };
403 let attenuator = super::Block {
404 name: "Attenuator".to_string(),
405 gain: -6.0,
406 noise_figure: 6.0,
407 output_1db_compression_point: None,
408 };
409 let blocks = vec![amplifier, attenuator];
410 let output_node = super::cascade_vector_return_output(input_node, blocks);
411
412 assert_eq!(output_node.power, -6.0);
413 assert_eq!(output_node.cumulative_gain, 24.0);
414
415 assert_eq!(output_node.name, "Attenuator Output");
416 let output_noise_figure = rfconversions::noise::noise_figure_from_noise_temperature(
418 output_node.noise_temperature,
419 );
420 assert_eq!(output_noise_figure, 3.018922107070044);
421 }
422
423 #[test]
424 fn two_part_node_cascade_vector_return_vector() {
425 let input_power: f64 = -30.0;
426 let input_node = super::SignalNode {
427 name: "Input".to_string(),
428 power: input_power,
429 noise_temperature: 290.0,
430 cumulative_gain: 0.0, };
432 let amplifier = super::Block {
433 name: "Low Noise Amplifier".to_string(),
434 gain: 30.0,
435 noise_figure: 3.0,
436 output_1db_compression_point: None,
437 };
438 let attenuator = super::Block {
439 name: "Attenuator".to_string(),
440 gain: -6.0,
441 noise_figure: 6.0,
442 output_1db_compression_point: None,
443 };
444 let blocks = vec![amplifier, attenuator];
445 let cascade_vector = super::cascade_vector_return_vector(input_node, blocks);
446
447 let output_node = cascade_vector.last().unwrap();
448 assert_eq!(output_node.power, -6.0);
449 assert_eq!(output_node.cumulative_gain, 24.0);
450
451 assert_eq!(output_node.name, "Attenuator Output");
452 let output_noise_figure = rfconversions::noise::noise_figure_from_noise_temperature(
454 output_node.noise_temperature,
455 );
456 assert_eq!(output_noise_figure, 3.018922107070044);
457 }
458
459 #[test]
460 fn two_part_node_cascade_vector_return_vector_with_compression() {
461 let input_power: f64 = -30.0;
462 let input_node = super::SignalNode {
463 name: "Input".to_string(),
464 power: input_power,
465 noise_temperature: 290.0,
466 cumulative_gain: 0.0, };
468 let low_noise_amplifier = super::Block {
469 name: "Low Noise Amplifier".to_string(),
470 gain: 30.0,
471 noise_figure: 3.0,
472 output_1db_compression_point: Some(5.0),
473 };
474 let attenuator = super::Block {
475 name: "Attenuator".to_string(),
476 gain: -6.0,
477 noise_figure: 6.0,
478 output_1db_compression_point: None,
479 };
480 let high_power_amplifier = super::Block {
481 name: "High Power Amplifier".to_string(),
482 gain: 30.0,
483 noise_figure: 3.0,
484 output_1db_compression_point: Some(20.0),
485 };
486 let blocks = vec![low_noise_amplifier, attenuator, high_power_amplifier];
487 let cascade_vector = super::cascade_vector_return_vector(input_node, blocks);
488
489 let output_node = cascade_vector.last().unwrap();
490 assert_eq!(output_node.power, 21.0);
491 assert_eq!(output_node.cumulative_gain, 51.0);
492
493 assert_eq!(output_node.name, "High Power Amplifier Output");
494 let output_noise_figure = rfconversions::noise::noise_figure_from_noise_temperature(
496 output_node.noise_temperature,
497 );
498 assert_eq!(output_noise_figure, 3.020645644372404);
499 }
500
501 use std::fs;
502 use std::path::Path;
503 use toml;
504
505 use crate::{cascade_vector_return_vector, SignalNode};
506
507 fn parse_test_config(content: &str) -> Result<Config, Box<dyn std::error::Error>> {
509 #[derive(Deserialize)]
510 struct IntermediateConfig {
511 input_power: f64,
512 frequency: f64,
513 blocks: Vec<BlockConfig>,
514 }
515 let intermediate_config: IntermediateConfig = toml::from_str(content)?;
516 let mut blocks = Vec::new();
517 let base_dir = Path::new(".");
519 load_blocks_recursive(
520 intermediate_config.blocks,
521 intermediate_config.frequency,
522 &mut blocks,
523 base_dir,
524 )?;
525 Ok(Config {
526 input_power: intermediate_config.input_power,
527 frequency: intermediate_config.frequency,
528 blocks,
529 })
530 }
531
532 #[test]
533 fn test_load_simple_config() {
534 let cwd = std::env::current_dir().unwrap();
535 let config_path = std::env::args()
536 .nth(1)
537 .unwrap_or_else(|| "files/simple_config.toml".to_string());
538 let full_path_to_config = cwd.join(config_path);
539 let config_content = fs::read_to_string(full_path_to_config.display().to_string()).unwrap();
540 let config = parse_test_config(&config_content).unwrap();
541 assert_eq!(config.input_power, -70.0);
542 assert_eq!(config.frequency, 6.0e9);
543 assert_eq!(config.blocks.len(), 3);
544 }
545
546 #[test]
547 fn test_load_include_config() {
548 let cwd = std::env::current_dir().unwrap();
549 let config_path = std::env::args()
550 .nth(1)
551 .unwrap_or_else(|| "files/include_directive/config.toml".to_string());
552 let full_path_to_config = cwd.join(config_path);
553 let config = load_config(&full_path_to_config.display().to_string()).unwrap();
555 assert_eq!(config.blocks.len(), 6);
556 }
557
558 #[test]
559 fn test_compression() {
560 let cwd = std::env::current_dir().unwrap();
561 let config_path = std::env::args()
562 .nth(1)
563 .unwrap_or_else(|| "files/compression/compression_test.toml".to_string());
564 let full_path_to_config = cwd.join(config_path);
565 let config = load_config(&full_path_to_config.display().to_string()).unwrap();
567 assert_eq!(config.blocks.len(), 3);
568
569 let input_node = SignalNode {
570 name: "Input".to_string(),
571 power: config.input_power,
572 noise_temperature: 290.0,
573 cumulative_gain: 0.0,
574 };
575 let cascade = cascade_vector_return_vector(input_node, config.blocks);
576
577 assert_eq!(cascade.last().unwrap().power, 21.0);
578 }
579
580 #[test]
581 fn test_touchstone_options() {
582 let cwd = std::env::current_dir().unwrap();
583 let config_path = std::env::args()
584 .nth(1)
585 .unwrap_or_else(|| "files/touchstone_options/config.toml".to_string());
586 let full_path_to_config = cwd.join(config_path);
587 let config = load_config(&full_path_to_config.display().to_string()).unwrap();
589 assert_eq!(config.blocks.len(), 3);
590 }
591}