Compare commits

..

3 commits

Author SHA1 Message Date
af18a8e7d1 Write a deployment packaging script (#113)
Adds a packaging script to help automate deployment. Documents the deployment process in `README.md`.

Also, moves `run-examples.sh` into the tools folder that we created for the packaging script.

Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Reviewed-on: StudioInfinity/dyna3#113
Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net>
Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2025-08-11 03:33:19 +00:00
a4565281d5 Refactor: rename loaders and adopt 'Self' type convention (#111)
Resolves #109.
Resolves #110.

Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Reviewed-on: StudioInfinity/dyna3#111
Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net>
Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2025-08-07 23:24:07 +00:00
ef1a579ac0 refactor: Code formatting (#108)
Primarily, switch to using trailing commas. Also uniformizes commas with respect to switch branches, makes function call layout more consistent, line breaking more consistent, alphabetizes imports, uses the field init shorthand when possible, etc.

Resolves #99.

Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Reviewed-on: StudioInfinity/dyna3#108
Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net>
Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2025-08-04 23:34:33 +00:00
12 changed files with 121 additions and 78 deletions

View file

@ -25,32 +25,37 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter
### Install the prerequisites
1. Install [`rustup`](https://rust-lang.github.io/rustup/): the officially recommended Rust toolchain manager
* It's available on Ubuntu as a [Snap](https://snapcraft.io/rustup)
- It's available on Ubuntu as a [Snap](https://snapcraft.io/rustup)
2. Call `rustup default stable` to "download the latest stable release of Rust and set it as your default toolchain"
* If you forget, the `rustup` [help system](https://github.com/rust-lang/rustup/blob/d9b3601c3feb2e88cf3f8ca4f7ab4fdad71441fd/src/errors.rs#L109-L112) will remind you
- If you forget, the `rustup` [help system](https://github.com/rust-lang/rustup/blob/d9b3601c3feb2e88cf3f8ca4f7ab4fdad71441fd/src/errors.rs#L109-L112) will remind you
3. Call `rustup target add wasm32-unknown-unknown` to add the [most generic 32-bit WebAssembly target](https://doc.rust-lang.org/nightly/rustc/platform-support/wasm32-unknown-unknown.html)
4. Call `cargo install wasm-pack` to install the [WebAssembly toolchain](https://rustwasm.github.io/docs/wasm-pack/)
5. Call `cargo install trunk` to install the [Trunk](https://trunkrs.dev/) web-build tool
6. Add the `.cargo/bin` folder in your home directory to your executable search path
* This lets you call Trunk, and other tools installed by Cargo, without specifying their paths
* On POSIX systems, the search path is stored in the `PATH` environment variable
- This lets you call Trunk, and other tools installed by Cargo, without specifying their paths
- On POSIX systems, the search path is stored in the `PATH` environment variable
### Play with the prototype
1. From the `app-proto` folder, call `trunk serve --release` to build and serve the prototype
* *The crates the prototype depends on will be downloaded and served automatically*
* *For a faster build, at the expense of a much slower prototype, you can call `trunk serve` without the `--release` flag*
* *If you want to stay in the top-level folder, you can call `trunk serve --config app-proto [--release]`* from there instead.
- The crates the prototype depends on will be downloaded and served automatically
- For a faster build, at the expense of a much slower prototype, you can call `trunk serve` without the `--release` flag
- If you want to stay in the top-level folder, you can call `trunk serve --config app-proto [--release]` from there instead.
3. In a web browser, visit one of the URLs listed under the message `INFO 📡 server listening at:`
* *Touching any file in the `app-proto` folder will make Trunk rebuild and live-reload the prototype*
- Touching any file in the `app-proto` folder will make Trunk rebuild and live-reload the prototype
4. Press *ctrl+C* in the shell where Trunk is running to stop serving the prototype
### Run the engine on some example problems
1. Go into the `app-proto` folder
2. Call `./run-examples`
* *For each example problem, the engine will print the value of the loss function at each optimization step*
* *The first example that prints is the same as the Irisawa hexlet example from the Julia version of the engine prototype. If you go into `engine-proto/gram-test`, launch Julia, and then*
1. Use `sh` to run the script `tools/run-examples.sh`
- The script is location-independent, so you can do this from anywhere in the dyna3 repository
- The call from the top level of the repository is:
```bash
sh tools/run-examples.sh
```
- For each example problem, the engine will print the value of the loss function at each optimization step
- The first example that prints is the same as the Irisawa hexlet example from the Julia version of the engine prototype. If you go into `engine-proto/gram-test`, launch Julia, and then
```julia
include("irisawa-hexlet.jl")
@ -59,9 +64,24 @@ The latest prototype is in the folder `app-proto`. It includes both a user inter
end
```
*you should see that it prints basically the same loss history until the last few steps, when the lower default precision of the Rust engine really starts to show*
you should see that it prints basically the same loss history until the last few steps, when the lower default precision of the Rust engine really starts to show
### Run the automated tests
1. Go into the `app-proto` folder
2. Call `cargo test`
### Deploy the prototype
1. From the `app-proto` folder, call `trunk build --release`
- Building in [release mode](https://doc.rust-lang.org/cargo/reference/profiles.html#release) produces an executable which is smaller and often much faster, but harder to debug and more time-consuming to build
- If you want to stay in the top-level folder, you can call `trunk build --config app-proto --release` from there instead
2. Use `sh` to run the packaging script `tools/package-for-deployment.sh`.
- The script is location-independent, so you can do this from anywhere in the dyna3 repository
- The call from the top level of the repository is:
```bash
sh tools/package-for-deployment.sh
```
- This will overwrite or replace the files in `deploy/dyna3`
3. Put the contents of `deploy/dyna3` in the folder on your server that the prototype will be served from.
- To simplify uploading, you might want to combine these files into an archive called `deploy/dyna3.zip`. Git has been set to ignore this path

2
app-proto/Trunk.toml Normal file
View file

@ -0,0 +1,2 @@
[build]
public_url = "./"

View file

@ -175,8 +175,8 @@ impl Sphere {
label: String,
color: ElementColor,
representation: DVector<f64>,
) -> Sphere {
Sphere {
) -> Self {
Self {
id,
label,
color,
@ -194,8 +194,8 @@ impl Element for Sphere {
"sphere".to_string()
}
fn default(id: String, id_num: u64) -> Sphere {
Sphere::new(
fn default(id: String, id_num: u64) -> Self {
Self::new(
id,
format!("Sphere {id_num}"),
[0.75_f32, 0.75_f32, 0.75_f32],
@ -275,8 +275,8 @@ impl Point {
label: String,
color: ElementColor,
representation: DVector<f64>,
) -> Point {
Point {
) -> Self {
Self {
id,
label,
color,
@ -294,8 +294,8 @@ impl Element for Point {
"point".to_string()
}
fn default(id: String, id_num: u64) -> Point {
Point::new(
fn default(id: String, id_num: u64) -> Self {
Self::new(
id,
format!("Point {id_num}"),
[0.75_f32, 0.75_f32, 0.75_f32],
@ -348,7 +348,7 @@ impl ProblemPoser for Point {
format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str()
);
problem.gram.push_sym(index, index, 0.0);
problem.frozen.push(Point::WEIGHT_COMPONENT, index, 0.5);
problem.frozen.push(Self::WEIGHT_COMPONENT, index, 0.5);
problem.guess.set_column(index, &self.representation.get_clone_untracked());
}
}
@ -393,7 +393,7 @@ pub struct InversiveDistanceRegulator {
}
impl InversiveDistanceRegulator {
pub fn new(subjects: [Rc<dyn Element>; 2]) -> InversiveDistanceRegulator {
pub fn new(subjects: [Rc<dyn Element>; 2]) -> Self {
let representations = subjects.each_ref().map(|subj| subj.representation());
let measurement = create_memo(move || {
representations[0].with(|rep_0|
@ -406,7 +406,7 @@ impl InversiveDistanceRegulator {
let set_point = create_signal(SpecifiedValue::from_empty_spec());
let serial = Self::next_serial();
InversiveDistanceRegulator { subjects, measurement, set_point, serial }
Self { subjects, measurement, set_point, serial }
}
}
@ -453,7 +453,7 @@ pub struct HalfCurvatureRegulator {
}
impl HalfCurvatureRegulator {
pub fn new(subject: Rc<dyn Element>) -> HalfCurvatureRegulator {
pub fn new(subject: Rc<dyn Element>) -> Self {
let measurement = subject.representation().map(
|rep| rep[Sphere::CURVATURE_COMPONENT]
);
@ -461,7 +461,7 @@ impl HalfCurvatureRegulator {
let set_point = create_signal(SpecifiedValue::from_empty_spec());
let serial = Self::next_serial();
HalfCurvatureRegulator { subject, measurement, set_point, serial }
Self { subject, measurement, set_point, serial }
}
}

View file

@ -15,8 +15,8 @@ struct DiagnosticsState {
}
impl DiagnosticsState {
fn new(initial_tab: String) -> DiagnosticsState {
DiagnosticsState { active_tab: create_signal(initial_tab) }
fn new(initial_tab: String) -> Self {
Self { active_tab: create_signal(initial_tab) }
}
}

View file

@ -41,8 +41,8 @@ struct SceneSpheres {
}
impl SceneSpheres {
fn new() -> SceneSpheres {
SceneSpheres {
fn new() -> Self {
Self {
representations: Vec::new(),
colors_with_opacity: Vec::new(),
highlights: Vec::new(),
@ -71,8 +71,8 @@ struct ScenePoints {
}
impl ScenePoints {
fn new() -> ScenePoints {
ScenePoints {
fn new() -> Self {
Self {
representations: Vec::new(),
colors_with_opacity: Vec::new(),
highlights: Vec::new(),
@ -97,8 +97,8 @@ pub struct Scene {
}
impl Scene {
fn new() -> Scene {
Scene {
fn new() -> Self {
Self {
spheres: SceneSpheres::new(),
points: ScenePoints::new(),
}

View file

@ -26,7 +26,7 @@ use crate::{
// done more work on saving and loading assemblies, we should come back to this
// code to see if it can be simplified
fn load_gen_assemb(assembly: &Assembly) {
fn load_general(assembly: &Assembly) {
let _ = assembly.try_insert_element(
Sphere::new(
String::from("gemini_a"),
@ -77,7 +77,7 @@ fn load_gen_assemb(assembly: &Assembly) {
);
}
fn load_low_curv_assemb(assembly: &Assembly) {
fn load_low_curvature(assembly: &Assembly) {
// create the spheres
let a = 0.75_f64.sqrt();
let _ = assembly.try_insert_element(
@ -196,7 +196,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
}
}
fn load_pointed_assemb(assembly: &Assembly) {
fn load_pointed(assembly: &Assembly) {
let _ = assembly.try_insert_element(
Point::new(
format!("point_front"),
@ -246,7 +246,7 @@ fn load_pointed_assemb(assembly: &Assembly) {
// B-C "
// C-C "
// A-C -0.25 * φ^2 = -0.6545084971874737
fn load_tridim_icosahedron_assemb(assembly: &Assembly) {
fn load_tridiminished_icosahedron(assembly: &Assembly) {
// create the vertices
const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32];
const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
@ -409,7 +409,7 @@ fn load_tridim_icosahedron_assemb(assembly: &Assembly) {
// to finish describing the dodecahedral circle packing, set the inversive
// distance regulators to -1. some of the regulators have already been set
fn load_dodeca_packing_assemb(assembly: &Assembly) {
fn load_dodecahedral_packing(assembly: &Assembly) {
// add the substrate
let _ = assembly.try_insert_element(
Sphere::new(
@ -550,7 +550,7 @@ fn load_dodeca_packing_assemb(assembly: &Assembly) {
// the initial configuration of this test assembly deliberately violates the
// constraints, so loading the assembly will trigger a non-trivial realization
fn load_balanced_assemb(assembly: &Assembly) {
fn load_balanced(assembly: &Assembly) {
// create the spheres
const R_OUTER: f64 = 10.0;
const R_INNER: f64 = 4.0;
@ -611,7 +611,7 @@ fn load_balanced_assemb(assembly: &Assembly) {
// the initial configuration of this test assembly deliberately violates the
// constraints, so loading the assembly will trigger a non-trivial realization
fn load_off_center_assemb(assembly: &Assembly) {
fn load_off_center(assembly: &Assembly) {
// create a point almost at the origin and a sphere centered on the origin
let _ = assembly.try_insert_element(
Point::new(
@ -648,7 +648,7 @@ fn load_off_center_assemb(assembly: &Assembly) {
// sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an
// inversive distance of -1 between the insphere and each face, and then set an
// inversive distance of 0 between the circumsphere and each vertex
fn load_radius_ratio_assemb(assembly: &Assembly) {
fn load_radius_ratio(assembly: &Assembly) {
let index_range = 1..=4;
// create the spheres
@ -789,7 +789,7 @@ fn load_radius_ratio_assemb(assembly: &Assembly) {
// conditions are exactly representable as floats, unlike the analogous numbers
// in the scaled-up problem. the inexact representations might break the
// symmetry that's getting the engine stuck
fn load_irisawa_hexlet_assemb(assembly: &Assembly) {
fn load_irisawa_hexlet(assembly: &Assembly) {
let index_range = 1..=6;
let colors = [
[1.00_f32, 0.00_f32, 0.25_f32],
@ -909,15 +909,15 @@ pub fn TestAssemblyChooser() -> View {
// load assembly
match name.as_str() {
"general" => load_gen_assemb(assembly),
"low-curv" => load_low_curv_assemb(assembly),
"pointed" => load_pointed_assemb(assembly),
"tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly),
"dodeca-packing" => load_dodeca_packing_assemb(assembly),
"balanced" => load_balanced_assemb(assembly),
"off-center" => load_off_center_assemb(assembly),
"radius-ratio" => load_radius_ratio_assemb(assembly),
"irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly),
"general" => load_general(assembly),
"low-curvature" => load_low_curvature(assembly),
"pointed" => load_pointed(assembly),
"tridiminished-icosahedron" => load_tridiminished_icosahedron(assembly),
"dodecahedral-packing" => load_dodecahedral_packing(assembly),
"balanced" => load_balanced(assembly),
"off-center" => load_off_center(assembly),
"radius-ratio" => load_radius_ratio(assembly),
"irisawa-hexlet" => load_irisawa_hexlet(assembly),
_ => (),
};
});
@ -927,10 +927,10 @@ pub fn TestAssemblyChooser() -> View {
view! {
select(bind:value = assembly_name) {
option(value = "general") { "General" }
option(value = "low-curv") { "Low-curvature" }
option(value = "low-curvature") { "Low-curvature" }
option(value = "pointed") { "Pointed" }
option(value = "tridim-icosahedron") { "Tridiminished icosahedron" }
option(value = "dodeca-packing") { "Dodecahedral packing" }
option(value = "tridiminished-icosahedron") { "Tridiminished icosahedron" }
option(value = "dodecahedral-packing") { "Dodecahedral packing" }
option(value = "balanced") { "Balanced" }
option(value = "off-center") { "Off-center" }
option(value = "radius-ratio") { "Radius ratio" }

View file

@ -59,12 +59,12 @@ pub struct MatrixEntry {
pub struct PartialMatrix(Vec<MatrixEntry>);
impl PartialMatrix {
pub fn new() -> PartialMatrix {
PartialMatrix(Vec::<MatrixEntry>::new())
pub fn new() -> Self {
Self(Vec::<MatrixEntry>::new())
}
pub fn push(&mut self, row: usize, col: usize, value: f64) {
let PartialMatrix(entries) = self;
let Self(entries) = self;
entries.push(MatrixEntry { index: (row, col), value });
}
@ -114,7 +114,7 @@ impl IntoIterator for PartialMatrix {
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let PartialMatrix(entries) = self;
let Self(entries) = self;
entries.into_iter()
}
}
@ -139,8 +139,8 @@ pub struct ConfigSubspace {
}
impl ConfigSubspace {
pub fn zero(assembly_dim: usize) -> ConfigSubspace {
ConfigSubspace {
pub fn zero(assembly_dim: usize) -> Self {
Self {
assembly_dim,
basis_proj: Vec::new(),
basis_std: Vec::new(),
@ -154,7 +154,7 @@ impl ConfigSubspace {
a: DMatrix<f64>,
proj_to_std: DMatrix<f64>,
assembly_dim: usize,
) -> ConfigSubspace {
) -> Self {
// find a basis for the kernel. the basis is expressed in the projection
// coordinates, and it's orthonormal with respect to the projection
// inner product
@ -173,7 +173,7 @@ impl ConfigSubspace {
const ELEMENT_DIM: usize = 5;
const UNIFORM_DIM: usize = 4;
ConfigSubspace {
Self {
assembly_dim,
basis_std: basis_std.column_iter().map(
|v| Into::<DMatrix<f64>>::into(
@ -224,8 +224,8 @@ pub struct DescentHistory {
}
impl DescentHistory {
pub fn new() -> DescentHistory {
DescentHistory {
pub fn new() -> Self {
Self {
config: Vec::<DMatrix<f64>>::new(),
scaled_loss: Vec::<f64>::new(),
neg_grad: Vec::<DMatrix<f64>>::new(),
@ -245,9 +245,9 @@ pub struct ConstraintProblem {
}
impl ConstraintProblem {
pub fn new(element_count: usize) -> ConstraintProblem {
pub fn new(element_count: usize) -> Self {
const ELEMENT_DIM: usize = 5;
ConstraintProblem {
Self {
gram: PartialMatrix::new(),
frozen: PartialMatrix::new(),
guess: DMatrix::<f64>::zeros(ELEMENT_DIM, element_count),
@ -255,8 +255,8 @@ impl ConstraintProblem {
}
#[cfg(feature = "dev")]
pub fn from_guess(guess_columns: &[DVector<f64>]) -> ConstraintProblem {
ConstraintProblem {
pub fn from_guess(guess_columns: &[DVector<f64>]) -> Self {
Self {
gram: PartialMatrix::new(),
frozen: PartialMatrix::new(),
guess: DMatrix::from_columns(guess_columns),
@ -284,10 +284,10 @@ struct SearchState {
}
impl SearchState {
fn from_config(gram: &PartialMatrix, config: DMatrix<f64>) -> SearchState {
fn from_config(gram: &PartialMatrix, config: DMatrix<f64>) -> Self {
let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config));
let loss = err_proj.norm_squared();
SearchState { config, err_proj, loss }
Self { config, err_proj, loss }
}
}

View file

@ -24,8 +24,8 @@ struct AppState {
}
impl AppState {
fn new() -> AppState {
AppState {
fn new() -> Self {
Self {
assembly: Assembly::new(),
selection: create_signal(BTreeSet::default()),
}

View file

@ -17,8 +17,8 @@ pub struct SpecifiedValue {
}
impl SpecifiedValue {
pub fn from_empty_spec() -> SpecifiedValue {
SpecifiedValue { spec: String::new(), value: None }
pub fn from_empty_spec() -> Self {
Self { spec: String::new(), value: None }
}
pub fn is_present(&self) -> bool {
@ -34,10 +34,10 @@ impl TryFrom<String> for SpecifiedValue {
fn try_from(spec: String) -> Result<Self, Self::Error> {
if spec.is_empty() {
Ok(SpecifiedValue::from_empty_spec())
Ok(Self::from_empty_spec())
} else {
spec.parse::<f64>().map(
|value| SpecifiedValue { spec, value: Some(value) }
|value| Self { spec, value: Some(value) }
)
}
}

5
deploy/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
/dyna3.zip
/dyna3/index.html
/dyna3/dyna3-*.js
/dyna3/dyna3-*.wasm
/dyna3/main-*.css

View file

@ -0,0 +1,16 @@
# set paths. this technique for getting the script location comes from
# `mklement0` on Stack Overflow
#
# https://stackoverflow.com/a/24114056
#
TOOLS=$(dirname -- $0)
SRC="$TOOLS/../app-proto/dist"
DEST="$TOOLS/../deploy/dyna3"
# remove the old hash-named files
[ -e "$DEST"/dyna3-*.js ] && rm "$DEST"/dyna3-*.js
[ -e "$DEST"/dyna3-*.wasm ] && rm "$DEST"/dyna3-*.wasm
[ -e "$DEST"/main-*.css ] && rm "$DEST"/main-*.css
# copy the distribution
cp -r "$SRC/." "$DEST"

View file

@ -8,7 +8,7 @@
# the application prototype
# find the manifest file for the application prototype
MANIFEST="$(dirname -- $0)/Cargo.toml"
MANIFEST="$(dirname -- $0)/../app-proto/Cargo.toml"
# set up the command that runs each example
RUN_EXAMPLE="cargo run --manifest-path $MANIFEST --example"