Skip to main content

sim_lib_numbers_cas_diff/implementation/
function.rs

1//! The `CasDiffLib` and the `diff` callable: the library wiring that registers
2//! symbolic differentiation and the `integrate-sym` function with the runtime.
3
4use std::{any::Any, sync::Arc};
5
6use sim_kernel::{
7    AbiVersion, Args, Callable, ClassRef, Cx, DefaultFactory, Dependency, Error, Export, Expr,
8    Factory, Lib, LibManifest, LibTarget, Linker, Object, Result, Symbol, Value, Version,
9};
10use sim_lib_numbers_cas::{
11    cas_expr_to_surface_expr, cas_expr_to_value, extract_symbolish, value_to_cas_expr,
12};
13use sim_lib_numbers_core::domains;
14
15use super::diff::{diff_cas, diff_symbol};
16use super::func_surface::func_surface_body;
17use super::integrate::integrate_sym_symbol;
18use super::integrate_function::IntegrateSymFunction;
19
20/// The CAS differentiation and integration library.
21///
22/// Loading this [`Lib`] registers the `diff` and `integrate-sym` functions over
23/// `numbers/cas` expressions. It requires the `numbers/cas` domain to be loaded
24/// first.
25pub struct CasDiffLib;
26
27impl CasDiffLib {
28    /// Construct the CAS differentiation library.
29    pub fn new() -> Self {
30        Self
31    }
32}
33
34impl Default for CasDiffLib {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl Lib for CasDiffLib {
41    fn manifest(&self) -> LibManifest {
42        LibManifest {
43            id: domains::cas_diff(),
44            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
45            abi: AbiVersion { major: 0, minor: 1 },
46            target: LibTarget::HostRegistered,
47            requires: Vec::<Dependency>::new(),
48            capabilities: Vec::new(),
49            exports: vec![
50                Export::Function {
51                    symbol: diff_symbol(),
52                    function_id: None,
53                },
54                Export::Function {
55                    symbol: integrate_sym_symbol(),
56                    function_id: None,
57                },
58            ],
59        }
60    }
61
62    fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
63        linker.function_value(
64            diff_symbol(),
65            DefaultFactory
66                .opaque(Arc::new(DiffFunction))
67                .expect("diff function should be boxable"),
68        )?;
69        linker.function_value(
70            integrate_sym_symbol(),
71            DefaultFactory
72                .opaque(Arc::new(IntegrateSymFunction))
73                .expect("integrate-sym function should be boxable"),
74        )?;
75        Ok(())
76    }
77}
78
79#[derive(Clone)]
80struct DiffFunction;
81
82impl Object for DiffFunction {
83    fn display(&self, _cx: &mut Cx) -> Result<String> {
84        Ok(format!("#<function {}>", diff_symbol()))
85    }
86
87    fn as_any(&self) -> &dyn Any {
88        self
89    }
90}
91
92impl sim_kernel::ObjectCompat for DiffFunction {
93    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
94        if let Some(value) = cx
95            .registry()
96            .class_by_symbol(&Symbol::qualified("core", "Function"))
97        {
98            return Ok(value.clone());
99        }
100        DefaultFactory.class_stub(
101            sim_kernel::CORE_FUNCTION_CLASS_ID,
102            Symbol::qualified("core", "Function"),
103        )
104    }
105    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
106        Ok(Expr::Symbol(diff_symbol()))
107    }
108    fn as_callable(&self) -> Option<&dyn Callable> {
109        Some(self)
110    }
111}
112
113impl Callable for DiffFunction {
114    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
115        let values = args.into_vec();
116        let [expr_value, var] = values.as_slice() else {
117            return Err(Error::Eval(format!(
118                "{} expects exactly two arguments",
119                diff_symbol()
120            )));
121        };
122        let var = extract_symbolish(cx, var)?.ok_or_else(|| {
123            Error::Eval(format!(
124                "{} expects a quoted symbol or symbol as its second argument",
125                diff_symbol()
126            ))
127        })?;
128        if let Some(number) = cx.number_value_ref(expr_value.clone())?
129            && number.domain == domains::func()
130        {
131            return diff_func_value(cx, expr_value.clone(), &var);
132        }
133        let expr = value_to_cas_expr(cx, expr_value.clone())?;
134        let derivative = diff_cas(cx, &expr, &var)?;
135        cas_expr_to_value(cx, derivative)
136    }
137}
138
139fn diff_func_value(cx: &mut Cx, value: Value, var: &Symbol) -> Result<Value> {
140    let (vars_expr, body) = func_surface_body(cx, &value)?;
141    let derivative = diff_cas(cx, &body, var)?;
142    let derivative_expr = cas_expr_to_surface_expr(cx, &derivative)?;
143    cx.eval_expr(Expr::Call {
144        operator: Box::new(Expr::Symbol(Symbol::new("fn"))),
145        args: vec![vars_expr, derivative_expr],
146    })
147}