Encounter with Rust

Cutting to the chase — after 10 days of experimenting, I’ve decided that the Rust language is not for me.
Link β†’ ‘Cut to the chase’
My First Encounter with Rust (the language)
Today, March 16, I started learning Rust.

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.


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.

Screenshot


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!");
        }
    }
}
March 19
I spent most of the day doing variations on my first program.

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]]
March 26
Unfortunately, I am no longer experimenting with the Rust language.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *