forked from StudioInfinity/dyna3
Compare commits
5 commits
more-test-
...
main
Author | SHA1 | Date | |
---|---|---|---|
af18a8e7d1 | |||
a4565281d5 | |||
ef1a579ac0 | |||
2eba80fb69 | |||
0801200210 |
23 changed files with 1389 additions and 621 deletions
46
README.md
46
README.md
|
@ -25,32 +25,37 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter
|
||||||
### Install the prerequisites
|
### Install the prerequisites
|
||||||
|
|
||||||
1. Install [`rustup`](https://rust-lang.github.io/rustup/): the officially recommended Rust toolchain manager
|
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"
|
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)
|
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/)
|
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
|
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
|
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
|
- 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
|
- On POSIX systems, the search path is stored in the `PATH` environment variable
|
||||||
|
|
||||||
### Play with the prototype
|
### Play with the prototype
|
||||||
|
|
||||||
1. From the `app-proto` folder, call `trunk serve --release` to build and serve 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*
|
- 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*
|
- 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.
|
- 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:`
|
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
|
4. Press *ctrl+C* in the shell where Trunk is running to stop serving the prototype
|
||||||
|
|
||||||
### Run the engine on some example problems
|
### Run the engine on some example problems
|
||||||
|
|
||||||
1. Go into the `app-proto` folder
|
1. Use `sh` to run the script `tools/run-examples.sh`
|
||||||
2. Call `./run-examples`
|
- The script is location-independent, so you can do this from anywhere in the dyna3 repository
|
||||||
* *For each example problem, the engine will print the value of the loss function at each optimization step*
|
- The call from the top level of the repository is:
|
||||||
* *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*
|
|
||||||
|
```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
|
```julia
|
||||||
include("irisawa-hexlet.jl")
|
include("irisawa-hexlet.jl")
|
||||||
|
@ -59,9 +64,24 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter
|
||||||
end
|
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
|
### Run the automated tests
|
||||||
|
|
||||||
1. Go into the `app-proto` folder
|
1. Go into the `app-proto` folder
|
||||||
2. Call `cargo test`
|
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
|
2
app-proto/Trunk.toml
Normal file
2
app-proto/Trunk.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[build]
|
||||||
|
public_url = "./"
|
|
@ -23,7 +23,7 @@ fn main() {
|
||||||
let twist_motion: DMatrix<_> = (0..N_POINTS).step_by(4).flat_map(
|
let twist_motion: DMatrix<_> = (0..N_POINTS).step_by(4).flat_map(
|
||||||
|n| [
|
|n| [
|
||||||
tangent.proj(&up.as_view(), n),
|
tangent.proj(&up.as_view(), n),
|
||||||
tangent.proj(&down.as_view(), n+1)
|
tangent.proj(&down.as_view(), n+1),
|
||||||
]
|
]
|
||||||
).sum();
|
).sum();
|
||||||
let normalization = 5.0 / twist_motion[(2, 0)];
|
let normalization = 5.0 / twist_motion[(2, 0)];
|
||||||
|
|
|
@ -6,7 +6,7 @@ use dyna3::engine::{
|
||||||
realize_gram,
|
realize_gram,
|
||||||
sphere,
|
sphere,
|
||||||
ConfigNeighborhood,
|
ConfigNeighborhood,
|
||||||
ConstraintProblem
|
ConstraintProblem,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -25,7 +25,7 @@ fn main() {
|
||||||
);
|
);
|
||||||
print::title("Point on a sphere");
|
print::title("Point on a sphere");
|
||||||
print::realization_diagnostics(&realization);
|
print::realization_diagnostics(&realization);
|
||||||
if let Ok(ConfigNeighborhood{ config, .. }) = realization.result {
|
if let Ok(ConfigNeighborhood { config, .. }) = realization.result {
|
||||||
print::gram_matrix(&config);
|
print::gram_matrix(&config);
|
||||||
print::config(&config);
|
print::config(&config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use dyna3::engine::{
|
||||||
realize_gram,
|
realize_gram,
|
||||||
sphere,
|
sphere,
|
||||||
ConfigNeighborhood,
|
ConfigNeighborhood,
|
||||||
ConstraintProblem
|
ConstraintProblem,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -14,7 +14,7 @@ fn main() {
|
||||||
&[
|
&[
|
||||||
sphere(1.0, 0.0, 0.0, 1.0),
|
sphere(1.0, 0.0, 0.0, 1.0),
|
||||||
sphere(-0.5, a, 0.0, 1.0),
|
sphere(-0.5, a, 0.0, 1.0),
|
||||||
sphere(-0.5, -a, 0.0, 1.0)
|
sphere(-0.5, -a, 0.0, 1.0),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
for j in 0..3 {
|
for j in 0..3 {
|
||||||
|
@ -27,7 +27,7 @@ fn main() {
|
||||||
);
|
);
|
||||||
print::title("Three spheres");
|
print::title("Three spheres");
|
||||||
print::realization_diagnostics(&realization);
|
print::realization_diagnostics(&realization);
|
||||||
if let Ok(ConfigNeighborhood{ config, .. }) = realization.result {
|
if let Ok(ConfigNeighborhood { config, .. }) = realization.result {
|
||||||
print::gram_matrix(&config);
|
print::gram_matrix(&config);
|
||||||
}
|
}
|
||||||
print::loss_history(&realization.history);
|
print::loss_history(&realization.history);
|
||||||
|
|
|
@ -1,259 +0,0 @@
|
||||||
use std::{f64::consts::FRAC_1_SQRT_2, rc::Rc};
|
|
||||||
use sycamore::prelude::*;
|
|
||||||
use web_sys::{console, wasm_bindgen::JsValue};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
AppState,
|
|
||||||
engine,
|
|
||||||
engine::DescentHistory,
|
|
||||||
assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* DEBUG */
|
|
||||||
// load an example assembly for testing. this code will be removed once we've
|
|
||||||
// built a more formal test assembly system
|
|
||||||
fn load_gen_assemb(assembly: &Assembly) {
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
String::from("gemini_a"),
|
|
||||||
String::from("Castor"),
|
|
||||||
[1.00_f32, 0.25_f32, 0.00_f32],
|
|
||||||
engine::sphere(0.5, 0.5, 0.0, 1.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
String::from("gemini_b"),
|
|
||||||
String::from("Pollux"),
|
|
||||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
|
||||||
engine::sphere(-0.5, -0.5, 0.0, 1.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
String::from("ursa_major"),
|
|
||||||
String::from("Ursa major"),
|
|
||||||
[0.25_f32, 0.00_f32, 1.00_f32],
|
|
||||||
engine::sphere(-0.5, 0.5, 0.0, 0.75)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
String::from("ursa_minor"),
|
|
||||||
String::from("Ursa minor"),
|
|
||||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
|
||||||
engine::sphere(0.5, -0.5, 0.0, 0.5)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
String::from("moon_deimos"),
|
|
||||||
String::from("Deimos"),
|
|
||||||
[0.75_f32, 0.75_f32, 0.00_f32],
|
|
||||||
engine::sphere(0.0, 0.15, 1.0, 0.25)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
String::from("moon_phobos"),
|
|
||||||
String::from("Phobos"),
|
|
||||||
[0.00_f32, 0.75_f32, 0.50_f32],
|
|
||||||
engine::sphere(0.0, -0.15, -1.0, 0.25)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DEBUG */
|
|
||||||
// load an example assembly for testing. this code will be removed once we've
|
|
||||||
// built a more formal test assembly system
|
|
||||||
fn load_low_curv_assemb(assembly: &Assembly) {
|
|
||||||
let a = 0.75_f64.sqrt();
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
"central".to_string(),
|
|
||||||
"Central".to_string(),
|
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
|
||||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
"assemb_plane".to_string(),
|
|
||||||
"Assembly plane".to_string(),
|
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
|
||||||
engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
"side1".to_string(),
|
|
||||||
"Side 1".to_string(),
|
|
||||||
[1.00_f32, 0.00_f32, 0.25_f32],
|
|
||||||
engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
"side2".to_string(),
|
|
||||||
"Side 2".to_string(),
|
|
||||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
|
||||||
engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
"side3".to_string(),
|
|
||||||
"Side 3".to_string(),
|
|
||||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
|
||||||
engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
"corner1".to_string(),
|
|
||||||
"Corner 1".to_string(),
|
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
|
||||||
engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
"corner2".to_string(),
|
|
||||||
"Corner 2".to_string(),
|
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
|
||||||
engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
String::from("corner3"),
|
|
||||||
String::from("Corner 3"),
|
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
|
||||||
engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_pointed_assemb(assembly: &Assembly) {
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Point::new(
|
|
||||||
format!("point_front"),
|
|
||||||
format!("Front point"),
|
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
|
||||||
engine::point(0.0, 0.0, FRAC_1_SQRT_2)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Point::new(
|
|
||||||
format!("point_back"),
|
|
||||||
format!("Back point"),
|
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
|
||||||
engine::point(0.0, 0.0, -FRAC_1_SQRT_2)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
for index_x in 0..=1 {
|
|
||||||
for index_y in 0..=1 {
|
|
||||||
let x = index_x as f64 - 0.5;
|
|
||||||
let y = index_y as f64 - 0.5;
|
|
||||||
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Sphere::new(
|
|
||||||
format!("sphere{index_x}{index_y}"),
|
|
||||||
format!("Sphere {index_x}{index_y}"),
|
|
||||||
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
|
||||||
engine::sphere(x, y, 0.0, 1.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = assembly.try_insert_element(
|
|
||||||
Point::new(
|
|
||||||
format!("point{index_x}{index_y}"),
|
|
||||||
format!("Point {index_x}{index_y}"),
|
|
||||||
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
|
||||||
engine::point(x, y, 0.0)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn AddRemove() -> View {
|
|
||||||
/* DEBUG */
|
|
||||||
let assembly_name = create_signal("general".to_string());
|
|
||||||
create_effect(move || {
|
|
||||||
// get name of chosen assembly
|
|
||||||
let name = assembly_name.get_clone();
|
|
||||||
console::log_1(
|
|
||||||
&JsValue::from(format!("Showing assembly \"{}\"", name.clone()))
|
|
||||||
);
|
|
||||||
|
|
||||||
batch(|| {
|
|
||||||
let state = use_context::<AppState>();
|
|
||||||
let assembly = &state.assembly;
|
|
||||||
|
|
||||||
// clear state
|
|
||||||
assembly.regulators.update(|regs| regs.clear());
|
|
||||||
assembly.elements.update(|elts| elts.clear());
|
|
||||||
assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear());
|
|
||||||
assembly.descent_history.set(DescentHistory::new());
|
|
||||||
state.selection.update(|sel| sel.clear());
|
|
||||||
|
|
||||||
// load assembly
|
|
||||||
match name.as_str() {
|
|
||||||
"general" => load_gen_assemb(assembly),
|
|
||||||
"low-curv" => load_low_curv_assemb(assembly),
|
|
||||||
"pointed" => load_pointed_assemb(assembly),
|
|
||||||
_ => ()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
view! {
|
|
||||||
div(id="add-remove") {
|
|
||||||
button(
|
|
||||||
on:click=|_| {
|
|
||||||
let state = use_context::<AppState>();
|
|
||||||
state.assembly.insert_element_default::<Sphere>();
|
|
||||||
}
|
|
||||||
) { "Add sphere" }
|
|
||||||
button(
|
|
||||||
on:click=|_| {
|
|
||||||
let state = use_context::<AppState>();
|
|
||||||
state.assembly.insert_element_default::<Point>();
|
|
||||||
}
|
|
||||||
) { "Add point" }
|
|
||||||
button(
|
|
||||||
class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
|
|
||||||
disabled={
|
|
||||||
let state = use_context::<AppState>();
|
|
||||||
state.selection.with(|sel| sel.len() != 2)
|
|
||||||
},
|
|
||||||
on:click=|_| {
|
|
||||||
let state = use_context::<AppState>();
|
|
||||||
let subjects: [_; 2] = state.selection.with(
|
|
||||||
// the button is only enabled when two elements are
|
|
||||||
// selected, so we know the cast to a two-element array
|
|
||||||
// will succeed
|
|
||||||
|sel| sel
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.try_into()
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
state.assembly.insert_regulator(
|
|
||||||
Rc::new(InversiveDistanceRegulator::new(subjects))
|
|
||||||
);
|
|
||||||
state.selection.update(|sel| sel.clear());
|
|
||||||
}
|
|
||||||
) { "🔗" }
|
|
||||||
select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser
|
|
||||||
option(value="general") { "General" }
|
|
||||||
option(value="low-curv") { "Low-curvature" }
|
|
||||||
option(value="pointed") { "Pointed" }
|
|
||||||
option(value="empty") { "Empty" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,21 @@
|
||||||
use nalgebra::{DMatrix, DVector, DVectorView};
|
use nalgebra::{DMatrix, DVector, DVectorView};
|
||||||
use std::{
|
use std::{
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
collections::{BTreeMap, BTreeSet},
|
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
collections::{BTreeMap, BTreeSet},
|
||||||
fmt,
|
fmt,
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{atomic, atomic::AtomicU64}
|
sync::{atomic, atomic::AtomicU64},
|
||||||
};
|
};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display::DisplayItem,
|
components::{display::DisplayItem, outline::OutlineItem},
|
||||||
engine::{
|
engine::{
|
||||||
Q,
|
Q,
|
||||||
change_half_curvature,
|
|
||||||
local_unif_to_std,
|
local_unif_to_std,
|
||||||
point,
|
point,
|
||||||
project_point_to_normalized,
|
project_point_to_normalized,
|
||||||
|
@ -27,10 +26,9 @@ use crate::{
|
||||||
ConfigSubspace,
|
ConfigSubspace,
|
||||||
ConstraintProblem,
|
ConstraintProblem,
|
||||||
DescentHistory,
|
DescentHistory,
|
||||||
Realization
|
Realization,
|
||||||
},
|
},
|
||||||
outline::OutlineItem,
|
specified::SpecifiedValue,
|
||||||
specified::SpecifiedValue
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ElementColor = [f32; 3];
|
pub type ElementColor = [f32; 3];
|
||||||
|
@ -166,7 +164,7 @@ pub struct Sphere {
|
||||||
pub ghost: Signal<bool>,
|
pub ghost: Signal<bool>,
|
||||||
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
||||||
serial: u64,
|
serial: u64,
|
||||||
column_index: Cell<Option<usize>>
|
column_index: Cell<Option<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sphere {
|
impl Sphere {
|
||||||
|
@ -176,17 +174,17 @@ impl Sphere {
|
||||||
id: String,
|
id: String,
|
||||||
label: String,
|
label: String,
|
||||||
color: ElementColor,
|
color: ElementColor,
|
||||||
representation: DVector<f64>
|
representation: DVector<f64>,
|
||||||
) -> Sphere {
|
) -> Self {
|
||||||
Sphere {
|
Self {
|
||||||
id: id,
|
id,
|
||||||
label: label,
|
label,
|
||||||
color: color,
|
color,
|
||||||
representation: create_signal(representation),
|
representation: create_signal(representation),
|
||||||
ghost: create_signal(false),
|
ghost: create_signal(false),
|
||||||
regulators: create_signal(BTreeSet::new()),
|
regulators: create_signal(BTreeSet::new()),
|
||||||
serial: Self::next_serial(),
|
serial: Self::next_serial(),
|
||||||
column_index: None.into()
|
column_index: None.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,12 +194,12 @@ impl Element for Sphere {
|
||||||
"sphere".to_string()
|
"sphere".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default(id: String, id_num: u64) -> Sphere {
|
fn default(id: String, id_num: u64) -> Self {
|
||||||
Sphere::new(
|
Self::new(
|
||||||
id,
|
id,
|
||||||
format!("Sphere {id_num}"),
|
format!("Sphere {id_num}"),
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
sphere(0.0, 0.0, 0.0, 1.0)
|
sphere(0.0, 0.0, 0.0, 1.0),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +264,7 @@ pub struct Point {
|
||||||
pub ghost: Signal<bool>,
|
pub ghost: Signal<bool>,
|
||||||
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
||||||
serial: u64,
|
serial: u64,
|
||||||
column_index: Cell<Option<usize>>
|
column_index: Cell<Option<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Point {
|
impl Point {
|
||||||
|
@ -276,9 +274,9 @@ impl Point {
|
||||||
id: String,
|
id: String,
|
||||||
label: String,
|
label: String,
|
||||||
color: ElementColor,
|
color: ElementColor,
|
||||||
representation: DVector<f64>
|
representation: DVector<f64>,
|
||||||
) -> Point {
|
) -> Self {
|
||||||
Point {
|
Self {
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
color,
|
color,
|
||||||
|
@ -286,7 +284,7 @@ impl Point {
|
||||||
ghost: create_signal(false),
|
ghost: create_signal(false),
|
||||||
regulators: create_signal(BTreeSet::new()),
|
regulators: create_signal(BTreeSet::new()),
|
||||||
serial: Self::next_serial(),
|
serial: Self::next_serial(),
|
||||||
column_index: None.into()
|
column_index: None.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,12 +294,12 @@ impl Element for Point {
|
||||||
"point".to_string()
|
"point".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default(id: String, id_num: u64) -> Point {
|
fn default(id: String, id_num: u64) -> Self {
|
||||||
Point::new(
|
Self::new(
|
||||||
id,
|
id,
|
||||||
format!("Point {id_num}"),
|
format!("Point {id_num}"),
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
point(0.0, 0.0, 0.0)
|
point(0.0, 0.0, 0.0),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +348,7 @@ impl ProblemPoser for Point {
|
||||||
format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str()
|
format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str()
|
||||||
);
|
);
|
||||||
problem.gram.push_sym(index, index, 0.0);
|
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());
|
problem.guess.set_column(index, &self.representation.get_clone_untracked());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,16 +357,6 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem {
|
||||||
fn subjects(&self) -> Vec<Rc<dyn Element>>;
|
fn subjects(&self) -> Vec<Rc<dyn Element>>;
|
||||||
fn measurement(&self) -> ReadSignal<f64>;
|
fn measurement(&self) -> ReadSignal<f64>;
|
||||||
fn set_point(&self) -> Signal<SpecifiedValue>;
|
fn set_point(&self) -> Signal<SpecifiedValue>;
|
||||||
|
|
||||||
// this method is used to responsively precondition the assembly for
|
|
||||||
// realization when the regulator becomes a constraint, or is edited while
|
|
||||||
// acting as a constraint. it should track the set point, do any desired
|
|
||||||
// preconditioning when the set point is present, and use its return value
|
|
||||||
// to report whether the set is present. the default implementation does no
|
|
||||||
// preconditioning
|
|
||||||
fn try_activate(&self) -> bool {
|
|
||||||
self.set_point().with(|set_pt| set_pt.is_present())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for dyn Regulator {
|
impl Hash for dyn Regulator {
|
||||||
|
@ -401,11 +389,11 @@ pub struct InversiveDistanceRegulator {
|
||||||
pub subjects: [Rc<dyn Element>; 2],
|
pub subjects: [Rc<dyn Element>; 2],
|
||||||
pub measurement: ReadSignal<f64>,
|
pub measurement: ReadSignal<f64>,
|
||||||
pub set_point: Signal<SpecifiedValue>,
|
pub set_point: Signal<SpecifiedValue>,
|
||||||
serial: u64
|
serial: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InversiveDistanceRegulator {
|
impl InversiveDistanceRegulator {
|
||||||
pub fn new(subjects: [Rc<dyn Element>; 2]) -> InversiveDistanceRegulator {
|
pub fn new(subjects: [Rc<dyn Element>; 2]) -> Self {
|
||||||
let representations = subjects.each_ref().map(|subj| subj.representation());
|
let representations = subjects.each_ref().map(|subj| subj.representation());
|
||||||
let measurement = create_memo(move || {
|
let measurement = create_memo(move || {
|
||||||
representations[0].with(|rep_0|
|
representations[0].with(|rep_0|
|
||||||
|
@ -418,7 +406,7 @@ impl InversiveDistanceRegulator {
|
||||||
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
||||||
let serial = Self::next_serial();
|
let serial = Self::next_serial();
|
||||||
|
|
||||||
InversiveDistanceRegulator { subjects, measurement, set_point, serial }
|
Self { subjects, measurement, set_point, serial }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,11 +449,11 @@ pub struct HalfCurvatureRegulator {
|
||||||
pub subject: Rc<dyn Element>,
|
pub subject: Rc<dyn Element>,
|
||||||
pub measurement: ReadSignal<f64>,
|
pub measurement: ReadSignal<f64>,
|
||||||
pub set_point: Signal<SpecifiedValue>,
|
pub set_point: Signal<SpecifiedValue>,
|
||||||
serial: u64
|
serial: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HalfCurvatureRegulator {
|
impl HalfCurvatureRegulator {
|
||||||
pub fn new(subject: Rc<dyn Element>) -> HalfCurvatureRegulator {
|
pub fn new(subject: Rc<dyn Element>) -> Self {
|
||||||
let measurement = subject.representation().map(
|
let measurement = subject.representation().map(
|
||||||
|rep| rep[Sphere::CURVATURE_COMPONENT]
|
|rep| rep[Sphere::CURVATURE_COMPONENT]
|
||||||
);
|
);
|
||||||
|
@ -473,7 +461,7 @@ impl HalfCurvatureRegulator {
|
||||||
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
||||||
let serial = Self::next_serial();
|
let serial = Self::next_serial();
|
||||||
|
|
||||||
HalfCurvatureRegulator { subject, measurement, set_point, serial }
|
Self { subject, measurement, set_point, serial }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,18 +477,6 @@ impl Regulator for HalfCurvatureRegulator {
|
||||||
fn set_point(&self) -> Signal<SpecifiedValue> {
|
fn set_point(&self) -> Signal<SpecifiedValue> {
|
||||||
self.set_point
|
self.set_point
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_activate(&self) -> bool {
|
|
||||||
match self.set_point.with(|set_pt| set_pt.value) {
|
|
||||||
Some(half_curv) => {
|
|
||||||
self.subject.representation().update(
|
|
||||||
|rep| change_half_curvature(rep, half_curv)
|
|
||||||
);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serial for HalfCurvatureRegulator {
|
impl Serial for HalfCurvatureRegulator {
|
||||||
|
@ -525,7 +501,7 @@ impl ProblemPoser for HalfCurvatureRegulator {
|
||||||
// the velocity is expressed in uniform coordinates
|
// the velocity is expressed in uniform coordinates
|
||||||
pub struct ElementMotion<'a> {
|
pub struct ElementMotion<'a> {
|
||||||
pub element: Rc<dyn Element>,
|
pub element: Rc<dyn Element>,
|
||||||
pub velocity: DVectorView<'a, f64>
|
pub velocity: DVectorView<'a, f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssemblyMotion<'a> = Vec<ElementMotion<'a>>;
|
type AssemblyMotion<'a> = Vec<ElementMotion<'a>>;
|
||||||
|
@ -552,21 +528,42 @@ pub struct Assembly {
|
||||||
// indexing
|
// indexing
|
||||||
pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>>,
|
pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>>,
|
||||||
|
|
||||||
|
// realization control
|
||||||
|
pub realization_trigger: Signal<()>,
|
||||||
|
|
||||||
// realization diagnostics
|
// realization diagnostics
|
||||||
pub realization_status: Signal<Result<(), String>>,
|
pub realization_status: Signal<Result<(), String>>,
|
||||||
pub descent_history: Signal<DescentHistory>
|
pub descent_history: Signal<DescentHistory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assembly {
|
impl Assembly {
|
||||||
pub fn new() -> Assembly {
|
pub fn new() -> Assembly {
|
||||||
Assembly {
|
// create an assembly
|
||||||
|
let assembly = Assembly {
|
||||||
elements: create_signal(BTreeSet::new()),
|
elements: create_signal(BTreeSet::new()),
|
||||||
regulators: create_signal(BTreeSet::new()),
|
regulators: create_signal(BTreeSet::new()),
|
||||||
tangent: create_signal(ConfigSubspace::zero(0)),
|
tangent: create_signal(ConfigSubspace::zero(0)),
|
||||||
elements_by_id: create_signal(BTreeMap::default()),
|
elements_by_id: create_signal(BTreeMap::default()),
|
||||||
|
realization_trigger: create_signal(()),
|
||||||
realization_status: create_signal(Ok(())),
|
realization_status: create_signal(Ok(())),
|
||||||
descent_history: create_signal(DescentHistory::new())
|
descent_history: create_signal(DescentHistory::new()),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// realize the assembly whenever the element list, the regulator list,
|
||||||
|
// a regulator's set point, or the realization trigger is updated
|
||||||
|
let assembly_for_effect = assembly.clone();
|
||||||
|
create_effect(move || {
|
||||||
|
assembly_for_effect.elements.track();
|
||||||
|
assembly_for_effect.regulators.with(
|
||||||
|
|regs| for reg in regs {
|
||||||
|
reg.set_point().track();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assembly_for_effect.realization_trigger.track();
|
||||||
|
assembly_for_effect.realize();
|
||||||
|
});
|
||||||
|
|
||||||
|
assembly
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- inserting elements and regulators ---
|
// --- inserting elements and regulators ---
|
||||||
|
@ -627,19 +624,6 @@ impl Assembly {
|
||||||
regulators.update(|regs| regs.insert(regulator.clone()));
|
regulators.update(|regs| regs.insert(regulator.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the realization when the regulator becomes a constraint, or is
|
|
||||||
// edited while acting as a constraint
|
|
||||||
let self_for_effect = self.clone();
|
|
||||||
create_effect(move || {
|
|
||||||
/* DEBUG */
|
|
||||||
// log the regulator update
|
|
||||||
console_log!("Updated regulator with subjects {:?}", regulator.subjects());
|
|
||||||
|
|
||||||
if regulator.try_activate() {
|
|
||||||
self_for_effect.realize();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* DEBUG */
|
/* DEBUG */
|
||||||
// print an updated list of regulators
|
// print an updated list of regulators
|
||||||
console_log!("Regulators:");
|
console_log!("Regulators:");
|
||||||
|
@ -707,8 +691,10 @@ impl Assembly {
|
||||||
} else {
|
} else {
|
||||||
console_log!("✅️ Target accuracy achieved!");
|
console_log!("✅️ Target accuracy achieved!");
|
||||||
}
|
}
|
||||||
console_log!("Steps: {}", history.scaled_loss.len() - 1);
|
if history.scaled_loss.len() > 0 {
|
||||||
console_log!("Loss: {}", history.scaled_loss.last().unwrap());
|
console_log!("Steps: {}", history.scaled_loss.len() - 1);
|
||||||
|
console_log!("Loss: {}", history.scaled_loss.last().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
// report the loss history
|
// report the loss history
|
||||||
self.descent_history.set(history);
|
self.descent_history.set(history);
|
||||||
|
@ -738,7 +724,7 @@ impl Assembly {
|
||||||
// `Err(message)` we received from the match: we're changing the
|
// `Err(message)` we received from the match: we're changing the
|
||||||
// `Ok` type from `Realization` to `()`
|
// `Ok` type from `Realization` to `()`
|
||||||
self.realization_status.set(Err(message))
|
self.realization_status.set(Err(message))
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,15 +807,15 @@ impl Assembly {
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
console_log!("No velocity to unpack for fresh element \"{}\"", elt.id())
|
console_log!("No velocity to unpack for fresh element \"{}\"", elt.id())
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// bring the configuration back onto the solution variety. this also
|
// trigger a realization to bring the configuration back onto the
|
||||||
// gets the elements' column indices and the saved tangent space back in
|
// solution variety. this also gets the elements' column indices and the
|
||||||
// sync
|
// saved tangent space back in sync
|
||||||
self.realize();
|
self.realization_trigger.set(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -881,7 +867,7 @@ mod tests {
|
||||||
String::from(sphere_id),
|
String::from(sphere_id),
|
||||||
String::from("Sphere 0"),
|
String::from("Sphere 0"),
|
||||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
engine::sphere(0.0, 0.0, 0.0, INITIAL_RADIUS)
|
engine::sphere(0.0, 0.0, 0.0, INITIAL_RADIUS),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -895,7 +881,7 @@ mod tests {
|
||||||
vec![
|
vec![
|
||||||
ElementMotion {
|
ElementMotion {
|
||||||
element: sphere.clone(),
|
element: sphere.clone(),
|
||||||
velocity: velocity.as_view()
|
velocity: velocity.as_view(),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
5
app-proto/src/components.rs
Normal file
5
app-proto/src/components.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod add_remove;
|
||||||
|
pub mod diagnostics;
|
||||||
|
pub mod display;
|
||||||
|
pub mod outline;
|
||||||
|
pub mod test_assembly_chooser;
|
69
app-proto/src/components/add_remove.rs
Normal file
69
app-proto/src/components/add_remove.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
use super::test_assembly_chooser::TestAssemblyChooser;
|
||||||
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
assembly::{InversiveDistanceRegulator, Point, Sphere},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn AddRemove() -> View {
|
||||||
|
view! {
|
||||||
|
div(id = "add-remove") {
|
||||||
|
button(
|
||||||
|
on:click = |_| {
|
||||||
|
let state = use_context::<AppState>();
|
||||||
|
batch(|| {
|
||||||
|
// this call is batched to avoid redundant realizations.
|
||||||
|
// it updates the element list and the regulator list,
|
||||||
|
// which are both tracked by the realization effect
|
||||||
|
/* TO DO */
|
||||||
|
// it would make more to do the batching inside
|
||||||
|
// `insert_element_default`, but that will have to wait
|
||||||
|
// until Sycamore handles nested batches correctly.
|
||||||
|
//
|
||||||
|
// https://github.com/sycamore-rs/sycamore/issues/802
|
||||||
|
//
|
||||||
|
// the nested batch issue is relevant here because the
|
||||||
|
// assembly loaders in the test assembly chooser use
|
||||||
|
// `insert_element_default` within larger batches
|
||||||
|
state.assembly.insert_element_default::<Sphere>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
) { "Add sphere" }
|
||||||
|
button(
|
||||||
|
on:click = |_| {
|
||||||
|
let state = use_context::<AppState>();
|
||||||
|
state.assembly.insert_element_default::<Point>();
|
||||||
|
}
|
||||||
|
) { "Add point" }
|
||||||
|
button(
|
||||||
|
class = "emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
|
||||||
|
disabled = {
|
||||||
|
let state = use_context::<AppState>();
|
||||||
|
state.selection.with(|sel| sel.len() != 2)
|
||||||
|
},
|
||||||
|
on:click = |_| {
|
||||||
|
let state = use_context::<AppState>();
|
||||||
|
let subjects: [_; 2] = state.selection.with(
|
||||||
|
// the button is only enabled when two elements are
|
||||||
|
// selected, so we know the cast to a two-element array
|
||||||
|
// will succeed
|
||||||
|
|sel| sel
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
state.assembly.insert_regulator(
|
||||||
|
Rc::new(InversiveDistanceRegulator::new(subjects))
|
||||||
|
);
|
||||||
|
state.selection.update(|sel| sel.clear());
|
||||||
|
}
|
||||||
|
) { "🔗" }
|
||||||
|
TestAssemblyChooser {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,14 +11,12 @@ use crate::AppState;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct DiagnosticsState {
|
struct DiagnosticsState {
|
||||||
active_tab: Signal<String>
|
active_tab: Signal<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticsState {
|
impl DiagnosticsState {
|
||||||
fn new(initial_tab: String) -> DiagnosticsState {
|
fn new(initial_tab: String) -> Self {
|
||||||
DiagnosticsState {
|
Self { active_tab: create_signal(initial_tab) }
|
||||||
active_tab: create_signal(initial_tab)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,20 +27,20 @@ fn RealizationStatus() -> View {
|
||||||
let realization_status = state.assembly.realization_status;
|
let realization_status = state.assembly.realization_status;
|
||||||
view! {
|
view! {
|
||||||
div(
|
div(
|
||||||
id="realization-status",
|
id = "realization-status",
|
||||||
class=realization_status.with(
|
class = realization_status.with(
|
||||||
|status| match status {
|
|status| match status {
|
||||||
Ok(_) => "",
|
Ok(_) => "",
|
||||||
Err(_) => "invalid"
|
Err(_) => "invalid",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
div(class="status")
|
div(class = "status")
|
||||||
div {
|
div {
|
||||||
(realization_status.with(
|
(realization_status.with(
|
||||||
|status| match status {
|
|status| match status {
|
||||||
Ok(_) => "Target accuracy achieved".to_string(),
|
Ok(_) => "Target accuracy achieved".to_string(),
|
||||||
Err(message) => message.clone()
|
Err(message) => message.clone(),
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -53,7 +51,7 @@ fn RealizationStatus() -> View {
|
||||||
fn into_log10_time_point((step, value): (usize, f64)) -> Vec<Option<f64>> {
|
fn into_log10_time_point((step, value): (usize, f64)) -> Vec<Option<f64>> {
|
||||||
vec![
|
vec![
|
||||||
Some(step as f64),
|
Some(step as f64),
|
||||||
if value == 0.0 { None } else { Some(value.abs().log10()) }
|
if value == 0.0 { None } else { Some(value.abs().log10()) },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +103,7 @@ fn LossHistory() -> View {
|
||||||
});
|
});
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
div(id=CONTAINER_ID, class="diagnostics-chart")
|
div(id = CONTAINER_ID, class = "diagnostics-chart")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +120,7 @@ fn SpectrumHistory() -> View {
|
||||||
// positive, negative, and strictly-zero parts
|
// positive, negative, and strictly-zero parts
|
||||||
let (
|
let (
|
||||||
hess_eigvals_zero,
|
hess_eigvals_zero,
|
||||||
hess_eigvals_nonzero
|
hess_eigvals_nonzero,
|
||||||
): (Vec<_>, Vec<_>) = state.assembly.descent_history.with(
|
): (Vec<_>, Vec<_>) = state.assembly.descent_history.with(
|
||||||
|history| history.hess_eigvals
|
|history| history.hess_eigvals
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -143,7 +141,7 @@ fn SpectrumHistory() -> View {
|
||||||
.unwrap_or(1.0);
|
.unwrap_or(1.0);
|
||||||
let (
|
let (
|
||||||
hess_eigvals_pos,
|
hess_eigvals_pos,
|
||||||
hess_eigvals_neg
|
hess_eigvals_neg,
|
||||||
): (Vec<_>, Vec<_>) = hess_eigvals_nonzero
|
): (Vec<_>, Vec<_>) = hess_eigvals_nonzero
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.partition(|&(_, val)| val > 0.0);
|
.partition(|&(_, val)| val > 0.0);
|
||||||
|
@ -211,7 +209,7 @@ fn SpectrumHistory() -> View {
|
||||||
});
|
});
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
div(id=CONTAINER_ID, class="diagnostics-chart")
|
div(id = CONTAINER_ID, class = "diagnostics-chart")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,8 +218,8 @@ fn DiagnosticsPanel(name: &'static str, children: Children) -> View {
|
||||||
let diagnostics_state = use_context::<DiagnosticsState>();
|
let diagnostics_state = use_context::<DiagnosticsState>();
|
||||||
view! {
|
view! {
|
||||||
div(
|
div(
|
||||||
class="diagnostics-panel",
|
class = "diagnostics-panel",
|
||||||
"hidden"=diagnostics_state.active_tab.with(
|
"hidden" = diagnostics_state.active_tab.with(
|
||||||
|active_tab| {
|
|active_tab| {
|
||||||
if active_tab == name {
|
if active_tab == name {
|
||||||
None
|
None
|
||||||
|
@ -243,16 +241,16 @@ pub fn Diagnostics() -> View {
|
||||||
provide_context(diagnostics_state);
|
provide_context(diagnostics_state);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
div(id="diagnostics") {
|
div(id = "diagnostics") {
|
||||||
div(id="diagnostics-bar") {
|
div(id = "diagnostics-bar") {
|
||||||
RealizationStatus {}
|
RealizationStatus {}
|
||||||
select(bind:value=active_tab) {
|
select(bind:value = active_tab) {
|
||||||
option(value="loss") { "Loss" }
|
option(value = "loss") { "Loss" }
|
||||||
option(value="spectrum") { "Spectrum" }
|
option(value = "spectrum") { "Spectrum" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiagnosticsPanel(name="loss") { LossHistory {} }
|
DiagnosticsPanel(name = "loss") { LossHistory {} }
|
||||||
DiagnosticsPanel(name="spectrum") { SpectrumHistory {} }
|
DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,12 +12,12 @@ use web_sys::{
|
||||||
WebGlProgram,
|
WebGlProgram,
|
||||||
WebGlShader,
|
WebGlShader,
|
||||||
WebGlUniformLocation,
|
WebGlUniformLocation,
|
||||||
wasm_bindgen::{JsCast, JsValue}
|
wasm_bindgen::{JsCast, JsValue},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
assembly::{Element, ElementColor, ElementMotion, Point, Sphere}
|
assembly::{Element, ElementColor, ElementMotion, Point, Sphere},
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- color ---
|
// --- color ---
|
||||||
|
@ -37,15 +37,15 @@ fn combine_channels(color: ElementColor, opacity: f32) -> ColorWithOpacity {
|
||||||
struct SceneSpheres {
|
struct SceneSpheres {
|
||||||
representations: Vec<DVector<f64>>,
|
representations: Vec<DVector<f64>>,
|
||||||
colors_with_opacity: Vec<ColorWithOpacity>,
|
colors_with_opacity: Vec<ColorWithOpacity>,
|
||||||
highlights: Vec<f32>
|
highlights: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SceneSpheres {
|
impl SceneSpheres {
|
||||||
fn new() -> SceneSpheres{
|
fn new() -> Self {
|
||||||
SceneSpheres {
|
Self {
|
||||||
representations: Vec::new(),
|
representations: Vec::new(),
|
||||||
colors_with_opacity: Vec::new(),
|
colors_with_opacity: Vec::new(),
|
||||||
highlights: Vec::new()
|
highlights: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,10 @@ impl SceneSpheres {
|
||||||
self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer")
|
self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, representation: DVector<f64>, color: ElementColor, opacity: f32, highlight: f32) {
|
fn push(
|
||||||
|
&mut self, representation: DVector<f64>,
|
||||||
|
color: ElementColor, opacity: f32, highlight: f32,
|
||||||
|
) {
|
||||||
self.representations.push(representation);
|
self.representations.push(representation);
|
||||||
self.colors_with_opacity.push(combine_channels(color, opacity));
|
self.colors_with_opacity.push(combine_channels(color, opacity));
|
||||||
self.highlights.push(highlight);
|
self.highlights.push(highlight);
|
||||||
|
@ -64,20 +67,23 @@ struct ScenePoints {
|
||||||
representations: Vec<DVector<f64>>,
|
representations: Vec<DVector<f64>>,
|
||||||
colors_with_opacity: Vec<ColorWithOpacity>,
|
colors_with_opacity: Vec<ColorWithOpacity>,
|
||||||
highlights: Vec<f32>,
|
highlights: Vec<f32>,
|
||||||
selections: Vec<f32>
|
selections: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScenePoints {
|
impl ScenePoints {
|
||||||
fn new() -> ScenePoints {
|
fn new() -> Self {
|
||||||
ScenePoints {
|
Self {
|
||||||
representations: Vec::new(),
|
representations: Vec::new(),
|
||||||
colors_with_opacity: Vec::new(),
|
colors_with_opacity: Vec::new(),
|
||||||
highlights: Vec::new(),
|
highlights: Vec::new(),
|
||||||
selections: Vec::new()
|
selections: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, representation: DVector<f64>, color: ElementColor, opacity: f32, highlight: f32, selected: bool) {
|
fn push(
|
||||||
|
&mut self, representation: DVector<f64>,
|
||||||
|
color: ElementColor, opacity: f32, highlight: f32, selected: bool,
|
||||||
|
) {
|
||||||
self.representations.push(representation);
|
self.representations.push(representation);
|
||||||
self.colors_with_opacity.push(combine_channels(color, opacity));
|
self.colors_with_opacity.push(combine_channels(color, opacity));
|
||||||
self.highlights.push(highlight);
|
self.highlights.push(highlight);
|
||||||
|
@ -87,14 +93,14 @@ impl ScenePoints {
|
||||||
|
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
spheres: SceneSpheres,
|
spheres: SceneSpheres,
|
||||||
points: ScenePoints
|
points: ScenePoints,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
fn new() -> Scene {
|
fn new() -> Self {
|
||||||
Scene {
|
Self {
|
||||||
spheres: SceneSpheres::new(),
|
spheres: SceneSpheres::new(),
|
||||||
points: ScenePoints::new()
|
points: ScenePoints::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +111,12 @@ pub trait DisplayItem {
|
||||||
// the smallest positive depth, represented as a multiple of `dir`, where
|
// the smallest positive depth, represented as a multiple of `dir`, where
|
||||||
// the line generated by `dir` hits the element. returns `None` if the line
|
// the line generated by `dir` hits the element. returns `None` if the line
|
||||||
// misses the element
|
// misses the element
|
||||||
fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, pixel_size: f64) -> Option<f64>;
|
fn cast(
|
||||||
|
&self,
|
||||||
|
dir: Vector3<f64>,
|
||||||
|
assembly_to_world: &DMatrix<f64>,
|
||||||
|
pixel_size: f64,
|
||||||
|
) -> Option<f64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayItem for Sphere {
|
impl DisplayItem for Sphere {
|
||||||
|
@ -124,7 +135,12 @@ impl DisplayItem for Sphere {
|
||||||
|
|
||||||
// this method should be kept synchronized with `sphere_cast` in
|
// this method should be kept synchronized with `sphere_cast` in
|
||||||
// `spheres.frag`, which does essentially the same thing on the GPU side
|
// `spheres.frag`, which does essentially the same thing on the GPU side
|
||||||
fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, _pixel_size: f64) -> Option<f64> {
|
fn cast(
|
||||||
|
&self,
|
||||||
|
dir: Vector3<f64>,
|
||||||
|
assembly_to_world: &DMatrix<f64>,
|
||||||
|
_pixel_size: f64,
|
||||||
|
) -> Option<f64> {
|
||||||
// if `a/b` is less than this threshold, we approximate
|
// if `a/b` is less than this threshold, we approximate
|
||||||
// `a*u^2 + b*u + c` by the linear function `b*u + c`
|
// `a*u^2 + b*u + c` by the linear function `b*u + c`
|
||||||
const DEG_THRESHOLD: f64 = 1e-9;
|
const DEG_THRESHOLD: f64 = 1e-9;
|
||||||
|
@ -177,7 +193,12 @@ impl DisplayItem for Point {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SCAFFOLDING */
|
/* SCAFFOLDING */
|
||||||
fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, pixel_size: f64) -> Option<f64> {
|
fn cast(
|
||||||
|
&self,
|
||||||
|
dir: Vector3<f64>,
|
||||||
|
assembly_to_world: &DMatrix<f64>,
|
||||||
|
pixel_size: f64,
|
||||||
|
) -> Option<f64> {
|
||||||
let rep = self.representation.with_untracked(|rep| assembly_to_world * rep);
|
let rep = self.representation.with_untracked(|rep| assembly_to_world * rep);
|
||||||
if rep[2] < 0.0 {
|
if rep[2] < 0.0 {
|
||||||
// this constant should be kept synchronized with `point.frag`
|
// this constant should be kept synchronized with `point.frag`
|
||||||
|
@ -220,7 +241,7 @@ fn compile_shader(
|
||||||
fn set_up_program(
|
fn set_up_program(
|
||||||
context: &WebGl2RenderingContext,
|
context: &WebGl2RenderingContext,
|
||||||
vertex_shader_source: &str,
|
vertex_shader_source: &str,
|
||||||
fragment_shader_source: &str
|
fragment_shader_source: &str,
|
||||||
) -> WebGlProgram {
|
) -> WebGlProgram {
|
||||||
// compile the shaders
|
// compile the shaders
|
||||||
let vertex_shader = compile_shader(
|
let vertex_shader = compile_shader(
|
||||||
|
@ -260,12 +281,12 @@ fn get_uniform_array_locations<const N: usize>(
|
||||||
context: &WebGl2RenderingContext,
|
context: &WebGl2RenderingContext,
|
||||||
program: &WebGlProgram,
|
program: &WebGlProgram,
|
||||||
var_name: &str,
|
var_name: &str,
|
||||||
member_name_opt: Option<&str>
|
member_name_opt: Option<&str>,
|
||||||
) -> [Option<WebGlUniformLocation>; N] {
|
) -> [Option<WebGlUniformLocation>; N] {
|
||||||
array::from_fn(|n| {
|
array::from_fn(|n| {
|
||||||
let name = match member_name_opt {
|
let name = match member_name_opt {
|
||||||
Some(member_name) => format!("{var_name}[{n}].{member_name}"),
|
Some(member_name) => format!("{var_name}[{n}].{member_name}"),
|
||||||
None => format!("{var_name}[{n}]")
|
None => format!("{var_name}[{n}]"),
|
||||||
};
|
};
|
||||||
context.get_uniform_location(&program, name.as_str())
|
context.get_uniform_location(&program, name.as_str())
|
||||||
})
|
})
|
||||||
|
@ -276,7 +297,7 @@ fn bind_to_attribute(
|
||||||
context: &WebGl2RenderingContext,
|
context: &WebGl2RenderingContext,
|
||||||
attr_index: u32,
|
attr_index: u32,
|
||||||
attr_size: i32,
|
attr_size: i32,
|
||||||
buffer: &Option<WebGlBuffer>
|
buffer: &Option<WebGlBuffer>,
|
||||||
) {
|
) {
|
||||||
context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref());
|
context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref());
|
||||||
context.vertex_attrib_pointer_with_i32(
|
context.vertex_attrib_pointer_with_i32(
|
||||||
|
@ -292,7 +313,7 @@ fn bind_to_attribute(
|
||||||
// load the given data into a new vertex buffer object
|
// load the given data into a new vertex buffer object
|
||||||
fn load_new_buffer(
|
fn load_new_buffer(
|
||||||
context: &WebGl2RenderingContext,
|
context: &WebGl2RenderingContext,
|
||||||
data: &[f32]
|
data: &[f32],
|
||||||
) -> Option<WebGlBuffer> {
|
) -> Option<WebGlBuffer> {
|
||||||
// create a buffer and bind it to ARRAY_BUFFER
|
// create a buffer and bind it to ARRAY_BUFFER
|
||||||
let buffer = context.create_buffer();
|
let buffer = context.create_buffer();
|
||||||
|
@ -319,7 +340,7 @@ fn bind_new_buffer_to_attribute(
|
||||||
context: &WebGl2RenderingContext,
|
context: &WebGl2RenderingContext,
|
||||||
attr_index: u32,
|
attr_index: u32,
|
||||||
attr_size: i32,
|
attr_size: i32,
|
||||||
data: &[f32]
|
data: &[f32],
|
||||||
) {
|
) {
|
||||||
let buffer = load_new_buffer(context, data);
|
let buffer = load_new_buffer(context, data);
|
||||||
bind_to_attribute(context, attr_index, attr_size, &buffer);
|
bind_to_attribute(context, attr_index, attr_size, &buffer);
|
||||||
|
@ -341,9 +362,9 @@ fn event_dir(event: &MouseEvent) -> (Vector3<f64>, f64) {
|
||||||
Vector3::new(
|
Vector3::new(
|
||||||
FOCAL_SLOPE * (2.0*(f64::from(event.client_x()) - rect.left()) - width) / shortdim,
|
FOCAL_SLOPE * (2.0*(f64::from(event.client_x()) - rect.left()) - width) / shortdim,
|
||||||
FOCAL_SLOPE * (2.0*(rect.bottom() - f64::from(event.client_y())) - height) / shortdim,
|
FOCAL_SLOPE * (2.0*(rect.bottom() - f64::from(event.client_y())) - height) / shortdim,
|
||||||
-1.0
|
-1.0,
|
||||||
),
|
),
|
||||||
FOCAL_SLOPE * 2.0 / shortdim
|
FOCAL_SLOPE * 2.0 / shortdim,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,14 +464,14 @@ pub fn Display() -> View {
|
||||||
let sphere_program = set_up_program(
|
let sphere_program = set_up_program(
|
||||||
&ctx,
|
&ctx,
|
||||||
include_str!("identity.vert"),
|
include_str!("identity.vert"),
|
||||||
include_str!("spheres.frag")
|
include_str!("spheres.frag"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// set up the point rendering program
|
// set up the point rendering program
|
||||||
let point_program = set_up_program(
|
let point_program = set_up_program(
|
||||||
&ctx,
|
&ctx,
|
||||||
include_str!("point.vert"),
|
include_str!("point.vert"),
|
||||||
include_str!("point.frag")
|
include_str!("point.frag"),
|
||||||
);
|
);
|
||||||
|
|
||||||
/* DEBUG */
|
/* DEBUG */
|
||||||
|
@ -467,7 +488,7 @@ pub fn Display() -> View {
|
||||||
// capped at 1024 elements
|
// capped at 1024 elements
|
||||||
console::log_2(
|
console::log_2(
|
||||||
&ctx.get_parameter(WebGl2RenderingContext::MAX_FRAGMENT_UNIFORM_VECTORS).unwrap(),
|
&ctx.get_parameter(WebGl2RenderingContext::MAX_FRAGMENT_UNIFORM_VECTORS).unwrap(),
|
||||||
&JsValue::from("uniform vectors available")
|
&JsValue::from("uniform vectors available"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// find the sphere program's vertex attribute
|
// find the sphere program's vertex attribute
|
||||||
|
@ -503,7 +524,7 @@ pub fn Display() -> View {
|
||||||
// southeast triangle
|
// southeast triangle
|
||||||
-1.0, -1.0, 0.0,
|
-1.0, -1.0, 0.0,
|
||||||
1.0, 1.0, 0.0,
|
1.0, 1.0, 0.0,
|
||||||
1.0, -1.0, 0.0
|
1.0, -1.0, 0.0,
|
||||||
];
|
];
|
||||||
let viewport_position_buffer = load_new_buffer(&ctx, &viewport_positions);
|
let viewport_position_buffer = load_new_buffer(&ctx, &viewport_positions);
|
||||||
|
|
||||||
|
@ -596,7 +617,7 @@ pub fn Display() -> View {
|
||||||
vec![
|
vec![
|
||||||
ElementMotion {
|
ElementMotion {
|
||||||
element: sel,
|
element: sel,
|
||||||
velocity: elt_motion.as_view()
|
velocity: elt_motion.as_view(),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -629,7 +650,7 @@ pub fn Display() -> View {
|
||||||
0.0, 1.0, 0.0, 0.0, 0.0,
|
0.0, 1.0, 0.0, 0.0, 0.0,
|
||||||
0.0, 0.0, 1.0, 0.0, u,
|
0.0, 0.0, 1.0, 0.0, u,
|
||||||
0.0, 0.0, 2.0*u, 1.0, u*u,
|
0.0, 0.0, 2.0*u, 1.0, u*u,
|
||||||
0.0, 0.0, 0.0, 0.0, 1.0
|
0.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
])
|
])
|
||||||
};
|
};
|
||||||
let asm_to_world = &location * &orientation;
|
let asm_to_world = &location * &orientation;
|
||||||
|
@ -668,19 +689,19 @@ pub fn Display() -> View {
|
||||||
let v = &sphere_reps_world[n];
|
let v = &sphere_reps_world[n];
|
||||||
ctx.uniform3fv_with_f32_array(
|
ctx.uniform3fv_with_f32_array(
|
||||||
sphere_sp_locs[n].as_ref(),
|
sphere_sp_locs[n].as_ref(),
|
||||||
v.rows(0, 3).as_slice()
|
v.rows(0, 3).as_slice(),
|
||||||
);
|
);
|
||||||
ctx.uniform2fv_with_f32_array(
|
ctx.uniform2fv_with_f32_array(
|
||||||
sphere_lt_locs[n].as_ref(),
|
sphere_lt_locs[n].as_ref(),
|
||||||
v.rows(3, 2).as_slice()
|
v.rows(3, 2).as_slice(),
|
||||||
);
|
);
|
||||||
ctx.uniform4fv_with_f32_array(
|
ctx.uniform4fv_with_f32_array(
|
||||||
sphere_color_locs[n].as_ref(),
|
sphere_color_locs[n].as_ref(),
|
||||||
&scene.spheres.colors_with_opacity[n]
|
&scene.spheres.colors_with_opacity[n],
|
||||||
);
|
);
|
||||||
ctx.uniform1f(
|
ctx.uniform1f(
|
||||||
sphere_highlight_locs[n].as_ref(),
|
sphere_highlight_locs[n].as_ref(),
|
||||||
scene.spheres.highlights[n]
|
scene.spheres.highlights[n],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -773,7 +794,7 @@ pub fn Display() -> View {
|
||||||
"ArrowLeft" if shift => roll_ccw.set(value),
|
"ArrowLeft" if shift => roll_ccw.set(value),
|
||||||
"ArrowRight" => yaw_right.set(value),
|
"ArrowRight" => yaw_right.set(value),
|
||||||
"ArrowLeft" => yaw_left.set(value),
|
"ArrowLeft" => yaw_left.set(value),
|
||||||
_ => navigating = false
|
_ => navigating = false,
|
||||||
};
|
};
|
||||||
if navigating {
|
if navigating {
|
||||||
scene_changed.set(true);
|
scene_changed.set(true);
|
||||||
|
@ -793,7 +814,7 @@ pub fn Display() -> View {
|
||||||
"s" | "S" => translate_neg_y.set(value),
|
"s" | "S" => translate_neg_y.set(value),
|
||||||
"]" | "}" => shrink_neg.set(value),
|
"]" | "}" => shrink_neg.set(value),
|
||||||
"[" | "{" => shrink_pos.set(value),
|
"[" | "{" => shrink_pos.set(value),
|
||||||
_ => manipulating = false
|
_ => manipulating = false,
|
||||||
};
|
};
|
||||||
if manipulating {
|
if manipulating {
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
|
@ -805,12 +826,12 @@ pub fn Display() -> View {
|
||||||
// switch back to integer-valued parameters when that becomes possible
|
// switch back to integer-valued parameters when that becomes possible
|
||||||
// again
|
// again
|
||||||
canvas(
|
canvas(
|
||||||
ref=display,
|
ref = display,
|
||||||
id="display",
|
id = "display",
|
||||||
width="600",
|
width = "600",
|
||||||
height="600",
|
height = "600",
|
||||||
tabindex="0",
|
tabindex = "0",
|
||||||
on:keydown=move |event: KeyboardEvent| {
|
on:keydown = move |event: KeyboardEvent| {
|
||||||
if event.key() == "Shift" {
|
if event.key() == "Shift" {
|
||||||
// swap navigation inputs
|
// swap navigation inputs
|
||||||
roll_cw.set(yaw_right.get());
|
roll_cw.set(yaw_right.get());
|
||||||
|
@ -836,7 +857,7 @@ pub fn Display() -> View {
|
||||||
set_manip_signal(&event, 1.0);
|
set_manip_signal(&event, 1.0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
on:keyup=move |event: KeyboardEvent| {
|
on:keyup = move |event: KeyboardEvent| {
|
||||||
if event.key() == "Shift" {
|
if event.key() == "Shift" {
|
||||||
// swap navigation inputs
|
// swap navigation inputs
|
||||||
yaw_right.set(roll_cw.get());
|
yaw_right.set(roll_cw.get());
|
||||||
|
@ -858,7 +879,7 @@ pub fn Display() -> View {
|
||||||
set_manip_signal(&event, 0.0);
|
set_manip_signal(&event, 0.0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
on:blur=move |_| {
|
on:blur = move |_| {
|
||||||
pitch_up.set(0.0);
|
pitch_up.set(0.0);
|
||||||
pitch_down.set(0.0);
|
pitch_down.set(0.0);
|
||||||
yaw_right.set(0.0);
|
yaw_right.set(0.0);
|
||||||
|
@ -866,7 +887,7 @@ pub fn Display() -> View {
|
||||||
roll_ccw.set(0.0);
|
roll_ccw.set(0.0);
|
||||||
roll_cw.set(0.0);
|
roll_cw.set(0.0);
|
||||||
},
|
},
|
||||||
on:click=move |event: MouseEvent| {
|
on:click = move |event: MouseEvent| {
|
||||||
// find the nearest element along the pointer direction
|
// find the nearest element along the pointer direction
|
||||||
let (dir, pixel_size) = event_dir(&event);
|
let (dir, pixel_size) = event_dir(&event);
|
||||||
console::log_1(&JsValue::from(dir.to_string()));
|
console::log_1(&JsValue::from(dir.to_string()));
|
||||||
|
@ -883,18 +904,18 @@ pub fn Display() -> View {
|
||||||
clicked = Some((elt, depth))
|
clicked = Some((elt, depth))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => clicked = Some((elt, depth))
|
None => clicked = Some((elt, depth)),
|
||||||
}
|
},
|
||||||
None => ()
|
None => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we clicked something, select it
|
// if we clicked something, select it
|
||||||
match clicked {
|
match clicked {
|
||||||
Some((elt, _)) => state.select(&elt, event.shift_key()),
|
Some((elt, _)) => state.select(&elt, event.shift_key()),
|
||||||
None => state.selection.update(|sel| sel.clear())
|
None => state.selection.update(|sel| sel.clear()),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,7 @@
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use web_sys::{
|
use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::JsCast};
|
||||||
KeyboardEvent,
|
|
||||||
MouseEvent,
|
|
||||||
wasm_bindgen::JsCast
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -13,7 +9,7 @@ use crate::{
|
||||||
Element,
|
Element,
|
||||||
HalfCurvatureRegulator,
|
HalfCurvatureRegulator,
|
||||||
InversiveDistanceRegulator,
|
InversiveDistanceRegulator,
|
||||||
Regulator
|
Regulator,
|
||||||
},
|
},
|
||||||
specified::SpecifiedValue
|
specified::SpecifiedValue
|
||||||
};
|
};
|
||||||
|
@ -49,8 +45,8 @@ fn RegulatorInput(regulator: Rc<dyn Regulator>) -> View {
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
input(
|
input(
|
||||||
r#type="text",
|
r#type = "text",
|
||||||
class=move || {
|
class = move || {
|
||||||
if valid.get() {
|
if valid.get() {
|
||||||
set_point.with(|set_pt| {
|
set_point.with(|set_pt| {
|
||||||
if set_pt.is_present() {
|
if set_pt.is_present() {
|
||||||
|
@ -63,27 +59,27 @@ fn RegulatorInput(regulator: Rc<dyn Regulator>) -> View {
|
||||||
"regulator-input invalid"
|
"regulator-input invalid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
placeholder=measurement.with(|result| result.to_string()),
|
placeholder = measurement.with(|result| result.to_string()),
|
||||||
bind:value=value,
|
bind:value = value,
|
||||||
on:change=move |_| {
|
on:change = move |_| {
|
||||||
valid.set(
|
valid.set(
|
||||||
match SpecifiedValue::try_from(value.get_clone_untracked()) {
|
match SpecifiedValue::try_from(value.get_clone_untracked()) {
|
||||||
Ok(set_pt) => {
|
Ok(set_pt) => {
|
||||||
set_point.set(set_pt);
|
set_point.set(set_pt);
|
||||||
true
|
true
|
||||||
}
|
},
|
||||||
Err(_) => false
|
Err(_) => false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
on:keydown={
|
on:keydown = {
|
||||||
move |event: KeyboardEvent| {
|
move |event: KeyboardEvent| {
|
||||||
match event.key().as_str() {
|
match event.key().as_str() {
|
||||||
"Escape" => reset_value(),
|
"Escape" => reset_value(),
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,11 +96,11 @@ impl OutlineItem for InversiveDistanceRegulator {
|
||||||
self.subjects[0].label()
|
self.subjects[0].label()
|
||||||
}.clone();
|
}.clone();
|
||||||
view! {
|
view! {
|
||||||
li(class="regulator") {
|
li(class = "regulator") {
|
||||||
div(class="regulator-label") { (other_subject_label) }
|
div(class = "regulator-label") { (other_subject_label) }
|
||||||
div(class="regulator-type") { "Inversive distance" }
|
div(class = "regulator-type") { "Inversive distance" }
|
||||||
RegulatorInput(regulator=self)
|
RegulatorInput(regulator = self)
|
||||||
div(class="status")
|
div(class = "status")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,11 +109,11 @@ impl OutlineItem for InversiveDistanceRegulator {
|
||||||
impl OutlineItem for HalfCurvatureRegulator {
|
impl OutlineItem for HalfCurvatureRegulator {
|
||||||
fn outline_item(self: Rc<Self>, _element: &Rc<dyn Element>) -> View {
|
fn outline_item(self: Rc<Self>, _element: &Rc<dyn Element>) -> View {
|
||||||
view! {
|
view! {
|
||||||
li(class="regulator") {
|
li(class = "regulator") {
|
||||||
div(class="regulator-label") // for spacing
|
div(class = "regulator-label") // for spacing
|
||||||
div(class="regulator-type") { "Half-curvature" }
|
div(class = "regulator-type") { "Half-curvature" }
|
||||||
RegulatorInput(regulator=self)
|
RegulatorInput(regulator = self)
|
||||||
div(class="status")
|
div(class = "status")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,10 +152,10 @@ fn ElementOutlineItem(element: Rc<dyn Element>) -> View {
|
||||||
let details_node = create_node_ref();
|
let details_node = create_node_ref();
|
||||||
view! {
|
view! {
|
||||||
li {
|
li {
|
||||||
details(ref=details_node) {
|
details(ref = details_node) {
|
||||||
summary(
|
summary(
|
||||||
class=class.get(),
|
class = class.get(),
|
||||||
on:keydown={
|
on:keydown = {
|
||||||
let element_for_handler = element.clone();
|
let element_for_handler = element.clone();
|
||||||
move |event: KeyboardEvent| {
|
move |event: KeyboardEvent| {
|
||||||
match event.key().as_str() {
|
match event.key().as_str() {
|
||||||
|
@ -179,18 +175,18 @@ fn ElementOutlineItem(element: Rc<dyn Element>) -> View {
|
||||||
.unchecked_into::<web_sys::Element>()
|
.unchecked_into::<web_sys::Element>()
|
||||||
.remove_attribute("open");
|
.remove_attribute("open");
|
||||||
},
|
},
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
div(
|
div(
|
||||||
class="element-switch",
|
class = "element-switch",
|
||||||
on:click=|event: MouseEvent| event.stop_propagation()
|
on:click = |event: MouseEvent| event.stop_propagation()
|
||||||
)
|
)
|
||||||
div(
|
div(
|
||||||
class="element",
|
class = "element",
|
||||||
on:click={
|
on:click = {
|
||||||
let state_for_handler = state.clone();
|
let state_for_handler = state.clone();
|
||||||
let element_for_handler = element.clone();
|
let element_for_handler = element.clone();
|
||||||
move |event: MouseEvent| {
|
move |event: MouseEvent| {
|
||||||
|
@ -200,20 +196,20 @@ fn ElementOutlineItem(element: Rc<dyn Element>) -> View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
div(class="element-label") { (label) }
|
div(class = "element-label") { (label) }
|
||||||
div(class="element-representation") { (rep_components) }
|
div(class = "element-representation") { (rep_components) }
|
||||||
input(
|
input(
|
||||||
r#type="checkbox",
|
r#type = "checkbox",
|
||||||
bind:checked=element.ghost(),
|
bind:checked = element.ghost(),
|
||||||
on:click=|event: MouseEvent| event.stop_propagation()
|
on:click = |event: MouseEvent| event.stop_propagation()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ul(class="regulators") {
|
ul(class = "regulators") {
|
||||||
Keyed(
|
Keyed(
|
||||||
list=regulator_list,
|
list = regulator_list,
|
||||||
view=move |reg| reg.outline_item(&element),
|
view = move |reg| reg.outline_item(&element),
|
||||||
key=|reg| reg.serial()
|
key = |reg| reg.serial()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,18 +242,18 @@ pub fn Outline() -> View {
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
ul(
|
ul(
|
||||||
id="outline",
|
id = "outline",
|
||||||
on:click={
|
on:click = {
|
||||||
let state = use_context::<AppState>();
|
let state = use_context::<AppState>();
|
||||||
move |_| state.selection.update(|sel| sel.clear())
|
move |_| state.selection.update(|sel| sel.clear())
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Keyed(
|
Keyed(
|
||||||
list=element_list,
|
list = element_list,
|
||||||
view=|elt| view! {
|
view = |elt| view! {
|
||||||
ElementOutlineItem(element=elt)
|
ElementOutlineItem(element = elt)
|
||||||
},
|
},
|
||||||
key=|elt| elt.serial()
|
key = |elt| elt.serial()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
941
app-proto/src/components/test_assembly_chooser.rs
Normal file
941
app-proto/src/components/test_assembly_chooser.rs
Normal file
|
@ -0,0 +1,941 @@
|
||||||
|
use itertools::izip;
|
||||||
|
use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc};
|
||||||
|
use nalgebra::Vector3;
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
use web_sys::{console, wasm_bindgen::JsValue};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
assembly::{
|
||||||
|
Assembly,
|
||||||
|
Element,
|
||||||
|
ElementColor,
|
||||||
|
InversiveDistanceRegulator,
|
||||||
|
Point,
|
||||||
|
Sphere,
|
||||||
|
},
|
||||||
|
engine,
|
||||||
|
engine::DescentHistory,
|
||||||
|
specified::SpecifiedValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- loaders ---
|
||||||
|
|
||||||
|
/* DEBUG */
|
||||||
|
// each of these functions loads an example assembly for testing. once we've
|
||||||
|
// done more work on saving and loading assemblies, we should come back to this
|
||||||
|
// code to see if it can be simplified
|
||||||
|
|
||||||
|
fn load_general(assembly: &Assembly) {
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
String::from("gemini_a"),
|
||||||
|
String::from("Castor"),
|
||||||
|
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||||
|
engine::sphere(0.5, 0.5, 0.0, 1.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
String::from("gemini_b"),
|
||||||
|
String::from("Pollux"),
|
||||||
|
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||||
|
engine::sphere(-0.5, -0.5, 0.0, 1.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
String::from("ursa_major"),
|
||||||
|
String::from("Ursa major"),
|
||||||
|
[0.25_f32, 0.00_f32, 1.00_f32],
|
||||||
|
engine::sphere(-0.5, 0.5, 0.0, 0.75),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
String::from("ursa_minor"),
|
||||||
|
String::from("Ursa minor"),
|
||||||
|
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||||
|
engine::sphere(0.5, -0.5, 0.0, 0.5),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
String::from("moon_deimos"),
|
||||||
|
String::from("Deimos"),
|
||||||
|
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||||
|
engine::sphere(0.0, 0.15, 1.0, 0.25),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
String::from("moon_phobos"),
|
||||||
|
String::from("Phobos"),
|
||||||
|
[0.00_f32, 0.75_f32, 0.50_f32],
|
||||||
|
engine::sphere(0.0, -0.15, -1.0, 0.25),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_low_curvature(assembly: &Assembly) {
|
||||||
|
// create the spheres
|
||||||
|
let a = 0.75_f64.sqrt();
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"central".to_string(),
|
||||||
|
"Central".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(0.0, 0.0, 0.0, 1.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"assemb_plane".to_string(),
|
||||||
|
"Assembly plane".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"side1".to_string(),
|
||||||
|
"Side 1".to_string(),
|
||||||
|
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||||
|
engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"side2".to_string(),
|
||||||
|
"Side 2".to_string(),
|
||||||
|
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||||
|
engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"side3".to_string(),
|
||||||
|
"Side 3".to_string(),
|
||||||
|
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||||
|
engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"corner1".to_string(),
|
||||||
|
"Corner 1".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"corner2".to_string(),
|
||||||
|
"Corner 2".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
String::from("corner3"),
|
||||||
|
String::from("Corner 3"),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// impose the desired tangencies and make the sides planar
|
||||||
|
let index_range = 1..=3;
|
||||||
|
let [central, assemb_plane] = ["central", "assemb_plane"].map(
|
||||||
|
|id| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[id].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let sides = index_range.clone().map(
|
||||||
|
|k| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("side{k}")].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let corners = index_range.map(
|
||||||
|
|k| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("corner{k}")].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) {
|
||||||
|
// fix the curvature of each plane
|
||||||
|
let curvature = plane.regulators().with_untracked(
|
||||||
|
|regs| regs.first().unwrap().clone()
|
||||||
|
);
|
||||||
|
curvature.set_point().set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||||
|
}
|
||||||
|
let all_perpendicular = [central.clone()].into_iter()
|
||||||
|
.chain(sides.clone())
|
||||||
|
.chain(corners.clone());
|
||||||
|
for sphere in all_perpendicular {
|
||||||
|
// make each side and packed sphere perpendicular to the assembly plane
|
||||||
|
let right_angle = InversiveDistanceRegulator::new([sphere, assemb_plane.clone()]);
|
||||||
|
right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(right_angle));
|
||||||
|
}
|
||||||
|
for sphere in sides.clone().chain(corners.clone()) {
|
||||||
|
// make each side and corner sphere tangent to the central sphere
|
||||||
|
let tangency = InversiveDistanceRegulator::new([sphere.clone(), central.clone()]);
|
||||||
|
tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(tangency));
|
||||||
|
}
|
||||||
|
for (side_index, side) in sides.enumerate() {
|
||||||
|
// make each side tangent to the two adjacent corner spheres
|
||||||
|
for (corner_index, corner) in corners.clone().enumerate() {
|
||||||
|
if side_index != corner_index {
|
||||||
|
let tangency = InversiveDistanceRegulator::new([side.clone(), corner]);
|
||||||
|
tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(tangency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_pointed(assembly: &Assembly) {
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Point::new(
|
||||||
|
format!("point_front"),
|
||||||
|
format!("Front point"),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::point(0.0, 0.0, FRAC_1_SQRT_2),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Point::new(
|
||||||
|
format!("point_back"),
|
||||||
|
format!("Back point"),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::point(0.0, 0.0, -FRAC_1_SQRT_2),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for index_x in 0..=1 {
|
||||||
|
for index_y in 0..=1 {
|
||||||
|
let x = index_x as f64 - 0.5;
|
||||||
|
let y = index_y as f64 - 0.5;
|
||||||
|
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
format!("sphere{index_x}{index_y}"),
|
||||||
|
format!("Sphere {index_x}{index_y}"),
|
||||||
|
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
||||||
|
engine::sphere(x, y, 0.0, 1.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Point::new(
|
||||||
|
format!("point{index_x}{index_y}"),
|
||||||
|
format!("Point {index_x}{index_y}"),
|
||||||
|
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
||||||
|
engine::point(x, y, 0.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to finish describing the tridiminished icosahedron, set the inversive
|
||||||
|
// distance regulators as follows:
|
||||||
|
// A-A -0.25
|
||||||
|
// A-B "
|
||||||
|
// B-C "
|
||||||
|
// C-C "
|
||||||
|
// A-C -0.25 * φ^2 = -0.6545084971874737
|
||||||
|
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];
|
||||||
|
const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32];
|
||||||
|
let vertices = [
|
||||||
|
Point::new(
|
||||||
|
"a1".to_string(),
|
||||||
|
"A₁".to_string(),
|
||||||
|
COLOR_A,
|
||||||
|
engine::point(0.25, 0.75, 0.75),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
"a2".to_string(),
|
||||||
|
"A₂".to_string(),
|
||||||
|
COLOR_A,
|
||||||
|
engine::point(0.75, 0.25, 0.75),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
"a3".to_string(),
|
||||||
|
"A₃".to_string(),
|
||||||
|
COLOR_A,
|
||||||
|
engine::point(0.75, 0.75, 0.25),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
"b1".to_string(),
|
||||||
|
"B₁".to_string(),
|
||||||
|
COLOR_B,
|
||||||
|
engine::point(0.75, -0.25, -0.25),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
"b2".to_string(),
|
||||||
|
"B₂".to_string(),
|
||||||
|
COLOR_B,
|
||||||
|
engine::point(-0.25, 0.75, -0.25),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
"b3".to_string(),
|
||||||
|
"B₃".to_string(),
|
||||||
|
COLOR_B,
|
||||||
|
engine::point(-0.25, -0.25, 0.75),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
"c1".to_string(),
|
||||||
|
"C₁".to_string(),
|
||||||
|
COLOR_C,
|
||||||
|
engine::point(0.0, -1.0, -1.0),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
"c2".to_string(),
|
||||||
|
"C₂".to_string(),
|
||||||
|
COLOR_C,
|
||||||
|
engine::point(-1.0, 0.0, -1.0),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
"c3".to_string(),
|
||||||
|
"C₃".to_string(),
|
||||||
|
COLOR_C,
|
||||||
|
engine::point(-1.0, -1.0, 0.0),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for vertex in vertices {
|
||||||
|
let _ = assembly.try_insert_element(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the faces
|
||||||
|
const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
|
||||||
|
let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt();
|
||||||
|
let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6;
|
||||||
|
let faces = [
|
||||||
|
Sphere::new(
|
||||||
|
"face1".to_string(),
|
||||||
|
"Face 1".to_string(),
|
||||||
|
COLOR_FACE,
|
||||||
|
engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0),
|
||||||
|
),
|
||||||
|
Sphere::new(
|
||||||
|
"face2".to_string(),
|
||||||
|
"Face 2".to_string(),
|
||||||
|
COLOR_FACE,
|
||||||
|
engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0),
|
||||||
|
),
|
||||||
|
Sphere::new(
|
||||||
|
"face3".to_string(),
|
||||||
|
"Face 3".to_string(),
|
||||||
|
COLOR_FACE,
|
||||||
|
engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for face in faces {
|
||||||
|
face.ghost().set(true);
|
||||||
|
let _ = assembly.try_insert_element(face);
|
||||||
|
}
|
||||||
|
|
||||||
|
let index_range = 1..=3;
|
||||||
|
for j in index_range.clone() {
|
||||||
|
// make each face planar
|
||||||
|
let face = assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("face{j}")].clone()
|
||||||
|
);
|
||||||
|
let curvature_regulator = face.regulators().with_untracked(
|
||||||
|
|regs| regs.first().unwrap().clone()
|
||||||
|
);
|
||||||
|
curvature_regulator.set_point().set(
|
||||||
|
SpecifiedValue::try_from("0".to_string()).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// put each A vertex on the face it belongs to
|
||||||
|
let vertex_a = assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("a{j}")].clone()
|
||||||
|
);
|
||||||
|
let incidence_a = InversiveDistanceRegulator::new([face.clone(), vertex_a.clone()]);
|
||||||
|
incidence_a.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(incidence_a));
|
||||||
|
|
||||||
|
// regulate the B-C vertex distances
|
||||||
|
let vertices_bc = ["b", "c"].map(
|
||||||
|
|series| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("{series}{j}")].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assembly.insert_regulator(
|
||||||
|
Rc::new(InversiveDistanceRegulator::new(vertices_bc))
|
||||||
|
);
|
||||||
|
|
||||||
|
// get the pair of indices adjacent to `j`
|
||||||
|
let adjacent_indices = [j % 3 + 1, (j + 1) % 3 + 1];
|
||||||
|
|
||||||
|
for k in adjacent_indices.clone() {
|
||||||
|
for series in ["b", "c"] {
|
||||||
|
// put each B and C vertex on the faces it belongs to
|
||||||
|
let vertex = assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("{series}{k}")].clone()
|
||||||
|
);
|
||||||
|
let incidence = InversiveDistanceRegulator::new([face.clone(), vertex.clone()]);
|
||||||
|
incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(incidence));
|
||||||
|
|
||||||
|
// regulate the A-B and A-C vertex distances
|
||||||
|
assembly.insert_regulator(
|
||||||
|
Rc::new(InversiveDistanceRegulator::new([vertex_a.clone(), vertex]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// regulate the A-A and C-C vertex distances
|
||||||
|
let adjacent_pairs = ["a", "c"].map(
|
||||||
|
|series| adjacent_indices.map(
|
||||||
|
|index| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("{series}{index}")].clone()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for pair in adjacent_pairs {
|
||||||
|
assembly.insert_regulator(
|
||||||
|
Rc::new(InversiveDistanceRegulator::new(pair))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to finish describing the dodecahedral circle packing, set the inversive
|
||||||
|
// distance regulators to -1. some of the regulators have already been set
|
||||||
|
fn load_dodecahedral_packing(assembly: &Assembly) {
|
||||||
|
// add the substrate
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"substrate".to_string(),
|
||||||
|
"Substrate".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(0.0, 0.0, 0.0, 1.0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let substrate = assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id["substrate"].clone()
|
||||||
|
);
|
||||||
|
|
||||||
|
// fix the substrate's curvature
|
||||||
|
substrate.regulators().with_untracked(
|
||||||
|
|regs| regs.first().unwrap().clone()
|
||||||
|
).set_point().set(
|
||||||
|
SpecifiedValue::try_from("0.5".to_string()).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// add the circles to be packed
|
||||||
|
const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32];
|
||||||
|
const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32];
|
||||||
|
const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32];
|
||||||
|
let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized
|
||||||
|
let phi_inv = 1.0 / phi;
|
||||||
|
let coord_scale = (phi + 2.0).sqrt();
|
||||||
|
let face_scales = [phi_inv, (13.0 / 12.0) / coord_scale];
|
||||||
|
let face_radii = [phi_inv, 5.0 / 12.0];
|
||||||
|
let mut faces = Vec::<Rc<dyn Element>>::new();
|
||||||
|
let subscripts = ["₀", "₁"];
|
||||||
|
for j in 0..2 {
|
||||||
|
for k in 0..2 {
|
||||||
|
let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0);
|
||||||
|
let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi;
|
||||||
|
|
||||||
|
let id_num = format!("{j}{k}");
|
||||||
|
let label_sub = format!("{}{}", subscripts[j], subscripts[k]);
|
||||||
|
|
||||||
|
// add the A face
|
||||||
|
let id_a = format!("a{id_num}");
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
id_a.clone(),
|
||||||
|
format!("A{label_sub}"),
|
||||||
|
COLOR_A,
|
||||||
|
engine::sphere(0.0, small_coord, big_coord, face_radii[k]),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
faces.push(
|
||||||
|
assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&id_a].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// add the B face
|
||||||
|
let id_b = format!("b{id_num}");
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
id_b.clone(),
|
||||||
|
format!("B{label_sub}"),
|
||||||
|
COLOR_B,
|
||||||
|
engine::sphere(small_coord, big_coord, 0.0, face_radii[k]),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
faces.push(
|
||||||
|
assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&id_b].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// add the C face
|
||||||
|
let id_c = format!("c{id_num}");
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
id_c.clone(),
|
||||||
|
format!("C{label_sub}"),
|
||||||
|
COLOR_C,
|
||||||
|
engine::sphere(big_coord, 0.0, small_coord, face_radii[k]),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
faces.push(
|
||||||
|
assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&id_c].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make each face sphere perpendicular to the substrate
|
||||||
|
for face in faces {
|
||||||
|
let right_angle = InversiveDistanceRegulator::new([face, substrate.clone()]);
|
||||||
|
right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(right_angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up the tangencies that define the packing
|
||||||
|
for [long_edge_plane, short_edge_plane] in [["a", "b"], ["b", "c"], ["c", "a"]] {
|
||||||
|
for k in 0..2 {
|
||||||
|
let long_edge_ids = [
|
||||||
|
format!("{long_edge_plane}{k}0"),
|
||||||
|
format!("{long_edge_plane}{k}1")
|
||||||
|
];
|
||||||
|
let short_edge_ids = [
|
||||||
|
format!("{short_edge_plane}0{k}"),
|
||||||
|
format!("{short_edge_plane}1{k}")
|
||||||
|
];
|
||||||
|
let [long_edge, short_edge] = [long_edge_ids, short_edge_ids].map(
|
||||||
|
|edge_ids| edge_ids.map(
|
||||||
|
|id| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&id].clone()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// set up the short-edge tangency
|
||||||
|
let short_tangency = InversiveDistanceRegulator::new(short_edge.clone());
|
||||||
|
if k == 0 {
|
||||||
|
short_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap());
|
||||||
|
}
|
||||||
|
assembly.insert_regulator(Rc::new(short_tangency));
|
||||||
|
|
||||||
|
// set up the side tangencies
|
||||||
|
for i in 0..2 {
|
||||||
|
for j in 0..2 {
|
||||||
|
let side_tangency = InversiveDistanceRegulator::new(
|
||||||
|
[long_edge[i].clone(), short_edge[j].clone()]
|
||||||
|
);
|
||||||
|
if i == 0 && k == 0 {
|
||||||
|
side_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap());
|
||||||
|
}
|
||||||
|
assembly.insert_regulator(Rc::new(side_tangency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the initial configuration of this test assembly deliberately violates the
|
||||||
|
// constraints, so loading the assembly will trigger a non-trivial realization
|
||||||
|
fn load_balanced(assembly: &Assembly) {
|
||||||
|
// create the spheres
|
||||||
|
const R_OUTER: f64 = 10.0;
|
||||||
|
const R_INNER: f64 = 4.0;
|
||||||
|
let spheres = [
|
||||||
|
Sphere::new(
|
||||||
|
"outer".to_string(),
|
||||||
|
"Outer".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(0.0, 0.0, 0.0, R_OUTER),
|
||||||
|
),
|
||||||
|
Sphere::new(
|
||||||
|
"a".to_string(),
|
||||||
|
"A".to_string(),
|
||||||
|
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||||
|
engine::sphere(0.0, 4.0, 0.0, R_INNER),
|
||||||
|
),
|
||||||
|
Sphere::new(
|
||||||
|
"b".to_string(),
|
||||||
|
"B".to_string(),
|
||||||
|
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||||
|
engine::sphere(0.0, -4.0, 0.0, R_INNER),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for sphere in spheres {
|
||||||
|
let _ = assembly.try_insert_element(sphere);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get references to the spheres
|
||||||
|
let [outer, a, b] = ["outer", "a", "b"].map(
|
||||||
|
|id| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[id].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// fix the diameters of the outer, sun, and moon spheres
|
||||||
|
for (sphere, radius) in [
|
||||||
|
(outer.clone(), R_OUTER),
|
||||||
|
(a.clone(), R_INNER),
|
||||||
|
(b.clone(), R_INNER),
|
||||||
|
] {
|
||||||
|
let curvature_regulator = sphere.regulators().with_untracked(
|
||||||
|
|regs| regs.first().unwrap().clone()
|
||||||
|
);
|
||||||
|
let curvature = 0.5 / radius;
|
||||||
|
curvature_regulator.set_point().set(
|
||||||
|
SpecifiedValue::try_from(curvature.to_string()).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the inversive distances between the spheres. as described above, the
|
||||||
|
// initial configuration deliberately violates these constraints
|
||||||
|
for inner in [a, b] {
|
||||||
|
let tangency = InversiveDistanceRegulator::new([outer.clone(), inner]);
|
||||||
|
tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(tangency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(assembly: &Assembly) {
|
||||||
|
// create a point almost at the origin and a sphere centered on the origin
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Point::new(
|
||||||
|
"point".to_string(),
|
||||||
|
"Point".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::point(1e-9, 0.0, 0.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
Sphere::new(
|
||||||
|
"sphere".to_string(),
|
||||||
|
"Sphere".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(0.0, 0.0, 0.0, 1.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// get references to the elements
|
||||||
|
let point_and_sphere = ["point", "sphere"].map(
|
||||||
|
|id| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[id].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// put the point on the sphere
|
||||||
|
let incidence = InversiveDistanceRegulator::new(point_and_sphere);
|
||||||
|
incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(incidence));
|
||||||
|
}
|
||||||
|
|
||||||
|
// setting the inversive distances between the vertices to -2 gives a regular
|
||||||
|
// tetrahedron with side length 1, whose insphere and circumsphere have radii
|
||||||
|
// 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(assembly: &Assembly) {
|
||||||
|
let index_range = 1..=4;
|
||||||
|
|
||||||
|
// create the spheres
|
||||||
|
const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
|
||||||
|
let spheres = [
|
||||||
|
Sphere::new(
|
||||||
|
"sphere_faces".to_string(),
|
||||||
|
"Insphere".to_string(),
|
||||||
|
GRAY,
|
||||||
|
engine::sphere(0.0, 0.0, 0.0, 0.5),
|
||||||
|
),
|
||||||
|
Sphere::new(
|
||||||
|
"sphere_vertices".to_string(),
|
||||||
|
"Circumsphere".to_string(),
|
||||||
|
GRAY,
|
||||||
|
engine::sphere(0.0, 0.0, 0.0, 0.25),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for sphere in spheres {
|
||||||
|
let _ = assembly.try_insert_element(sphere);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the vertices
|
||||||
|
let vertices = izip!(
|
||||||
|
index_range.clone(),
|
||||||
|
[
|
||||||
|
[1.00_f32, 0.50_f32, 0.75_f32],
|
||||||
|
[1.00_f32, 0.75_f32, 0.50_f32],
|
||||||
|
[1.00_f32, 1.00_f32, 0.50_f32],
|
||||||
|
[0.75_f32, 0.50_f32, 1.00_f32],
|
||||||
|
].into_iter(),
|
||||||
|
[
|
||||||
|
engine::point(-0.6, -0.8, -0.6),
|
||||||
|
engine::point(-0.6, 0.8, 0.6),
|
||||||
|
engine::point(0.6, -0.8, 0.6),
|
||||||
|
engine::point(0.6, 0.8, -0.6),
|
||||||
|
].into_iter()
|
||||||
|
).map(
|
||||||
|
|(k, color, representation)| {
|
||||||
|
Point::new(
|
||||||
|
format!("v{k}"),
|
||||||
|
format!("Vertex {k}"),
|
||||||
|
color,
|
||||||
|
representation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
for vertex in vertices {
|
||||||
|
let _ = assembly.try_insert_element(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the faces
|
||||||
|
let base_dir = Vector3::new(1.0, 0.75, 1.0).normalize();
|
||||||
|
let offset = base_dir.dot(&Vector3::new(-0.6, 0.8, 0.6));
|
||||||
|
let faces = izip!(
|
||||||
|
index_range.clone(),
|
||||||
|
[
|
||||||
|
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||||
|
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||||
|
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||||
|
[0.25_f32, 0.00_f32, 1.00_f32],
|
||||||
|
].into_iter(),
|
||||||
|
[
|
||||||
|
engine::sphere_with_offset(base_dir[0], base_dir[1], base_dir[2], offset, 0.0),
|
||||||
|
engine::sphere_with_offset(base_dir[0], -base_dir[1], -base_dir[2], offset, 0.0),
|
||||||
|
engine::sphere_with_offset(-base_dir[0], base_dir[1], -base_dir[2], offset, 0.0),
|
||||||
|
engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0),
|
||||||
|
].into_iter()
|
||||||
|
).map(
|
||||||
|
|(k, color, representation)| {
|
||||||
|
Sphere::new(
|
||||||
|
format!("f{k}"),
|
||||||
|
format!("Face {k}"),
|
||||||
|
color,
|
||||||
|
representation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
for face in faces {
|
||||||
|
face.ghost().set(true);
|
||||||
|
let _ = assembly.try_insert_element(face);
|
||||||
|
}
|
||||||
|
|
||||||
|
// impose the constraints
|
||||||
|
for j in index_range.clone() {
|
||||||
|
let [face_j, vertex_j] = [
|
||||||
|
format!("f{j}"),
|
||||||
|
format!("v{j}"),
|
||||||
|
].map(
|
||||||
|
|id| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&id].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// make the faces planar
|
||||||
|
let curvature_regulator = face_j.regulators().with_untracked(
|
||||||
|
|regs| regs.first().unwrap().clone()
|
||||||
|
);
|
||||||
|
curvature_regulator.set_point().set(
|
||||||
|
SpecifiedValue::try_from("0".to_string()).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
for k in index_range.clone().filter(|&index| index != j) {
|
||||||
|
let vertex_k = assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("v{k}")].clone()
|
||||||
|
);
|
||||||
|
|
||||||
|
// fix the distances between the vertices
|
||||||
|
if j < k {
|
||||||
|
let distance_regulator = InversiveDistanceRegulator::new(
|
||||||
|
[vertex_j.clone(), vertex_k.clone()]
|
||||||
|
);
|
||||||
|
assembly.insert_regulator(Rc::new(distance_regulator));
|
||||||
|
}
|
||||||
|
|
||||||
|
// put the vertices on the faces
|
||||||
|
let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]);
|
||||||
|
incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(incidence_regulator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to finish setting up the problem, fix the following curvatures:
|
||||||
|
// sun 1
|
||||||
|
// moon 5/3 = 1.666666666666666...
|
||||||
|
// chain1 2
|
||||||
|
// a tiny `x` or `z` nudge of the outer sphere reliably prevents realization
|
||||||
|
// failures before they happen, or resolves them after they happen. the result
|
||||||
|
// depends sensitively on the translation direction, suggesting that realization
|
||||||
|
// is failing because the engine is having trouble breaking a symmetry
|
||||||
|
// /* TO DO */
|
||||||
|
// the engine's performance on this problem is scale-dependent! with the current
|
||||||
|
// initial conditions, realization fails for any order of imposing the remaining
|
||||||
|
// curvature constraints. scaling everything up by a factor of ten, as done in
|
||||||
|
// the original problem, makes realization succeed reliably. one potentially
|
||||||
|
// relevant difference is that a lot of the numbers in the current initial
|
||||||
|
// 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(assembly: &Assembly) {
|
||||||
|
let index_range = 1..=6;
|
||||||
|
let colors = [
|
||||||
|
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||||
|
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||||
|
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||||
|
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||||
|
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||||
|
[0.25_f32, 0.00_f32, 1.00_f32],
|
||||||
|
].into_iter();
|
||||||
|
|
||||||
|
// create the spheres
|
||||||
|
let spheres = [
|
||||||
|
Sphere::new(
|
||||||
|
"outer".to_string(),
|
||||||
|
"Outer".to_string(),
|
||||||
|
[0.5_f32, 0.5_f32, 0.5_f32],
|
||||||
|
engine::sphere(0.0, 0.0, 0.0, 1.5),
|
||||||
|
),
|
||||||
|
Sphere::new(
|
||||||
|
"sun".to_string(),
|
||||||
|
"Sun".to_string(),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(0.0, -0.75, 0.0, 0.75),
|
||||||
|
),
|
||||||
|
Sphere::new(
|
||||||
|
"moon".to_string(),
|
||||||
|
"Moon".to_string(),
|
||||||
|
[0.25_f32, 0.25_f32, 0.25_f32],
|
||||||
|
engine::sphere(0.0, 0.75, 0.0, 0.75),
|
||||||
|
),
|
||||||
|
].into_iter().chain(
|
||||||
|
index_range.clone().zip(colors).map(
|
||||||
|
|(k, color)| {
|
||||||
|
let ang = (k as f64) * PI/3.0;
|
||||||
|
Sphere::new(
|
||||||
|
format!("chain{k}"),
|
||||||
|
format!("Chain {k}"),
|
||||||
|
color,
|
||||||
|
engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for sphere in spheres {
|
||||||
|
let _ = assembly.try_insert_element(sphere);
|
||||||
|
}
|
||||||
|
|
||||||
|
// put the outer sphere in ghost mode and fix its curvature
|
||||||
|
let outer = assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id["outer"].clone()
|
||||||
|
);
|
||||||
|
outer.ghost().set(true);
|
||||||
|
let outer_curvature_regulator = outer.regulators().with_untracked(
|
||||||
|
|regs| regs.first().unwrap().clone()
|
||||||
|
);
|
||||||
|
outer_curvature_regulator.set_point().set(
|
||||||
|
SpecifiedValue::try_from((1.0 / 3.0).to_string()).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// impose the desired tangencies
|
||||||
|
let [outer, sun, moon] = ["outer", "sun", "moon"].map(
|
||||||
|
|id| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[id].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let chain = index_range.map(
|
||||||
|
|k| assembly.elements_by_id.with_untracked(
|
||||||
|
|elts_by_id| elts_by_id[&format!("chain{k}")].clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) {
|
||||||
|
for (other_sphere, inversive_distance) in [
|
||||||
|
(outer.clone(), "1"),
|
||||||
|
(sun.clone(), "-1"),
|
||||||
|
(moon.clone(), "-1"),
|
||||||
|
(chain_sphere_next.clone(), "-1"),
|
||||||
|
] {
|
||||||
|
let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]);
|
||||||
|
tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(tangency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let outer_sun_tangency = InversiveDistanceRegulator::new([outer.clone(), sun]);
|
||||||
|
outer_sun_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(outer_sun_tangency));
|
||||||
|
|
||||||
|
let outer_moon_tangency = InversiveDistanceRegulator::new([outer.clone(), moon]);
|
||||||
|
outer_moon_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap());
|
||||||
|
assembly.insert_regulator(Rc::new(outer_moon_tangency));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- chooser ---
|
||||||
|
|
||||||
|
/* DEBUG */
|
||||||
|
#[component]
|
||||||
|
pub fn TestAssemblyChooser() -> View {
|
||||||
|
// create an effect that loads the selected test assembly
|
||||||
|
let assembly_name = create_signal("general".to_string());
|
||||||
|
create_effect(move || {
|
||||||
|
// get name of chosen assembly
|
||||||
|
let name = assembly_name.get_clone();
|
||||||
|
console::log_1(
|
||||||
|
&JsValue::from(format!("Showing assembly \"{}\"", name.clone()))
|
||||||
|
);
|
||||||
|
|
||||||
|
batch(|| {
|
||||||
|
let state = use_context::<AppState>();
|
||||||
|
let assembly = &state.assembly;
|
||||||
|
|
||||||
|
// clear state
|
||||||
|
assembly.regulators.update(|regs| regs.clear());
|
||||||
|
assembly.elements.update(|elts| elts.clear());
|
||||||
|
assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear());
|
||||||
|
assembly.descent_history.set(DescentHistory::new());
|
||||||
|
state.selection.update(|sel| sel.clear());
|
||||||
|
|
||||||
|
// load assembly
|
||||||
|
match name.as_str() {
|
||||||
|
"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),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// build the chooser
|
||||||
|
view! {
|
||||||
|
select(bind:value = assembly_name) {
|
||||||
|
option(value = "general") { "General" }
|
||||||
|
option(value = "low-curvature") { "Low-curvature" }
|
||||||
|
option(value = "pointed") { "Pointed" }
|
||||||
|
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" }
|
||||||
|
option(value = "irisawa-hexlet") { "Irisawa hexlet" }
|
||||||
|
option(value = "empty") { "Empty" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen};
|
use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen};
|
||||||
use std::fmt::{Display, Error, Formatter};
|
use std::fmt::{Display, Error, Formatter};
|
||||||
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
|
||||||
|
|
||||||
// --- elements ---
|
// --- elements ---
|
||||||
|
|
||||||
|
@ -17,7 +16,7 @@ pub fn sphere(center_x: f64, center_y: f64, center_z: f64, radius: f64) -> DVect
|
||||||
center_y / radius,
|
center_y / radius,
|
||||||
center_z / radius,
|
center_z / radius,
|
||||||
0.5 / radius,
|
0.5 / radius,
|
||||||
0.5 * (center_norm_sq / radius - radius)
|
0.5 * (center_norm_sq / radius - radius),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +30,7 @@ pub fn sphere_with_offset(dir_x: f64, dir_y: f64, dir_z: f64, off: f64, curv: f6
|
||||||
norm_sp * dir_y,
|
norm_sp * dir_y,
|
||||||
norm_sp * dir_z,
|
norm_sp * dir_z,
|
||||||
0.5 * curv,
|
0.5 * curv,
|
||||||
off * (1.0 + 0.5 * off * curv)
|
off * (1.0 + 0.5 * off * curv),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,57 +49,23 @@ pub fn project_point_to_normalized(rep: &mut DVector<f64>) {
|
||||||
rep.scale_mut(0.5 / rep[3]);
|
rep.scale_mut(0.5 / rep[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// given a sphere's representation vector, change the sphere's half-curvature to
|
|
||||||
// `half-curv` and then restore normalization by contracting the representation
|
|
||||||
// vector toward the curvature axis
|
|
||||||
pub fn change_half_curvature(rep: &mut DVector<f64>, half_curv: f64) {
|
|
||||||
// set the sphere's half-curvature to the desired value
|
|
||||||
rep[3] = half_curv;
|
|
||||||
|
|
||||||
// restore normalization by contracting toward the curvature axis
|
|
||||||
const SIZE_THRESHOLD: f64 = 1e-9;
|
|
||||||
let half_q_lt = -2.0 * half_curv * rep[4];
|
|
||||||
let half_q_lt_sq = half_q_lt * half_q_lt;
|
|
||||||
let mut spatial = rep.fixed_rows_mut::<3>(0);
|
|
||||||
let q_sp = spatial.norm_squared();
|
|
||||||
if q_sp < SIZE_THRESHOLD && half_q_lt_sq < SIZE_THRESHOLD {
|
|
||||||
spatial.copy_from_slice(
|
|
||||||
&[0.0, 0.0, (1.0 - 2.0 * half_q_lt).sqrt()]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt();
|
|
||||||
spatial.scale_mut(1.0 / scaling);
|
|
||||||
rep[4] /= scaling;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DEBUG */
|
|
||||||
// verify normalization
|
|
||||||
let rep_for_debug = rep.clone();
|
|
||||||
console::log_1(&JsValue::from(
|
|
||||||
format!(
|
|
||||||
"Sphere self-product after curvature change: {}",
|
|
||||||
rep_for_debug.dot(&(&*Q * &rep_for_debug))
|
|
||||||
)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- partial matrices ---
|
// --- partial matrices ---
|
||||||
|
|
||||||
pub struct MatrixEntry {
|
pub struct MatrixEntry {
|
||||||
index: (usize, usize),
|
index: (usize, usize),
|
||||||
value: f64
|
value: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PartialMatrix(Vec<MatrixEntry>);
|
pub struct PartialMatrix(Vec<MatrixEntry>);
|
||||||
|
|
||||||
impl PartialMatrix {
|
impl PartialMatrix {
|
||||||
pub fn new() -> PartialMatrix {
|
pub fn new() -> Self {
|
||||||
PartialMatrix(Vec::<MatrixEntry>::new())
|
Self(Vec::<MatrixEntry>::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, row: usize, col: usize, value: f64) {
|
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: value });
|
entries.push(MatrixEntry { index: (row, col), value });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_sym(&mut self, row: usize, col: usize, value: f64) {
|
pub fn push_sym(&mut self, row: usize, col: usize, value: f64) {
|
||||||
|
@ -149,7 +114,7 @@ impl IntoIterator for PartialMatrix {
|
||||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
let PartialMatrix(entries) = self;
|
let Self(entries) = self;
|
||||||
entries.into_iter()
|
entries.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,22 +135,26 @@ impl<'a> IntoIterator for &'a PartialMatrix {
|
||||||
pub struct ConfigSubspace {
|
pub struct ConfigSubspace {
|
||||||
assembly_dim: usize,
|
assembly_dim: usize,
|
||||||
basis_std: Vec<DMatrix<f64>>,
|
basis_std: Vec<DMatrix<f64>>,
|
||||||
basis_proj: Vec<DMatrix<f64>>
|
basis_proj: Vec<DMatrix<f64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigSubspace {
|
impl ConfigSubspace {
|
||||||
pub fn zero(assembly_dim: usize) -> ConfigSubspace {
|
pub fn zero(assembly_dim: usize) -> Self {
|
||||||
ConfigSubspace {
|
Self {
|
||||||
assembly_dim: assembly_dim,
|
assembly_dim,
|
||||||
basis_proj: Vec::new(),
|
basis_proj: Vec::new(),
|
||||||
basis_std: Vec::new()
|
basis_std: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// approximate the kernel of a symmetric endomorphism of the configuration
|
// approximate the kernel of a symmetric endomorphism of the configuration
|
||||||
// space for `assembly_dim` elements. we consider an eigenvector to be part
|
// space for `assembly_dim` elements. we consider an eigenvector to be part
|
||||||
// of the kernel if its eigenvalue is smaller than the constant `THRESHOLD`
|
// of the kernel if its eigenvalue is smaller than the constant `THRESHOLD`
|
||||||
fn symmetric_kernel(a: DMatrix<f64>, proj_to_std: DMatrix<f64>, assembly_dim: usize) -> ConfigSubspace {
|
fn symmetric_kernel(
|
||||||
|
a: DMatrix<f64>,
|
||||||
|
proj_to_std: DMatrix<f64>,
|
||||||
|
assembly_dim: usize,
|
||||||
|
) -> Self {
|
||||||
// find a basis for the kernel. the basis is expressed in the projection
|
// find a basis for the kernel. the basis is expressed in the projection
|
||||||
// coordinates, and it's orthonormal with respect to the projection
|
// coordinates, and it's orthonormal with respect to the projection
|
||||||
// inner product
|
// inner product
|
||||||
|
@ -199,20 +168,13 @@ impl ConfigSubspace {
|
||||||
).collect::<Vec<_>>().as_slice()
|
).collect::<Vec<_>>().as_slice()
|
||||||
);
|
);
|
||||||
|
|
||||||
/* DEBUG */
|
|
||||||
// print the eigenvalues
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
|
||||||
console::log_1(&JsValue::from(
|
|
||||||
format!("Eigenvalues used to find kernel:{}", eig.eigenvalues)
|
|
||||||
));
|
|
||||||
|
|
||||||
// express the basis in the standard coordinates
|
// express the basis in the standard coordinates
|
||||||
let basis_std = proj_to_std * &basis_proj;
|
let basis_std = proj_to_std * &basis_proj;
|
||||||
|
|
||||||
const ELEMENT_DIM: usize = 5;
|
const ELEMENT_DIM: usize = 5;
|
||||||
const UNIFORM_DIM: usize = 4;
|
const UNIFORM_DIM: usize = 4;
|
||||||
ConfigSubspace {
|
Self {
|
||||||
assembly_dim: assembly_dim,
|
assembly_dim,
|
||||||
basis_std: basis_std.column_iter().map(
|
basis_std: basis_std.column_iter().map(
|
||||||
|v| Into::<DMatrix<f64>>::into(
|
|v| Into::<DMatrix<f64>>::into(
|
||||||
v.reshape_generic(Dyn(ELEMENT_DIM), Dyn(assembly_dim))
|
v.reshape_generic(Dyn(ELEMENT_DIM), Dyn(assembly_dim))
|
||||||
|
@ -222,7 +184,7 @@ impl ConfigSubspace {
|
||||||
|v| Into::<DMatrix<f64>>::into(
|
|v| Into::<DMatrix<f64>>::into(
|
||||||
v.reshape_generic(Dyn(UNIFORM_DIM), Dyn(assembly_dim))
|
v.reshape_generic(Dyn(UNIFORM_DIM), Dyn(assembly_dim))
|
||||||
)
|
)
|
||||||
).collect()
|
).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,14 +218,14 @@ pub struct DescentHistory {
|
||||||
pub config: Vec<DMatrix<f64>>,
|
pub config: Vec<DMatrix<f64>>,
|
||||||
pub scaled_loss: Vec<f64>,
|
pub scaled_loss: Vec<f64>,
|
||||||
pub neg_grad: Vec<DMatrix<f64>>,
|
pub neg_grad: Vec<DMatrix<f64>>,
|
||||||
pub hess_eigvals: Vec::<DVector<f64>>,
|
pub hess_eigvals: Vec<DVector<f64>>,
|
||||||
pub base_step: Vec<DMatrix<f64>>,
|
pub base_step: Vec<DMatrix<f64>>,
|
||||||
pub backoff_steps: Vec<i32>
|
pub backoff_steps: Vec<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DescentHistory {
|
impl DescentHistory {
|
||||||
pub fn new() -> DescentHistory {
|
pub fn new() -> Self {
|
||||||
DescentHistory {
|
Self {
|
||||||
config: Vec::<DMatrix<f64>>::new(),
|
config: Vec::<DMatrix<f64>>::new(),
|
||||||
scaled_loss: Vec::<f64>::new(),
|
scaled_loss: Vec::<f64>::new(),
|
||||||
neg_grad: Vec::<DMatrix<f64>>::new(),
|
neg_grad: Vec::<DMatrix<f64>>::new(),
|
||||||
|
@ -283,21 +245,21 @@ pub struct ConstraintProblem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConstraintProblem {
|
impl ConstraintProblem {
|
||||||
pub fn new(element_count: usize) -> ConstraintProblem {
|
pub fn new(element_count: usize) -> Self {
|
||||||
const ELEMENT_DIM: usize = 5;
|
const ELEMENT_DIM: usize = 5;
|
||||||
ConstraintProblem {
|
Self {
|
||||||
gram: PartialMatrix::new(),
|
gram: PartialMatrix::new(),
|
||||||
frozen: PartialMatrix::new(),
|
frozen: PartialMatrix::new(),
|
||||||
guess: DMatrix::<f64>::zeros(ELEMENT_DIM, element_count)
|
guess: DMatrix::<f64>::zeros(ELEMENT_DIM, element_count),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dev")]
|
#[cfg(feature = "dev")]
|
||||||
pub fn from_guess(guess_columns: &[DVector<f64>]) -> ConstraintProblem {
|
pub fn from_guess(guess_columns: &[DVector<f64>]) -> Self {
|
||||||
ConstraintProblem {
|
Self {
|
||||||
gram: PartialMatrix::new(),
|
gram: PartialMatrix::new(),
|
||||||
frozen: PartialMatrix::new(),
|
frozen: PartialMatrix::new(),
|
||||||
guess: DMatrix::from_columns(guess_columns)
|
guess: DMatrix::from_columns(guess_columns),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,25 +273,21 @@ lazy_static! {
|
||||||
0.0, 1.0, 0.0, 0.0, 0.0,
|
0.0, 1.0, 0.0, 0.0, 0.0,
|
||||||
0.0, 0.0, 1.0, 0.0, 0.0,
|
0.0, 0.0, 1.0, 0.0, 0.0,
|
||||||
0.0, 0.0, 0.0, 0.0, -2.0,
|
0.0, 0.0, 0.0, 0.0, -2.0,
|
||||||
0.0, 0.0, 0.0, -2.0, 0.0
|
0.0, 0.0, 0.0, -2.0, 0.0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SearchState {
|
struct SearchState {
|
||||||
config: DMatrix<f64>,
|
config: DMatrix<f64>,
|
||||||
err_proj: DMatrix<f64>,
|
err_proj: DMatrix<f64>,
|
||||||
loss: f64
|
loss: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchState {
|
impl SearchState {
|
||||||
fn from_config(gram: &PartialMatrix, config: DMatrix<f64>) -> SearchState {
|
fn from_config(gram: &PartialMatrix, config: DMatrix<f64>) -> Self {
|
||||||
let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config));
|
let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config));
|
||||||
let loss = err_proj.norm_squared();
|
let loss = err_proj.norm_squared();
|
||||||
SearchState {
|
Self { config, err_proj, loss }
|
||||||
config: config,
|
|
||||||
err_proj: err_proj,
|
|
||||||
loss: loss
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +314,7 @@ pub fn local_unif_to_std(v: DVectorView<f64>) -> DMatrix<f64> {
|
||||||
curv, 0.0, 0.0, 0.0, v[0],
|
curv, 0.0, 0.0, 0.0, v[0],
|
||||||
0.0, curv, 0.0, 0.0, v[1],
|
0.0, curv, 0.0, 0.0, v[1],
|
||||||
0.0, 0.0, curv, 0.0, v[2],
|
0.0, 0.0, curv, 0.0, v[2],
|
||||||
0.0, 0.0, 0.0, 0.0, 1.0
|
0.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
// `v` represents a sphere. the normalization condition says that the
|
// `v` represents a sphere. the normalization condition says that the
|
||||||
|
@ -365,7 +323,7 @@ pub fn local_unif_to_std(v: DVectorView<f64>) -> DMatrix<f64> {
|
||||||
curv, 0.0, 0.0, 0.0, v[0],
|
curv, 0.0, 0.0, 0.0, v[0],
|
||||||
0.0, curv, 0.0, 0.0, v[1],
|
0.0, curv, 0.0, 0.0, v[1],
|
||||||
0.0, 0.0, curv, 0.0, v[2],
|
0.0, 0.0, curv, 0.0, v[2],
|
||||||
curv*v[0], curv*v[1], curv*v[2], curv*v[3], curv*v[4] + 1.0
|
curv*v[0], curv*v[1], curv*v[2], curv*v[3], curv*v[4] + 1.0,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,7 +336,7 @@ fn seek_better_config(
|
||||||
base_target_improvement: f64,
|
base_target_improvement: f64,
|
||||||
min_efficiency: f64,
|
min_efficiency: f64,
|
||||||
backoff: f64,
|
backoff: f64,
|
||||||
max_backoff_steps: i32
|
max_backoff_steps: i32,
|
||||||
) -> Option<(SearchState, i32)> {
|
) -> Option<(SearchState, i32)> {
|
||||||
let mut rate = 1.0;
|
let mut rate = 1.0;
|
||||||
for backoff_steps in 0..max_backoff_steps {
|
for backoff_steps in 0..max_backoff_steps {
|
||||||
|
@ -396,12 +354,12 @@ fn seek_better_config(
|
||||||
// a first-order neighborhood of a configuration
|
// a first-order neighborhood of a configuration
|
||||||
pub struct ConfigNeighborhood {
|
pub struct ConfigNeighborhood {
|
||||||
pub config: DMatrix<f64>,
|
pub config: DMatrix<f64>,
|
||||||
pub nbhd: ConfigSubspace
|
pub nbhd: ConfigSubspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Realization {
|
pub struct Realization {
|
||||||
pub result: Result<ConfigNeighborhood, String>,
|
pub result: Result<ConfigNeighborhood, String>,
|
||||||
pub history: DescentHistory
|
pub history: DescentHistory,
|
||||||
}
|
}
|
||||||
|
|
||||||
// seek a matrix `config` that matches the partial matrix `problem.frozen` and
|
// seek a matrix `config` that matches the partial matrix `problem.frozen` and
|
||||||
|
@ -415,19 +373,30 @@ pub fn realize_gram(
|
||||||
backoff: f64,
|
backoff: f64,
|
||||||
reg_scale: f64,
|
reg_scale: f64,
|
||||||
max_descent_steps: i32,
|
max_descent_steps: i32,
|
||||||
max_backoff_steps: i32
|
max_backoff_steps: i32,
|
||||||
) -> Realization {
|
) -> Realization {
|
||||||
// destructure the problem data
|
// destructure the problem data
|
||||||
let ConstraintProblem {
|
let ConstraintProblem { gram, guess, frozen } = problem;
|
||||||
gram, guess, frozen
|
|
||||||
} = problem;
|
|
||||||
|
|
||||||
// start the descent history
|
// start the descent history
|
||||||
let mut history = DescentHistory::new();
|
let mut history = DescentHistory::new();
|
||||||
|
|
||||||
|
// handle the case where the assembly is empty. our general realization
|
||||||
|
// routine can't handle this case because it builds the Hessian using
|
||||||
|
// `DMatrix::from_columns`, which panics when the list of columns is empty
|
||||||
|
let assembly_dim = guess.ncols();
|
||||||
|
if assembly_dim == 0 {
|
||||||
|
let result = Ok(
|
||||||
|
ConfigNeighborhood {
|
||||||
|
config: guess.clone(),
|
||||||
|
nbhd: ConfigSubspace::zero(0),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Realization { result, history };
|
||||||
|
}
|
||||||
|
|
||||||
// find the dimension of the search space
|
// find the dimension of the search space
|
||||||
let element_dim = guess.nrows();
|
let element_dim = guess.nrows();
|
||||||
let assembly_dim = guess.ncols();
|
|
||||||
let total_dim = element_dim * assembly_dim;
|
let total_dim = element_dim * assembly_dim;
|
||||||
|
|
||||||
// scale the tolerance
|
// scale the tolerance
|
||||||
|
@ -504,8 +473,8 @@ pub fn realize_gram(
|
||||||
Some(cholesky) => cholesky,
|
Some(cholesky) => cholesky,
|
||||||
None => return Realization {
|
None => return Realization {
|
||||||
result: Err("Cholesky decomposition failed".to_string()),
|
result: Err("Cholesky decomposition failed".to_string()),
|
||||||
history
|
history,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
let base_step_stacked = hess_cholesky.solve(&neg_grad_stacked);
|
let base_step_stacked = hess_cholesky.solve(&neg_grad_stacked);
|
||||||
let base_step = base_step_stacked.reshape_generic(Dyn(element_dim), Dyn(assembly_dim));
|
let base_step = base_step_stacked.reshape_generic(Dyn(element_dim), Dyn(assembly_dim));
|
||||||
|
@ -514,16 +483,16 @@ pub fn realize_gram(
|
||||||
// use backtracking line search to find a better configuration
|
// use backtracking line search to find a better configuration
|
||||||
if let Some((better_state, backoff_steps)) = seek_better_config(
|
if let Some((better_state, backoff_steps)) = seek_better_config(
|
||||||
gram, &state, &base_step, neg_grad.dot(&base_step),
|
gram, &state, &base_step, neg_grad.dot(&base_step),
|
||||||
min_efficiency, backoff, max_backoff_steps
|
min_efficiency, backoff, max_backoff_steps,
|
||||||
) {
|
) {
|
||||||
state = better_state;
|
state = better_state;
|
||||||
history.backoff_steps.push(backoff_steps);
|
history.backoff_steps.push(backoff_steps);
|
||||||
} else {
|
} else {
|
||||||
return Realization {
|
return Realization {
|
||||||
result: Err("Line search failed".to_string()),
|
result: Err("Line search failed".to_string()),
|
||||||
history
|
history,
|
||||||
}
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
let result = if state.loss < tol {
|
let result = if state.loss < tol {
|
||||||
// express the uniform basis in the standard basis
|
// express the uniform basis in the standard basis
|
||||||
|
@ -568,7 +537,7 @@ pub mod examples {
|
||||||
[
|
[
|
||||||
sphere(0.0, 0.0, 0.0, 15.0),
|
sphere(0.0, 0.0, 0.0, 15.0),
|
||||||
sphere(0.0, 0.0, -9.0, 5.0),
|
sphere(0.0, 0.0, -9.0, 5.0),
|
||||||
sphere(0.0, 0.0, 11.0, 3.0)
|
sphere(0.0, 0.0, 11.0, 3.0),
|
||||||
].into_iter().chain(
|
].into_iter().chain(
|
||||||
(1..=6).map(
|
(1..=6).map(
|
||||||
|k| {
|
|k| {
|
||||||
|
@ -627,7 +596,7 @@ pub mod examples {
|
||||||
point(0.0, 0.0, 0.0),
|
point(0.0, 0.0, 0.0),
|
||||||
point(ang_hor.cos(), ang_hor.sin(), 0.0),
|
point(ang_hor.cos(), ang_hor.sin(), 0.0),
|
||||||
point(x_vert, y_vert, -0.5),
|
point(x_vert, y_vert, -0.5),
|
||||||
point(x_vert, y_vert, 0.5)
|
point(x_vert, y_vert, 0.5),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
).collect::<Vec<_>>().as_slice()
|
).collect::<Vec<_>>().as_slice()
|
||||||
|
@ -670,15 +639,15 @@ mod tests {
|
||||||
MatrixEntry { index: (0, 0), value: 14.0 },
|
MatrixEntry { index: (0, 0), value: 14.0 },
|
||||||
MatrixEntry { index: (0, 2), value: 28.0 },
|
MatrixEntry { index: (0, 2), value: 28.0 },
|
||||||
MatrixEntry { index: (1, 1), value: 42.0 },
|
MatrixEntry { index: (1, 1), value: 42.0 },
|
||||||
MatrixEntry { index: (1, 2), value: 49.0 }
|
MatrixEntry { index: (1, 2), value: 49.0 },
|
||||||
]);
|
]);
|
||||||
let config = DMatrix::<f64>::from_row_slice(2, 3, &[
|
let config = DMatrix::<f64>::from_row_slice(2, 3, &[
|
||||||
1.0, 2.0, 3.0,
|
1.0, 2.0, 3.0,
|
||||||
4.0, 5.0, 6.0
|
4.0, 5.0, 6.0,
|
||||||
]);
|
]);
|
||||||
let expected_result = DMatrix::<f64>::from_row_slice(2, 3, &[
|
let expected_result = DMatrix::<f64>::from_row_slice(2, 3, &[
|
||||||
14.0, 2.0, 28.0,
|
14.0, 2.0, 28.0,
|
||||||
4.0, 42.0, 49.0
|
4.0, 42.0, 49.0,
|
||||||
]);
|
]);
|
||||||
assert_eq!(frozen.freeze(&config), expected_result);
|
assert_eq!(frozen.freeze(&config), expected_result);
|
||||||
}
|
}
|
||||||
|
@ -689,15 +658,15 @@ mod tests {
|
||||||
MatrixEntry { index: (0, 0), value: 19.0 },
|
MatrixEntry { index: (0, 0), value: 19.0 },
|
||||||
MatrixEntry { index: (0, 2), value: 39.0 },
|
MatrixEntry { index: (0, 2), value: 39.0 },
|
||||||
MatrixEntry { index: (1, 1), value: 59.0 },
|
MatrixEntry { index: (1, 1), value: 59.0 },
|
||||||
MatrixEntry { index: (1, 2), value: 69.0 }
|
MatrixEntry { index: (1, 2), value: 69.0 },
|
||||||
]);
|
]);
|
||||||
let attempt = DMatrix::<f64>::from_row_slice(2, 3, &[
|
let attempt = DMatrix::<f64>::from_row_slice(2, 3, &[
|
||||||
1.0, 2.0, 3.0,
|
1.0, 2.0, 3.0,
|
||||||
4.0, 5.0, 6.0
|
4.0, 5.0, 6.0,
|
||||||
]);
|
]);
|
||||||
let expected_result = DMatrix::<f64>::from_row_slice(2, 3, &[
|
let expected_result = DMatrix::<f64>::from_row_slice(2, 3, &[
|
||||||
18.0, 0.0, 36.0,
|
18.0, 0.0, 36.0,
|
||||||
0.0, 54.0, 63.0
|
0.0, 54.0, 63.0,
|
||||||
]);
|
]);
|
||||||
assert_eq!(target.sub_proj(&attempt), expected_result);
|
assert_eq!(target.sub_proj(&attempt), expected_result);
|
||||||
}
|
}
|
||||||
|
@ -715,7 +684,7 @@ mod tests {
|
||||||
DMatrix::from_columns(&[
|
DMatrix::from_columns(&[
|
||||||
sphere(1.0, 0.0, 0.0, a),
|
sphere(1.0, 0.0, 0.0, a),
|
||||||
sphere(-0.5, a, 0.0, a),
|
sphere(-0.5, a, 0.0, a),
|
||||||
sphere(-0.5, -a, 0.0, a)
|
sphere(-0.5, -a, 0.0, a),
|
||||||
])
|
])
|
||||||
};
|
};
|
||||||
let state = SearchState::from_config(&gram, config);
|
let state = SearchState::from_config(&gram, config);
|
||||||
|
@ -729,7 +698,7 @@ mod tests {
|
||||||
fn frozen_entry_test() {
|
fn frozen_entry_test() {
|
||||||
let mut problem = ConstraintProblem::from_guess(&[
|
let mut problem = ConstraintProblem::from_guess(&[
|
||||||
point(0.0, 0.0, 2.0),
|
point(0.0, 0.0, 2.0),
|
||||||
sphere(0.0, 0.0, 0.0, 0.95)
|
sphere(0.0, 0.0, 0.0, 0.95),
|
||||||
]);
|
]);
|
||||||
for j in 0..2 {
|
for j in 0..2 {
|
||||||
for k in j..2 {
|
for k in j..2 {
|
||||||
|
@ -773,7 +742,7 @@ mod tests {
|
||||||
let mut problem = ConstraintProblem::from_guess(&[
|
let mut problem = ConstraintProblem::from_guess(&[
|
||||||
sphere(0.0, 0.0, 0.0, -2.0),
|
sphere(0.0, 0.0, 0.0, -2.0),
|
||||||
sphere(0.0, 0.0, 1.0, 1.0),
|
sphere(0.0, 0.0, 1.0, 1.0),
|
||||||
sphere(0.0, 0.0, -1.0, 1.0)
|
sphere(0.0, 0.0, -1.0, 1.0),
|
||||||
]);
|
]);
|
||||||
for j in 0..3 {
|
for j in 0..3 {
|
||||||
for k in j..3 {
|
for k in j..3 {
|
||||||
|
@ -803,8 +772,8 @@ mod tests {
|
||||||
DMatrix::<f64>::from_column_slice(UNIFORM_DIM, assembly_dim, &[
|
DMatrix::<f64>::from_column_slice(UNIFORM_DIM, assembly_dim, &[
|
||||||
0.0, 0.0, 0.0, 0.0,
|
0.0, 0.0, 0.0, 0.0,
|
||||||
0.0, 0.0, -0.5, -0.5,
|
0.0, 0.0, -0.5, -0.5,
|
||||||
0.0, 0.0, -0.5, 0.5
|
0.0, 0.0, -0.5, 0.5,
|
||||||
])
|
]),
|
||||||
];
|
];
|
||||||
let tangent_motions_std = vec![
|
let tangent_motions_std = vec![
|
||||||
basis_matrix((0, 1), element_dim, assembly_dim),
|
basis_matrix((0, 1), element_dim, assembly_dim),
|
||||||
|
@ -814,8 +783,8 @@ mod tests {
|
||||||
DMatrix::<f64>::from_column_slice(element_dim, assembly_dim, &[
|
DMatrix::<f64>::from_column_slice(element_dim, assembly_dim, &[
|
||||||
0.0, 0.0, 0.0, 0.00, 0.0,
|
0.0, 0.0, 0.0, 0.00, 0.0,
|
||||||
0.0, 0.0, -1.0, -0.25, -1.0,
|
0.0, 0.0, -1.0, -0.25, -1.0,
|
||||||
0.0, 0.0, -1.0, 0.25, 1.0
|
0.0, 0.0, -1.0, 0.25, 1.0,
|
||||||
])
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
// confirm that the dimension of the tangent space is no greater than
|
// confirm that the dimension of the tangent space is no greater than
|
||||||
|
@ -891,10 +860,10 @@ mod tests {
|
||||||
DVector::from_column_slice(&[0.0, 0.0, 5.0, 0.0]),
|
DVector::from_column_slice(&[0.0, 0.0, 5.0, 0.0]),
|
||||||
DVector::from_column_slice(&[0.0, 0.0, 1.0, 0.0]),
|
DVector::from_column_slice(&[0.0, 0.0, 1.0, 0.0]),
|
||||||
DVector::from_column_slice(&[-vel_vert_x, -vel_vert_y, -3.0, 0.0]),
|
DVector::from_column_slice(&[-vel_vert_x, -vel_vert_y, -3.0, 0.0]),
|
||||||
DVector::from_column_slice(&[vel_vert_x, vel_vert_y, -3.0, 0.0])
|
DVector::from_column_slice(&[vel_vert_x, vel_vert_y, -3.0, 0.0]),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
).collect::<Vec<_>>()
|
).collect::<Vec<_>>(),
|
||||||
];
|
];
|
||||||
let tangent_motions_std = tangent_motions_unif.iter().map(
|
let tangent_motions_std = tangent_motions_unif.iter().map(
|
||||||
|motion| DMatrix::from_columns(
|
|motion| DMatrix::from_columns(
|
||||||
|
@ -927,7 +896,7 @@ mod tests {
|
||||||
0.0, 1.0, 0.0, 0.0, dis[1],
|
0.0, 1.0, 0.0, 0.0, dis[1],
|
||||||
0.0, 0.0, 1.0, 0.0, dis[2],
|
0.0, 0.0, 1.0, 0.0, dis[2],
|
||||||
2.0*dis[0], 2.0*dis[1], 2.0*dis[2], 1.0, dis.norm_squared(),
|
2.0*dis[0], 2.0*dis[1], 2.0*dis[2], 1.0, dis.norm_squared(),
|
||||||
0.0, 0.0, 0.0, 0.0, 1.0
|
0.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -939,7 +908,7 @@ mod tests {
|
||||||
const SCALED_TOL: f64 = 1.0e-12;
|
const SCALED_TOL: f64 = 1.0e-12;
|
||||||
let mut problem_orig = ConstraintProblem::from_guess(&[
|
let mut problem_orig = ConstraintProblem::from_guess(&[
|
||||||
sphere(0.0, 0.0, 0.5, 1.0),
|
sphere(0.0, 0.0, 0.5, 1.0),
|
||||||
sphere(0.0, 0.0, -0.5, 1.0)
|
sphere(0.0, 0.0, -0.5, 1.0),
|
||||||
]);
|
]);
|
||||||
problem_orig.gram.push_sym(0, 0, 1.0);
|
problem_orig.gram.push_sym(0, 0, 1.0);
|
||||||
problem_orig.gram.push_sym(1, 1, 1.0);
|
problem_orig.gram.push_sym(1, 1, 1.0);
|
||||||
|
@ -957,13 +926,13 @@ mod tests {
|
||||||
let a = 0.5 * FRAC_1_SQRT_2;
|
let a = 0.5 * FRAC_1_SQRT_2;
|
||||||
DMatrix::from_columns(&[
|
DMatrix::from_columns(&[
|
||||||
sphere(a, 0.0, 7.0 + a, 1.0),
|
sphere(a, 0.0, 7.0 + a, 1.0),
|
||||||
sphere(-a, 0.0, 7.0 - a, 1.0)
|
sphere(-a, 0.0, 7.0 - a, 1.0),
|
||||||
])
|
])
|
||||||
};
|
};
|
||||||
let problem_tfm = ConstraintProblem {
|
let problem_tfm = ConstraintProblem {
|
||||||
gram: problem_orig.gram,
|
gram: problem_orig.gram,
|
||||||
|
frozen: problem_orig.frozen,
|
||||||
guess: guess_tfm,
|
guess: guess_tfm,
|
||||||
frozen: problem_orig.frozen
|
|
||||||
};
|
};
|
||||||
let Realization { result: result_tfm, history: history_tfm } = realize_gram(
|
let Realization { result: result_tfm, history: history_tfm } = realize_gram(
|
||||||
&problem_tfm, SCALED_TOL, 0.5, 0.9, 1.1, 200, 110
|
&problem_tfm, SCALED_TOL, 0.5, 0.9, 1.1, 200, 110
|
||||||
|
@ -991,7 +960,7 @@ mod tests {
|
||||||
0.0, 1.0, 0.0, 0.0, 0.0,
|
0.0, 1.0, 0.0, 0.0, 0.0,
|
||||||
FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0, 0.0,
|
FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0, 0.0,
|
||||||
0.0, 0.0, 0.0, 1.0, 0.0,
|
0.0, 0.0, 0.0, 1.0, 0.0,
|
||||||
0.0, 0.0, 0.0, 0.0, 1.0
|
0.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
]);
|
]);
|
||||||
let transl = translation(Vector3::new(0.0, 0.0, 7.0));
|
let transl = translation(Vector3::new(0.0, 0.0, 7.0));
|
||||||
let motion_proj_tfm = transl * rot * motion_orig_proj;
|
let motion_proj_tfm = transl * rot * motion_orig_proj;
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
mod add_remove;
|
|
||||||
mod assembly;
|
mod assembly;
|
||||||
mod diagnostics;
|
mod components;
|
||||||
mod display;
|
|
||||||
mod engine;
|
mod engine;
|
||||||
mod outline;
|
|
||||||
mod specified;
|
mod specified;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -12,23 +9,25 @@ mod tests;
|
||||||
use std::{collections::BTreeSet, rc::Rc};
|
use std::{collections::BTreeSet, rc::Rc};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
use add_remove::AddRemove;
|
|
||||||
use assembly::{Assembly, Element};
|
use assembly::{Assembly, Element};
|
||||||
use diagnostics::Diagnostics;
|
use components::{
|
||||||
use display::Display;
|
add_remove::AddRemove,
|
||||||
use outline::Outline;
|
diagnostics::Diagnostics,
|
||||||
|
display::Display,
|
||||||
|
outline::Outline,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
assembly: Assembly,
|
assembly: Assembly,
|
||||||
selection: Signal<BTreeSet<Rc<dyn Element>>>
|
selection: Signal<BTreeSet<Rc<dyn Element>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
fn new() -> AppState {
|
fn new() -> Self {
|
||||||
AppState {
|
Self {
|
||||||
assembly: Assembly::new(),
|
assembly: Assembly::new(),
|
||||||
selection: create_signal(BTreeSet::default())
|
selection: create_signal(BTreeSet::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +58,7 @@ fn main() {
|
||||||
provide_context(AppState::new());
|
provide_context(AppState::new());
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
div(id="sidebar") {
|
div(id = "sidebar") {
|
||||||
AddRemove {}
|
AddRemove {}
|
||||||
Outline {}
|
Outline {}
|
||||||
Diagnostics {}
|
Diagnostics {}
|
||||||
|
|
|
@ -13,12 +13,12 @@ use std::num::ParseFloatError;
|
||||||
#[readonly::make]
|
#[readonly::make]
|
||||||
pub struct SpecifiedValue {
|
pub struct SpecifiedValue {
|
||||||
pub spec: String,
|
pub spec: String,
|
||||||
pub value: Option<f64>
|
pub value: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpecifiedValue {
|
impl SpecifiedValue {
|
||||||
pub fn from_empty_spec() -> SpecifiedValue {
|
pub fn from_empty_spec() -> Self {
|
||||||
SpecifiedValue { spec: String::new(), value: None }
|
Self { spec: String::new(), value: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_present(&self) -> bool {
|
pub fn is_present(&self) -> bool {
|
||||||
|
@ -34,10 +34,10 @@ impl TryFrom<String> for SpecifiedValue {
|
||||||
|
|
||||||
fn try_from(spec: String) -> Result<Self, Self::Error> {
|
fn try_from(spec: String) -> Result<Self, Self::Error> {
|
||||||
if spec.is_empty() {
|
if spec.is_empty() {
|
||||||
Ok(SpecifiedValue::from_empty_spec())
|
Ok(Self::from_empty_spec())
|
||||||
} else {
|
} else {
|
||||||
spec.parse::<f64>().map(
|
spec.parse::<f64>().map(
|
||||||
|value| SpecifiedValue { spec: spec, value: Some(value) }
|
|value| Self { spec, value: Some(value) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
5
deploy/.gitignore
vendored
Normal file
5
deploy/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/dyna3.zip
|
||||||
|
/dyna3/index.html
|
||||||
|
/dyna3/dyna3-*.js
|
||||||
|
/dyna3/dyna3-*.wasm
|
||||||
|
/dyna3/main-*.css
|
16
tools/package-for-deployment.sh
Normal file
16
tools/package-for-deployment.sh
Normal file
|
@ -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"
|
|
@ -8,7 +8,7 @@
|
||||||
# the application prototype
|
# the application prototype
|
||||||
|
|
||||||
# find the manifest file for 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
|
# set up the command that runs each example
|
||||||
RUN_EXAMPLE="cargo run --manifest-path $MANIFEST --example"
|
RUN_EXAMPLE="cargo run --manifest-path $MANIFEST --example"
|
Loading…
Add table
Add a link
Reference in a new issue