Rustの統計分布の関数をHTML/JSから呼ぶ

そんなわけで(?)これまでRust関係で以下のようなことをしてまいったわけです。

  • RustプログラムをWebAssembly経由でHTML/JSから呼ぶ
    • これはtest03あたりでやっておりました。
  • Rustの統計分布の関数について確認する
    • これはtest04test05test06 でやってみまして、ライブラリとして"probability"がよろしそうだと考えております。

今回はこれらを踏まえていよいよ、
HTML/JSから、Rustの統計分布の関数を呼ぶ
ということを試みます。

当方、環境はWindows 10。

この記事の自分的プロジェクト名はtest07。

ソースなどはこちら (Github)

何をしたか

具体的には、Rustで実装されているBeta分布の分布関数の逆関数 (InvCDF) を呼ぶソースコードを、wasmファイルにコンパイルしておいて、HTMLファイル内のJavaScriptからこのwasmの関数を読み込み、実際にJS上で実行する、てなものをつくりました。

書き方について、詳しくは、ソースを見ていただければ。。

できたもの

こちらにあります。
https://kojiynet.github.io/site/rust/test07/test07.html

このサイトを開くと、以下のような画面が出ますから、

初期画面

Beta分布のパラメータであるAlphaとBeta、それから引数となるpを入力して、"Calculate"ボタンを押せば、結果が表示されます。

計算結果の例

どうだっ!!(自己満足)

いずれ、応用してまいりますぜ。

RustのstatrsのBeta分布の分布関数の逆関数の精度

前回出てきた、Rustのstatrsの精度はどうなのか問題について、ちょっと詳しく書こう。

どんな話だったかというと、Beta分布の分布関数の逆関数 (Inverse of CDF; InvCDF) の精度がよろしくない気がする、というもの。

当方、環境はWindows 10。

この記事の自分的プロジェクト名はtest06。

ソースなどはこちら (Github)

今回は、ブログでは計算結果だけを示す。。

何をしたか

今回やったのは、Beta分布の分布関数の逆関数を、次の4つのライブラリで算出する、というもの。

  • Rustの"statrs"
  • Rustの"probability"
  • C++のBoost
  • R(の標準ライブラリ)

分布のパラメータとして alpha と beta が必要だが、これらにはそれぞれ { 0.5, 1, 2} から1つを与えていって、9パターンを試した。逆関数に与える確率pとしては { 0.01, 0.02, ... , 0.98, 0.99} を用いた。

結果

どの算出方法を基準にしようか。。

憶測で、C++/Boostの結果がいい精度を出しているのではないかと考えて、これを基準にしてみた。C++/Boostが「真値」を返しているとして、他の3つの算出方法の結果との差分を示したのが、次の図。

Beta分布のInvCDFの、C++/Boostとの差分

実はこのグラフ上でRustの"statrs"以外は完全に重なって見えている。つまり返す値がほとんど同じ。

なので重なってしまう算出方法の1つであるC++/Boostを基準としてよかろうて。あとは、"statrs"がどれぐらいズレているかが焦点だ。

(というか上図の真ん中のパネルは、見るからに規則的な模様になっていて、statrsで算出アルゴリズムによる系統的な誤差が生じていそうな感じがアリアリだが。。)

statrsの相対誤差

ではstatrsのInvCDFの相対誤差(のlog10)を示す(C++/Boostの返す値を「真値」としている)。それが次の図。

statrsのBeta分布のInvCDFの相対誤差

この図から、statrsの返す値には、4桁ぐらいの精度しか期待できなくて、例外的な状況だとそれも期待できない、という感じかなと。。

厳密な計算をするならstatrsはちょっとやめた方がよさそう。今後のライブラリの進化に期待。

でも用途次第か

まあでも、例えばBetaのInvCDFを簡単に図示するだけなら、精度は4桁もなくてもいいのかも。

上記の4つの算出方法による、Beta分布のInvCDFを、確率pに対してプロットしたのが、下図。

Beta分布のInvCDFをライブラリ別に示す

これを見る限り、statrsも含めて全部重なってる。

こんな図をつくる用途ならstatrsで十分そう。

現場からは以上です。

Rustの統計分布の関数を使う~逆関数の精度は要チェケラ~

またまた、Rustの統計分布の関数を使ってみる、続編。

去年12月5日のtest04で、Beta分布の分布関数 (CDF) を、"statrs"に加えて"probability"ライブラリを用いて算出してみた。いずれもいい感じの数値を出していた。

(ていうか越年してたんかい。。)

今回は、Beta分布の分布関数の逆関数 (Inverse of CDF; InvCDF) を使ってみる。

環境はWindows 10。

自分的プロジェクト名はtest05。

ソースなどはこちら (Github)

Rustソース

Rustのソースファイルで、Beta分布の分布関数の逆関数 (InvCDF) を出し、さらに得られた値を分布関数に入れて、InvCDFとCDF(InvCDF)の絶対誤差も出す、そんな関数を書く。

この関数を、statrs版とprobability版に分けて書く。この関数の部分を示すと以下のとおり。beta_statrs_crate()はstatrs版、beta_prob_crate()はprobability版。

main.rs

fn beta_statrs_crate( alpha: f64, beta: f64, p: f64) -> ( f64, f64, f64) 
{

    use statrs::distribution::Beta;
    use statrs::distribution::ContinuousCDF;

    let dist = Beta::new( alpha, beta).unwrap();
    let z_hat = dist.inverse_cdf( p);
    let p_hat = dist.cdf( z_hat);
    let abserr = ( p - p_hat).abs();

    return ( z_hat, p_hat, abserr);

}

fn beta_prob_crate( alpha: f64, beta: f64, p: f64) -> ( f64, f64, f64)
{

    use probability::distribution::Beta;
    use probability::distribution::Inverse;
    use probability::distribution::Distribution;

    let dist = Beta::new( alpha, beta, 0.0, 1.0);
    let z_hat = dist.inverse( p);
    let p_hat = dist.distribution( z_hat);
    let abserr = ( p - p_hat).abs();

    return ( z_hat, p_hat, abserr);

}

Cargo.tomlファイル

Cargo.tomlの[dependencies]の部分はtest04と同様に次のような感じにする。

Cargo.toml

[dependencies]
statrs = "0.16"
probability = "0.20.1"

結果

実行結果として出力されたものの一部は次のような感じ。

Test for InvCDF() of Beta(alpha,beta)
 Assume p = CDF(z)
 First, set p, and then calculate z_hat using InvCDF()
 After that, calculate p_hat = CDF(z_hat) for validation

Test two crates; "1" means "statrs" crate, "2" means "probability" crate

alpha   beta    p   z_hat_1 p_hat_1 abserr_1    z_hat_2 p_hat_2 abserr_2
0.5 0.5 0.01    0.000274658203125   0.010551064856976648    0.000551064856976648    0.00024671981713422173  0.010000000000000004    0.000000000000000003469446951953614
0.5 0.5 0.02    0.001007080078125   0.020206218608167428    0.0002062186081674272   0.0009866357858642186   0.019999999999999997    0.000000000000000003469446951953614
0.5 0.5 0.03    0.002227783203125   0.030059238187395775    0.00005923818739577563  0.002219017698459991    0.029999999999999985    0.000000000000000013877787807814457
0.5 0.5 0.04    0.003936767578125   0.03997011295735626 0.00002988704264374198  0.003942649342761082    0.03999999999999999 0.000000000000000013877787807814457
0.5 0.5 0.05    0.006134033203125   0.04991121962081565 0.00008878037918435416  0.006155829702431135    0.04999999999999998 0.000000000000000020816681711721685

表示を簡略化して整形した表で示すとこんな感じ。

alpha beta p z_hat_1 p_hat_1 abserr_1 z_hat_2 p_hat_2 abserr_2
0.5 0.5 0.01 0.000274658 0.010551065 0.000551065 0.00024672 0.01 3.46945E-18
0.5 0.5 0.02 0.00100708 0.020206219 0.000206219 0.000986636 0.02 3.46945E-18
0.5 0.5 0.03 0.002227783 0.030059238 5.92382E-05 0.002219018 0.03 1.38778E-17
0.5 0.5 0.04 0.003936768 0.039970113 2.9887E-05 0.003942649 0.04 1.38778E-17
0.5 0.5 0.05 0.006134033 0.04991122 8.87804E-05 0.00615583 0.05 2.08167E-17

ほう。。

いいんだ、"abserr_2"はとても小さい。つまり、"probability"版のInvCDFはCDFの逆関数としていい精度を出している。

他方で、"abserr_1"はまあまあ大きい。。つまり"statrs"版のInvCDFはCDFの逆関数としてちょっと誤差がある。。

確かにalpha < 1とかbeta < 1の範囲では分布が変則的な形状になるので、逆関数を数値的に出すなら困難がありそうだけど、alpha >= 1 and beta >= 1の範囲でも"statrs"版ではズレが大きいように見えたぜ。。

次回、もうちょっと詳しく調べようか。。

Rustの統計分布の関数を使う~statrs版とprobability版の競演~

Rustの統計分布の関数を使ってみる、続編。

すでに11月6日のtest02で、正規分布の累積密度の逆関数を使ってみていた。このときはライブラリとして"statrs"を用いた。

今回は、Beta分布の分布関数 (CDF) を、"statrs"に加えて"probability"ライブラリを用いて算出してみる。以下はそれぞれのライブラリのオフィシャルっぽいページ。

statrs
https://docs.rs/statrs/latest/statrs/
https://crates.io/crates/statrs

probabililty
https://docs.rs/probability/latest/probability/
https://crates.io/crates/probability

ではやってみる。

環境はWindows 10。

自分的プロジェクト名はtest04。

ソースなどはこちら (Github)

Rustソース

Rustのソースファイル中の、CDFを出すところは以下のような感じ。beta_cdf_statrs_crate()はstatrs版、beta_cdf_prob_crate()はprobability版。

main.rs

fn beta_cdf_statrs_crate( alpha: f64, beta: f64, z: f64) -> f64 
{

    use statrs::distribution::Beta;
    use statrs::distribution::ContinuousCDF;

    let dist = Beta::new( alpha, beta).unwrap();
    let p_hat = dist.cdf( z);

    return p_hat;

}

fn beta_cdf_prob_crate( alpha: f64, beta: f64, z: f64) -> f64
{

    use probability::distribution::Beta;
    use probability::distribution::Distribution;

    let dist = Beta::new( alpha, beta, 0.0, 1.0);
    let p_hat = dist.distribution( z);

    return p_hat;

}

Cargo.tomlファイル

Cargo.tomlの[dependencies]の部分は次のような感じにする。

Cargo.toml

[dependencies]
statrs = "0.16"
probability = "0.20.1"

結果

実行結果として出力されたものの一部は次のような感じ。

Test for CDF() of Beta(alpha,beta)
 p_hat = CDF(z) for various z values

Test two crates; "1" means "statrs" crate, "2" means "probability" crate

alpha   beta    z   p_hat_1 p_hat_2
0.5 0.5 0.01    0.06376856085851985 0.06376856085851985
0.5 0.5 0.02    0.09033447060173311 0.09033447060173312
0.5 0.5 0.03    0.11082468660445927 0.1108246866044594
0.5 0.5 0.04    0.1281884336979499  0.1281884336979499
0.5 0.5 0.05    0.1435662931287062  0.1435662931287063

整形した表で示すとこんな感じ。

alpha beta z p_hat_1 p_hat_2
0.5 0.5 0.01 0.06376856085851985 0.06376856085851985
0.5 0.5 0.02 0.09033447060173311 0.09033447060173312
0.5 0.5 0.03 0.11082468660445927 0.1108246866044594
0.5 0.5 0.04 0.1281884336979499 0.1281884336979499
0.5 0.5 0.05 0.1435662931287062 0.1435662931287063

要するに、"statrs"と"probability"の結果はほとんど同じ。差は最大で2.9976E-15でした。

めでたしめでたし。。

とはいかなかったのです!!

その真相は次回にでも。。(焦らし)

RustでWebAssembly: Cargo編

RustでWebAssemblyを動かすのだが(これは11月5日のtest01と同様)、このときCargoを使ってみるテスト。

環境はWindows 10。

自分的プロジェクト名はtest03。

ソースなどはこちら (Github)

Cargoでプロジェクトの作成

ターミナルで以下を実行。

cargo new --lib test03

wasmのプロジェクトはライブラリ扱いになるよう。

Rustソース

Rustのソースファイルは以下のような感じ。test01と同様のadd()関数を定義する。

lib.rs

#![no_main]    // this file does not contain a main function

#[no_mangle]   // we do not want to mangle the symbol when exporting
pub extern fn add(a: i32, b: i32) -> i32 { a + b }

Cargo.tomlファイル

Cargo.tomlにビルドに必要な情報を書く。[lib]の部分を追加することになる。

Cargo.toml

[package]
name = "test03"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]

Cargoでビルド

ターミナル上で、プロジェクトのディレクトリ内で以下を実行すればデバッグビルドがなされる。

cargo build --target=wasm32-unknown-unknown

これで、以下のwasmファイルが生成される。
./target/wasm32-unknown-unknown/debug/test03.wasm

リリースビルドは

cargo build --target=wasm32-unknown-unknown --release

で可能。できるwasmファイルは次のようなもの。
./target/wasm32-unknown-unknown/release/test03.wasm

HTML/JS側

HTMLファイルとして以下のようなものを書いた。

どっちみち自分の用途ではawaitが必要なので、asyncをJS全体にかけるようにした。wasm関数の取得時のみならず、関数実行時点もasync内にないと非同期になってしまうようなので。(このあたりよくわからない。。)

test03.html

<html>
<body>

<div>add( 10, 20) = </div>
<div id = "msg">(now calculating...)</div>

<script>
(async function(){ // もう、script部分は丸ごとasyncありきで。。。

  // 画面要素のIDの取得
  let msg_id = document.getElementById( "msg");

  // WASM関数の取得
  let wasm = await WebAssembly.instantiateStreaming( fetch( "test03.wasm"), {});
  let wasm_add = wasm.instance.exports.add;

  msg_id.innerText = wasm_add( 10, 20);

})();
</script>

</body>
</html>

以下のサイトを参考にさせていただいた。
福野泰介の一日一創 RustではじめるWebAssemblyはじめのいっぽ、足し算するwasmファイルは116byte

閲覧

test01と同様、HTMLファイルをHTTPサーバーに置いてretrieveする必要があるが、VSCodeの「Go Live」でもどうぞ。

やったぜ。

Rustの統計分布の関数(statrs版)を使う~Cargoの利用法を添えて~

Rustで、統計分布の関数を使ってみるテスト。この際、ライブラリとしてはstatrsを使う。Cargoを使ってビルドすることになる。

環境はWindows 10。

自分的プロジェクト名はtest02。

ソースなどはこちら (Github)

Cargoでプロジェクトの作成

ターミナルで以下を実行。

cargo new test02

これで、プロジェクトのディレクトリとして./test02がつくられる。その中のsrcディレクトリにソースファイルを置く。

Rustソース

Rustのソースファイルは以下のような感じ。

main.rs

use statrs::distribution::Normal;
use statrs::distribution::ContinuousCDF;

fn main()
{

    println!("Calculating InverseCDF(0.975) for N(0,1)");

    let n = Normal::new(0.0, 1.0).unwrap();
    let z = n.inverse_cdf( 0.975);

    println!("z = {z}");

}

Cargo.tomlファイル

Cargo.tomlにビルドに必要な情報を書く。デフォルトでは次のような内容だった。

Cargo.toml (default)

[package]
name = "test02"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

この[dependencies]以下に、必要なライブラリ(クレート?)に関する記述を追加する。今回はstatrsを使うので以下のように書く。

Cargo.toml

[package]
name = "test02"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
statrs = "0.16"

Cargoでビルド

ターミナル上で、プロジェクトのディレクトリ内で以下を実行すればデバッグビルドがなされる。

cargo build

できた実行ファイルは./target/debug/にある。

リリースビルドは

cargo build --release

で可能。できた実行ファイルは./target/release/にある。

実行結果

今回の実行結果は以下のようになった。

Calculating InverseCDF(0.975) for N(0,1)
z = 1.9599639845400538

めでたし、めでたし。

RustでWebAssembly

Rustで書いた関数をWebAssemblyのファイル(wasm)にして、それをJavaScriptから呼び出す、というテスト。

自分的プロジェクト名はtest01。

ソースなどはこちら (Github)

参考にさせていただいたサイト https://caddi.tech/archives/879

Rust側

Rustで関数を書いた。add()という加算する関数を。

test01.rs

#![no_main]    // this file does not contain a main function

#[no_mangle]   // we do not want to mangle the symbol when exporting
pub extern fn add(a: i32, b: i32) -> i32 { a + b }

このRustソースをwasmにコンパイルする。以下のバッチファイルを使った。

compile.bat

rustc --target wasm32-unknown-unknown ./test01.rs 

これでできたwasmファイルをHTML (JS)で読む。

JavaScript (HTML) 側

JSでwasmファイルから読んで実行するコードは以下。wasm内のadd()をadd_funcとして取り出している。

index.htmlの一部

  WebAssembly.instantiateStreaming(
    fetch( "test01.wasm"), {})
      .then( wasm => {
        let add_func = wasm.instance.exports.add;
        result = add_func(10, 20);
      }
  );

ただし、JSでwasmファイルから読むときと実行するとき、非同期になるので厄介。test01では結果が出るまでsleepするように書いた。

(test03で、もうすこしすっきり書けたのだが、それはまた別のお話。)

閲覧

index.htmlをローカルファイルとして開くだけではwasmは読み込まれない。HTTPサーバーに置く必要がある。

ただし、VSCodeでフォルダを開き、右下の「Go Live」をクリックすれば、ホスティングしてくれるらしく、ブラウザでwasmを使ったJS入りのHTMLが正常に表示される。やったね。