diff --git a/README.md b/README.md index 3a29eb0..cf3e589 100644 --- a/README.md +++ b/README.md @@ -25,32 +25,37 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter ### Install the prerequisites 1. Install [`rustup`](https://rust-lang.github.io/rustup/): the officially recommended Rust toolchain manager - * It's available on Ubuntu as a [Snap](https://snapcraft.io/rustup) + - It's available on Ubuntu as a [Snap](https://snapcraft.io/rustup) 2. Call `rustup default stable` to "download the latest stable release of Rust and set it as your default toolchain" - * If you forget, the `rustup` [help system](https://github.com/rust-lang/rustup/blob/d9b3601c3feb2e88cf3f8ca4f7ab4fdad71441fd/src/errors.rs#L109-L112) will remind you + - If you forget, the `rustup` [help system](https://github.com/rust-lang/rustup/blob/d9b3601c3feb2e88cf3f8ca4f7ab4fdad71441fd/src/errors.rs#L109-L112) will remind you 3. Call `rustup target add wasm32-unknown-unknown` to add the [most generic 32-bit WebAssembly target](https://doc.rust-lang.org/nightly/rustc/platform-support/wasm32-unknown-unknown.html) 4. Call `cargo install wasm-pack` to install the [WebAssembly toolchain](https://rustwasm.github.io/docs/wasm-pack/) 5. Call `cargo install trunk` to install the [Trunk](https://trunkrs.dev/) web-build tool 6. Add the `.cargo/bin` folder in your home directory to your executable search path - * This lets you call Trunk, and other tools installed by Cargo, without specifying their paths - * On POSIX systems, the search path is stored in the `PATH` environment variable + - This lets you call Trunk, and other tools installed by Cargo, without specifying their paths + - On POSIX systems, the search path is stored in the `PATH` environment variable ### Play with the prototype 1. From the `app-proto` folder, call `trunk serve --release` to build and serve the prototype - * *The crates the prototype depends on will be downloaded and served automatically* - * *For a faster build, at the expense of a much slower prototype, you can call `trunk serve` without the `--release` flag* - * *If you want to stay in the top-level folder, you can call `trunk serve --config app-proto [--release]`* from there instead. + - The crates the prototype depends on will be downloaded and served automatically + - For a faster build, at the expense of a much slower prototype, you can call `trunk serve` without the `--release` flag + - If you want to stay in the top-level folder, you can call `trunk serve --config app-proto [--release]` from there instead. 3. In a web browser, visit one of the URLs listed under the message `INFO 📡 server listening at:` - * *Touching any file in the `app-proto` folder will make Trunk rebuild and live-reload the prototype* + - Touching any file in the `app-proto` folder will make Trunk rebuild and live-reload the prototype 4. Press *ctrl+C* in the shell where Trunk is running to stop serving the prototype ### Run the engine on some example problems -1. Go into the `app-proto` folder -2. Call `./run-examples` - * *For each example problem, the engine will print the value of the loss function at each optimization step* - * *The first example that prints is the same as the Irisawa hexlet example from the Julia version of the engine prototype. If you go into `engine-proto/gram-test`, launch Julia, and then* +1. Use `sh` to run the script `tools/run-examples.sh` + - The script is location-independent, so you can do this from anywhere in the dyna3 repository + - The call from the top level of the repository is: + + ```bash + sh tools/run-examples.sh + ``` + - For each example problem, the engine will print the value of the loss function at each optimization step + - The first example that prints is the same as the Irisawa hexlet example from the Julia version of the engine prototype. If you go into `engine-proto/gram-test`, launch Julia, and then ```julia include("irisawa-hexlet.jl") @@ -59,9 +64,24 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter end ``` - *you should see that it prints basically the same loss history until the last few steps, when the lower default precision of the Rust engine really starts to show* + you should see that it prints basically the same loss history until the last few steps, when the lower default precision of the Rust engine really starts to show ### Run the automated tests 1. Go into the `app-proto` folder 2. Call `cargo test` + +### Deploy the prototype + +1. From the `app-proto` folder, call `trunk build --release` + - Building in [release mode](https://doc.rust-lang.org/cargo/reference/profiles.html#release) produces an executable which is smaller and often much faster, but harder to debug and more time-consuming to build + - If you want to stay in the top-level folder, you can call `trunk build --config app-proto --release` from there instead +2. Use `sh` to run the packaging script `tools/package-for-deployment.sh`. + - The script is location-independent, so you can do this from anywhere in the dyna3 repository + - The call from the top level of the repository is: + ```bash + sh tools/package-for-deployment.sh + ``` + - This will overwrite or replace the files in `deploy/dyna3` +3. Put the contents of `deploy/dyna3` in the folder on your server that the prototype will be served from. + - To simplify uploading, you might want to combine these files into an archive called `deploy/dyna3.zip`. Git has been set to ignore this path \ No newline at end of file diff --git a/app-proto/Trunk.toml b/app-proto/Trunk.toml new file mode 100644 index 0000000..017deba --- /dev/null +++ b/app-proto/Trunk.toml @@ -0,0 +1,2 @@ +[build] +public_url = "./" \ No newline at end of file diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 43066fd..94e7b3c 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -175,8 +175,8 @@ impl Sphere { label: String, color: ElementColor, representation: DVector, - ) -> Sphere { - Sphere { + ) -> Self { + Self { id, label, color, @@ -194,8 +194,8 @@ impl Element for Sphere { "sphere".to_string() } - fn default(id: String, id_num: u64) -> Sphere { - Sphere::new( + fn default(id: String, id_num: u64) -> Self { + Self::new( id, format!("Sphere {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], @@ -275,8 +275,8 @@ impl Point { label: String, color: ElementColor, representation: DVector, - ) -> Point { - Point { + ) -> Self { + Self { id, label, color, @@ -294,8 +294,8 @@ impl Element for Point { "point".to_string() } - fn default(id: String, id_num: u64) -> Point { - Point::new( + fn default(id: String, id_num: u64) -> Self { + Self::new( id, format!("Point {id_num}"), [0.75_f32, 0.75_f32, 0.75_f32], @@ -348,7 +348,7 @@ impl ProblemPoser for Point { format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str() ); problem.gram.push_sym(index, index, 0.0); - problem.frozen.push(Point::WEIGHT_COMPONENT, index, 0.5); + problem.frozen.push(Self::WEIGHT_COMPONENT, index, 0.5); problem.guess.set_column(index, &self.representation.get_clone_untracked()); } } @@ -393,7 +393,7 @@ pub struct InversiveDistanceRegulator { } impl InversiveDistanceRegulator { - pub fn new(subjects: [Rc; 2]) -> InversiveDistanceRegulator { + pub fn new(subjects: [Rc; 2]) -> Self { let representations = subjects.each_ref().map(|subj| subj.representation()); let measurement = create_memo(move || { representations[0].with(|rep_0| @@ -406,7 +406,7 @@ impl InversiveDistanceRegulator { let set_point = create_signal(SpecifiedValue::from_empty_spec()); let serial = Self::next_serial(); - InversiveDistanceRegulator { subjects, measurement, set_point, serial } + Self { subjects, measurement, set_point, serial } } } @@ -453,7 +453,7 @@ pub struct HalfCurvatureRegulator { } impl HalfCurvatureRegulator { - pub fn new(subject: Rc) -> HalfCurvatureRegulator { + pub fn new(subject: Rc) -> Self { let measurement = subject.representation().map( |rep| rep[Sphere::CURVATURE_COMPONENT] ); @@ -461,7 +461,7 @@ impl HalfCurvatureRegulator { let set_point = create_signal(SpecifiedValue::from_empty_spec()); let serial = Self::next_serial(); - HalfCurvatureRegulator { subject, measurement, set_point, serial } + Self { subject, measurement, set_point, serial } } } diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index b274dca..e265982 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/diagnostics.rs @@ -15,8 +15,8 @@ struct DiagnosticsState { } impl DiagnosticsState { - fn new(initial_tab: String) -> DiagnosticsState { - DiagnosticsState { active_tab: create_signal(initial_tab) } + fn new(initial_tab: String) -> Self { + Self { active_tab: create_signal(initial_tab) } } } diff --git a/app-proto/src/components/display.rs b/app-proto/src/components/display.rs index a0cdba6..da921dd 100644 --- a/app-proto/src/components/display.rs +++ b/app-proto/src/components/display.rs @@ -41,8 +41,8 @@ struct SceneSpheres { } impl SceneSpheres { - fn new() -> SceneSpheres { - SceneSpheres { + fn new() -> Self { + Self { representations: Vec::new(), colors_with_opacity: Vec::new(), highlights: Vec::new(), @@ -71,8 +71,8 @@ struct ScenePoints { } impl ScenePoints { - fn new() -> ScenePoints { - ScenePoints { + fn new() -> Self { + Self { representations: Vec::new(), colors_with_opacity: Vec::new(), highlights: Vec::new(), @@ -97,8 +97,8 @@ pub struct Scene { } impl Scene { - fn new() -> Scene { - Scene { + fn new() -> Self { + Self { spheres: SceneSpheres::new(), points: ScenePoints::new(), } diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 5ed94ad..0d387d3 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -26,7 +26,7 @@ use crate::{ // done more work on saving and loading assemblies, we should come back to this // code to see if it can be simplified -fn load_gen_assemb(assembly: &Assembly) { +fn load_general(assembly: &Assembly) { let _ = assembly.try_insert_element( Sphere::new( String::from("gemini_a"), @@ -77,7 +77,7 @@ fn load_gen_assemb(assembly: &Assembly) { ); } -fn load_low_curv_assemb(assembly: &Assembly) { +fn load_low_curvature(assembly: &Assembly) { // create the spheres let a = 0.75_f64.sqrt(); let _ = assembly.try_insert_element( @@ -196,7 +196,7 @@ fn load_low_curv_assemb(assembly: &Assembly) { } } -fn load_pointed_assemb(assembly: &Assembly) { +fn load_pointed(assembly: &Assembly) { let _ = assembly.try_insert_element( Point::new( format!("point_front"), @@ -246,7 +246,7 @@ fn load_pointed_assemb(assembly: &Assembly) { // B-C " // C-C " // A-C -0.25 * φ^2 = -0.6545084971874737 -fn load_tridim_icosahedron_assemb(assembly: &Assembly) { +fn load_tridiminished_icosahedron(assembly: &Assembly) { // create the vertices const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32]; const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; @@ -409,7 +409,7 @@ fn load_tridim_icosahedron_assemb(assembly: &Assembly) { // to finish describing the dodecahedral circle packing, set the inversive // distance regulators to -1. some of the regulators have already been set -fn load_dodeca_packing_assemb(assembly: &Assembly) { +fn load_dodecahedral_packing(assembly: &Assembly) { // add the substrate let _ = assembly.try_insert_element( Sphere::new( @@ -550,7 +550,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) { // the initial configuration of this test assembly deliberately violates the // constraints, so loading the assembly will trigger a non-trivial realization -fn load_balanced_assemb(assembly: &Assembly) { +fn load_balanced(assembly: &Assembly) { // create the spheres const R_OUTER: f64 = 10.0; const R_INNER: f64 = 4.0; @@ -611,7 +611,7 @@ fn load_balanced_assemb(assembly: &Assembly) { // the initial configuration of this test assembly deliberately violates the // constraints, so loading the assembly will trigger a non-trivial realization -fn load_off_center_assemb(assembly: &Assembly) { +fn load_off_center(assembly: &Assembly) { // create a point almost at the origin and a sphere centered on the origin let _ = assembly.try_insert_element( Point::new( @@ -648,7 +648,7 @@ fn load_off_center_assemb(assembly: &Assembly) { // sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an // inversive distance of -1 between the insphere and each face, and then set an // inversive distance of 0 between the circumsphere and each vertex -fn load_radius_ratio_assemb(assembly: &Assembly) { +fn load_radius_ratio(assembly: &Assembly) { let index_range = 1..=4; // create the spheres @@ -789,7 +789,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) { // conditions are exactly representable as floats, unlike the analogous numbers // in the scaled-up problem. the inexact representations might break the // symmetry that's getting the engine stuck -fn load_irisawa_hexlet_assemb(assembly: &Assembly) { +fn load_irisawa_hexlet(assembly: &Assembly) { let index_range = 1..=6; let colors = [ [1.00_f32, 0.00_f32, 0.25_f32], @@ -909,15 +909,15 @@ pub fn TestAssemblyChooser() -> View { // load assembly match name.as_str() { - "general" => load_gen_assemb(assembly), - "low-curv" => load_low_curv_assemb(assembly), - "pointed" => load_pointed_assemb(assembly), - "tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly), - "dodeca-packing" => load_dodeca_packing_assemb(assembly), - "balanced" => load_balanced_assemb(assembly), - "off-center" => load_off_center_assemb(assembly), - "radius-ratio" => load_radius_ratio_assemb(assembly), - "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), + "general" => load_general(assembly), + "low-curvature" => load_low_curvature(assembly), + "pointed" => load_pointed(assembly), + "tridiminished-icosahedron" => load_tridiminished_icosahedron(assembly), + "dodecahedral-packing" => load_dodecahedral_packing(assembly), + "balanced" => load_balanced(assembly), + "off-center" => load_off_center(assembly), + "radius-ratio" => load_radius_ratio(assembly), + "irisawa-hexlet" => load_irisawa_hexlet(assembly), _ => (), }; }); @@ -927,10 +927,10 @@ pub fn TestAssemblyChooser() -> View { view! { select(bind:value = assembly_name) { option(value = "general") { "General" } - option(value = "low-curv") { "Low-curvature" } + option(value = "low-curvature") { "Low-curvature" } option(value = "pointed") { "Pointed" } - option(value = "tridim-icosahedron") { "Tridiminished icosahedron" } - option(value = "dodeca-packing") { "Dodecahedral packing" } + option(value = "tridiminished-icosahedron") { "Tridiminished icosahedron" } + option(value = "dodecahedral-packing") { "Dodecahedral packing" } option(value = "balanced") { "Balanced" } option(value = "off-center") { "Off-center" } option(value = "radius-ratio") { "Radius ratio" } diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index dc6b470..d033c01 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -59,12 +59,12 @@ pub struct MatrixEntry { pub struct PartialMatrix(Vec); impl PartialMatrix { - pub fn new() -> PartialMatrix { - PartialMatrix(Vec::::new()) + pub fn new() -> Self { + Self(Vec::::new()) } pub fn push(&mut self, row: usize, col: usize, value: f64) { - let PartialMatrix(entries) = self; + let Self(entries) = self; entries.push(MatrixEntry { index: (row, col), value }); } @@ -114,7 +114,7 @@ impl IntoIterator for PartialMatrix { type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { - let PartialMatrix(entries) = self; + let Self(entries) = self; entries.into_iter() } } @@ -139,8 +139,8 @@ pub struct ConfigSubspace { } impl ConfigSubspace { - pub fn zero(assembly_dim: usize) -> ConfigSubspace { - ConfigSubspace { + pub fn zero(assembly_dim: usize) -> Self { + Self { assembly_dim, basis_proj: Vec::new(), basis_std: Vec::new(), @@ -154,7 +154,7 @@ impl ConfigSubspace { a: DMatrix, proj_to_std: DMatrix, assembly_dim: usize, - ) -> ConfigSubspace { + ) -> Self { // find a basis for the kernel. the basis is expressed in the projection // coordinates, and it's orthonormal with respect to the projection // inner product @@ -173,7 +173,7 @@ impl ConfigSubspace { const ELEMENT_DIM: usize = 5; const UNIFORM_DIM: usize = 4; - ConfigSubspace { + Self { assembly_dim, basis_std: basis_std.column_iter().map( |v| Into::>::into( @@ -224,8 +224,8 @@ pub struct DescentHistory { } impl DescentHistory { - pub fn new() -> DescentHistory { - DescentHistory { + pub fn new() -> Self { + Self { config: Vec::>::new(), scaled_loss: Vec::::new(), neg_grad: Vec::>::new(), @@ -245,9 +245,9 @@ pub struct ConstraintProblem { } impl ConstraintProblem { - pub fn new(element_count: usize) -> ConstraintProblem { + pub fn new(element_count: usize) -> Self { const ELEMENT_DIM: usize = 5; - ConstraintProblem { + Self { gram: PartialMatrix::new(), frozen: PartialMatrix::new(), guess: DMatrix::::zeros(ELEMENT_DIM, element_count), @@ -255,8 +255,8 @@ impl ConstraintProblem { } #[cfg(feature = "dev")] - pub fn from_guess(guess_columns: &[DVector]) -> ConstraintProblem { - ConstraintProblem { + pub fn from_guess(guess_columns: &[DVector]) -> Self { + Self { gram: PartialMatrix::new(), frozen: PartialMatrix::new(), guess: DMatrix::from_columns(guess_columns), @@ -284,10 +284,10 @@ struct SearchState { } impl SearchState { - fn from_config(gram: &PartialMatrix, config: DMatrix) -> SearchState { + fn from_config(gram: &PartialMatrix, config: DMatrix) -> Self { let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config)); let loss = err_proj.norm_squared(); - SearchState { config, err_proj, loss } + Self { config, err_proj, loss } } } diff --git a/app-proto/src/main.rs b/app-proto/src/main.rs index 7ca0731..a03b026 100644 --- a/app-proto/src/main.rs +++ b/app-proto/src/main.rs @@ -24,8 +24,8 @@ struct AppState { } impl AppState { - fn new() -> AppState { - AppState { + fn new() -> Self { + Self { assembly: Assembly::new(), selection: create_signal(BTreeSet::default()), } diff --git a/app-proto/src/specified.rs b/app-proto/src/specified.rs index ea1731c..788460b 100644 --- a/app-proto/src/specified.rs +++ b/app-proto/src/specified.rs @@ -17,8 +17,8 @@ pub struct SpecifiedValue { } impl SpecifiedValue { - pub fn from_empty_spec() -> SpecifiedValue { - SpecifiedValue { spec: String::new(), value: None } + pub fn from_empty_spec() -> Self { + Self { spec: String::new(), value: None } } pub fn is_present(&self) -> bool { @@ -34,10 +34,10 @@ impl TryFrom for SpecifiedValue { fn try_from(spec: String) -> Result { if spec.is_empty() { - Ok(SpecifiedValue::from_empty_spec()) + Ok(Self::from_empty_spec()) } else { spec.parse::().map( - |value| SpecifiedValue { spec, value: Some(value) } + |value| Self { spec, value: Some(value) } ) } } diff --git a/deploy/.gitignore b/deploy/.gitignore new file mode 100644 index 0000000..192f529 --- /dev/null +++ b/deploy/.gitignore @@ -0,0 +1,5 @@ +/dyna3.zip +/dyna3/index.html +/dyna3/dyna3-*.js +/dyna3/dyna3-*.wasm +/dyna3/main-*.css \ No newline at end of file diff --git a/tools/package-for-deployment.sh b/tools/package-for-deployment.sh new file mode 100644 index 0000000..fdda434 --- /dev/null +++ b/tools/package-for-deployment.sh @@ -0,0 +1,16 @@ +# set paths. this technique for getting the script location comes from +# `mklement0` on Stack Overflow +# +# https://stackoverflow.com/a/24114056 +# +TOOLS=$(dirname -- $0) +SRC="$TOOLS/../app-proto/dist" +DEST="$TOOLS/../deploy/dyna3" + +# remove the old hash-named files +[ -e "$DEST"/dyna3-*.js ] && rm "$DEST"/dyna3-*.js +[ -e "$DEST"/dyna3-*.wasm ] && rm "$DEST"/dyna3-*.wasm +[ -e "$DEST"/main-*.css ] && rm "$DEST"/main-*.css + +# copy the distribution +cp -r "$SRC/." "$DEST" diff --git a/app-proto/run-examples.sh b/tools/run-examples.sh similarity index 89% rename from app-proto/run-examples.sh rename to tools/run-examples.sh index 861addf..0946d92 100644 --- a/app-proto/run-examples.sh +++ b/tools/run-examples.sh @@ -8,7 +8,7 @@ # the application prototype # find the manifest file for the application prototype -MANIFEST="$(dirname -- $0)/Cargo.toml" +MANIFEST="$(dirname -- $0)/../app-proto/Cargo.toml" # set up the command that runs each example RUN_EXAMPLE="cargo run --manifest-path $MANIFEST --example"