Skip to main content

sim_lib_femm_function/
model_value.rs

1//! The runtime object wrapping a FEMM model as a first-class value.
2//!
3//! Defines the model value and its kernel object integration so a model can be
4//! held, displayed, and dispatched on inside the runtime.
5
6use std::any::Any;
7
8use sim_kernel::{
9    ClassId, ClassRef, Cx, DefaultFactory, Expr, Factory, Object, ObjectEncode, ObjectEncoding,
10    Result as KernelResult, Symbol, Value,
11};
12use sim_lib_femm_core::{Formulation, PhysicsKind};
13use sim_lib_femm_mesh::FemmModel;
14
15/// A FEMM model held as a first-class runtime object.
16///
17/// Wraps a [`FemmModel`] so it can be stored in a [`Value`], displayed,
18/// encoded, and dispatched on inside the runtime; the model itself stays
19/// behavior defined by this constellation, while the kernel supplies the
20/// object/encoding contracts. See the [crate README](index.html).
21///
22/// # Examples
23///
24/// ```
25/// use sim_lib_femm_fixtures::parallel_plate_capacitor;
26/// use sim_lib_femm_function::model_value;
27///
28/// let value = model_value(parallel_plate_capacitor());
29/// assert_eq!(value.model.name.as_qualified_str(), "parallel-plate-capacitor");
30/// ```
31#[derive(Clone)]
32pub struct ModelValue {
33    /// The wrapped finite-element model.
34    pub model: FemmModel,
35}
36
37impl Object for ModelValue {
38    fn display(&self, _cx: &mut Cx) -> KernelResult<String> {
39        Ok(format!(
40            "#<femm-model {}:{}>",
41            self.model.id.0, self.model.name
42        ))
43    }
44
45    fn as_any(&self) -> &dyn Any {
46        self
47    }
48}
49
50impl sim_kernel::ObjectCompat for ModelValue {
51    fn class(&self, cx: &mut Cx) -> KernelResult<ClassRef> {
52        if let Some(class) = cx
53            .registry()
54            .class_by_symbol(&Symbol::qualified("femm", "Model"))
55        {
56            return Ok(class.clone());
57        }
58        DefaultFactory.class_stub(ClassId(34), Symbol::qualified("femm", "Model"))
59    }
60    fn as_expr(&self, cx: &mut Cx) -> KernelResult<Expr> {
61        sim_citizen::constructor_expr(cx, self)
62    }
63    fn as_table(&self, cx: &mut Cx) -> KernelResult<Value> {
64        cx.factory().table(vec![
65            (
66                Symbol::new("kind"),
67                cx.factory().string("femm-model".to_owned())?,
68            ),
69            (
70                Symbol::new("id"),
71                cx.factory().string(self.model.id.0.to_string())?,
72            ),
73            (
74                Symbol::new("name"),
75                cx.factory().string(self.model.name.to_string())?,
76            ),
77            (
78                Symbol::new("query-kind"),
79                cx.factory().string("solution".to_owned())?,
80            ),
81        ])
82    }
83    fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
84        Some(self)
85    }
86}
87
88impl ObjectEncode for ModelValue {
89    fn object_encoding(&self, _cx: &mut Cx) -> KernelResult<ObjectEncoding> {
90        Ok(ObjectEncoding::Constructor {
91            class: model_class_symbol(),
92            args: model_constructor_args(self),
93        })
94    }
95}
96
97impl sim_citizen::Citizen for ModelValue {
98    fn citizen_symbol() -> Symbol {
99        model_class_symbol()
100    }
101
102    fn citizen_version() -> u32 {
103        1
104    }
105
106    fn citizen_arity() -> usize {
107        5
108    }
109
110    fn citizen_fields() -> &'static [&'static str] {
111        &["id", "name", "physics", "formulation", "params"]
112    }
113}
114
115/// Wraps a model as a [`ModelValue`] runtime object.
116pub fn model_value(model: FemmModel) -> ModelValue {
117    ModelValue { model }
118}
119
120fn model_class_symbol() -> Symbol {
121    Symbol::qualified("femm", "Model")
122}
123
124fn model_constructor_args(value: &ModelValue) -> Vec<Expr> {
125    vec![
126        Expr::Symbol(Symbol::new("v1")),
127        int_expr(value.model.id.0),
128        Expr::String(value.model.name.to_string()),
129        Expr::String(physics_name(&value.model.physics).to_owned()),
130        Expr::String(formulation_name(&value.model.formulation).to_owned()),
131        Expr::List(
132            value
133                .model
134                .inputs
135                .iter()
136                .map(|param| Expr::String(param.name.to_string()))
137                .collect(),
138        ),
139    ]
140}
141
142fn int_expr(value: impl ToString) -> Expr {
143    Expr::Number(sim_kernel::NumberLiteral {
144        domain: Symbol::qualified("citizen", "int"),
145        canonical: value.to_string(),
146    })
147}
148
149fn physics_name(physics: &PhysicsKind) -> &'static str {
150    match physics {
151        PhysicsKind::Magnetostatic => "magnetostatic",
152        PhysicsKind::MagneticsHarmonic => "magnetics-harmonic",
153        PhysicsKind::Electrostatic => "electrostatic",
154        PhysicsKind::HeatSteady => "heat-steady",
155        PhysicsKind::CurrentSteady => "current-steady",
156    }
157}
158
159fn formulation_name(formulation: &Formulation) -> &'static str {
160    match formulation {
161        Formulation::Planar => "planar",
162        Formulation::Axisymmetric => "axisymmetric",
163    }
164}