Compare commits

..

1 commit

Author SHA1 Message Date
2c8c09d20d feat: Point coordinate regulators (#118)
Implement regulators for the Euclidean coordinates of `Point` entities, automatically creating all three of them for each added point entity. When such a regulator is set, it freezes the corresponding representation coordinate to the set point. In addition, if all three coordinates of a given `Point` are set, the coradius coordinate (which holds the norm of the point) is frozen as well.

Note that a `PointCoordinateRegulator` must be created with a `Point` as the subject. This commit modifies `HalfCurvatureRegulator` analogously, so that it can only be created with a `Sphere`.
Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Co-committed-by: Glen Whitney <glen@studioinfinity.org>
2025-10-13 22:52:02 +00:00
14 changed files with 343 additions and 1539 deletions

View file

@ -12,11 +12,11 @@ Note that currently this is just the barest beginnings of the project, more of a
### Implementation goals ### Implementation goals
* Comfortable, intuitive UI * Provide a comfortable, intuitive UI
* Able to run in browser (so implemented in WASM-compatible language) * Allow execution in browser (so implemented in WASM-compatible language)
* Produce scalable graphics of 3D diagrams, and maybe STL files (or other fabricatable file format) as well. * Produce scalable graphics of 3D diagrams, and maybe STL files (or other fabricatable file format) as well
## Prototype ## Prototype
@ -24,38 +24,40 @@ 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.
- In the future, `trunk` can be updated with the same command. (You may need the `--locked` flag if your ambient version of `rustc` does not match that required by `trunk`.)
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.
- Alternatively, if you don't want to adjust your `PATH`, you can install `trunk` in another directory `DIR` via `cargo install --root DIR trunk`.
### 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. Use `sh` to run the script `tools/run-examples.sh` 1. Use `sh` to run the script `tools/run-examples.sh`.
- The script is location-independent, so you can do this from anywhere in the dyna3 repository - The 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: - The call from the top level of the repository is:
```bash ```bash
sh tools/run-examples.sh sh tools/run-examples.sh
``` ```
- For each example problem, the engine will print the value of the loss function at each optimization step - 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 - 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 execute
```julia ```julia
include("irisawa-hexlet.jl") include("irisawa-hexlet.jl")
@ -64,24 +66,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 ### Deploy the prototype
1. From the `app-proto` folder, call `trunk build --release` 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 - 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 - 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`. 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 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: - The call from the top level of the repository is:
```bash ```bash
sh tools/package-for-deployment.sh sh tools/package-for-deployment.sh
``` ```
- This will overwrite or replace the files in `deploy/dyna3` - 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. 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 - 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.

View file

@ -227,10 +227,6 @@ details[open]:has(li) .element-switch::after {
border-radius: 8px; border-radius: 8px;
} }
#distortion-gauge {
margin-top: 8px;
}
/* display */ /* display */
#display { #display {

View file

@ -1,2 +0,0 @@
[toolchain]
channel = "nightly"

View file

@ -1,13 +1,11 @@
use enum_iterator::{all, Sequence}; use enum_iterator::{all, Sequence};
use nalgebra::{DMatrix, DVector, DVectorView}; use nalgebra::{DMatrix, DVector, DVectorView};
use std::{ use std::{
any::Any,
cell::Cell, cell::Cell,
cmp::Ordering, cmp::Ordering,
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
f64::consts::SQRT_2,
fmt, fmt,
fmt::{Debug, Formatter}, fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
rc::Rc, rc::Rc,
sync::{atomic, atomic::AtomicU64}, sync::{atomic, atomic::AtomicU64},
@ -88,6 +86,14 @@ impl Ord for dyn Serial {
} }
} }
// Small helper function to generate consistent errors when there
// are indexing issues in a ProblemPoser
fn indexing_error(item: &str, name: &str, actor: &str) -> String {
format!(
"{item} \"{name}\" must be indexed before {actor} writes problem data"
)
}
pub trait ProblemPoser { pub trait ProblemPoser {
fn pose(&self, problem: &mut ConstraintProblem); fn pose(&self, problem: &mut ConstraintProblem);
} }
@ -97,7 +103,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem {
fn default_id() -> String where Self: Sized; fn default_id() -> String where Self: Sized;
// the default example of an element of this type // the default example of an element of this type
fn default(id: &str, id_num: u64) -> Self where Self: Sized; fn default(id: String, id_num: u64) -> Self where Self: Sized;
// the default regulators that come with this element // the default regulators that come with this element
fn default_regulators(self: Rc<Self>) -> Vec<Rc<dyn Regulator>> { fn default_regulators(self: Rc<Self>) -> Vec<Rc<dyn Regulator>> {
@ -126,16 +132,11 @@ pub trait Element: Serial + ProblemPoser + DisplayItem {
// be used carefully to preserve invariant (1), described in the comment on // be used carefully to preserve invariant (1), described in the comment on
// the `tangent` field of the `Assembly` structure // the `tangent` field of the `Assembly` structure
fn set_column_index(&self, index: usize); fn set_column_index(&self, index: usize);
/* KLUDGE */
fn has_distortion(&self) -> bool {
false
}
} }
impl Debug for dyn Element { impl Debug for dyn Element {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.id().fmt(f) Debug::fmt(&self.id(), f)
} }
} }
@ -180,14 +181,14 @@ impl Sphere {
const CURVATURE_COMPONENT: usize = 3; const CURVATURE_COMPONENT: usize = 3;
pub fn new( pub fn new(
id: &str, id: String,
label: &str, label: String,
color: ElementColor, color: ElementColor,
representation: DVector<f64>, representation: DVector<f64>,
) -> Self { ) -> Self {
Self { Self {
id: id.to_string(), id,
label: label.to_string(), label,
color, color,
representation: create_signal(representation), representation: create_signal(representation),
ghost: create_signal(false), ghost: create_signal(false),
@ -203,11 +204,11 @@ impl Element for Sphere {
"sphere".to_string() "sphere".to_string()
} }
fn default(id: &str, id_num: u64) -> Self { fn default(id: String, id_num: u64) -> Self {
Self::new( Self::new(
id, id,
&format!("Sphere {id_num}"), format!("Sphere {id_num}"),
[0.75, 0.75, 0.75], [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),
) )
} }
@ -258,8 +259,7 @@ impl Serial for Sphere {
impl ProblemPoser for Sphere { impl ProblemPoser for Sphere {
fn pose(&self, problem: &mut ConstraintProblem) { fn pose(&self, problem: &mut ConstraintProblem) {
let index = self.column_index().expect( let index = self.column_index().expect(
format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str() indexing_error("Sphere", &self.id, "it").as_str());
);
problem.gram.push_sym(index, index, 1.0); problem.gram.push_sym(index, index, 1.0);
problem.guess.set_column(index, &self.representation.get_clone_untracked()); problem.guess.set_column(index, &self.representation.get_clone_untracked());
} }
@ -281,14 +281,14 @@ impl Point {
const NORM_COMPONENT: usize = 4; const NORM_COMPONENT: usize = 4;
pub fn new( pub fn new(
id: &str, id: String,
label: &str, label: String,
color: ElementColor, color: ElementColor,
representation: DVector<f64>, representation: DVector<f64>,
) -> Self { ) -> Self {
Self { Self {
id: id.to_string(), id,
label: label.to_string(), label,
color, color,
representation: create_signal(representation), representation: create_signal(representation),
ghost: create_signal(false), ghost: create_signal(false),
@ -304,10 +304,10 @@ impl Element for Point {
"point".to_string() "point".to_string()
} }
fn default(id: &str, id_num: u64) -> Self { fn default(id: String, id_num: u64) -> Self {
Self::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),
) )
@ -353,10 +353,6 @@ impl Element for Point {
fn set_column_index(&self, index: usize) { fn set_column_index(&self, index: usize) {
self.column_index.set(Some(index)); self.column_index.set(Some(index));
} }
fn has_distortion(&self) -> bool {
true
}
} }
impl Serial for Point { impl Serial for Point {
@ -368,33 +364,17 @@ impl Serial for Point {
impl ProblemPoser for Point { impl ProblemPoser for Point {
fn pose(&self, problem: &mut ConstraintProblem) { fn pose(&self, problem: &mut ConstraintProblem) {
let index = self.column_index().expect( let index = self.column_index().expect(
format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str() indexing_error("Point", &self.id, "it").as_str());
);
problem.gram.push_sym(index, index, 0.0); problem.gram.push_sym(index, index, 0.0);
problem.frozen.push(Self::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());
} }
} }
pub trait Regulator: Any + Serial + ProblemPoser + OutlineItem { 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>;
fn distortion(&self) -> Option<ReadSignal<f64>> { /* KLUDGE */
None
}
fn set_to(&self, val: f64) {
self.set_point().set(SpecifiedValue::from(Some(val)))
}
// QUESTION: Why doesn't the following work? When I used it to
// set the coordinate regulators on pinned points, dyna3 would panic
// when trying to nudge unpinned points :-(
// fn set_to_current(&self) {
// self.set_point().set(
// SpecifiedValue::from(Some(self.measurement().get())),
// )
// }
fn as_any(&self) -> &dyn Any;
} }
impl Hash for dyn Regulator { impl Hash for dyn Regulator {
@ -427,7 +407,6 @@ 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>,
distortion: Option<ReadSignal<f64>>, /* KLUDGE */
serial: u64, serial: u64,
} }
@ -443,23 +422,9 @@ impl InversiveDistanceRegulator {
}); });
let set_point = create_signal(SpecifiedValue::from_empty_spec()); let set_point = create_signal(SpecifiedValue::from_empty_spec());
let distortion = if subjects.iter().all(|subj| subj.has_distortion()) {
Some(create_memo(move || {
let set_point_opt = set_point.with(|set_pt| set_pt.value);
let measurement_val = measurement.get();
match set_point_opt {
None => 0.0,
Some(set_point_val) => SQRT_2 * (
(-set_point_val).sqrt() - (-measurement_val).sqrt()
).abs(),
}
}))
} else {
None
};
let serial = Self::next_serial(); let serial = Self::next_serial();
Self { subjects, measurement, set_point, distortion, serial } Self { subjects, measurement, set_point, serial }
} }
} }
@ -475,12 +440,6 @@ impl Regulator for InversiveDistanceRegulator {
fn set_point(&self) -> Signal<SpecifiedValue> { fn set_point(&self) -> Signal<SpecifiedValue> {
self.set_point self.set_point
} }
fn distortion(&self) -> Option<ReadSignal<f64>> {
self.distortion
}
fn as_any(&self) -> &dyn Any {self}
} }
impl Serial for InversiveDistanceRegulator { impl Serial for InversiveDistanceRegulator {
@ -495,8 +454,8 @@ impl ProblemPoser for InversiveDistanceRegulator {
if let Some(val) = set_pt.value { if let Some(val) = set_pt.value {
let [row, col] = self.subjects.each_ref().map( let [row, col] = self.subjects.each_ref().map(
|subj| subj.column_index().expect( |subj| subj.column_index().expect(
"Subjects should be indexed before inversive distance regulator writes problem data" indexing_error("Subject", subj.id(),
) "inversive distance regulator").as_str())
); );
problem.gram.push_sym(row, col, val); problem.gram.push_sym(row, col, val);
} }
@ -536,8 +495,6 @@ impl Regulator for HalfCurvatureRegulator {
fn set_point(&self) -> Signal<SpecifiedValue> { fn set_point(&self) -> Signal<SpecifiedValue> {
self.set_point self.set_point
} }
fn as_any(&self) -> &dyn Any {self}
} }
impl Serial for HalfCurvatureRegulator { impl Serial for HalfCurvatureRegulator {
@ -551,8 +508,8 @@ impl ProblemPoser for HalfCurvatureRegulator {
self.set_point.with_untracked(|set_pt| { self.set_point.with_untracked(|set_pt| {
if let Some(val) = set_pt.value { if let Some(val) = set_pt.value {
let col = self.subject.column_index().expect( let col = self.subject.column_index().expect(
"Subject should be indexed before half-curvature regulator writes problem data" indexing_error("Subject", &self.subject.id,
); "half-curvature regulator").as_str());
problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val); problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val);
} }
}); });
@ -560,11 +517,18 @@ impl ProblemPoser for HalfCurvatureRegulator {
} }
#[derive(Clone, Copy, Sequence)] #[derive(Clone, Copy, Sequence)]
pub enum Axis {X = 0, Y = 1, Z = 2} pub enum Axis { X = 0, Y = 1, Z = 2 }
impl Axis { impl Axis {
pub const N_AXIS: usize = (Axis::Z as usize) + 1; fn name(&self) -> &'static str {
pub const NAME: [&str; Axis::N_AXIS] = ["X", "Y", "Z"]; match self { Axis::X => "X", Axis::Y => "Y", Axis::Z => "Z" }
}
}
impl Display for Axis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
} }
pub struct PointCoordinateRegulator { pub struct PointCoordinateRegulator {
@ -593,7 +557,6 @@ impl Regulator for PointCoordinateRegulator {
fn subjects(&self) -> Vec<Rc<dyn Element>> { vec![self.subject.clone()] } fn subjects(&self) -> Vec<Rc<dyn Element>> { vec![self.subject.clone()] }
fn measurement(&self) -> ReadSignal<f64> { self.measurement } fn measurement(&self) -> ReadSignal<f64> { self.measurement }
fn set_point(&self) -> Signal<SpecifiedValue> { self.set_point } fn set_point(&self) -> Signal<SpecifiedValue> { self.set_point }
fn as_any(&self) -> &dyn Any {self}
} }
impl ProblemPoser for PointCoordinateRegulator { impl ProblemPoser for PointCoordinateRegulator {
@ -601,19 +564,20 @@ impl ProblemPoser for PointCoordinateRegulator {
self.set_point.with_untracked(|set_pt| { self.set_point.with_untracked(|set_pt| {
if let Some(val) = set_pt.value { if let Some(val) = set_pt.value {
let col = self.subject.column_index().expect( let col = self.subject.column_index().expect(
"Subject must be indexed before point-coordinate regulator poses."); indexing_error("Subject", &self.subject.id,
"point-coordinate regulator").as_str());
problem.frozen.push(self.axis as usize, col, val); problem.frozen.push(self.axis as usize, col, val);
// Check if all three coordinates have been frozen, and if so, // If all three of the subject's spatial coordinates have been
// freeze the coradius as well // frozen, then freeze its norm component:
let mut coords = [0.0; Axis::N_AXIS]; let mut coords = [0.0; Axis::CARDINALITY];
let mut nset: usize = 0; let mut nset: usize = 0;
for &MatrixEntry {index, value} in &(problem.frozen) { for &MatrixEntry {index, value} in &(problem.frozen) {
if index.1 == col && index.0 < Axis::N_AXIS { if index.1 == col && index.0 < Axis::CARDINALITY {
nset += 1; nset += 1;
coords[index.0] = value coords[index.0] = value
} }
} }
if nset == Axis::N_AXIS { if nset == Axis::CARDINALITY {
let [x, y, z] = coords; let [x, y, z] = coords;
problem.frozen.push( problem.frozen.push(
Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]); Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]);
@ -746,12 +710,9 @@ impl Assembly {
} }
// create and insert the default example of `T` // create and insert the default example of `T`
let _ = self.insert_element_unchecked(T::default(&id, id_num)); let _ = self.insert_element_unchecked(T::default(id, id_num));
} }
// FIXME: insert_element and insert_regulator are not parallel;
// the former takes an element and calls Rc::new, inserting clones of
// the result, whereas the latter takes an Rc and inserts clones of that
pub fn insert_regulator(&self, regulator: Rc<dyn Regulator>) { pub fn insert_regulator(&self, regulator: Rc<dyn Regulator>) {
// add the regulator to the assembly's regulator list // add the regulator to the assembly's regulator list
self.regulators.update( self.regulators.update(
@ -788,11 +749,6 @@ impl Assembly {
} }
}); });
} }
// --- finding entities ---
pub fn find_element(&self, id: &str) -> Rc<dyn Element> {
self.elements_by_id.with_untracked( |elts| elts[id].clone() )
}
// --- updating the configuration --- // --- updating the configuration ---
@ -831,6 +787,7 @@ impl Assembly {
/* DEBUG */ /* DEBUG */
// log the Gram matrix // log the Gram matrix
console_log!("Gram matrix:\n{}", problem.gram); console_log!("Gram matrix:\n{}", problem.gram);
console_log!("Frozen entries:\n{}", problem.frozen);
/* DEBUG */ /* DEBUG */
// log the initial configuration matrix // log the initial configuration matrix
@ -990,7 +947,8 @@ mod tests {
use crate::engine; use crate::engine;
#[test] #[test]
#[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")] #[should_panic(expected =
"Sphere \"sphere\" must be indexed before it writes problem data")]
fn unindexed_element_test() { fn unindexed_element_test() {
let _ = create_root(|| { let _ = create_root(|| {
let elt = Sphere::default("sphere".to_string(), 0); let elt = Sphere::default("sphere".to_string(), 0);
@ -999,7 +957,8 @@ mod tests {
} }
#[test] #[test]
#[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")] #[should_panic(expected = "Subject \"sphere1\" must be indexed before \
inversive distance regulator writes problem data")]
fn unindexed_subject_test_inversive_distance() { fn unindexed_subject_test_inversive_distance() {
let _ = create_root(|| { let _ = create_root(|| {
let subjects = [0, 1].map( let subjects = [0, 1].map(
@ -1060,4 +1019,4 @@ mod tests {
assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() < DRIFT_TOL); assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() < DRIFT_TOL);
}); });
} }
} }

View file

@ -111,28 +111,6 @@ fn StepInput() -> View {
} }
} }
#[component]
fn DistortionGauge() -> View {
let state = use_context::<AppState>();
let total_distortion = create_memo(move || {
state.assembly.regulators.with(|regs| {
let mut total = 0.0;
for reg in regs {
if let Some(distortion) = reg.distortion() {
total += distortion.get();
}
}
total
})
});
view! {
div(id = "distortion-gauge") {
"Distortion: " (total_distortion.with(|distort| distort.to_string()))
}
}
}
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),
@ -337,7 +315,6 @@ pub fn Diagnostics() -> View {
} }
DiagnosticsPanel(name = "loss") { LossHistory {} } DiagnosticsPanel(name = "loss") { LossHistory {} }
DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} } DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} }
DistortionGauge {}
} }
} }
} }

View file

@ -1,12 +1,11 @@
use itertools::Itertools; use itertools::Itertools;
use std::rc::Rc; use std::rc::Rc;
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::{JsCast,}}; use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::JsCast};
use crate::{ use crate::{
AppState, AppState,
assembly::{ assembly::{
Axis,
Element, Element,
HalfCurvatureRegulator, HalfCurvatureRegulator,
InversiveDistanceRegulator, InversiveDistanceRegulator,
@ -123,10 +122,11 @@ impl OutlineItem for HalfCurvatureRegulator {
impl OutlineItem for PointCoordinateRegulator { impl OutlineItem for PointCoordinateRegulator {
fn outline_item(self: Rc<Self>, _element: &Rc<dyn Element>) -> View { fn outline_item(self: Rc<Self>, _element: &Rc<dyn Element>) -> View {
let name = format!("{} coordinate", self.axis);
view! { view! {
li(class = "regulator") { li(class = "regulator") {
div(class = "regulator-label") { (Axis::NAME[self.axis as usize]) } div(class = "regulator-label") // for spacing
div(class = "regulator-type") { "Coordinate" } div(class = "regulator-type") { (name) }
RegulatorInput(regulator = self) RegulatorInput(regulator = self)
div(class = "status") div(class = "status")
} }
@ -261,31 +261,6 @@ pub fn Outline() -> View {
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())
},
on:keydown = {
let rep_list = state.assembly.elements.map(
|elts| elts
.clone()
.into_iter()
.map(|elt| format!("{}: {}", elt.id(), elt.representation()))
.collect::<Vec<_>>()
);
move |event: KeyboardEvent| {
match event.key().as_str() {
"c" => {
console_log!("Dumping all coordinates");
rep_list.map(
|reps| {
for item in reps {
console_log!("{}", item);
}
}
);
},
_ => {},
}
}
} }
) { ) {
Keyed( Keyed(

View file

@ -1,5 +1,5 @@
use itertools::izip; use itertools::izip;
use std::{f64::consts::{FRAC_1_SQRT_2, PI, SQRT_2}, rc::Rc}; use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc};
use nalgebra::Vector3; use nalgebra::Vector3;
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue}; use web_sys::{console, wasm_bindgen::JsValue};
@ -12,80 +12,13 @@ use crate::{
ElementColor, ElementColor,
InversiveDistanceRegulator, InversiveDistanceRegulator,
Point, Point,
PointCoordinateRegulator,
Regulator,
Sphere, Sphere,
}, },
engine, engine,
engine::{DescentHistory, point}, engine::DescentHistory,
specified::SpecifiedValue, specified::SpecifiedValue,
}; };
// Convenience: macro to allow elision of const array lengths
// adapted from https://stackoverflow.com/a/59905715
macro_rules! const_array {
($name: ident: $ty: ty = $value: expr) => {
const $name: [$ty; $value.len()] = $value;
}
}
// Convenience: use sqrt() as a function to get the square root of
// anything that can be losslessly converted to f64, since we happen
// to always want our sqrts to be f64.
// RUST KVETCHES: (I) Why is this so convoluted?
// In particular, what would be so bad about allowing
// const fn sqrt<T: Into<f64>>(x: T) -> f64 { (x as f64).sqrt() }
// ???
// (II) Oh dear, sqrt is not computable in a const context, so we have to
// roll our own. I hope I get it right! I definitely don't know how to ensure
// that the final double is correctly rounded :-(
const SIXTH: f64 = 1./6.;
const FOURTH: f64 = 1./4.;
const fn invsqrt(xin: f64) -> f64 {
if !xin.is_finite() { return f64::NAN; }
let mut log4 = 0;
let mut x = xin;
while x < 1. { x *= 4.; log4 -= 1; }
while x > 4. { x *= FOURTH; log4 += 1; }
let mut next = 1.1 - SIXTH * x;
let mut diff = 1.;
while diff > 4. * f64::EPSILON { // termination condition??
let last = next;
next = last * (1.5 - 0.5 * x * last * last);
diff = next - last;
if diff < 0. { diff *= -1.; }
}
while log4 > 0 { next *= 0.5; log4 -= 1; }
while log4 < 0 { next *= 2.; log4 += 1; }
return next;
}
#[const_trait]
trait ConstSqrt {fn sqr(self) -> f64;}
impl const ConstSqrt for f64 {fn sqr(self) -> f64 {self * invsqrt(self)}}
impl const ConstSqrt for i32 {fn sqr(self) -> f64 {(self as f64).sqr()}}
const fn sqrt<T: const ConstSqrt>(x: T) -> f64 {x.sqr()}
// RUST KVETCHES: (I) It is annoying that we must redundantly specify
// the type of the well-typed RHS to assign it to a const.
// See https://github.com/rust-lang/rfcs/pull/3546
// Can we fix this in husht? Seems like we will need to be able to do full
// rust type inference in the transpiler; how will we accomplish that?
// (2) It is very annoying that there doesn't seem to be any way to specify
// that we want an expression like `5.0 / 2` to be evaluated by converting
// the 2 to a float, because of the "orphan rule". Can we fix this in husht?
// Again, it seems we would need full rust type inference.
// FIXME: replace with std::f64::consts::PHI when that gets stabilized
const PHI: f64 = (1. + sqrt(5)) / 2.;
const fn gray(level: f32) -> ElementColor { [level, level, level] }
const GRAY: ElementColor = gray(0.75);
const RED: ElementColor = [1., 0., 0.25];
const GREEN: ElementColor = [0.25, 1., 0.];
const BLUE: ElementColor = [0., 0.25, 1.];
// --- loaders --- // --- loaders ---
/* DEBUG */ /* DEBUG */
@ -96,37 +29,49 @@ const BLUE: ElementColor = [0., 0.25, 1.];
fn load_general(assembly: &Assembly) { fn load_general(assembly: &Assembly) {
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"gemini_a", "Castor", [1.00, 0.25, 0.00], 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), engine::sphere(0.5, 0.5, 0.0, 1.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"gemini_b", "Pollux", BLUE, 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), engine::sphere(-0.5, -0.5, 0.0, 1.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"ursa_major", "Ursa major", [0.25, 0.00, 1.00], 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), engine::sphere(-0.5, 0.5, 0.0, 0.75),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"ursa_minor", "Ursa minor", GREEN, 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), engine::sphere(0.5, -0.5, 0.0, 0.5),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"moon_deimos", "Deimos", [0.75, 0.75, 0.00], 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), engine::sphere(0.0, 0.15, 1.0, 0.25),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"moon_phobos", "Phobos", [0.00, 0.75, 0.50], 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), engine::sphere(0.0, -0.15, -1.0, 0.25),
) )
); );
@ -134,66 +79,88 @@ fn load_general(assembly: &Assembly) {
fn load_low_curvature(assembly: &Assembly) { fn load_low_curvature(assembly: &Assembly) {
// create the spheres // create the spheres
const A: f64 = sqrt(0.75); let a = 0.75_f64.sqrt();
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"central", "Central", GRAY, "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), engine::sphere(0.0, 0.0, 0.0, 1.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"assemb_plane", "Assembly plane", GRAY, "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), engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"side1", "Side 1", RED, "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), engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"side2", "Side 2", GREEN, "side2".to_string(),
engine::sphere_with_offset(-0.5, A, 0.0, 1.0, 0.0), "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( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"side3", "Side 3", BLUE, "side3".to_string(),
engine::sphere_with_offset(-0.5, -A, 0.0, 1.0, 0.0), "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( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"corner1", "Corner 1", GRAY, "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), engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"corner2", "Corner 2", GRAY, "corner2".to_string(),
engine::sphere(2.0/3.0, -4.0/3.0 * A, 0.0, 1.0/3.0), "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( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"corner3", "Corner 3", GRAY, String::from("corner3"),
engine::sphere(2.0/3.0, 4.0/3.0 * A, 0.0, 1.0/3.0), 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 // impose the desired tangencies and make the sides planar
let index_range = 1..=3; let index_range = 1..=3;
let [central, assemb_plane] = ["central", "assemb_plane"].map( let [central, assemb_plane] = ["central", "assemb_plane"].map(
|id| assembly.find_element(id) |id| assembly.elements_by_id.with_untracked(
|elts_by_id| elts_by_id[id].clone()
)
); );
let sides = index_range.clone().map( let sides = index_range.clone().map(
|k| assembly.find_element(&format!("side{k}")) |k| assembly.elements_by_id.with_untracked(
|elts_by_id| elts_by_id[&format!("side{k}")].clone()
)
); );
let corners = index_range.map( let corners = index_range.map(
|k| assembly.find_element(&format!("corner{k}")) |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()) { for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) {
// fix the curvature of each plane // fix the curvature of each plane
@ -232,16 +199,16 @@ fn load_low_curvature(assembly: &Assembly) {
fn load_pointed(assembly: &Assembly) { fn load_pointed(assembly: &Assembly) {
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Point::new( Point::new(
"point_front", format!("point_front"),
"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(
Point::new( Point::new(
"point_back", format!("point_back"),
"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),
) )
@ -253,8 +220,8 @@ fn load_pointed(assembly: &Assembly) {
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
&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),
) )
@ -262,8 +229,8 @@ fn load_pointed(assembly: &Assembly) {
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Point::new( Point::new(
&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),
) )
@ -281,42 +248,91 @@ fn load_pointed(assembly: &Assembly) {
// A-C -0.25 * φ^2 = -0.6545084971874737 // A-C -0.25 * φ^2 = -0.6545084971874737
fn load_tridiminished_icosahedron(assembly: &Assembly) { fn load_tridiminished_icosahedron(assembly: &Assembly) {
// create the vertices // create the vertices
const COLOR_A: ElementColor = [1.00, 0.25, 0.25]; const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32];
const COLOR_B: ElementColor = [0.75, 0.75, 0.75]; const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
const COLOR_C: ElementColor = [0.25, 0.50, 1.00]; const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32];
let vertices = [ let vertices = [
Point::new("a1", "A₁", COLOR_A, point( 0.25, 0.75, 0.75)), Point::new(
Point::new("a2", "A₂", COLOR_A, point( 0.75, 0.25, 0.75)), "a1".to_string(),
Point::new("a3", "A₃", COLOR_A, point( 0.75, 0.75, 0.25)), "A₁".to_string(),
Point::new("b1", "B₁", COLOR_B, point( 0.75, -0.25, -0.25)), COLOR_A,
Point::new("b2", "B₂", COLOR_B, point(-0.25, 0.75, -0.25)), engine::point(0.25, 0.75, 0.75),
Point::new("b3", "B₃", COLOR_B, point(-0.25, -0.25, 0.75)), ),
Point::new("c1", "C₁", COLOR_C, point( 0.0, -1.0, -1.0)), Point::new(
Point::new("c2", "C₂", COLOR_C, point(-1.0, 0.0, -1.0)), "a2".to_string(),
Point::new("c3", "C₃", COLOR_C, point(-1.0, -1.0, 0.0)), "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 { for vertex in vertices {
let _ = assembly.try_insert_element(vertex); let _ = assembly.try_insert_element(vertex);
} }
// create the faces // create the faces
const SQRT_1_6: f64 = invsqrt(6.); const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
const SQRT_2_3: f64 = 2. * SQRT_1_6; let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt();
let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6;
let faces = [ let faces = [
Sphere::new( Sphere::new(
"face1", "Face 1", GRAY, "face1".to_string(),
engine::sphere_with_offset( "Face 1".to_string(),
SQRT_2_3, -SQRT_1_6, -SQRT_1_6, -SQRT_1_6, 0.0), 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( Sphere::new(
"face2", "Face 2", GRAY, "face2".to_string(),
engine::sphere_with_offset( "Face 2".to_string(),
-SQRT_1_6, SQRT_2_3, -SQRT_1_6, -SQRT_1_6, 0.0), 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( Sphere::new(
"face3", "Face 3", GRAY, "face3".to_string(),
engine::sphere_with_offset( "Face 3".to_string(),
-SQRT_1_6, -SQRT_1_6, SQRT_2_3, -SQRT_1_6, 0.0), 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 { for face in faces {
@ -397,7 +413,9 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
// add the substrate // add the substrate
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"substrate", "Substrate", GRAY, "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), engine::sphere(0.0, 0.0, 0.0, 1.0),
) )
); );
@ -416,16 +434,17 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32]; 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_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32];
const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32];
const PHI_INV: f64 = 1.0 / PHI; let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized
const COORD_SCALE: f64 = sqrt(PHI + 2.0); let phi_inv = 1.0 / phi;
const_array!(FACE_SCALES: f64 = [PHI_INV, (13.0 / 12.0) / COORD_SCALE]); let coord_scale = (phi + 2.0).sqrt();
const_array!(FACE_RADII: f64 = [PHI_INV, 5.0 / 12.0]); 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 mut faces = Vec::<Rc<dyn Element>>::new();
let subscripts = ["", ""]; let subscripts = ["", ""];
for j in 0..2 { for j in 0..2 {
for k in 0..2 { for k in 0..2 {
let small_coord = FACE_SCALES[k] * if j > 0 {1.} else {-1.}; let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0);
let big_coord = FACE_SCALES[k] * PHI * if k > 0 {1.} else {-1.}; let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi;
let id_num = format!("{j}{k}"); let id_num = format!("{j}{k}");
let label_sub = format!("{}{}", subscripts[j], subscripts[k]); let label_sub = format!("{}{}", subscripts[j], subscripts[k]);
@ -434,10 +453,10 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
let id_a = format!("a{id_num}"); let id_a = format!("a{id_num}");
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
&id_a, 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(
@ -450,10 +469,10 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
let id_b = format!("b{id_num}"); let id_b = format!("b{id_num}");
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
&id_b, 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(
@ -466,10 +485,10 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
let id_c = format!("c{id_num}"); let id_c = format!("c{id_num}");
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
&id_c, 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(
@ -537,9 +556,23 @@ fn load_balanced(assembly: &Assembly) {
const R_INNER: f64 = 4.0; const R_INNER: f64 = 4.0;
let spheres = [ let spheres = [
Sphere::new( Sphere::new(
"outer","Outer", GRAY, engine::sphere(0.0, 0.0, 0.0, R_OUTER)), "outer".to_string(),
Sphere::new("a", "A", RED, engine::sphere(0.0, 4.0, 0.0, R_INNER)), "Outer".to_string(),
Sphere::new("b", "B", BLUE, engine::sphere(0.0, -4.0, 0.0, R_INNER)), [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 { for sphere in spheres {
let _ = assembly.try_insert_element(sphere); let _ = assembly.try_insert_element(sphere);
@ -547,7 +580,9 @@ fn load_balanced(assembly: &Assembly) {
// get references to the spheres // get references to the spheres
let [outer, a, b] = ["outer", "a", "b"].map( let [outer, a, b] = ["outer", "a", "b"].map(
|id| assembly.find_element(id) |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 // fix the diameters of the outer, sun, and moon spheres
@ -579,22 +614,32 @@ fn load_balanced(assembly: &Assembly) {
fn load_off_center(assembly: &Assembly) { fn load_off_center(assembly: &Assembly) {
// create a point almost at the origin and a sphere centered on the origin // create a point almost at the origin and a sphere centered on the origin
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Point::new("point", "Point", GRAY, point(1e-9, 0.0, 0.0)), 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( let _ = assembly.try_insert_element(
Sphere::new( Sphere::new(
"sphere", "Sphere", GRAY, engine::sphere(0.0, 0.0, 0.0, 1.0)), "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 // get references to the elements
let point_and_sphere = ["point", "sphere"].map( let point_and_sphere = ["point", "sphere"].map(
|id| assembly.find_element(id) |id| assembly.elements_by_id.with_untracked(
|elts_by_id| elts_by_id[id].clone()
)
); );
// put the point on the sphere // put the point on the sphere
let incidence = InversiveDistanceRegulator::new(point_and_sphere); let incidence = InversiveDistanceRegulator::new(point_and_sphere);
incidence.set_to(0.); incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
assembly.insert_regulator(Rc::new(incidence)); assembly.insert_regulator(Rc::new(incidence));
} }
@ -607,13 +652,18 @@ fn load_radius_ratio(assembly: &Assembly) {
let index_range = 1..=4; let index_range = 1..=4;
// create the spheres // create the spheres
const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
let spheres = [ let spheres = [
Sphere::new( Sphere::new(
"sphere_faces", "Insphere", GRAY, "sphere_faces".to_string(),
"Insphere".to_string(),
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", "Circumsphere", GRAY, "sphere_vertices".to_string(),
"Circumsphere".to_string(),
GRAY,
engine::sphere(0.0, 0.0, 0.0, 0.25), engine::sphere(0.0, 0.0, 0.0, 0.25),
), ),
]; ];
@ -639,8 +689,8 @@ fn load_radius_ratio(assembly: &Assembly) {
).map( ).map(
|(k, color, representation)| { |(k, color, representation)| {
Point::new( Point::new(
&format!("v{k}"), format!("v{k}"),
&format!("Vertex {k}"), format!("Vertex {k}"),
color, color,
representation, representation,
) )
@ -670,8 +720,8 @@ fn load_radius_ratio(assembly: &Assembly) {
).map( ).map(
|(k, color, representation)| { |(k, color, representation)| {
Sphere::new( Sphere::new(
&format!("f{k}"), format!("f{k}"),
&format!("Face {k}"), format!("Face {k}"),
color, color,
representation, representation,
) )
@ -684,18 +734,27 @@ fn load_radius_ratio(assembly: &Assembly) {
// impose the constraints // impose the constraints
for j in index_range.clone() { for j in index_range.clone() {
let [face_j, vertex_j] = [format!("f{j}"),format!("v{j}")] let [face_j, vertex_j] = [
.map(|id| assembly.find_element(&id)); 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 // make the faces planar
let curvature_regulator = face_j.regulators().with_untracked( let curvature_regulator = face_j.regulators().with_untracked(
|regs| regs.first().unwrap().clone() |regs| regs.first().unwrap().clone()
); );
curvature_regulator.set_to(0.); curvature_regulator.set_point().set(
SpecifiedValue::try_from("0".to_string()).unwrap()
);
for k in index_range.clone().filter(|&index| index != j) { for k in index_range.clone().filter(|&index| index != j) {
let vertex_k = assembly.find_element(&format!("v{k}")); 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 // fix the distances between the vertices
if j < k { if j < k {
@ -707,7 +766,7 @@ fn load_radius_ratio(assembly: &Assembly) {
// put the vertices on the faces // put the vertices on the faces
let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]); let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]);
incidence_regulator.set_to(0.); incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
assembly.insert_regulator(Rc::new(incidence_regulator)); assembly.insert_regulator(Rc::new(incidence_regulator));
} }
} }
@ -733,26 +792,32 @@ fn load_radius_ratio(assembly: &Assembly) {
fn load_irisawa_hexlet(assembly: &Assembly) { fn load_irisawa_hexlet(assembly: &Assembly) {
let index_range = 1..=6; let index_range = 1..=6;
let colors = [ let colors = [
[1.00, 0.00, 0.25], [1.00_f32, 0.00_f32, 0.25_f32],
[1.00, 0.25, 0.00], [1.00_f32, 0.25_f32, 0.00_f32],
[0.75, 0.75, 0.00], [0.75_f32, 0.75_f32, 0.00_f32],
[0.25, 1.00, 0.00], [0.25_f32, 1.00_f32, 0.00_f32],
[0.00, 0.25, 1.00], [0.00_f32, 0.25_f32, 1.00_f32],
[0.25, 0.00, 1.00], [0.25_f32, 0.00_f32, 1.00_f32],
].into_iter(); ].into_iter();
// create the spheres // create the spheres
let spheres = [ let spheres = [
Sphere::new( Sphere::new(
"outer", "Outer", gray(0.5), "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), engine::sphere(0.0, 0.0, 0.0, 1.5),
), ),
Sphere::new( Sphere::new(
"sun", "Sun", GRAY, "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), engine::sphere(0.0, -0.75, 0.0, 0.75),
), ),
Sphere::new( Sphere::new(
"moon", "Moon", gray(0.25), "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), engine::sphere(0.0, 0.75, 0.0, 0.75),
), ),
].into_iter().chain( ].into_iter().chain(
@ -760,8 +825,8 @@ fn load_irisawa_hexlet(assembly: &Assembly) {
|(k, color)| { |(k, color)| {
let ang = (k as f64) * PI/3.0; let ang = (k as f64) * PI/3.0;
Sphere::new( Sphere::new(
&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),
) )
@ -773,7 +838,9 @@ fn load_irisawa_hexlet(assembly: &Assembly) {
} }
// put the outer sphere in ghost mode and fix its curvature // put the outer sphere in ghost mode and fix its curvature
let outer = assembly.find_element("outer"); let outer = assembly.elements_by_id.with_untracked(
|elts_by_id| elts_by_id["outer"].clone()
);
outer.ghost().set(true); outer.ghost().set(true);
let outer_curvature_regulator = outer.regulators().with_untracked( let outer_curvature_regulator = outer.regulators().with_untracked(
|regs| regs.first().unwrap().clone() |regs| regs.first().unwrap().clone()
@ -783,9 +850,15 @@ fn load_irisawa_hexlet(assembly: &Assembly) {
); );
// impose the desired tangencies // impose the desired tangencies
let [sun, moon] = ["sun", "moon"].map(|id| assembly.find_element(id)); 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( let chain = index_range.map(
|k| assembly.find_element(&format!("chain{k}")) |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 (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) {
for (other_sphere, inversive_distance) in [ for (other_sphere, inversive_distance) in [
@ -809,192 +882,6 @@ fn load_irisawa_hexlet(assembly: &Assembly) {
assembly.insert_regulator(Rc::new(outer_moon_tangency)); assembly.insert_regulator(Rc::new(outer_moon_tangency));
} }
const HPHI: f64 = PHI / 2.; // "half phi"
const RTHPHI: f64 = sqrt(HPHI); // "root half phi"
const RTPHIPH: f64 = sqrt(PHI + 0.5); // "root phi plus (half)"
const P: bool = true; // Pinned
const F: bool = false; // Free
// Initial data for the vertices commmon to 554aug2 and 554domed.
// Used the Vectornaut near_miss branch final positions for 554aug2,
// to the 3 decimal places currently shown.
const_array!(ACRON554_COMMON: (&str, [f64; 3], bool, usize, &str) = [
// id, coordinates, Pin/Free, level, earlier neighbor IDs.
("A_NE", [ 0.5, 0.5, 0.], P, 0, ""),
("A_NW", [-0.5, 0.5, 0.], P, 0, ""),
("A_SE", [ 0.5, -0.5, 0.], P, 0, ""),
("A_SW", [-0.5, -0.5, 0.], P, 0, ""),
("Z_E", [ 0.229, -0.002, 0.822], F, 1, "A_NE,A_SE"),
("Z_S", [ 0.002, -0.229, 0.822], F, 1, "A_SE,A_SW"),
("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"),
("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""),
("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"),
("B_SE", [ 0.812, -0.812, 0.897], F, 2, "A_SE,Z_E,Z_S"),
("Y_NE", [ 0.11, 0.104, 1.019], F, 3, "B_NE"),
("Y_NW", [-0.104, 0.104, 1.019], F, 3, "B_NW"),
("Y_SE", [ 0.11, -0.11, 1.018], F, 3, "B_SE"),
("Y_SW", [-0.104, -0.11, 1.019], F, 3, "B_SW"),
("C_N", [ 0., 1., RTPHIPH], P, 4, "B_NE,B_NW,Y_NE,Y_NW"),
("C_W", [-1., 0., RTPHIPH], P, 4, "B_NW,B_SW,Y_NW,Y_SW"),
("C_E", [ 1.003, -0.003, 1.454], F, 4, "Z_E,B_NE,B_SE,Y_NE,Y_SE"),
("C_S", [ 0.003, -1.003, 1.454], F, 4, "Z_S,B_SE,B_SW,Y_SE,Y_SW"),
("D_NE", [ 0.195, 0.186, 2.012], F, 5, "Y_NE,C_N,C_E"),
("D_NW", [-0.186, 0.186, 2.012], F, 5, "Y_NW,C_N,C_W"),
("D_SE", [ 0.195, -0.195, 2.011], F, 5, "Y_SE,C_E,C_S"),
("D_SW", [-0.186, -0.195, 2.012], F, 5, "Y_SW,C_W,C_S"),
("E_N", [ 0.005, 1.062, 2.455], F, 6, "C_N,D_NE,D_NW"),
("E_W", [-1.063, -0.005, 2.455], F, 6, "C_W,D_NW,D_SW"),
("E_E", [ 1.072, -0.005, 2.452], F, 6, "C_E,D_NE,D_SE"),
("E_S", [ 0.005, -1.072, 2.452], F, 6, "C_S,D_SE,D_SW"),
("F_NE", [ 0.286, 0.275, 3.003], F, 7, "D_NE,E_N,E_E"),
("F_NW", [-0.275, 0.275, 3.004], F, 7, "D_NW,E_N,E_W"),
("F_SE", [ 0.286, -0.286, 3.003], F, 7, "D_SE,E_E,E_S"),
("F_SW", [-0.275, -0.286, 3.003], F, 7, "D_SW,E_W,E_S"),
// The following must be in order around the octagon (in some orientation)
("G_1", [ 0.506, 1.201, 3.309], F, 8, "E_N,F_NE"),
("G_2", [ 1.213, 0.494, 3.307], F, 8, "E_E,F_NE"),
("G_4", [ 1.213, -0.506, 3.306], F, 8, "E_E,F_SE"),
("G_5", [ 0.506, -1.213, 3.306], F, 8, "E_S,F_SE"),
("G_7", [-0.494, -1.213, 3.307], F, 8, "E_S,F_SW"),
("G_8", [-1.201, -0.506, 3.309], F, 8, "E_W,F_SW"),
("G_10", [-1.201, 0.494, 3.311], F, 8, "E_W,F_NW"),
("G_11", [-0.494, 1.201, 3.311], F, 8, "E_N,F_NW"),
]);
const_array!(LEVEL_COLORS: ElementColor = [
[0.75, 0., 0.75],
[1., 0.4, 0.6],
[1., 0., 0.25],
[1., 0.75, 0.25],
[0.75, 0.5, 0.],
[0.9, 0.9, 0.],
[0.25, 0.75, 0.],
[0., 0.5, 0.75],
[0.25, 0., 1.],
[0.6, 0.15, 0.6],
[0.9, 0.5, 0.6],
[0.85, 0.15, 0.35]
]);
fn load_554aug2(assembly: &Assembly) {
// first a plane for the octagon
let oct_id = "f_g";
let engsph = engine::sphere_with_offset(
0., 0., 1., ACRON554_COMMON[37].1[2], 0.);
let octaface = Sphere::new(oct_id, "Octagon", [0.7, 0.7, 0.7], engsph);
octaface.ghost().set(true);
assembly.try_insert_element(octaface);
let face_rc = assembly.find_element(oct_id);
let face_curvature = face_rc.regulators().with_untracked(
|regs| regs.first().unwrap().clone()
);
face_curvature.set_to(0.);
// Octagon vertices and side/diagonal lengths
let mut oct_verts = Vec::<Rc<dyn Element>>::new();
const OCT_LONG: usize = 4;
const OCT_N_DIAG: usize = 5;
const OCT_N: usize = 8;
const OCT_DIST: [f64; OCT_N_DIAG] = [0., // dummy at start
-0.5,
-0.5*(2. + SQRT_2),
-0.5*(3. + 2.*SQRT_2),
-0.5*(4. + 2.*SQRT_2)
];
// Now process the acron data
for (id, v, pinned, l, neighbors) in ACRON554_COMMON {
let pt = Point::new(id, id, LEVEL_COLORS[l], point(v[0], v[1], v[2]));
assembly.try_insert_element(pt);
// QUESTION: Would there be a way to insert an Rc<dyn Element> into
// an assembly to avoid the need to re-lookup pt in the assembly
// after just inserting it? Or could/should try_insert_element return
// the Rc<dyn Element> ?
let pt_rc = assembly.find_element(id);
if pinned { // regulate each coordinate to its given value
let mut freeze = Vec::<Rc<dyn Regulator>>::new();
// filter the three coordinate regulators into freeze
pt_rc.regulators().with_untracked( |regs| {
for reg in regs {
if let Some(_pcr) = reg
.as_any().downcast_ref::<PointCoordinateRegulator>() {
freeze.push(reg.clone())
}
}
});
// now set them to their originally specified values.
let mut coord: usize = 0;
for reg in freeze {
reg.set_to(v[coord]);
coord += 1;
}
}
// If part of the octagon, make incident to the plane:
if l == 8 {
let oct_index = oct_verts.len();
oct_verts.push(pt_rc.clone());
let incidence = InversiveDistanceRegulator::new(
[face_rc.clone(), pt_rc.clone()]);
// incidence.set_to(0.0);
assembly.insert_regulator(Rc::new(incidence));
// And regulate the length to the other vertices of the octagon
for offset in 1..OCT_N_DIAG {
if offset <= oct_index {
let dist = InversiveDistanceRegulator::new(
[oct_verts[oct_index - offset].clone(), pt_rc.clone()]);
if offset == 1 { dist.set_to(OCT_DIST[offset]); }
assembly.insert_regulator(Rc::new(dist));
}
if offset < OCT_LONG && oct_index + offset >= OCT_N {
let forward = oct_index + offset - OCT_N;
let dist = InversiveDistanceRegulator::new(
[oct_verts[forward].clone(), pt_rc.clone()]);
if offset == 1 { dist.set_to(OCT_DIST[offset]); }
assembly.insert_regulator(Rc::new(dist));
}
}
}
// Finally, add any specified neighbors
for id in neighbors.split(",") {
if id.len() == 0 { continue; }
let strut = InversiveDistanceRegulator::new(
[assembly.find_element(id), pt_rc.clone()]);
strut.set_to(-0.5);
assembly.insert_regulator(Rc::new(strut));
}
}
}
const_array!(ACRON554_DOME: (&str, [f64; 3], bool, usize, &str) = [
// id, coordinates, Pin/Free, level, earlier neighbor IDs.
("H_E", [ 0.359, -0.006, 3.161], F, 9, "G_2,G_4"),
("H_N", [ 0.005, 0.348, 3.158], F, 9, "G_1,G_11"),
("H_S", [ 0.006, 0.359, 3.159], F, 9, "G_5,G_7"),
("H_W", [-0.347, -0.005, 3.163], F, 9, "G_8,G_10"),
("I_NE", [ 0.507, 0.493, 4.015], F, 10, "G_1,G_2,H_E,H_N"),
("I_NW", [-0.493, 0.493, 4.013], F, 10, "G_10,G_11,H_N,H_W"),
("I_SE", [ 0.507, -0.507, 4.012], F, 10, "G_4,G_5,H_E,H_S"),
("I_SW", [-0.493, -0.507, 4.015], F, 10, "G_7,G_8,H_S,H_W"),
("J", [ 0.004, -0.010, 3.303], F, 11, "I_NE,I_NW,I_SE,I_SW"),
]);
fn load_554domed(assembly: &Assembly) {
load_554aug2(assembly);
// Now process the additional data
for (id, v, _pinned, l, neighbors) in ACRON554_DOME {
let pt = Point::new(id, id, LEVEL_COLORS[l], point(v[0], v[1], v[2]));
assembly.try_insert_element(pt);
let pt_rc = assembly.find_element(id);
// Add any specified neighbors
for id in neighbors.split(",") {
if id.len() == 0 { continue; }
let strut = InversiveDistanceRegulator::new(
[assembly.find_element(id), pt_rc.clone()]);
strut.set_to(-0.5);
assembly.insert_regulator(Rc::new(strut));
}
}
}
// --- chooser --- // --- chooser ---
/* DEBUG */ /* DEBUG */
@ -1031,15 +918,11 @@ pub fn TestAssemblyChooser() -> View {
"off-center" => load_off_center(assembly), "off-center" => load_off_center(assembly),
"radius-ratio" => load_radius_ratio(assembly), "radius-ratio" => load_radius_ratio(assembly),
"irisawa-hexlet" => load_irisawa_hexlet(assembly), "irisawa-hexlet" => load_irisawa_hexlet(assembly),
"aug554" => load_554aug2(assembly),
"domed554" => load_554domed(assembly),
_ => (), _ => (),
}; };
}); });
}); });
// FIXME: Non-DRY -- should not need to reiterate thie list of assembly
// labels
// build the chooser // build the chooser
view! { view! {
select(bind:value = assembly_name) { select(bind:value = assembly_name) {
@ -1052,8 +935,6 @@ pub fn TestAssemblyChooser() -> View {
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 = "aug554") { "McNeill acron 554" }
option(value = "domed554") { "Domed acron 554" }
option(value = "empty") { "Empty" } option(value = "empty") { "Empty" }
} }
} }

View file

@ -46,7 +46,7 @@ pub fn project_sphere_to_normalized(rep: &mut DVector<f64>) {
// normalize a point's representation vector by scaling // normalize a point's representation vector by scaling
pub fn project_point_to_normalized(rep: &mut DVector<f64>) { pub fn project_point_to_normalized(rep: &mut DVector<f64>) {
rep.scale_mut(0.5 / rep[3]); //FIXME: This 3 should be Point::WEIGHT_COMPONENT rep.scale_mut(0.5 / rep[3]);
} }
// --- partial matrices --- // --- partial matrices ---

View file

@ -1,3 +1 @@
#![feature(const_trait_impl)]
pub mod engine; pub mod engine;

View file

@ -1,5 +1,3 @@
#![feature(const_trait_impl)]
mod assembly; mod assembly;
mod components; mod components;
mod engine; mod engine;

View file

@ -1,378 +0,0 @@
A_NE:
┌ ┐
│ 0.5 │
│ 0.5 │
│ 0 │
│ 0.5 │
│ 0.25 │
└ ┘
A_NW:
┌ ┐
│ -0.5 │
│ 0.5 │
│ 0 │
│ 0.5 │
│ 0.25 │
└ ┘
A_SE:
┌ ┐
│ 0.5 │
│ -0.5 │
│ 0 │
│ 0.5 │
│ 0.25 │
└ ┘
A_SW:
┌ ┐
│ -0.5 │
│ -0.5 │
│ 0 │
│ 0.5 │
│ 0.25 │
└ ┘
Z_E:
┌ ┐
│ 0.22634143549538555 │
│ 0.000032427771744506665 │
│ 0.8216513780396574 │
│ 0.5 │
│ 0.363170717377161 │
└ ┘
Z_S:
┌ ┐
│ -0.000032427771744220924 │
│ -0.22634143549538618 │
│ 0.8216513780396576 │
│ 0.5 │
│ 0.36317071737716133 │
└ ┘
B_NE:
┌ ┐
│ 0.8090169943749475 │
│ 0.8090169943749475 │
│ 0.8994537199739336 │
│ 0.5 │
│ 1.0590169943749475 │
└ ┘
B_NW:
┌ ┐
│ -0.8090169943749475 │
│ 0.8090169943749475 │
│ 0.8994537199739336 │
│ 0.5 │
│ 1.0590169943749475 │
└ ┘
B_SW:
┌ ┐
│ -0.8090169943749475 │
│ -0.8090169943749475 │
│ 0.8994537199739336 │
│ 0.5 │
│ 1.0590169943749475 │
└ ┘
B_SE:
┌ ┐
│ 0.8089673881587364 │
│ -0.8089673881587335 │
│ 0.8995105586041169 │
│ 0.5 │
│ 1.0589799582637938 │
└ ┘
Y_NE:
┌ ┐
│ 0.10684030416478518 │
│ 0.1069861535711018 │
│ 1.0181778574241942 │
│ 0.5 │
│ 0.5297735192825956 │
└ ┘
Y_NW:
┌ ┐
│ -0.10693925692260356 │
│ 0.10693925692260356 │
│ 1.0182278452645257 │
│ 0.5 │
│ 0.5298299819167654 │
└ ┘
Y_SE:
┌ ┐
│ 0.10684605297358556 │
│ -0.10684605297358302 │
│ 1.0181767492960683 │
│ 0.5 │
│ 0.5297580227088338 │
└ ┘
Y_SW:
┌ ┐
│ -0.10698615357110258 │
│ -0.10684030416478382 │
│ 1.0181778574241929 │
│ 0.5 │
│ 0.5297735192825943 │
└ ┘
C_N:
┌ ┐
│ 0 │
│ 1 │
│ 1.4553466902253547 │
│ 0.5 │
│ 1.5590169943749472 │
└ ┘
C_W:
┌ ┐
│ -1 │
│ 0 │
│ 1.4553466902253547 │
│ 0.5 │
│ 1.5590169943749472 │
└ ┘
C_E:
┌ ┐
│ 0.9997685166344451 │
│ 0.000045591796741388215 │
│ 1.4555126531079483 │
│ 0.5 │
│ 1.5590202705588938 │
└ ┘
C_S:
┌ ┐
│ -0.00004559179673933571 │
│ -0.9997685166344433 │
│ 1.4555126531079468 │
│ 0.5 │
│ 1.5590202705588896 │
└ ┘
D_NE:
┌ ┐
│ 0.19050130249898212 │
│ 0.19080920821293734 │
│ 2.0111349254444733 │
│ 0.5 │
│ 2.0586841044725492 │
└ ┘
D_NW:
┌ ┐
│ -0.19087531658436754 │
│ 0.1908753165843677 │
│ 2.0111018697108243 │
│ 0.5 │
│ 2.058720870516732 │
└ ┘
D_SE:
┌ ┐
│ 0.1904710244638656 │
│ -0.1904710244638643 │
│ 2.011170408319603 │
│ 0.5 │
│ 2.0586783801164676 │
└ ┘
D_SW:
┌ ┐
│ -0.19080920821293662 │
│ -0.19050130249898098 │
│ 2.011134925444472 │
│ 0.5 │
│ 2.0586841044725475 │
└ ┘
E_N:
┌ ┐
│ -0.0001774225161991373 │
│ 1.0673561656973407 │
│ 2.453087108367921 │
│ 0.5 │
│ 3.5784389787247153 │
└ ┘
E_W:
┌ ┐
│ -1.0673561656973403 │
│ 0.00017742251619961636 │
│ 2.4530871083679195 │
│ 0.5 │
│ 3.578438978724712 │
└ ┘
E_E:
┌ ┐
│ 1.0669548967200053 │
│ 0.0001843766584768728 │
│ 2.4532662028373866 │
│ 0.5 │
│ 3.5784510553790545 │
└ ┘
E_S:
┌ ┐
│ -0.00018437665847651806 │
│ -1.0669548967200044 │
│ 2.453266202837385 │
│ 0.5 │
│ 3.57845105537905 │
└ ┘
F_NE:
┌ ┐
│ 0.28011657185554717 │
│ 0.280597474289424 │
│ 3.003049209443169 │
│ 0.5 │
│ 4.587753011062896 │
└ ┘
F_NW:
┌ ┐
│ -0.280598407806719 │
│ 0.28059840780671874 │
│ 3.002988046197754 │
│ 0.5 │
│ 4.587705454166589 │
└ ┘
F_SE:
┌ ┐
│ 0.28011643423019444 │
│ -0.2801164342301949 │
│ 3.0031051243209257 │
│ 0.5 │
│ 4.587785491049536 │
└ ┘
F_SW:
┌ ┐
│ -0.2805974742894247 │
│ -0.28011657185554784 │
│ 3.003049209443168 │
│ 0.5 │
│ 4.587753011062893 │
└ ┘
G_1:
┌ ┐
│ 0.4997377212516245 │
│ 1.2073683664308739 │
│ 3.3077679987306667 │
│ 0.5 │
│ 6.324404048677748 │
└ ┘
G_2:
┌ ┐
│ 1.2068449832232118 │
│ 0.5002622274345682 │
│ 3.307880423181695 │
│ 0.5 │
│ 6.3244041808325075 │
└ ┘
G_4:
┌ ┐
│ 1.2068444751045393 │
│ -0.4997377595067899 │
│ 3.307966697528916 │
│ 0.5 │
│ 6.324427820224868 │
└ ┘
G_5:
┌ ┐
│ 0.4997377595067881 │
│ -1.2068444751045408 │
│ 3.307966697528914 │
│ 0.5 │
│ 6.324427820224864 │
└ ┘
G_7:
┌ ┐
│ -0.5002622274345697 │
│ -1.2068449832232129 │
│ 3.307880423181691 │
│ 0.5 │
│ 6.324404180832497 │
└ ┘
G_8:
┌ ┐
│ -1.2073683664308756 │
│ -0.4997377212516256 │
│ 3.307767998730663 │
│ 0.5 │
│ 6.3244040486777395 │
└ ┘
G_10:
┌ ┐
│ -1.2073693671510268 │
│ 0.50026206687508 │
│ 3.307678829535478 │
│ 0.5 │
│ 6.3243704479462215 │
└ ┘
G_11:
┌ ┐
│ -0.5002620668750811 │
│ 1.2073693671510257 │
│ 3.30767882953548 │
│ 0.5 │
│ 6.324370447946225 │
└ ┘

View file

@ -1,422 +0,0 @@
A_NE:
┌ ┐
│ 0.5 │
│ 0.5 │
│ 0 │
│ 0.5 │
│ 0.25 │
└ ┘
A_NW:
┌ ┐
│ -0.5 │
│ 0.5 │
│ 0 │
│ 0.5 │
│ 0.25 │
└ ┘
A_SE:
┌ ┐
│ 0.5 │
│ -0.5 │
│ 0 │
│ 0.5 │
│ 0.25 │
└ ┘
A_SW:
┌ ┐
│ -0.5 │
│ -0.5 │
│ 0 │
│ 0.5 │
│ 0.25 │
└ ┘
Z_E:
┌ ┐
│ 0.226403187618189 │
│ -0.0000011852849153538265 │
│ 0.8216719444221399 │
│ 0.5 │
│ 0.36320159380866957 │
└ ┘
Z_S:
┌ ┐
│ 0.0000012064271737704876 │
│ -0.22640322646306607 │
│ 0.8216719573565728 │
│ 0.5 │
│ 0.36320161323110667 │
└ ┘
B_NE:
┌ ┐
│ 0.8090169943749475 │
│ 0.8090169943749475 │
│ 0.8994537199739336 │
│ 0.5 │
│ 1.0590169943749475 │
└ ┘
B_NW:
┌ ┐
│ -0.8090169943749475 │
│ 0.8090169943749475 │
│ 0.8994537199739336 │
│ 0.5 │
│ 1.0590169943749475 │
└ ┘
B_SW:
┌ ┐
│ -0.8090169943749475 │
│ -0.8090169943749475 │
│ 0.8994537199739336 │
│ 0.5 │
│ 1.0590169943749475 │
└ ┘
B_SE:
┌ ┐
│ 0.8090190229759617 │
│ -0.8090188141637347 │
│ 0.899451950040933 │
│ 0.5 │
│ 1.059018649731349 │
└ ┘
Y_NE:
┌ ┐
│ 0.10639264310226318 │
│ 0.10730337799321536 │
│ 1.0174285282252145 │
│ 0.5 │
│ 0.5289971090900759 │
└ ┘
Y_NW:
┌ ┐
│ -0.10692454007698927 │
│ 0.10692454007699025 │
│ 1.0183290894713781 │
│ 0.5 │
│ 0.5299299245035725 │
└ ┘
Y_SE:
┌ ┐
│ 0.10619251333984216 │
│ -0.10758965782463996 │
│ 1.0179033510994517 │
│ 0.5 │
│ 0.5294898085465076 │
└ ┘
Y_SW:
┌ ┐
│ -0.1067218861162948 │
│ -0.10720833408227096 │
│ 1.018815416393646 │
│ 0.5 │
│ 0.5304340206102979 │
└ ┘
C_N:
┌ ┐
│ 0 │
│ 1 │
│ 1.4553466902253547 │
│ 0.5 │
│ 1.5590169943749472 │
└ ┘
C_W:
┌ ┐
│ -1 │
│ 0 │
│ 1.4553466902253547 │
│ 0.5 │
│ 1.5590169943749472 │
└ ┘
C_E:
┌ ┐
│ 0.9987752175493758 │
│ -0.0000014991453936079486 │
│ 1.4557639853989686 │
│ 0.5 │
│ 1.558400358790791 │
└ ┘
C_S:
┌ ┐
│ 0.0000016241779451142522 │
│ -1.0006558417024733 │
│ 1.4551187604443314 │
│ 0.5 │
│ 1.5593413623171548 │
└ ┘
D_NE:
┌ ┐
│ 0.18880014858596228 │
│ 0.1900242678455326 │
│ 2.0105891402812603 │
│ 0.5 │
│ 2.057111349663646 │
└ ┘
D_NW:
┌ ┐
│ -0.19098138154171115 │
│ 0.19098137963576622 │
│ 2.0112375319589524 │
│ 0.5 │
│ 2.0590124438119553 │
└ ┘
D_SE:
┌ ┐
│ 0.18930742120763205 │
│ -0.19118865645940125 │
│ 2.0109297224500113 │
│ 0.5 │
│ 2.0581147310902232 │
└ ┘
D_SW:
┌ ┐
│ -0.19150073518747784 │
│ -0.19215789298794494 │
│ 2.011588230853374 │
│ 0.5 │
│ 2.060041842611764 │
└ ┘
E_N:
┌ ┐
│ 0.0018705386847266854 │
│ 1.067093230632768 │
│ 2.4530916457713374 │
│ 0.5 │
│ 3.5781750425903667 │
└ ┘
E_W:
┌ ┐
│ -1.0677431983248686 │
│ 0.0010069929693730891 │
│ 2.453048970298719 │
│ 0.5 │
│ 3.578762902144111 │
└ ┘
E_E:
┌ ┐
│ 1.0651802756853244 │
│ 0.0009763264191362732 │
│ 2.4535562533844497 │
│ 0.5 │
│ 3.5772741307914204 │
└ ┘
E_S:
┌ ┐
│ 0.0019014550057512374 │
│ -1.0684657533845878 │
│ 2.4528152065600635 │
│ 0.5 │
│ 3.5789625604223154 │
└ ┘
F_NE:
┌ ┐
│ 0.2768627273275836 │
│ 0.27828736361573514 │
│ 3.0027865275597208 │
│ 0.5 │
│ 4.58541185017254 │
└ ┘
F_NW:
┌ ┐
│ -0.28103002630311136 │
│ 0.2812882756471644 │
│ 3.0030716277259866 │
│ 0.5 │
│ 4.588270112066947 │
└ ┘
F_SE:
┌ ┐
│ 0.2784445942560668 │
│ -0.28049951604978735 │
│ 3.002936318943545 │
│ 0.5 │
│ 4.586918980412344 │
└ ┘
F_SW:
┌ ┐
│ -0.28267553068657514 │
│ -0.2835640963165625 │
│ 3.0032197706602255 │
│ 0.5 │
│ 4.58982149472019 │
└ ┘
G_1:
┌ ┐
│ 0.49499953176717715 │
│ 1.2038466206487632 │
│ 3.3122320493386623 │
│ 0.5 │
│ 6.332576199448855 │
└ ┘
G_2:
┌ ┐
│ 1.202713808441931 │
│ 0.49734958577973293 │
│ 3.3107015388161924 │
│ 0.5 │
│ 6.327310911161432 │
└ ┘
G_4:
┌ ┐
│ 1.205139169495155 │
│ -0.5026367636047644 │
│ 3.3060733731956917 │
│ 0.5 │
│ 6.3175626265818385 │
└ ┘
G_5:
┌ ┐
│ 0.49496884524644963 │
│ -1.2066437505221832 │
│ 3.3117630879125723 │
│ 0.5 │
│ 6.334379009718901 │
└ ┘
G_7:
┌ ┐
│ -0.5049809193563459 │
│ -1.2113397181531034 │
│ 3.3029076805907547 │
│ 0.5 │
│ 6.315774407531318 │
└ ┘
G_8:
┌ ┐
│ -1.2105141090163352 │
│ -0.502666189603216 │
│ 3.305064063797819 │
│ 0.5 │
│ 6.320733099502539 │
└ ┘
G_10:
┌ ┐
│ -1.2079897553472638 │
│ 0.49731945797605365 │
│ 3.309789835947903 │
│ 0.5 │
│ 6.330637311024589 │
└ ┘
G_11:
┌ ┐
│ -0.5049515565196006 │
│ 1.2084435744379876 │
│ 3.303474805253526 │
│ 0.5 │
│ 6.314128853476387 │
└ ┘
H_E:
┌ ┐
│ 0.3493418610082748 │
│ -0.004066882149056481 │
│ 3.1680910206723154 │
│ 0.5 │
│ 5.079428493907433 │
└ ┘
H_N:
┌ ┐
│ -0.007700036653444471 │
│ 0.35115095882911657 │
│ 3.1700941994675693 │
│ 0.5 │
│ 5.086431758305634 │
└ ┘
H_S:
┌ ┐
│ -0.007865804611166411 │
│ -0.3529429622292672 │
│ 3.176292181037656 │
│ 0.5 │
│ 5.10673131179707 │
└ ┘
H_W:
┌ ┐
│ -0.35269124233601623 │
│ -0.004232365263278458 │
│ 3.179750192542026 │
│ 0.5 │
│ 5.117610155394313 │
└ ┘
I_NE:
┌ ┐
│ 0.49805064236295854 │
│ 0.4976522130513484 │
│ 4.020243412022774 │
│ 0.5 │
│ 8.329034633323353 │
└ ┘
I_NW:
┌ ┐
│ -0.5060555022841527 │
│ 0.5128022974270082 │
│ 4.021863319771454 │
│ 0.5 │
│ 8.34722145884986 │
└ ┘
I_SE:
┌ ┐
│ 0.5060495556329386 │
│ -0.5018683273402639 │
│ 4.021107068324604 │
│ 0.5 │
│ 8.338630006433696 │
└ ┘
I_SW:
┌ ┐
│ -0.5143110146278468 │
│ -0.5172754412791255 │
│ 4.0227602162137135 │
│ 0.5 │
│ 8.357344731885544 │
└ ┘
J:
┌ ┐
│ -0.012852519748535315 │
│ -0.006830373978588703 │
│ 3.3242009136904245 │
│ 0.5 │
│ 5.525261774194571 │
└ ┘

View file

@ -1,85 +0,0 @@
from math import fabs, nan, sqrt
import sys
vertices = {}
current_id = ""
for line in sys.stdin:
text = line.strip()
if text.endswith(":"):
new_id = text.strip(":")
vertices[new_id] = []
if current_id: print(current_id, vertices[current_id])
current_id = new_id
continue
remainder = text.strip("┌ ┐└┘│")
if len(remainder): vertices[current_id].append(float(remainder))
print(len(vertices), "vertices found")
P = True
F = False
HPHI = nan
RTHPHI = nan
RTPHIPH = nan
# Taken verbatim from test_assembly_chooser.rs
acron_data = [
("A_NE", [ 0.5, 0.5, 0.], P, 0, ""),
("A_NW", [-0.5, 0.5, 0.], P, 0, ""),
("A_SE", [ 0.5, -0.5, 0.], P, 0, ""),
("A_SW", [-0.5, -0.5, 0.], P, 0, ""),
("Z_E", [ 0.229, -0.002, 0.821], F, 1, "A_NE,A_SE"),
("Z_S", [ 0.002, -0.229, 0.821], F, 1, "A_SE,A_SW"),
("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"),
("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""),
("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"),
("B_SE", [ 0.812, -0.812, 0.89], F, 2, "A_SE,Z_E,Z_S"),
("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"),
("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"),
("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"),
("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"),
("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"),
("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"),
("C_E", [ 1.006, -0.006, 1.45], F, 4, "B_NE,B_SE,Y_NE,Y_SE"),
("C_S", [ 0.006, -1.006, 1.45], F, 4, "B_SE,B_SW,Y_SE,Y_SW"),
("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"),
("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"),
("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"),
("D_SW", [-0.181, -0.2, 2.011], F, 5, "Y_SW,C_W,C_S"),
("E_N", [ 0.012, 1.055, 2.46], F, 6, "C_N,D_NE,D_NW"),
("E_W", [-1.055, -0.012, 2.46], F, 6, "C_W,D_NW,D_SW"),
("E_E", [ 1.079, -0.012, 2.447], F, 6, "C_E,D_NE,D_SE"),
("E_S", [ 0.012, -1.079, 2.447], F, 6, "C_S,D_SE,D_SW"),
("F_NE", [ 0.296, 0.265, 3.003], F, 7, "D_NE,E_N,E_E"),
("F_NW", [-0.265, 0.265, 3.007], F, 7, "D_NW,E_N,E_W"),
("F_SE", [ 0.296, -0.296, 3.0], F, 7, "D_SE,E_E,E_S"),
("F_SW", [-0.265, -0.296, 3.003], F, 7, "D_SW,E_W,E_S"),
# The following must be in order around the octagon (in some orientation)
("G_1", [ 0.517, 1.19, 3.312], F, 8, "E_N,F_NE"),
("G_2", [ 1.224, 0.483, 3.304], F, 8, "E_E,F_NE"),
("G_4", [ 1.224, -0.517, 3.298], F, 8, "E_E,F_SE"),
("G_5", [ 0.517, -1.224, 3.298], F, 8, "E_S,F_SE"),
("G_7", [-0.483, -1.224, 3.304], F, 8, "E_S,F_SW"),
("G_8", [-1.19, -0.517, 3.312], F, 8, "E_W,F_SW"),
("G_10", [-1.19, 0.483, 3.318], F, 8, "E_W,F_NW"),
("G_11", [-0.483, 1.19, 3.318], F, 8, "E_N,F_NW"),
]
E = 0.0
n_struts = 11 # for the pinned vertices, which all have length exactly 1
for vi in range(0, len(acron_data)):
start_id = acron_data[vi][0]
start = vertices[start_id]
ends = acron_data[vi][4].split(",")
if acron_data[vi][3] == 8:
if start_id != "G_1":
ends.append(acron_data[vi-1][0])
if vi+1 == len(acron_data):
ends.append("G_1")
for end_id in ends:
if not(end_id): continue
end = vertices[end_id]
dist = sqrt(
(end[0]-start[0])**2 + (end[1]-start[1])**2 + (end[2]-start[2])**2)
print(f"{start_id}-{end_id}: {dist} {dist-1}")
E += fabs(dist-1)
n_struts += 1
print(n_struts, "unit edges")
print(f"----> Total distortion E={E}")

View file

@ -1,95 +0,0 @@
from math import fabs, nan, sqrt
import sys
vertices = {}
current_id = ""
for line in sys.stdin:
text = line.strip()
if text.endswith(":"):
new_id = text.strip(":")
vertices[new_id] = []
if current_id: print(current_id, vertices[current_id])
current_id = new_id
continue
remainder = text.strip("┌ ┐└┘│")
if len(remainder): vertices[current_id].append(float(remainder))
print(len(vertices), "vertices found")
P = True
F = False
HPHI = nan
RTHPHI = nan
RTPHIPH = nan
# Taken verbatim from test_assembly_chooser.rs
acron_data = [
("A_NE", [ 0.5, 0.5, 0.], P, 0, ""),
("A_NW", [-0.5, 0.5, 0.], P, 0, ""),
("A_SE", [ 0.5, -0.5, 0.], P, 0, ""),
("A_SW", [-0.5, -0.5, 0.], P, 0, ""),
("Z_E", [ 0.229, -0.002, 0.821], F, 1, "A_NE,A_SE"),
("Z_S", [ 0.002, -0.229, 0.821], F, 1, "A_SE,A_SW"),
("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"),
("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""),
("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"),
("B_SE", [ 0.812, -0.812, 0.89], F, 2, "A_SE,Z_E,Z_S"),
("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"),
("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"),
("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"),
("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"),
("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"),
("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"),
("C_E", [ 1.006, -0.006, 1.45], F, 4, "B_NE,B_SE,Y_NE,Y_SE"),
("C_S", [ 0.006, -1.006, 1.45], F, 4, "B_SE,B_SW,Y_SE,Y_SW"),
("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"),
("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"),
("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"),
("D_SW", [-0.181, -0.2, 2.011], F, 5, "Y_SW,C_W,C_S"),
("E_N", [ 0.012, 1.055, 2.46], F, 6, "C_N,D_NE,D_NW"),
("E_W", [-1.055, -0.012, 2.46], F, 6, "C_W,D_NW,D_SW"),
("E_E", [ 1.079, -0.012, 2.447], F, 6, "C_E,D_NE,D_SE"),
("E_S", [ 0.012, -1.079, 2.447], F, 6, "C_S,D_SE,D_SW"),
("F_NE", [ 0.296, 0.265, 3.003], F, 7, "D_NE,E_N,E_E"),
("F_NW", [-0.265, 0.265, 3.007], F, 7, "D_NW,E_N,E_W"),
("F_SE", [ 0.296, -0.296, 3.0], F, 7, "D_SE,E_E,E_S"),
("F_SW", [-0.265, -0.296, 3.003], F, 7, "D_SW,E_W,E_S"),
# The following must be in order around the octagon (in some orientation)
("G_1", [ 0.517, 1.19, 3.312], F, 8, "E_N,F_NE"),
("G_2", [ 1.224, 0.483, 3.304], F, 8, "E_E,F_NE"),
("G_4", [ 1.224, -0.517, 3.298], F, 8, "E_E,F_SE"),
("G_5", [ 0.517, -1.224, 3.298], F, 8, "E_S,F_SE"),
("G_7", [-0.483, -1.224, 3.304], F, 8, "E_S,F_SW"),
("G_8", [-1.19, -0.517, 3.312], F, 8, "E_W,F_SW"),
("G_10", [-1.19, 0.483, 3.318], F, 8, "E_W,F_NW"),
("G_11", [-0.483, 1.19, 3.318], F, 8, "E_N,F_NW"),
# Additional data
("H_E", [ 0.359, -0.006, 3.161], F, 9, "G_2,G_4"),
("H_N", [ 0.005, 0.348, 3.158], F, 9, "G_1,G_11"),
("H_S", [ 0.006, 0.359, 3.159], F, 9, "G_5,G_7"),
("H_W", [-0.347, -0.005, 3.163], F, 9, "G_8,G_10"),
("I_NE", [ 0.507, 0.493, 4.015], F, 10, "G_1,G_2,H_E,H_N"),
("I_NW", [-0.493, 0.493, 4.013], F, 10, "G_10,G_11,H_N,H_W"),
("I_SE", [ 0.507, -0.507, 4.012], F, 10, "G_4,G_5,H_E,H_S"),
("I_SW", [-0.493, -0.507, 4.015], F, 10, "G_7,G_8,H_S,H_W"),
("J", [ 0.004, -0.010, 3.303], F, 11, "I_NE,I_NW,I_SE,I_SW"),
]
E = 0.0
n_struts = 11 # for the pinned vertices, which all have length exactly 1
for vi in range(0, len(acron_data)):
start_id = acron_data[vi][0]
start = vertices[start_id]
ends = acron_data[vi][4].split(",")
if acron_data[vi][3] == 8:
if start_id != "G_1":
ends.append(acron_data[vi-1][0])
if vi+1 == len(acron_data) or not(acron_data[vi+1][0].startswith("G")):
ends.append("G_1")
for end_id in ends:
if not(end_id): continue
end = vertices[end_id]
dist = sqrt(
(end[0]-start[0])**2 + (end[1]-start[1])**2 + (end[2]-start[2])**2)
print(f"{start_id}-{end_id}: {dist} {dist-1}")
E += fabs(dist-1)
n_struts += 1
print(n_struts, "unit edges")
print(f"----> Total distortion E={E}")