#![forbid(unsafe_code)]
use std::{
any::Any,
collections::BTreeSet,
hash::{Hash, Hasher},
sync::Arc,
time::Duration,
};
use sim_kernel::{
AbiVersion, Args, Callable, ClassRef, Cx, DefaultFactory, Dependency, Expr, Factory, Lib,
LibManifest, LibTarget, Linker, Object, RawArgs, Result as KernelResult, Symbol, Value,
Version,
};
use crate::{FemmError, FemmResult};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StableId(pub u64);
impl StableId {
pub fn from_hashable<T: Hash>(value: &T) -> Self {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
value.hash(&mut hasher);
Self(hasher.finish())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum PhysicsKind {
Magnetostatic,
MagneticsHarmonic,
Electrostatic,
HeatSteady,
CurrentSteady,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Formulation {
Planar,
Axisymmetric,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LengthUnit {
Meter,
Millimeter,
Inch,
Custom(Symbol),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ParamRole {
Design,
Excitation,
OdeState,
Time,
Geometry,
Material,
}
#[derive(Clone, Debug)]
pub struct ParamSpec {
pub name: Symbol,
pub default: Option<Value>,
pub unit: Option<Symbol>,
pub role: ParamRole,
}
#[derive(Clone, Debug, Default)]
pub struct ParamSet {
pub entries: Vec<(Symbol, Value)>,
}
impl ParamSet {
pub fn new(entries: Vec<(Symbol, Value)>) -> Self {
Self { entries }
}
pub fn get(&self, name: &Symbol) -> Option<&Value> {
self.entries
.iter()
.find(|(symbol, _)| symbol == name)
.map(|(_, value)| value)
}
pub fn symbols(&self) -> BTreeSet<Symbol> {
self.entries
.iter()
.map(|(symbol, _)| symbol.clone())
.collect()
}
pub fn fingerprint(&self, cx: &mut Cx) -> StableId {
let mut text = String::new();
for (symbol, value) in &self.entries {
let display = value
.object()
.display(cx)
.unwrap_or_else(|_| "#<display-error>".to_owned());
text.push_str(&symbol.to_string());
text.push('=');
text.push_str(&display);
text.push(';');
}
StableId::from_hashable(&text)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct FemmLimits {
pub max_nodes: usize,
pub max_elements: usize,
pub max_nnz: usize,
pub max_solve_iters: usize,
pub max_output_samples: usize,
pub max_femm_solves: usize,
pub max_wall_ms: u64,
}
impl Default for FemmLimits {
fn default() -> Self {
Self {
max_nodes: 10_000,
max_elements: 20_000,
max_nnz: 200_000,
max_solve_iters: 4_000,
max_output_samples: 20_000,
max_femm_solves: 1_000,
max_wall_ms: Duration::from_secs(30).as_millis() as u64,
}
}
}
pub fn femm_capabilities(
installed_field: bool,
installed_ptc: bool,
installed_adjoint: bool,
) -> Vec<String> {
let mut values = vec![
"Magnetostatic".to_owned(),
"MagneticsHarmonic".to_owned(),
"Electrostatic".to_owned(),
"HeatSteady".to_owned(),
"CurrentSteady".to_owned(),
];
values.push(
if installed_ptc {
"femm-ptc:installed"
} else {
"femm-ptc:unavailable"
}
.to_owned(),
);
values.push(
if installed_adjoint {
"femm-adjoint:installed"
} else {
"femm-adjoint:unavailable"
}
.to_owned(),
);
values.push(
if installed_field {
"numbers/field:installed"
} else {
"numbers/field:unavailable"
}
.to_owned(),
);
values
}
pub fn parse_finite_number(text: &str) -> Option<f64> {
let value = if let Some((num, den)) = text.split_once('/') {
let num = num.parse::<f64>().ok()?;
let den = den.parse::<f64>().ok()?;
if den == 0.0 {
return None;
}
num / den
} else {
text.parse::<f64>().ok()?
};
value.is_finite().then_some(value)
}
pub fn parse_displayed_number(text: &str) -> Option<f64> {
parse_finite_number(text)
}
pub fn value_as_f64(cx: &mut Cx, value: &Value) -> FemmResult<f64> {
let display = value
.object()
.display(cx)
.map_err(|err| FemmError::InvalidGeometry(err.to_string()))?;
parse_displayed_number(&display)
.ok_or_else(|| FemmError::InvalidGeometry(format!("expected scalar number, got {display}")))
}
pub fn stable_summary(name: &str, fields: &[(&str, String)]) -> String {
let mut out = format!("{name}(");
for (index, (field, value)) in fields.iter().enumerate() {
if index > 0 {
out.push_str(", ");
}
out.push_str(field);
out.push('=');
out.push_str(value);
}
out.push(')');
out
}
fn version_symbol() -> Symbol {
Symbol::qualified("femm", "version")
}
fn capabilities_symbol() -> Symbol {
Symbol::qualified("femm", "capabilities")
}
#[derive(Clone)]
struct FemmCoreFunction {
symbol: Symbol,
}
impl Object for FemmCoreFunction {
fn display(&self, _cx: &mut Cx) -> KernelResult<String> {
Ok(format!("#<function {}>", self.symbol))
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl sim_kernel::ObjectCompat for FemmCoreFunction {
fn class(&self, cx: &mut Cx) -> KernelResult<ClassRef> {
if let Some(class) = cx
.registry()
.class_by_symbol(&Symbol::qualified("core", "Function"))
{
return Ok(class.clone());
}
DefaultFactory.class_stub(
sim_kernel::CORE_FUNCTION_CLASS_ID,
Symbol::qualified("core", "Function"),
)
}
fn as_expr(&self, _cx: &mut Cx) -> KernelResult<Expr> {
Ok(Expr::Symbol(self.symbol.clone()))
}
fn as_callable(&self) -> Option<&dyn Callable> {
Some(self)
}
}
impl Callable for FemmCoreFunction {
fn call(&self, cx: &mut Cx, _args: Args) -> KernelResult<Value> {
if self.symbol == version_symbol() {
return cx.factory().string("0.1.0".to_owned());
}
let installed_field = cx
.registry()
.number_domain_by_symbol(&Symbol::qualified("numbers", "field"))
.is_some();
let installed_ptc = sim_lib_numbers_numeric::global_numeric_registry()
.read()
.map(|registry| registry.ode_fixed(&Symbol::new("femm-ptc")).is_some())
.unwrap_or(false);
let installed_adjoint = sim_lib_numbers_numeric::global_numeric_registry()
.read()
.map(|registry| {
registry
.differentiator(&Symbol::new("femm-adjoint"))
.is_some()
})
.unwrap_or(false);
let values = femm_capabilities(installed_field, installed_ptc, installed_adjoint)
.into_iter()
.map(|item| cx.factory().string(item))
.collect::<KernelResult<Vec<_>>>()?;
cx.factory().list(values)
}
fn call_exprs(&self, cx: &mut Cx, _args: RawArgs) -> KernelResult<Value> {
self.call(cx, Args::default())
}
}
pub struct FemmCoreLib;
impl FemmCoreLib {
pub fn new() -> Self {
Self
}
}
impl Default for FemmCoreLib {
fn default() -> Self {
Self::new()
}
}
impl Lib for FemmCoreLib {
fn manifest(&self) -> LibManifest {
LibManifest {
id: Symbol::qualified("femm", "core"),
version: Version(env!("CARGO_PKG_VERSION").to_owned()),
abi: AbiVersion { major: 0, minor: 1 },
target: LibTarget::HostRegistered,
requires: vec![Dependency {
id: Symbol::qualified("numbers", "numeric"),
minimum_version: None,
}],
capabilities: Vec::new(),
exports: vec![
sim_kernel::Export::Function {
symbol: version_symbol(),
function_id: None,
},
sim_kernel::Export::Function {
symbol: capabilities_symbol(),
function_id: None,
},
],
}
}
fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> KernelResult<()> {
for symbol in [version_symbol(), capabilities_symbol()] {
linker.function_value(
symbol.clone(),
DefaultFactory.opaque(Arc::new(FemmCoreFunction { symbol }))?,
)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests;