Skip to content

Commit a6d0b3c

Browse files
authored
Merge pull request #25 from TorBorve/feature/restructure-files
Feature/restructure files
2 parents a386779 + e3c3c33 commit a6d0b3c

20 files changed

Lines changed: 1071 additions & 981 deletions

.github/workflows/ci.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ jobs:
1313
uses: actions/checkout@v4
1414
with:
1515
submodules: true
16-
16+
- name: Setup cmake
17+
uses: jwlawson/actions-setup-cmake@v2
18+
with:
19+
cmake-version: '3.28.x'
1720
- name: Setup | Toolchain
1821
uses: dtolnay/rust-toolchain@nightly
1922
with:
@@ -36,6 +39,11 @@ jobs:
3639
with:
3740
submodules: true
3841

42+
- name: Setup cmake
43+
uses: jwlawson/actions-setup-cmake@v2
44+
with:
45+
cmake-version: '3.28.x'
46+
3947
- name: Setup | Rust
4048
uses: dtolnay/rust-toolchain@nightly
4149

@@ -53,6 +61,13 @@ jobs:
5361
with:
5462
submodules: true
5563

64+
- name: Setup cmake
65+
uses: jwlawson/actions-setup-cmake@v2
66+
with:
67+
cmake-version: '3.28.x'
68+
69+
- name: Use cmake
70+
run: cmake --version
5671
- name: Setup | Rust
5772
uses: dtolnay/rust-toolchain@nightly
5873

@@ -69,6 +84,11 @@ jobs:
6984
uses: actions/checkout@v4
7085
with:
7186
submodules: true
87+
88+
- name: Setup cmake
89+
uses: jwlawson/actions-setup-cmake@v2
90+
with:
91+
cmake-version: '3.28.x'
7292

7393
- run: |
7494
echo "CC=$(brew --prefix gcc@13)/bin/gcc-13" >> "${GITHUB_ENV}"

.github/workflows/coverage.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ jobs:
1212
with:
1313
submodules: true
1414

15+
- name: Setup cmake
16+
uses: jwlawson/actions-setup-cmake@v2
17+
with:
18+
cmake-version: '3.28.x'
19+
1520
- name: Setup | Toolchain
1621
uses: dtolnay/rust-toolchain@nightly
1722

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "control_systems_torbox"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
edition = "2024"
55
authors = ["Tor Børve Rasmussen <tor.jobb.skule@gmail.com>"]
66
description = "Control systems toolbox"

TODO.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
- [X] Transfer functions
44
- [X] State Space
55
- [ ] General Linear Time Invariant systems. (delays in feedback)
6-
- [ ] Connecting and feedback of LTI systems.
7-
- [ ] poles and zeros of LTI systems
6+
- [X] Connecting and feedback of LTI systems.
7+
- [X] poles and zeros of LTI systems
88
- [X] Bodeplot
99
- [X] Nyquistplot
1010
- [ ] pole zero plot
@@ -23,6 +23,7 @@
2323
- [x] Home button in nyquist and bodeplot
2424
- [x] Tf^(i) implement
2525
- [x] Can SLICOT be used to implement some routines and transforms?
26+
- [ ] Make better system/code for calling SLICOT and so on.
2627

2728

2829
## Resources

examples/plotting.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
use control_systems_torbox as cst;
22

3-
use cst::{
4-
BodePlot, BodePlotData, BodePlotOptions, Continuous, NyquistPlot,
5-
NyquistPlotData, NyquistPlotOptions, Plot, RGBAColor, Tf, bode, nyquist,
6-
};
3+
use cst::{plot::*, systems::Tf, utils::traits::Continuous};
74

85
fn main() {
96
let sys: Tf<f64, Continuous> = Tf::new(&[1.0], &[0.0, 1.0]);
@@ -15,14 +12,14 @@ fn main() {
1512

1613
let nyq_opts = NyquistPlotOptions::default().set_y_limits([-2., 2.]);
1714
let mut nyq_plot = NyquistPlot::new(nyq_opts);
18-
let nyq_data = nyquist(sys, 0.01, 100.);
15+
let nyq_data = sys.nyquist(0.01, 100.);
1916
nyq_plot.add_system(nyq_data.into());
2017

21-
let data = NyquistPlotData::new(nyquist(sys_p1, 0.01, 100.))
18+
let data = NyquistPlotData::new(sys_p1.nyquist(0.01, 100.))
2219
.set_name("Pade 1")
2320
.set_color(RGBAColor::RED);
2421
nyq_plot.add_system(data);
25-
let data = NyquistPlotData::new(nyquist(sys_p2, 0.01, 100.))
22+
let data = NyquistPlotData::new(sys_p2.nyquist(0.01, 100.))
2623
.set_name("Pade 2")
2724
.set_color(RGBAColor::GREEN);
2825
nyq_plot.add_system(data);
@@ -35,7 +32,7 @@ fn main() {
3532
let mut bodeplot = BodePlot::new(bodeopts);
3633

3734
let sys: Tf<f64, Continuous> = Tf::new(&[0.0, 1.0], &[1.0, 1.0]);
38-
let mag_phase_freq_vec = bode(sys, 0.1, 100.);
35+
let mag_phase_freq_vec = sys.bode(0.1, 100.);
3936

4037
bodeplot.add_system(
4138
BodePlotData::from(mag_phase_freq_vec.clone()).set_name("System 1"),
@@ -47,7 +44,7 @@ fn main() {
4744
bodeplot.add_system(data);
4845

4946
let sys2: Tf<f64, Continuous> = Tf::new(&[1.0], &[1.0, 1.0]);
50-
let mag_phase_freq_vec = bode(sys2, 0.1, 100.);
47+
let mag_phase_freq_vec = sys2.bode(0.1, 100.);
5148

5249
bodeplot.add_system(mag_phase_freq_vec.into());
5350

src/analysis/frequency_response.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use num_complex::{Complex64, c64};
2+
3+
use crate::{
4+
systems::Tf,
5+
utils::traits::{Mag2Db, Rad2Deg, Time},
6+
};
7+
/// Generates a linearly spaced iterator between `start` and `end`, inclusive.
8+
///
9+
/// # Arguments
10+
/// - `start`: The starting value of the sequence.
11+
/// - `end`: The ending value of the sequence.
12+
/// - `n`: The number of points to generate (must be greater than 1).
13+
///
14+
/// # Returns
15+
/// - An iterator producing `n` evenly spaced `f64` values from `start` to
16+
/// `end`.
17+
///
18+
/// # Panics
19+
/// - Panics if `n` is less than or equal to 1.
20+
pub fn lin_space(
21+
start: f64,
22+
end: f64,
23+
n: usize,
24+
) -> impl ExactSizeIterator<Item = f64> {
25+
assert!(n >= 1, "n must be greater than or equal to one");
26+
let step = (end - start) / (n as f64 - 1.0);
27+
(0..n).map(move |i| start + step * i as f64)
28+
}
29+
30+
/// Generates a logarithmically spaced iterator between `start` and `end`, using
31+
/// the specified logarithmic base.
32+
///
33+
/// # Arguments
34+
/// - `start`: The starting value of the sequence (must be greater than 0).
35+
/// - `end`: The ending value of the sequence (must be greater than 0).
36+
/// - `n`: The number of points to generate.
37+
/// - `base`: The logarithmic base to use for spacing.
38+
///
39+
/// # Returns
40+
/// - An iterator producing `n` logarithmically spaced `f64` values from `start`
41+
/// to `end`.
42+
///
43+
/// # Panics
44+
/// - Panics if `start` or `end` is less than or equal to 0.
45+
pub fn log_space(
46+
start: f64,
47+
end: f64,
48+
n: usize,
49+
base: usize,
50+
) -> impl ExactSizeIterator<Item = f64> {
51+
assert!(
52+
start > 0.,
53+
"logarithm of negative numbers are not implemented"
54+
);
55+
assert!(
56+
end > 0.,
57+
"logarithm of negative numbers are not implemented"
58+
);
59+
assert!(base > 1, "log_base() must be well defined");
60+
let start_log = start.log(base as f64);
61+
let end_log = end.log(base as f64);
62+
63+
let nums = lin_space(start_log, end_log, n);
64+
65+
nums.map(move |x| (base as f64).powf(x))
66+
}
67+
68+
impl<U: Time> Tf<f64, U> {
69+
/// Computes the Bode plot (magnitude and phase) for a transfer function
70+
/// over a frequency range.
71+
///
72+
/// # Arguments
73+
/// - `sys`: The transfer function of the system to evaluate.
74+
/// - `min_freq`: The minimum frequency for the plot.
75+
/// - `max_freq`: The maximum frequency for the plot.
76+
///
77+
/// # Returns
78+
/// - A vector of `[magnitude (dB), phase (degrees), frequency]` tuples for
79+
/// each evaluated frequency.
80+
pub fn bode(&self, min_freq: f64, max_freq: f64) -> Vec<[f64; 3]> {
81+
let freqs = log_space(min_freq, max_freq, 1000, 10);
82+
self.bode_freqs(freqs)
83+
}
84+
85+
/// Computes the Bode plot (magnitude and phase) for a transfer function
86+
/// over a given set of frequencies.
87+
///
88+
/// # Arguments
89+
/// - `sys`: The transfer function of the system to evaluate.
90+
/// - `freqs`: An iterator of frequencies to evaluate the system at.
91+
///
92+
/// # Returns
93+
/// - A vector of `[magnitude (dB), phase (degrees), frequency]` tuples for
94+
/// each evaluated frequency.
95+
pub fn bode_freqs(
96+
&self,
97+
freqs: impl Iterator<Item = f64>,
98+
) -> Vec<[f64; 3]> {
99+
let mut mag_phase_freq_vec = Vec::with_capacity(freqs.size_hint().0);
100+
101+
for omega in freqs {
102+
let c = c64(0., omega);
103+
let sys_val = self.eval(&c);
104+
mag_phase_freq_vec.push([
105+
sys_val.norm().mag2db(),
106+
sys_val.arg().rad2deg(),
107+
omega,
108+
]);
109+
}
110+
mag_phase_freq_vec
111+
}
112+
113+
/// Computes the Nyquist plot for a transfer function over a frequency
114+
/// range.
115+
///
116+
/// # Arguments
117+
/// - `sys`: The transfer function of the system to evaluate.
118+
/// - `min_freq`: The minimum frequency for the plot.
119+
/// - `max_freq`: The maximum frequency for the plot.
120+
///
121+
/// # Returns
122+
/// - A vector of complex numbers representing the Nyquist plot.
123+
pub fn nyquist(&self, min_freq: f64, max_freq: f64) -> Vec<Complex64> {
124+
let freqs = log_space(min_freq, max_freq, 1000, 10);
125+
self.nyquist_freqs(freqs)
126+
}
127+
128+
/// Computes the Nyquist plot for a transfer function over a given set of
129+
/// frequencies.
130+
///
131+
/// # Arguments
132+
/// - `sys`: The transfer function of the system to evaluate.
133+
/// - `freqs`: An iterator of frequencies to evaluate the system at.
134+
///
135+
/// # Returns
136+
/// - A vector of complex numbers representing the Nyquist plot.
137+
pub fn nyquist_freqs(
138+
&self,
139+
freqs: impl Iterator<Item = f64>,
140+
) -> Vec<Complex64> {
141+
let mut pos_vals = Vec::with_capacity(freqs.size_hint().0);
142+
let mut neg_vals = Vec::with_capacity(freqs.size_hint().0);
143+
144+
for freq in freqs {
145+
pos_vals.push(self.eval(&c64(0., freq)));
146+
neg_vals.push(self.eval(&c64(0., -freq)));
147+
}
148+
149+
pos_vals.extend(neg_vals.iter().rev());
150+
pos_vals
151+
}
152+
}

src/analysis/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod frequency_response;
2+
pub mod system_properties;

0 commit comments

Comments
 (0)