Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install fontconfig
run: sudo apt-get install libfontconfig-dev
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libfontconfig1-dev
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ include = [
plotters = "0.3.3"
nalgebra = "0.33.2"

[[examples]]
[[example]]
name = "step_response"
path = "examples/step_response.rs"

[[examples]]
[[example]]
name = "controllability"
path = "examples/controllability.rs"
path = "examples/controllability.rs"

[[example]]
name = "end_to_end"
path = "examples/end_to_end.rs"

[[example]]
name = "analysis_report"
path = "examples/analysis_report.rs"
174 changes: 35 additions & 139 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,159 +2,55 @@

![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/rdesarz/control-sys-rs/rust.yml)

**control-sys** is a control system library written in Rust. It implements tools to represent and analyze LTI systems using state-space model.
**control-sys** is a Rust library to represent, analyze, simulate, and control LTI systems using state-space models.

## Examples
## Highlights

### Continuous state space model
- Checked model constructors with typed errors (`ModelError`)
- Continuous-to-discrete conversion with explicit methods:
- ZOH
- Time-domain simulation with full output equation `y = Cx + Du`
- Controllability and observability analysis
- Stability checks and rank diagnostics
- Controller helpers:
- SISO pole placement
- Discrete LQR
- Continuous LQR approximation
- Closed-loop assembly (`A-BK`)
Comment on lines +15 to +19
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove non-existent controller APIs from README highlights

The README now advertises controller helpers (pole placement/LQR/closed-loop assembly), but this commit’s public API only exposes analysis, model, simulator, and trajectory modules (src/lib.rs), so users relying on these bullets will look for APIs that are not present and hit compile-time failures when trying to adopt the library from documentation alone. Please align the highlights with what this revision actually ships.

Useful? React with 👍 / 👎.


A continuous state space model can be defined with `ContinuousStateSpaceModel`. As an example, a state-space model representing a DC motor can be defined this way. We are using `nalgebra` to represent the matrices:
## Example: End-to-End Workflow

```rust
use control_sys::{analysis, model, simulator};
use nalgebra as na;
use control_sys::model;

// DC motor parameters
let b = 0.1f64;
let j = 0.01f64;
let k = 0.01f64;
let l = 0.5f64;
let r = 1.0f64;

let mat_ac = na::dmatrix![
-b / j, k / j;
-k / l, -r / l;
];
let mat_bc = na::dmatrix![0.0;
1.0 / l];
let mat_cc = na::dmatrix![1.0, 0.0];

let cont_model = model::ContinuousStateSpaceModel::from_matrices(&mat_ac, &mat_bc, &mat_cc, &na::dmatrix![]);
```

### Continuous to discrete model conversion

A `DiscreteStateSpaceModel` can be built from a continuous one. You then need to specify the sampling step `ts`:

```rust
use nalgebra as na;
use control_sys::model;

// DC motor parameters
let b = 0.1f64;
let j = 0.01f64;
let k = 0.01f64;
let l = 0.5f64;
let r = 1.0f64;

let mat_ac = na::dmatrix![
-b / j, k / j;
-k / l, -r / l;
];
let mat_bc = na::dmatrix![0.0;
1.0 / l];
let mat_cc = na::dmatrix![1.0, 0.0];

let cont_model = model::ContinuousStateSpaceModel::from_matrices(&mat_ac, &mat_bc, &mat_cc, &na::dmatrix![]);

let discrete_model =
model::DiscreteStateSpaceModel::from_continuous_ss_forward_euler(&cont_model, 0.05);
```
let a = na::dmatrix![0.0, 1.0; -2.0, -3.0];
let b = na::dmatrix![0.0; 1.0];
let c = na::dmatrix![1.0, 0.0];
let d = na::dmatrix![0.0];

### Step response
let cont = model::ContinuousStateSpaceModel::try_from_matrices(&a, &b, &c, &d).unwrap();
let disc = model::DiscreteStateSpaceModel::from_continuous_zoh(&cont, 0.05).unwrap();

You can compute the step response of a system. For a discrete system, the simulation steps are given by the sampling step of the discretization:
let (y, u, _x) = simulator::step_for_discrete_ss(&disc, 5.0).unwrap();
assert_eq!(y.nrows(), 1);
assert_eq!(u.nrows(), 1);

```rust
use nalgebra as na;
use control_sys::{model, simulator};

// DC motor parameters
let b = 0.1f64;
let j = 0.01f64;
let k = 0.01f64;
let l = 0.5f64;
let r = 1.0f64;

let mat_ac = na::dmatrix![
-b / j, k / j;
-k / l, -r / l;
];
let mat_bc = na::dmatrix![0.0;
1.0 / l];
let mat_cc = na::dmatrix![1.0, 0.0];

let cont_model = model::ContinuousStateSpaceModel::from_matrices(&mat_ac, &mat_bc, &mat_cc, &na::dmatrix![]);

let discrete_model =
model::DiscreteStateSpaceModel::from_continuous_ss_forward_euler(&cont_model, 0.05);

// where model implements the traits `StateSpaceModel` and `Discrete`
let duration = 10.0; // [s]
let (response, input, states) = simulator::step_for_discrete_ss(
&discrete_model,
duration,
);
let (is_ctrb, _ctrb_mat) = analysis::is_ss_controllable(&disc);
assert!(is_ctrb);
```

You can also compute the step response for a continuous model. You will need to provide the sampling step and the model will be discretized before computing the step response:
## Discretization Methods and Assumptions

```rust
use nalgebra as na;
use control_sys::{model, simulator};

// DC motor parameters
let b = 0.1f64;
let j = 0.01f64;
let k = 0.01f64;
let l = 0.5f64;
let r = 1.0f64;

let mat_ac = na::dmatrix![
-b / j, k / j;
-k / l, -r / l;
];
let mat_bc = na::dmatrix![0.0;
1.0 / l];
let mat_cc = na::dmatrix![1.0, 0.0];

let cont_model = model::ContinuousStateSpaceModel::from_matrices(&mat_ac, &mat_bc, &mat_cc, &na::dmatrix![]);

// where model is a continuous state space model
let sampling_dt = 0.05; // [s]
let duration = 10.0; // [s]
let (response, input, states) = simulator::step_for_continuous_ss(
&cont_model,
sampling_dt,
duration,
);
```

### Controllability
`DiscreteStateSpaceModel` has explicit conversion methods:

The controllability of a system can be evaluated using the `is_ss_controllable` method:
- `from_continuous_zoh`: exact ZOH (recommended default)

```rust
use nalgebra as na;
use control_sys::{model, analysis};

let ss_model = model::DiscreteStateSpaceModel::from_matrices(
&nalgebra::dmatrix![1.0, -2.0;
2.0, 1.0],
&nalgebra::dmatrix![1.0;
2.0],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![],
0.05,
);

let (is_controllable, controllability_matrix) = analysis::is_ss_controllable(&ss_model);

if is_controllable
{
println!("The system is controllable");
println!("Its controllability matrix is: {}", controllability_matrix);
}
```
Use ZOH by default for sampled-data systems with held inputs.

## Running checks

```bash
cargo test
cargo clippy --all-targets --all-features -- -D warnings
```
34 changes: 34 additions & 0 deletions examples/analysis_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// LTI analysis-first example:
// Build a model and run a single consolidated analysis pass
// (poles, stability, controllability, observability).
use control_sys::{analysis, model};
use nalgebra as na;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let system = model::DiscreteStateSpaceModel::try_from_matrices(
&na::dmatrix![0.9, 0.1; 0.0, 0.8],
&na::dmatrix![1.0; 0.5],
&na::dmatrix![1.0, 0.0],
&na::dmatrix![0.0],
0.1,
)?;

let report = analysis::analyze_lti(&system, analysis::TimeDomain::Discrete, 1e-6)?;

println!("stable: {}", report.is_stable);
println!("spectral radius: {}", report.spectral_radius);
println!(
"controllable: {} (rank {}/{})",
report.controllability.is_full_rank,
report.controllability.rank,
report.controllability.expected_rank
);
println!(
"observable: {} (rank {}/{})",
report.observability.is_full_rank,
report.observability.rank,
report.observability.expected_rank
);

Ok(())
}
14 changes: 7 additions & 7 deletions examples/controllability.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// Controllability example:
// Build a discrete state-space model and compute its controllability matrix/rank.
use control_sys::analysis;
use control_sys::model;

extern crate nalgebra as na;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let ss_model = model::DiscreteStateSpaceModel::from_matrices(
&nalgebra::dmatrix![1.0, -2.0;
let ss_model = model::DiscreteStateSpaceModel::try_from_matrices(
&nalgebra::dmatrix![1.0, -2.0;
2.0, 1.0],
&nalgebra::dmatrix![1.0;
2.0],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![1.0, 0.0],
&nalgebra::dmatrix![0.0],
0.05,
);
)?;

let (is_controllable, controllability_matrix) = analysis::is_ss_controllable(&ss_model);

Expand Down
25 changes: 25 additions & 0 deletions examples/end_to_end.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// End-to-end workflow:
// 1) Build a continuous model.
// 2) Discretize with ZOH.
// 3) Run a step simulation.
// 4) Check controllability.
use control_sys::{analysis, model, simulator};
use nalgebra as na;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let a = na::dmatrix![0.0, 1.0; -2.0, -3.0];
let b = na::dmatrix![0.0; 1.0];
let c = na::dmatrix![1.0, 0.0];
let d = na::dmatrix![0.0];

let continuous = model::ContinuousStateSpaceModel::try_from_matrices(&a, &b, &c, &d)?;
let discrete = model::DiscreteStateSpaceModel::from_continuous_zoh(&continuous, 0.05)?;

let (y, _u, _x) = simulator::step_for_discrete_ss(&discrete, 5.0)?;
let (is_controllable, _ctrb) = analysis::is_ss_controllable(&discrete);

println!("step samples: {}", y.ncols());
println!("controllable: {}", is_controllable);

Ok(())
}
Loading