概要
pythonを使用している方ならSymPy
などを扱っている人が多いと思いますが、symbolica
を使用するとpython,Rustともに扱えるので、このライブラリについて簡単にまとめた。
ライセンス
symbolica
には、未ライセンスの状態でも扱えますが制限があります。
制限としては以下の様な項目がありました。
- デバイスごとに1インスタンス1コア
その為、いまどきのPCを扱っている方だとリソースが有り余っているはずです。
(そこまで使いこなせるかようわからんけど)
詳細は参考資料のSymbolica.io
のPricing
を参照してください。
今回の記事では、私的にはHobbyist
に属するはずなのでこれからのやり方でライセンスを取得します。
環境
rustup showの結果を記載。
Default host: x86_64-unknown-linux-gnu
rustup home: /usr/local/lib/rust/rustup
installed toolchains
nightly-x86_64-unknown-linux-gnu (active, default)
active toolchain
name: nightly-x86_64-unknown-linux-gnu
active because: it's the default toolchain
installed targets:
x86_64-unknown-linux-gnu
環境構築
今回の環境では以下のパッケージをaptで入れています。
libxx系のパッケージは不要かもしれないです。
symbolicaでビルド時に使用する際はm4, gitっぽい?
別のパッケージで使用しているのでインストールしています。
apt install -y
curl \
build-essential \
libx11-dev \
libxcursor-dev \
libxcb1-dev \
libxi-dev \
libxkbcommon-dev \
libxkbcommon-x11-dev \
libxrandr2 \
m4 \
git \
プロジェクト作成
数式処理を行う為のプロジェクトを作成。
cargo new <project name>
ライセンスキーの取得
一度しか使用しないのでcargo run
したらコードを削除します。
use symbolica::LicenseManager;
fn main() {
LicenseManager::request_hobbyist_license("Name", "Email").unwrap();
}
Email
に登録したアドレスへライセンスキーが来るのでコピーをします。
ライセンスキーを環境変数に登録します。(登録方法は良しなに)
export SYMBOLICA_LICENSE=xxxxxxxxxx-yyyyy-zzzzz-wwww-0000000
内容
基本的に次のsymbolicaクレートのモジュールをインポートします。
use symbolica::{atom::{AtomCore, Atom}, parse, symbol};
AtomCore
には、式を変更せずに維持する拡張やパターンマッチングなどの式のすべてのコア機能が搭載されています。(参考)
parse
マクロでは、文字列からAtomを解析します。
リテラルコードから解析するにはparse_lit
マクロを使用し、誤りのある解析にはtry_parse
を使用します。(参考)
symbol
マクロでは、シンボルを作成するか、同じ名前の既存のシンボルを取得します。
名前空間が指定されていない場合は、現在の名前空間にシンボルが作成されます。(参考)
その為、基本Atom
構造体を作成して
そのメソッドを利用する形になります。
以下が使用例。
導関数・微分(derivative)
変数 x
に関して微分を行う例
let exp: Atom = parse!("(1+x)^3");
let a: Atom = exp.derivative(symbol!("x"));
println!("{}\n {}", exp, a);
結果:
(x+1)^3
3*(x+1)^2
式の展開(expand)
let exp: Atom = parse!("(1+x)^3");
let b: Atom = input.expand();
println!("{}\n {}", exp, b);
結果:
(x+1)^3
3*x+3*x^2+x^3+1
評価結果
数式の評価結果を得る方法
let expr = parse!("cos(x)*3 + f1(x, 2) + f2(x)");
変数x
を作成し、対応する値として 1.25 を定数として登録する。
let x = parse!("x");
let mut const_map = HashMap::default();
const_map.insert(x.clone(), 1.25);
関数f1
とf2
のシンボルを定義し、関数を格納するマップを初期化する。
use std::collections::HashMap;
...
let f1 = symbol!("f1");
let f2 = symbol!("f2");
let mut function_map = HashMap::default();
各関数の処理をシンボルごとに登録
function_map.insert(
f1,
EvaluationFn::new(Box::new(|args: &[f64], _, _, _| {
args[0] + 2.0 * args[1]
})),
);
function_map.insert(
f2,
EvaluationFn::new(Box::new(|args: &[f64], var_map, fn_map, cache| {
println!("var map = {:?}", var_map);
println!("cache = {:?}", cache);
let f1_func = fn_map.get(&symbol!("f1")).unwrap().get();
args[0] + 3.0 + f1_func(&[args[0], 3.], var_map, fn_map, cache)
})),
);
これまでの定数や関数情報を評価時に渡す
evaluteの第一引数の処理は何を行えばよいかがまだ分からないが、公式のサンプル通りにやったら動いたので一旦よし。
let result: f64 = expr
.evaluate(|r| {
r.to_f64()
} ,
&const_map,
&function_map)
.unwrap();
グラフ
つかれたので省略