Link β ‘Cut to the chase’
Last week, I did a quick review of the pros and cons and a few of the features. Why Rust? Why now?
Iβve been wanting to build Windows executable programs.
My programming involves a lot of mathematics and graphics. To be able to share Python programs or Jupyter notebooks with fellow engineers requires that the recipient be versed in deploying and maintaining the core language elements as well as the many library imports.
In the newer Python versions, there is much emphasis on explicit typing. If Iβm going to use typed variables, I would rather switch to a language where typing was in the original specification.
Then thereβs Armin Ronacher. Recently, there was a tweet that got me thinking β Rust might have what Iβm looking for.
https://twitter.com/mitsuhiko/status/1633508706267668493?s=46&t=_UmdY2kxa1aJ0ItXEjvwPwIβm
Ronacher’s Thoughts and Writings
This essay from 2015 is illuminating.
Thanks to PyCharm and VS Studio and one productive afternoon, I was able to build β with 25 lines of code β a simple executable program.
However, the simple program is impressive:
- it runs from the command line
- it inverts a matrix
- it verifies the inversion
- it produces an interactive Plotly HTML plot file and saves it to disk
- plot.show() does not open the browser; this is a known issue
- I transferred the EXE to another computer (via Dropbox). The program performed perfectly on the other computer.
Click for LIVE Plotly chart Here’s the output.
Here’s the code.
use plotly::{ImageFormat, Plot, Scatter};
use nalgebra::Matrix3;
fn plotthis() {
let mut plot = Plot::new();
let trace = Scatter::new(vec![0, 1, 2, 3, 4, 5, 6], vec![0, 1, 4, 9, 16, 25, 36]);
plot.add_trace(trace);
// plot.show();
plot.write_html("out.html");
}
fn main() {
plotthis();
let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
println!("m1 = {}", m1);
match m1.try_inverse() {
Some(inv) => {
println!("The inverse of m1 is: {}", inv);
let verification = &m1 * &inv;
println!("Verification: m1 * inverse of m1 is: {}", verification);
}
None => {
println!("m1 is not invertible!");
}
}
}
Variations to matrix math
Tried the simplest of changes β input a 4 x 4 matrix instead of a 3 x 3 matrix. Who would imagine that I would have to redefine the type of matrix/array to make this work? Turns out, it’s a simple redefinition though: just say Matrix4
instead of Matrix3
.
3 x 3
use nalgebra::Matrix3;
..
let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
4 x 4
// use nalgebra::Matrix3;
use nalgebra::Matrix4;
..
let mm1 = Matrix4::new(4., 3., 2., 1.,
3., 4., 2., 1.,
2., 2., 4., 1.,
1., 1., 1., 4.);
After a couple hours of online browsing and reading, I gather there are several linear algebra libraries (crates) for rust, that nalgebra is considered a bit complex, but that it is a popular choice for matrix math. I decide to proceed with it.
I also investigated the rulinalg crate. After a bit of experimenting, I decided that the nalgebra crate is best for my purposes.
In the new code shown below, I experimented with several ideas:
- using rulinalg
- using dynamic matrix input with nalgebra
let test = DMatrix::from_row_slice(3, 4, &[00.,01.,02.,03., 10.,11.,12.,13., 20.,21.,22.,23.]);
- using the cute crate to simulate list comprehension
use cute::c; .. println!("print one row of a matrix"); let victor = c![e, for e in row.as_slice()]; println!(" using cute to display a matrix {:?}\n", victor);
- In the Plotly section, adding a polygon path
layout.add_shape( Shape::new() .shape_type(ShapeType::Path) .path("M 2 2 L 4 20 L 5 2 L 2 2 Z") .fill_color(NamedColor::LightPink) .line(ShapeLine::new().color(NamedColor::LightSeaGreen).width(3.).dash(DashType::DashDot), ), );
Using Plotly in Python, I have been pleased with the feature set, impressed with the extent to which a plot can be customized. In Rust, by comparison, there is little in the way of available customization. Unfortunately, Rust Plotly is a small subset of Plotly proper.
Here is a link to a recent review of plotting options within Rust.
https://morioh.com/p/3fcb92074ceb
Here’s my March 19 code.
extern crate ndarray;
extern crate rulinalg;
use plotly::{
color::NamedColor,
common::{DashType, Fill, Font, Mode},
layout::{
Axis, GridPattern, Layout, LayoutGrid, Margin, Shape, ShapeLayer, ShapeLine, ShapeType,
},
Bar, Plot, Scatter,
};
// use nalgebra::Matrix3;
use nalgebra::{Matrix, Matrix4, DMatrix};
use rulinalg::matrix::{Matrix as rulinalg_Matrix, BaseMatrix};
use rulinalg::matrix as rulinalg_matrix;
use cute::c;
extern crate version_check as rustc;
use version_check::Version;
fn vers() {
let temp = match Version::read() {
Some(d) => format!("Version is: {}", d),
None => format!("Failed to read the version.")
};
println!("\n{}\n", temp);
}
fn plotthis() {
vers();
let mut plot = Plot::new();
let trace = Scatter::new(vec![0, 1, 2, 3, 4, 5, 6], vec![0, 1, 4, 9, 16, 25, 36]);
let trace2 = Scatter::new(vec![ 1, 2, 3, 4, 5], vec![21., 32., 1.4, -0.6, 11.]);
plot.add_trace(trace);
plot.add_trace(trace2);
let mut layout = Layout::new()
.x_axis(Axis::new().range(vec![0.0, 6.]))
.y_axis(Axis::new().range(vec![-10, 40]));
layout.add_shape(
Shape::new()
.shape_type(ShapeType::Line)
.x0(1)
.y0(0)
.x1(1)
.y1(2)
.line(ShapeLine::new().color(NamedColor::RoyalBlue).width(3.)),
);
layout.add_shape(
Shape::new()
.shape_type(ShapeType::Path)
.path("M 2 2 L 4 20 L 5 2 L 2 2 Z")
.fill_color(NamedColor::LightPink)
.line(ShapeLine::new().color(NamedColor::LightSeaGreen).width(3.).dash(DashType::DashDot),
),
);
layout.add_shape(
Shape::new()
.shape_type(ShapeType::Line)
.x0(4)
.y0(0)
.x1(6)
.y1(2)
.line(
ShapeLine::new()
.color(NamedColor::MediumPurple)
.width(3.)
.dash(DashType::Dot),
),
);
plot.set_layout(layout);
plot.show();
plot.write_html("out.html");
}
fn main() {
plotthis();
let mat = rulinalg_matrix![0, 1, 2, 4;
5, 6, 7, 8;
9, 10, 11, 12;
13, 14, 15, 16];
println!("print a matrix {:?}\n", mat);
let row = mat.row(1);
println!("print one row of a matrix");
let victor = c![e, for e in row.as_slice()];
println!(" using cute to display a matrix {:?}\n", victor);
println!("print one row of a matrix");
for e in row.as_slice() {
println!(" using for loop to display a matrix {:?}", e);
}
println!();
let test = DMatrix::from_row_slice(3, 4, &[00.,01.,02.,03.,
10.,11.,12.,13.,
20.,21.,22.,23.]);
println!("test = {}\n", test);
println!("test[2,1] = {}\n", test[(2,1)]);
// let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
let mm1 = Matrix4::new(4., 3., 2., 1.,
3., 4., 2., 1.,
2., 2., 4., 1.,
1., 1., 1., 4.);
println!("mm1 = {}\n", mm1);
match mm1.try_inverse() {
Some(inv) => {
println!("The inverse of mm1 is: {}\n", inv);
let verification = &mm1 * &inv;
println!("Verification: mm1 * inverse of mm1 is: {}\n", verification);
println!("inv = {:?}\n", inv);
println!("inv: {:#?}, \n", inv);
println!("verification = {:?}\n", &inv * mm1);
}
None => {
println!("m1 is not invertible!");
}
}
}
Here’s my March 19 output.
Version is: 1.68.0
print a matrix Matrix { rows: 4, cols: 4, data: [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] }
print one row of a matrix
using cute to display a matrix [5, 6, 7, 8]
print one row of a matrix
using for loop to display a matrix 5
using for loop to display a matrix 6
using for loop to display a matrix 7
using for loop to display a matrix 8
test =
β β
β 0 1 2 3 β
β 10 11 12 13 β
β 20 21 22 23 β
β β
test[2,1] = 21
mm1 =
β β
β 4 3 2 1 β
β 3 4 2 1 β
β 2 2 4 1 β
β 1 1 1 4 β
β β
The inverse of mm1 is:
β β
β 0.6027397260273972 -0.3972602739726027 -0.0958904109589041 -0.0273972602739726 β
β -0.3972602739726027 0.6027397260273972 -0.0958904109589041 -0.0273972602739726 β
β -0.0958904109589041 -0.0958904109589041 0.3561643835616438 -0.0410958904109589 β
β -0.0273972602739726 -0.0273972602739726 -0.0410958904109589 0.273972602739726 β
β β
Verification: mm1 * inverse of mm1 is:
β
β
β 1 0 -0.00000000000000005551115123125783
0 β
β 0 1 -0.00000000000000005551115123125783
0 β
β 0 0 1
0 β
β 0 0 0
1 β
β
β
inv = [[0.6027397260273972, -0.3972602739726027, -0.0958904109589041, -0.0273972602739726], [-0.3972602739726027, 0.6027397260273972, -0.0958904109589041, -0.0273972602739726], [-0.0958904109589041, -0.0958904109589041, 0.3561643835616438, -0.0410958904109589], [-0.0273972602739726, -0.0273972602739726, -0.0410958904109589, 0.273972602739726]]
inv: [
[
0.6027397260273972,
-0.3972602739726027,
-0.0958904109589041,
-0.0273972602739726,
],
[
-0.3972602739726027,
0.6027397260273972,
-0.0958904109589041,
-0.0273972602739726,
],
[
-0.0958904109589041,
-0.0958904109589041,
0.3561643835616438,
-0.0410958904109589,
],
[
-0.0273972602739726,
-0.0273972602739726,
-0.0410958904109589,
0.273972602739726,
],
],
verification = [[1.0, 0.0, -5.551115123125783e-17, 0.0], [0.0, 1.0, -5.551115123125783e-17, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]]
Lets say I’m disappointed in what I’ve found.
My Pros:
- It compiles
- It seems quite capable of engineering mathematics
- The language suits me
My Cons:
- Plotting/charting/graphics capabilities are disappointing
- The choices of plotting crates seem limited
- In particular, Plotly is a meager subset of Plotly for Python
- I’m pretty well sold on Plotly, I like the dynamic capabilities
- Plotpy looks promising for static plots but I wasn’t able to get that working; I gave it a solid first, second, and third effort. I conceded when I couldn’t get past compile/link errors (see below). Apparently, the compilation of openblas is a multistep process in the Windows OS —https://crates.io/crates/openblas-src
First Big Error
Running 'cargo build --bin=plotpy_klh --package=plotpy_klh --message-format=json'...
Compiling openblas-src v0.10.8
error: failed to run custom build command for 'openblas-src v0.10.8'
and then I got past that one. Next …
Second Big Error
Running 'cargo build --bin=plotpy_klh --package=plotpy_klh --message-format=json'...
Compiling ndarray v0.14.0
error: linking with 'link.exe' failed: exit code: 1104
|
...
= note: LINK : fatal error LNK1104: cannot open file 'c:\Users\khoit\plotpy_klh\target\debug\build\ndarray-e9548657cb3b1cf7\build_script_build-e9548657cb3b1cf7.exe'
error: aborting due to previous error
… conceded.