185 lines
		
	
	
		
			No EOL
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			No EOL
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use nalgebra::{DMatrix, DVector};
 | 
						|
use rustc_hash::FxHashMap;
 | 
						|
use slab::Slab;
 | 
						|
use std::collections::BTreeSet;
 | 
						|
use sycamore::prelude::*;
 | 
						|
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
 | 
						|
 | 
						|
use crate::engine::{realize_gram, PartialMatrix};
 | 
						|
 | 
						|
#[derive(Clone, PartialEq)]
 | 
						|
pub struct Element {
 | 
						|
    pub id: String,
 | 
						|
    pub label: String,
 | 
						|
    pub color: [f32; 3],
 | 
						|
    pub rep: DVector<f64>,
 | 
						|
    pub constraints: BTreeSet<usize>,
 | 
						|
    
 | 
						|
    // internal properties, not reflected in any view
 | 
						|
    pub index: usize
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Clone)]
 | 
						|
pub struct Constraint {
 | 
						|
    pub args: (usize, usize),
 | 
						|
    pub rep: Signal<f64>,
 | 
						|
    pub rep_text: Signal<String>,
 | 
						|
    pub rep_valid: Signal<bool>,
 | 
						|
    pub active: Signal<bool>
 | 
						|
}
 | 
						|
 | 
						|
// a complete, view-independent description of an assembly
 | 
						|
#[derive(Clone)]
 | 
						|
pub struct Assembly {
 | 
						|
    // elements and constraints
 | 
						|
    pub elements: Signal<Slab<Element>>,
 | 
						|
    pub constraints: Signal<Slab<Constraint>>,
 | 
						|
    
 | 
						|
    // indexing
 | 
						|
    pub elements_by_id: Signal<FxHashMap<String, usize>>
 | 
						|
}
 | 
						|
 | 
						|
impl Assembly {
 | 
						|
    pub fn new() -> Assembly {
 | 
						|
        Assembly {
 | 
						|
            elements: create_signal(Slab::new()),
 | 
						|
            constraints: create_signal(Slab::new()),
 | 
						|
            elements_by_id: create_signal(FxHashMap::default())
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // --- inserting elements and constraints ---
 | 
						|
    
 | 
						|
    // insert an element into the assembly without checking whether we already
 | 
						|
    // have an element with the same identifier. any element that does have the
 | 
						|
    // same identifier will get kicked out of the `elements_by_id` index
 | 
						|
    fn insert_element_unchecked(&self, elt: Element) {
 | 
						|
        let id = elt.id.clone();
 | 
						|
        let key = self.elements.update(|elts| elts.insert(elt));
 | 
						|
        self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key));
 | 
						|
    }
 | 
						|
    
 | 
						|
    pub fn try_insert_element(&self, elt: Element) -> bool {
 | 
						|
        let can_insert = self.elements_by_id.with_untracked(
 | 
						|
            |elts_by_id| !elts_by_id.contains_key(&elt.id)
 | 
						|
        );
 | 
						|
        if can_insert {
 | 
						|
            self.insert_element_unchecked(elt);
 | 
						|
        }
 | 
						|
        can_insert
 | 
						|
    }
 | 
						|
    
 | 
						|
    pub fn insert_new_element(&self) {
 | 
						|
        // find the next unused identifier in the default sequence
 | 
						|
        let mut id_num = 1;
 | 
						|
        let mut id = format!("sphere{}", id_num);
 | 
						|
        while self.elements_by_id.with_untracked(
 | 
						|
            |elts_by_id| elts_by_id.contains_key(&id)
 | 
						|
        ) {
 | 
						|
            id_num += 1;
 | 
						|
            id = format!("sphere{}", id_num);
 | 
						|
        }
 | 
						|
        
 | 
						|
        // create and insert a new element
 | 
						|
        self.insert_element_unchecked(
 | 
						|
            Element {
 | 
						|
                id: id,
 | 
						|
                label: format!("Sphere {}", id_num),
 | 
						|
                color: [0.75_f32, 0.75_f32, 0.75_f32],
 | 
						|
                rep: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]),
 | 
						|
                constraints: BTreeSet::default(),
 | 
						|
                index: 0
 | 
						|
            }
 | 
						|
        );
 | 
						|
    }
 | 
						|
    
 | 
						|
    pub fn insert_constraint(&self, constraint: Constraint) {
 | 
						|
        let args = constraint.args;
 | 
						|
        let key = self.constraints.update(|csts| csts.insert(constraint));
 | 
						|
        self.elements.update(|elts| {
 | 
						|
            elts[args.0].constraints.insert(key);
 | 
						|
            elts[args.1].constraints.insert(key);
 | 
						|
        });
 | 
						|
    }
 | 
						|
    
 | 
						|
    // --- realization ---
 | 
						|
    
 | 
						|
    pub fn realize(&self) {
 | 
						|
        // index the elements
 | 
						|
        self.elements.update_silent(|elts| {
 | 
						|
            for (index, (_, elt)) in elts.into_iter().enumerate() {
 | 
						|
                elt.index = index;
 | 
						|
            }
 | 
						|
        });
 | 
						|
        
 | 
						|
        // set up the Gram matrix and the initial configuration matrix
 | 
						|
        let (gram, guess) = self.elements.with_untracked(|elts| {
 | 
						|
            // set up the off-diagonal part of the Gram matrix
 | 
						|
            let mut gram_to_be = PartialMatrix::new();
 | 
						|
            self.constraints.with_untracked(|csts| {
 | 
						|
                for (_, cst) in csts {
 | 
						|
                    if cst.active.get_untracked() && cst.rep_valid.get_untracked() {
 | 
						|
                        let args = cst.args;
 | 
						|
                        let row = elts[args.0].index;
 | 
						|
                        let col = elts[args.1].index;
 | 
						|
                        gram_to_be.push_sym(row, col, cst.rep.get_untracked());
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            });
 | 
						|
            
 | 
						|
            // set up the initial configuration matrix and the diagonal of the
 | 
						|
            // Gram matrix
 | 
						|
            let mut guess_to_be = DMatrix::<f64>::zeros(5, elts.len());
 | 
						|
            for (_, elt) in elts {
 | 
						|
                let index = elt.index;
 | 
						|
                gram_to_be.push_sym(index, index, 1.0);
 | 
						|
                guess_to_be.set_column(index, &elt.rep);
 | 
						|
            }
 | 
						|
            
 | 
						|
            (gram_to_be, guess_to_be)
 | 
						|
        });
 | 
						|
        
 | 
						|
        /* DEBUG */
 | 
						|
        // log the Gram matrix
 | 
						|
        console::log_1(&JsValue::from("Gram matrix:"));
 | 
						|
        gram.log_to_console();
 | 
						|
        
 | 
						|
        /* DEBUG */
 | 
						|
        // log the initial configuration matrix
 | 
						|
        console::log_1(&JsValue::from("Old configuration:"));
 | 
						|
        for j in 0..guess.nrows() {
 | 
						|
            let mut row_str = String::new();
 | 
						|
            for k in 0..guess.ncols() {
 | 
						|
                row_str.push_str(format!(" {:>8.3}", guess[(j, k)]).as_str());
 | 
						|
            }
 | 
						|
            console::log_1(&JsValue::from(row_str));
 | 
						|
        }
 | 
						|
        
 | 
						|
        // look for a configuration with the given Gram matrix
 | 
						|
        let (config, success, history) = realize_gram(
 | 
						|
            &gram, guess, &[],
 | 
						|
            1.0e-12, 0.5, 0.9, 1.1, 200, 110
 | 
						|
        );
 | 
						|
        
 | 
						|
        /* DEBUG */
 | 
						|
        // report the outcome of the search
 | 
						|
        console::log_1(&JsValue::from(
 | 
						|
            if success {
 | 
						|
                "Target accuracy achieved!"
 | 
						|
            } else {
 | 
						|
                "Failed to reach target accuracy"
 | 
						|
            }
 | 
						|
        ));
 | 
						|
        console::log_2(&JsValue::from("Steps:"), &JsValue::from(history.scaled_loss.len() - 1));
 | 
						|
        console::log_2(&JsValue::from("Loss:"), &JsValue::from(*history.scaled_loss.last().unwrap()));
 | 
						|
        
 | 
						|
        if success {
 | 
						|
            // read out the solution
 | 
						|
            self.elements.update(|elts| {
 | 
						|
                for (_, elt) in elts.iter_mut() {
 | 
						|
                    elt.rep.set_column(0, &config.column(elt.index));
 | 
						|
                }
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |