diff --git a/app-proto/Cargo.toml b/app-proto/Cargo.toml index e623b26..3814c38 100644 --- a/app-proto/Cargo.toml +++ b/app-proto/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [features] default = ["console_error_panic_hook"] +irisawa = [] [dependencies] itertools = "0.13.0" @@ -36,7 +37,12 @@ features = [ 'WebGlVertexArrayObject' ] +# the self-dependency specifies features to use for tests and examples +# +# https://github.com/rust-lang/cargo/issues/2911#issuecomment-1483256987 +# [dev-dependencies] +dyna3 = { path = ".", default-features = false, features = ["irisawa"] } wasm-bindgen-test = "0.3.34" [profile.release] diff --git a/app-proto/examples/irisawa-hexlet.rs b/app-proto/examples/irisawa-hexlet.rs new file mode 100644 index 0000000..fc14f91 --- /dev/null +++ b/app-proto/examples/irisawa-hexlet.rs @@ -0,0 +1,25 @@ +use dyna3::engine::{Q, irisawa::realize_irisawa_hexlet}; + +fn main() { + const SCALED_TOL: f64 = 1.0e-12; + let (config, success, history) = realize_irisawa_hexlet(SCALED_TOL); + print!("\nCompleted Gram matrix:{}", config.tr_mul(&*Q) * &config); + if success { + println!("Target accuracy achieved!"); + } else { + println!("Failed to reach target accuracy"); + } + println!("Steps: {}", history.scaled_loss.len() - 1); + println!("Loss: {}", history.scaled_loss.last().unwrap()); + if success { + println!("\nChain diameters:"); + println!(" {} sun (given)", 1.0 / config[(3, 3)]); + for k in 4..9 { + println!(" {} sun", 1.0 / config[(3, k)]); + } + } + println!("\nStep │ Loss\n─────┼────────────────────────────────"); + for (step, scaled_loss) in history.scaled_loss.into_iter().enumerate() { + println!("{:<4} │ {}", step, scaled_loss); + } +} \ No newline at end of file diff --git a/app-proto/run-examples b/app-proto/run-examples index 4d35882..9791a9d 100755 --- a/app-proto/run-examples +++ b/app-proto/run-examples @@ -4,5 +4,6 @@ # http://xion.io/post/code/rust-examples.html # +cargo run --example irisawa-hexlet cargo run --example three-spheres cargo run --example point-on-sphere \ No newline at end of file diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index 35bd5b0..d41a6dd 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -279,66 +279,21 @@ pub fn realize_gram( // --- tests --- -#[cfg(test)] -mod tests { +// this problem is from a sangaku by Irisawa Shintarō Hiroatsu. the article +// below includes a nice translation of the problem statement, which was +// recorded in Uchida Itsumi's book _Kokon sankan_ (_Mathematics, Past and +// Present_) +// +// "Japan's 'Wasan' Mathematical Tradition", by Abe Haruki +// https://www.nippon.com/en/japan-topics/c12801/ +// +#[cfg(feature = "irisawa")] +pub mod irisawa { use std::{array, f64::consts::PI}; use super::*; - #[test] - fn sub_proj_test() { - let target = PartialMatrix(vec![ - MatrixEntry { index: (0, 0), value: 19.0 }, - MatrixEntry { index: (0, 2), value: 39.0 }, - MatrixEntry { index: (1, 1), value: 59.0 }, - MatrixEntry { index: (1, 2), value: 69.0 } - ]); - let attempt = DMatrix::::from_row_slice(2, 3, &[ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0 - ]); - let expected_result = DMatrix::::from_row_slice(2, 3, &[ - 18.0, 0.0, 36.0, - 0.0, 54.0, 63.0 - ]); - assert_eq!(target.sub_proj(&attempt), expected_result); - } - - #[test] - fn zero_loss_test() { - let gram = PartialMatrix({ - let mut entries = Vec::::new(); - for j in 0..3 { - for k in 0..3 { - entries.push(MatrixEntry { - index: (j, k), - value: if j == k { 1.0 } else { -1.0 } - }); - } - } - entries - }); - let config = { - let a: f64 = 0.75_f64.sqrt(); - DMatrix::from_columns(&[ - sphere(1.0, 0.0, 0.0, a), - sphere(-0.5, a, 0.0, a), - sphere(-0.5, -a, 0.0, a) - ]) - }; - let state = SearchState::from_config(&gram, config); - assert!(state.loss.abs() < f64::EPSILON); - } - - // this problem is from a sangaku by Irisawa Shintarō Hiroatsu. the article - // below includes a nice translation of the problem statement, which was - // recorded in Uchida Itsumi's book _Kokon sankan_ (_Mathematics, Past and - // Present_) - // - // "Japan's 'Wasan' Mathematical Tradition", by Abe Haruki - // https://www.nippon.com/en/japan-topics/c12801/ - // - fn realize_irisawa_hexlet(scaled_tol: f64) -> (DMatrix, bool, DescentHistory) { + pub fn realize_irisawa_hexlet(scaled_tol: f64) -> (DMatrix, bool, DescentHistory) { let gram = { let mut gram_to_be = PartialMatrix::new(); for s in 0..9 { @@ -391,12 +346,62 @@ mod tests { scaled_tol, 0.5, 0.9, 1.1, 200, 110 ) } +} + +#[cfg(test)] +mod tests { + use super::{*, irisawa::realize_irisawa_hexlet}; + + #[test] + fn sub_proj_test() { + let target = PartialMatrix(vec![ + MatrixEntry { index: (0, 0), value: 19.0 }, + MatrixEntry { index: (0, 2), value: 39.0 }, + MatrixEntry { index: (1, 1), value: 59.0 }, + MatrixEntry { index: (1, 2), value: 69.0 } + ]); + let attempt = DMatrix::::from_row_slice(2, 3, &[ + 1.0, 2.0, 3.0, + 4.0, 5.0, 6.0 + ]); + let expected_result = DMatrix::::from_row_slice(2, 3, &[ + 18.0, 0.0, 36.0, + 0.0, 54.0, 63.0 + ]); + assert_eq!(target.sub_proj(&attempt), expected_result); + } + + #[test] + fn zero_loss_test() { + let gram = PartialMatrix({ + let mut entries = Vec::::new(); + for j in 0..3 { + for k in 0..3 { + entries.push(MatrixEntry { + index: (j, k), + value: if j == k { 1.0 } else { -1.0 } + }); + } + } + entries + }); + let config = { + let a: f64 = 0.75_f64.sqrt(); + DMatrix::from_columns(&[ + sphere(1.0, 0.0, 0.0, a), + sphere(-0.5, a, 0.0, a), + sphere(-0.5, -a, 0.0, a) + ]) + }; + let state = SearchState::from_config(&gram, config); + assert!(state.loss.abs() < f64::EPSILON); + } #[test] fn irisawa_hexlet_test() { // solve Irisawa's problem const SCALED_TOL: f64 = 1.0e-12; - let (config, success, history) = realize_irisawa_hexlet(SCALED_TOL); + let (config, _, _) = realize_irisawa_hexlet(SCALED_TOL); // check against Irisawa's solution let entry_tol = SCALED_TOL.sqrt(); @@ -404,27 +409,6 @@ mod tests { for (k, diam) in solution_diams.into_iter().enumerate() { assert!((config[(3, k)] - 1.0 / diam).abs() < entry_tol); } - - // print info - print!("\nCompleted Gram matrix:{}", config.tr_mul(&*Q) * &config); - if success { - println!("Target accuracy achieved!"); - } else { - println!("Failed to reach target accuracy"); - } - println!("Steps: {}", history.scaled_loss.len() - 1); - println!("Loss: {}", history.scaled_loss.last().unwrap()); - if success { - println!("\nChain diameters:"); - println!(" {} sun (given)", 1.0 / config[(3, 3)]); - for k in 4..9 { - println!(" {} sun", 1.0 / config[(3, k)]); - } - } - println!("\nStep │ Loss\n─────┼────────────────────────────────"); - for (step, scaled_loss) in history.scaled_loss.into_iter().enumerate() { - println!("{:<4} │ {}", step, scaled_loss); - } } // at the frozen indices, the optimization steps should have exact zeros,