Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Aaron Fenyes
e67a658e00 Group the parameters of the scene push methods
All checks were successful
/ test (pull_request) Successful in 3m39s
2025-08-03 20:09:50 -07:00
Aaron Fenyes
b02e682e15 Add space around = for Sycamore props
All checks were successful
/ test (pull_request) Successful in 3m35s
Also, add a trailing comma to a match arm in `outline.rs`.
2025-08-02 00:15:46 -07:00
Aaron Fenyes
bfd5d8e35f Add commas after match arms wrapped in blocks
All checks were successful
/ test (pull_request) Successful in 3m29s
2025-08-01 22:57:46 -07:00
Aaron Fenyes
ebad512a03 Collapse a structure expression onto one line
All checks were successful
/ test (pull_request) Successful in 3m34s
I had put `active_tab: /* ... */` on its own line to make the
`create_signal` call more readable, but it wasn't worth the cost in
vertical space.
2025-08-01 22:39:59 -07:00
Aaron Fenyes
af59166906 Add trailing commas; clean up formatting
All checks were successful
/ test (pull_request) Successful in 3m55s
2025-08-01 13:50:51 -07:00
12 changed files with 310 additions and 297 deletions

View file

@ -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)];

View file

@ -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);
} }

View file

@ -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);

View file

@ -1,13 +1,13 @@
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 */
@ -26,9 +26,9 @@ use crate::{
ConfigSubspace, ConfigSubspace,
ConstraintProblem, ConstraintProblem,
DescentHistory, DescentHistory,
Realization Realization,
}, },
specified::SpecifiedValue specified::SpecifiedValue,
}; };
pub type ElementColor = [f32; 3]; pub type ElementColor = [f32; 3];
@ -164,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 {
@ -174,17 +174,17 @@ impl Sphere {
id: String, id: String,
label: String, label: String,
color: ElementColor, color: ElementColor,
representation: DVector<f64> representation: DVector<f64>,
) -> Sphere { ) -> Sphere {
Sphere { Sphere {
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(),
} }
} }
} }
@ -199,7 +199,7 @@ impl Element for Sphere {
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),
) )
} }
@ -264,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 {
@ -274,7 +274,7 @@ impl Point {
id: String, id: String,
label: String, label: String,
color: ElementColor, color: ElementColor,
representation: DVector<f64> representation: DVector<f64>,
) -> Point { ) -> Point {
Point { Point {
id, id,
@ -284,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(),
} }
} }
} }
@ -299,7 +299,7 @@ impl Element for Point {
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),
) )
} }
@ -389,7 +389,7 @@ 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 {
@ -449,7 +449,7 @@ 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 {
@ -501,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>>;
@ -533,7 +533,7 @@ pub struct Assembly {
// 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 {
@ -546,7 +546,7 @@ impl Assembly {
elements_by_id: create_signal(BTreeMap::default()), elements_by_id: create_signal(BTreeMap::default()),
realization_trigger: create_signal(()), 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, // realize the assembly whenever the element list, the regulator list,
@ -724,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))
} },
} }
} }
@ -807,7 +807,7 @@ 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())
} },
}; };
}); });
} }
@ -867,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),
) )
); );
@ -881,7 +881,7 @@ mod tests {
vec![ vec![
ElementMotion { ElementMotion {
element: sphere.clone(), element: sphere.clone(),
velocity: velocity.as_view() velocity: velocity.as_view(),
} }
] ]
); );

View file

@ -4,15 +4,15 @@ use sycamore::prelude::*;
use super::test_assembly_chooser::TestAssemblyChooser; use super::test_assembly_chooser::TestAssemblyChooser;
use crate::{ use crate::{
AppState, AppState,
assembly::{InversiveDistanceRegulator, Point, Sphere} assembly::{InversiveDistanceRegulator, Point, Sphere},
}; };
#[component] #[component]
pub fn AddRemove() -> View { pub fn AddRemove() -> View {
view! { view! {
div(id="add-remove") { div(id = "add-remove") {
button( button(
on:click=|_| { on:click = |_| {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
batch(|| { batch(|| {
// this call is batched to avoid redundant realizations. // this call is batched to avoid redundant realizations.
@ -33,18 +33,18 @@ pub fn AddRemove() -> View {
} }
) { "Add sphere" } ) { "Add sphere" }
button( button(
on:click=|_| { on:click = |_| {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
state.assembly.insert_element_default::<Point>(); state.assembly.insert_element_default::<Point>();
} }
) { "Add point" } ) { "Add point" }
button( button(
class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button class = "emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
disabled={ disabled = {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
state.selection.with(|sel| sel.len() != 2) state.selection.with(|sel| sel.len() != 2)
}, },
on:click=|_| { on:click = |_| {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
let subjects: [_; 2] = state.selection.with( let subjects: [_; 2] = state.selection.with(
// the button is only enabled when two elements are // the button is only enabled when two elements are

View file

@ -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) -> DiagnosticsState {
DiagnosticsState { DiagnosticsState { 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 {} }
} }
} }
} }

View file

@ -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() -> SceneSpheres {
SceneSpheres { SceneSpheres {
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,7 +67,7 @@ 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 {
@ -73,11 +76,14 @@ impl ScenePoints {
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() -> Scene {
Scene { Scene {
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()),
}; };
} },
) )
} }
} }

View file

@ -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()
) )
} }
} }

View file

@ -6,17 +6,17 @@ use web_sys::{console, wasm_bindgen::JsValue};
use crate::{ use crate::{
AppState, AppState,
engine,
engine::DescentHistory,
assembly::{ assembly::{
Assembly, Assembly,
Element, Element,
ElementColor, ElementColor,
InversiveDistanceRegulator, InversiveDistanceRegulator,
Point, Point,
Sphere Sphere,
}, },
specified::SpecifiedValue engine,
engine::DescentHistory,
specified::SpecifiedValue,
}; };
// --- loaders --- // --- loaders ---
@ -32,7 +32,7 @@ fn load_gen_assemb(assembly: &Assembly) {
String::from("gemini_a"), String::from("gemini_a"),
String::from("Castor"), String::from("Castor"),
[1.00_f32, 0.25_f32, 0.00_f32], [1.00_f32, 0.25_f32, 0.00_f32],
engine::sphere(0.5, 0.5, 0.0, 1.0) engine::sphere(0.5, 0.5, 0.0, 1.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -40,7 +40,7 @@ fn load_gen_assemb(assembly: &Assembly) {
String::from("gemini_b"), String::from("gemini_b"),
String::from("Pollux"), String::from("Pollux"),
[0.00_f32, 0.25_f32, 1.00_f32], [0.00_f32, 0.25_f32, 1.00_f32],
engine::sphere(-0.5, -0.5, 0.0, 1.0) engine::sphere(-0.5, -0.5, 0.0, 1.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -48,7 +48,7 @@ fn load_gen_assemb(assembly: &Assembly) {
String::from("ursa_major"), String::from("ursa_major"),
String::from("Ursa major"), String::from("Ursa major"),
[0.25_f32, 0.00_f32, 1.00_f32], [0.25_f32, 0.00_f32, 1.00_f32],
engine::sphere(-0.5, 0.5, 0.0, 0.75) engine::sphere(-0.5, 0.5, 0.0, 0.75),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -56,7 +56,7 @@ fn load_gen_assemb(assembly: &Assembly) {
String::from("ursa_minor"), String::from("ursa_minor"),
String::from("Ursa minor"), String::from("Ursa minor"),
[0.25_f32, 1.00_f32, 0.00_f32], [0.25_f32, 1.00_f32, 0.00_f32],
engine::sphere(0.5, -0.5, 0.0, 0.5) engine::sphere(0.5, -0.5, 0.0, 0.5),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -64,7 +64,7 @@ fn load_gen_assemb(assembly: &Assembly) {
String::from("moon_deimos"), String::from("moon_deimos"),
String::from("Deimos"), String::from("Deimos"),
[0.75_f32, 0.75_f32, 0.00_f32], [0.75_f32, 0.75_f32, 0.00_f32],
engine::sphere(0.0, 0.15, 1.0, 0.25) engine::sphere(0.0, 0.15, 1.0, 0.25),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -72,7 +72,7 @@ fn load_gen_assemb(assembly: &Assembly) {
String::from("moon_phobos"), String::from("moon_phobos"),
String::from("Phobos"), String::from("Phobos"),
[0.00_f32, 0.75_f32, 0.50_f32], [0.00_f32, 0.75_f32, 0.50_f32],
engine::sphere(0.0, -0.15, -1.0, 0.25) engine::sphere(0.0, -0.15, -1.0, 0.25),
) )
); );
} }
@ -85,7 +85,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
"central".to_string(), "central".to_string(),
"Central".to_string(), "Central".to_string(),
[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, 1.0) engine::sphere(0.0, 0.0, 0.0, 1.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -93,7 +93,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
"assemb_plane".to_string(), "assemb_plane".to_string(),
"Assembly plane".to_string(), "Assembly plane".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -101,7 +101,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
"side1".to_string(), "side1".to_string(),
"Side 1".to_string(), "Side 1".to_string(),
[1.00_f32, 0.00_f32, 0.25_f32], [1.00_f32, 0.00_f32, 0.25_f32],
engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -109,7 +109,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
"side2".to_string(), "side2".to_string(),
"Side 2".to_string(), "Side 2".to_string(),
[0.25_f32, 1.00_f32, 0.00_f32], [0.25_f32, 1.00_f32, 0.00_f32],
engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -117,7 +117,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
"side3".to_string(), "side3".to_string(),
"Side 3".to_string(), "Side 3".to_string(),
[0.00_f32, 0.25_f32, 1.00_f32], [0.00_f32, 0.25_f32, 1.00_f32],
engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -125,7 +125,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
"corner1".to_string(), "corner1".to_string(),
"Corner 1".to_string(), "Corner 1".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -133,7 +133,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
"corner2".to_string(), "corner2".to_string(),
"Corner 2".to_string(), "Corner 2".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [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) engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -141,7 +141,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
String::from("corner3"), String::from("corner3"),
String::from("Corner 3"), String::from("Corner 3"),
[0.75_f32, 0.75_f32, 0.75_f32], [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) engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0),
) )
); );
@ -202,7 +202,7 @@ fn load_pointed_assemb(assembly: &Assembly) {
format!("point_front"), format!("point_front"),
format!("Front point"), format!("Front point"),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::point(0.0, 0.0, FRAC_1_SQRT_2) engine::point(0.0, 0.0, FRAC_1_SQRT_2),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -210,7 +210,7 @@ fn load_pointed_assemb(assembly: &Assembly) {
format!("point_back"), format!("point_back"),
format!("Back point"), format!("Back point"),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::point(0.0, 0.0, -FRAC_1_SQRT_2) engine::point(0.0, 0.0, -FRAC_1_SQRT_2),
) )
); );
for index_x in 0..=1 { for index_x in 0..=1 {
@ -223,7 +223,7 @@ fn load_pointed_assemb(assembly: &Assembly) {
format!("sphere{index_x}{index_y}"), format!("sphere{index_x}{index_y}"),
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], [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) engine::sphere(x, y, 0.0, 1.0),
) )
); );
@ -232,7 +232,7 @@ fn load_pointed_assemb(assembly: &Assembly) {
format!("point{index_x}{index_y}"), format!("point{index_x}{index_y}"),
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], [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) engine::point(x, y, 0.0),
) )
); );
} }
@ -256,56 +256,56 @@ fn load_tridim_icosahedron_assemb(assembly: &Assembly) {
"a1".to_string(), "a1".to_string(),
"A₁".to_string(), "A₁".to_string(),
COLOR_A, COLOR_A,
engine::point(0.25, 0.75, 0.75) engine::point(0.25, 0.75, 0.75),
), ),
Point::new( Point::new(
"a2".to_string(), "a2".to_string(),
"A₂".to_string(), "A₂".to_string(),
COLOR_A, COLOR_A,
engine::point(0.75, 0.25, 0.75) engine::point(0.75, 0.25, 0.75),
), ),
Point::new( Point::new(
"a3".to_string(), "a3".to_string(),
"A₃".to_string(), "A₃".to_string(),
COLOR_A, COLOR_A,
engine::point(0.75, 0.75, 0.25) engine::point(0.75, 0.75, 0.25),
), ),
Point::new( Point::new(
"b1".to_string(), "b1".to_string(),
"B₁".to_string(), "B₁".to_string(),
COLOR_B, COLOR_B,
engine::point(0.75, -0.25, -0.25) engine::point(0.75, -0.25, -0.25),
), ),
Point::new( Point::new(
"b2".to_string(), "b2".to_string(),
"B₂".to_string(), "B₂".to_string(),
COLOR_B, COLOR_B,
engine::point(-0.25, 0.75, -0.25) engine::point(-0.25, 0.75, -0.25),
), ),
Point::new( Point::new(
"b3".to_string(), "b3".to_string(),
"B₃".to_string(), "B₃".to_string(),
COLOR_B, COLOR_B,
engine::point(-0.25, -0.25, 0.75) engine::point(-0.25, -0.25, 0.75),
), ),
Point::new( Point::new(
"c1".to_string(), "c1".to_string(),
"C₁".to_string(), "C₁".to_string(),
COLOR_C, COLOR_C,
engine::point(0.0, -1.0, -1.0) engine::point(0.0, -1.0, -1.0),
), ),
Point::new( Point::new(
"c2".to_string(), "c2".to_string(),
"C₂".to_string(), "C₂".to_string(),
COLOR_C, COLOR_C,
engine::point(-1.0, 0.0, -1.0) engine::point(-1.0, 0.0, -1.0),
), ),
Point::new( Point::new(
"c3".to_string(), "c3".to_string(),
"C₃".to_string(), "C₃".to_string(),
COLOR_C, COLOR_C,
engine::point(-1.0, -1.0, 0.0) engine::point(-1.0, -1.0, 0.0),
) ),
]; ];
for vertex in vertices { for vertex in vertices {
let _ = assembly.try_insert_element(vertex); let _ = assembly.try_insert_element(vertex);
@ -320,20 +320,20 @@ fn load_tridim_icosahedron_assemb(assembly: &Assembly) {
"face1".to_string(), "face1".to_string(),
"Face 1".to_string(), "Face 1".to_string(),
COLOR_FACE, COLOR_FACE,
engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0),
), ),
Sphere::new( Sphere::new(
"face2".to_string(), "face2".to_string(),
"Face 2".to_string(), "Face 2".to_string(),
COLOR_FACE, COLOR_FACE,
engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0) engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0),
), ),
Sphere::new( Sphere::new(
"face3".to_string(), "face3".to_string(),
"Face 3".to_string(), "Face 3".to_string(),
COLOR_FACE, COLOR_FACE,
engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0) 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 { for face in faces {
face.ghost().set(true); face.ghost().set(true);
@ -416,7 +416,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) {
"substrate".to_string(), "substrate".to_string(),
"Substrate".to_string(), "Substrate".to_string(),
[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, 1.0) engine::sphere(0.0, 0.0, 0.0, 1.0),
) )
); );
let substrate = assembly.elements_by_id.with_untracked( let substrate = assembly.elements_by_id.with_untracked(
@ -456,7 +456,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) {
id_a.clone(), id_a.clone(),
format!("A{label_sub}"), format!("A{label_sub}"),
COLOR_A, COLOR_A,
engine::sphere(0.0, small_coord, big_coord, face_radii[k]) engine::sphere(0.0, small_coord, big_coord, face_radii[k]),
) )
); );
faces.push( faces.push(
@ -472,7 +472,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) {
id_b.clone(), id_b.clone(),
format!("B{label_sub}"), format!("B{label_sub}"),
COLOR_B, COLOR_B,
engine::sphere(small_coord, big_coord, 0.0, face_radii[k]) engine::sphere(small_coord, big_coord, 0.0, face_radii[k]),
) )
); );
faces.push( faces.push(
@ -488,7 +488,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) {
id_c.clone(), id_c.clone(),
format!("C{label_sub}"), format!("C{label_sub}"),
COLOR_C, COLOR_C,
engine::sphere(big_coord, 0.0, small_coord, face_radii[k]) engine::sphere(big_coord, 0.0, small_coord, face_radii[k]),
) )
); );
faces.push( faces.push(
@ -559,19 +559,19 @@ fn load_balanced_assemb(assembly: &Assembly) {
"outer".to_string(), "outer".to_string(),
"Outer".to_string(), "Outer".to_string(),
[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, R_OUTER) engine::sphere(0.0, 0.0, 0.0, R_OUTER),
), ),
Sphere::new( Sphere::new(
"a".to_string(), "a".to_string(),
"A".to_string(), "A".to_string(),
[1.00_f32, 0.00_f32, 0.25_f32], [1.00_f32, 0.00_f32, 0.25_f32],
engine::sphere(0.0, 4.0, 0.0, R_INNER) engine::sphere(0.0, 4.0, 0.0, R_INNER),
), ),
Sphere::new( Sphere::new(
"b".to_string(), "b".to_string(),
"B".to_string(), "B".to_string(),
[0.00_f32, 0.25_f32, 1.00_f32], [0.00_f32, 0.25_f32, 1.00_f32],
engine::sphere(0.0, -4.0, 0.0, R_INNER) engine::sphere(0.0, -4.0, 0.0, R_INNER),
), ),
]; ];
for sphere in spheres { for sphere in spheres {
@ -589,7 +589,7 @@ fn load_balanced_assemb(assembly: &Assembly) {
for (sphere, radius) in [ for (sphere, radius) in [
(outer.clone(), R_OUTER), (outer.clone(), R_OUTER),
(a.clone(), R_INNER), (a.clone(), R_INNER),
(b.clone(), R_INNER) (b.clone(), R_INNER),
] { ] {
let curvature_regulator = sphere.regulators().with_untracked( let curvature_regulator = sphere.regulators().with_untracked(
|regs| regs.first().unwrap().clone() |regs| regs.first().unwrap().clone()
@ -618,7 +618,7 @@ fn load_off_center_assemb(assembly: &Assembly) {
"point".to_string(), "point".to_string(),
"Point".to_string(), "Point".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::point(1e-9, 0.0, 0.0) engine::point(1e-9, 0.0, 0.0),
), ),
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
@ -626,7 +626,7 @@ fn load_off_center_assemb(assembly: &Assembly) {
"sphere".to_string(), "sphere".to_string(),
"Sphere".to_string(), "Sphere".to_string(),
[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, 1.0) engine::sphere(0.0, 0.0, 0.0, 1.0),
), ),
); );
@ -658,14 +658,14 @@ fn load_radius_ratio_assemb(assembly: &Assembly) {
"sphere_faces".to_string(), "sphere_faces".to_string(),
"Insphere".to_string(), "Insphere".to_string(),
GRAY, GRAY,
engine::sphere(0.0, 0.0, 0.0, 0.5) engine::sphere(0.0, 0.0, 0.0, 0.5),
), ),
Sphere::new( Sphere::new(
"sphere_vertices".to_string(), "sphere_vertices".to_string(),
"Circumsphere".to_string(), "Circumsphere".to_string(),
GRAY, GRAY,
engine::sphere(0.0, 0.0, 0.0, 0.25) engine::sphere(0.0, 0.0, 0.0, 0.25),
) ),
]; ];
for sphere in spheres { for sphere in spheres {
let _ = assembly.try_insert_element(sphere); let _ = assembly.try_insert_element(sphere);
@ -678,13 +678,13 @@ fn load_radius_ratio_assemb(assembly: &Assembly) {
[1.00_f32, 0.50_f32, 0.75_f32], [1.00_f32, 0.50_f32, 0.75_f32],
[1.00_f32, 0.75_f32, 0.50_f32], [1.00_f32, 0.75_f32, 0.50_f32],
[1.00_f32, 1.00_f32, 0.50_f32], [1.00_f32, 1.00_f32, 0.50_f32],
[0.75_f32, 0.50_f32, 1.00_f32] [0.75_f32, 0.50_f32, 1.00_f32],
].into_iter(), ].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),
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() ].into_iter()
).map( ).map(
|(k, color, representation)| { |(k, color, representation)| {
@ -692,7 +692,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) {
format!("v{k}"), format!("v{k}"),
format!("Vertex {k}"), format!("Vertex {k}"),
color, color,
representation representation,
) )
} }
); );
@ -709,13 +709,13 @@ fn load_radius_ratio_assemb(assembly: &Assembly) {
[1.00_f32, 0.00_f32, 0.25_f32], [1.00_f32, 0.00_f32, 0.25_f32],
[1.00_f32, 0.25_f32, 0.00_f32], [1.00_f32, 0.25_f32, 0.00_f32],
[0.75_f32, 0.75_f32, 0.00_f32], [0.75_f32, 0.75_f32, 0.00_f32],
[0.25_f32, 0.00_f32, 1.00_f32] [0.25_f32, 0.00_f32, 1.00_f32],
].into_iter(), ].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),
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() ].into_iter()
).map( ).map(
|(k, color, representation)| { |(k, color, representation)| {
@ -723,7 +723,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) {
format!("f{k}"), format!("f{k}"),
format!("Face {k}"), format!("Face {k}"),
color, color,
representation representation,
) )
} }
); );
@ -736,7 +736,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) {
for j in index_range.clone() { for j in index_range.clone() {
let [face_j, vertex_j] = [ let [face_j, vertex_j] = [
format!("f{j}"), format!("f{j}"),
format!("v{j}") format!("v{j}"),
].map( ].map(
|id| assembly.elements_by_id.with_untracked( |id| assembly.elements_by_id.with_untracked(
|elts_by_id| elts_by_id[&id].clone() |elts_by_id| elts_by_id[&id].clone()
@ -797,7 +797,7 @@ fn load_irisawa_hexlet_assemb(assembly: &Assembly) {
[0.75_f32, 0.75_f32, 0.00_f32], [0.75_f32, 0.75_f32, 0.00_f32],
[0.25_f32, 1.00_f32, 0.00_f32], [0.25_f32, 1.00_f32, 0.00_f32],
[0.00_f32, 0.25_f32, 1.00_f32], [0.00_f32, 0.25_f32, 1.00_f32],
[0.25_f32, 0.00_f32, 1.00_f32] [0.25_f32, 0.00_f32, 1.00_f32],
].into_iter(); ].into_iter();
// create the spheres // create the spheres
@ -806,19 +806,19 @@ fn load_irisawa_hexlet_assemb(assembly: &Assembly) {
"outer".to_string(), "outer".to_string(),
"Outer".to_string(), "Outer".to_string(),
[0.5_f32, 0.5_f32, 0.5_f32], [0.5_f32, 0.5_f32, 0.5_f32],
engine::sphere(0.0, 0.0, 0.0, 1.5) engine::sphere(0.0, 0.0, 0.0, 1.5),
), ),
Sphere::new( Sphere::new(
"sun".to_string(), "sun".to_string(),
"Sun".to_string(), "Sun".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere(0.0, -0.75, 0.0, 0.75) engine::sphere(0.0, -0.75, 0.0, 0.75),
), ),
Sphere::new( Sphere::new(
"moon".to_string(), "moon".to_string(),
"Moon".to_string(), "Moon".to_string(),
[0.25_f32, 0.25_f32, 0.25_f32], [0.25_f32, 0.25_f32, 0.25_f32],
engine::sphere(0.0, 0.75, 0.0, 0.75) engine::sphere(0.0, 0.75, 0.0, 0.75),
), ),
].into_iter().chain( ].into_iter().chain(
index_range.clone().zip(colors).map( index_range.clone().zip(colors).map(
@ -828,7 +828,7 @@ fn load_irisawa_hexlet_assemb(assembly: &Assembly) {
format!("chain{k}"), format!("chain{k}"),
format!("Chain {k}"), format!("Chain {k}"),
color, color,
engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5) engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5),
) )
} }
) )
@ -865,7 +865,7 @@ fn load_irisawa_hexlet_assemb(assembly: &Assembly) {
(outer.clone(), "1"), (outer.clone(), "1"),
(sun.clone(), "-1"), (sun.clone(), "-1"),
(moon.clone(), "-1"), (moon.clone(), "-1"),
(chain_sphere_next.clone(), "-1") (chain_sphere_next.clone(), "-1"),
] { ] {
let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]); let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]);
tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap()); tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap());
@ -918,24 +918,24 @@ pub fn TestAssemblyChooser() -> View {
"off-center" => load_off_center_assemb(assembly), "off-center" => load_off_center_assemb(assembly),
"radius-ratio" => load_radius_ratio_assemb(assembly), "radius-ratio" => load_radius_ratio_assemb(assembly),
"irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly), "irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly),
_ => () _ => (),
}; };
}); });
}); });
// build the chooser // build the chooser
view! { view! {
select(bind:value=assembly_name) { select(bind:value = assembly_name) {
option(value="general") { "General" } option(value = "general") { "General" }
option(value="low-curv") { "Low-curvature" } option(value = "low-curv") { "Low-curvature" }
option(value="pointed") { "Pointed" } option(value = "pointed") { "Pointed" }
option(value="tridim-icosahedron") { "Tridiminished icosahedron" } option(value = "tridim-icosahedron") { "Tridiminished icosahedron" }
option(value="dodeca-packing") { "Dodecahedral packing" } option(value = "dodeca-packing") { "Dodecahedral packing" }
option(value="balanced") { "Balanced" } option(value = "balanced") { "Balanced" }
option(value="off-center") { "Off-center" } option(value = "off-center") { "Off-center" }
option(value="radius-ratio") { "Radius ratio" } option(value = "radius-ratio") { "Radius ratio" }
option(value="irisawa-hexlet") { "Irisawa hexlet" } option(value = "irisawa-hexlet") { "Irisawa hexlet" }
option(value="empty") { "Empty" } option(value = "empty") { "Empty" }
} }
} }
} }

View file

@ -16,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),
]) ])
} }
@ -30,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),
]) ])
} }
@ -53,7 +53,7 @@ pub fn project_point_to_normalized(rep: &mut DVector<f64>) {
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>);
@ -65,7 +65,7 @@ impl PartialMatrix {
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 PartialMatrix(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) {
@ -135,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) -> ConfigSubspace {
ConfigSubspace { ConfigSubspace {
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,
) -> ConfigSubspace {
// 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
@ -170,7 +174,7 @@ impl ConfigSubspace {
const ELEMENT_DIM: usize = 5; const ELEMENT_DIM: usize = 5;
const UNIFORM_DIM: usize = 4; const UNIFORM_DIM: usize = 4;
ConfigSubspace { ConfigSubspace {
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))
@ -180,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(),
} }
} }
@ -214,9 +218,9 @@ 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 {
@ -246,7 +250,7 @@ impl ConstraintProblem {
ConstraintProblem { ConstraintProblem {
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),
} }
} }
@ -255,7 +259,7 @@ impl ConstraintProblem {
ConstraintProblem { ConstraintProblem {
gram: PartialMatrix::new(), gram: PartialMatrix::new(),
frozen: PartialMatrix::new(), frozen: PartialMatrix::new(),
guess: DMatrix::from_columns(guess_columns) guess: DMatrix::from_columns(guess_columns),
} }
} }
} }
@ -269,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>) -> SearchState {
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 { SearchState { config, err_proj, loss }
config: config,
err_proj: err_proj,
loss: loss
}
} }
} }
@ -314,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
@ -323,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,
]) ])
} }
} }
@ -336,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 {
@ -354,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
@ -373,12 +373,10 @@ 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();
@ -391,10 +389,10 @@ pub fn realize_gram(
let result = Ok( let result = Ok(
ConfigNeighborhood { ConfigNeighborhood {
config: guess.clone(), config: guess.clone(),
nbhd: ConfigSubspace::zero(0) nbhd: ConfigSubspace::zero(0),
} }
); );
return Realization { result, history } return Realization { result, history };
} }
// find the dimension of the search space // find the dimension of the search space
@ -475,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));
@ -485,17 +483,17 @@ 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
const UNIFORM_DIM: usize = 4; const UNIFORM_DIM: usize = 4;
@ -539,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| {
@ -598,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()
@ -641,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);
} }
@ -660,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);
} }
@ -686,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);
@ -700,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 {
@ -744,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 {
@ -774,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),
@ -785,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
@ -862,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(
@ -898,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,
]) ])
} }
@ -910,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);
@ -928,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
@ -962,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;

View file

@ -14,20 +14,20 @@ use components::{
add_remove::AddRemove, add_remove::AddRemove,
diagnostics::Diagnostics, diagnostics::Diagnostics,
display::Display, display::Display,
outline::Outline 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() -> AppState {
AppState { AppState {
assembly: Assembly::new(), assembly: Assembly::new(),
selection: create_signal(BTreeSet::default()) selection: create_signal(BTreeSet::default()),
} }
} }
@ -58,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 {}

View file

@ -13,7 +13,7 @@ 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 {
@ -37,7 +37,7 @@ impl TryFrom<String> for SpecifiedValue {
Ok(SpecifiedValue::from_empty_spec()) Ok(SpecifiedValue::from_empty_spec())
} else { } else {
spec.parse::<f64>().map( spec.parse::<f64>().map(
|value| SpecifiedValue { spec: spec, value: Some(value) } |value| SpecifiedValue { spec, value: Some(value) }
) )
} }
} }