1use std::collections::HashMap;
18use std::f64::consts::{LN_10, LN_2};
19use std::sync::Arc;
20
21use sqlparser::ast::{
22 BinaryOperator, DataType, Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments,
23 ObjectNamePart, UnaryOperator,
24};
25
26use crate::colref::{ColRef, IdentCasing, Match};
27use crate::constructors::{
28 add, as_const, div, finite_num, func, func1, is_zero, mul, neg, num, one, sign, square, sub,
29 zero,
30};
31use crate::error::{DiffError, Result};
32
33const SUPPORTED: &str = "ddx differentiates the operators + - * /, unary calls to \
36sin/cos/tan/asin/acos/atan/exp/ln/log2/log10/sqrt/sinh/cosh/tanh/abs, power(...) with a \
37constant base or exponent, casts to a numeric type, and column/literal leaves";
38
39pub type Rule = Arc<dyn Fn(&Expr) -> Result<Expr> + Send + Sync>;
44
45#[derive(Clone)]
50pub struct RuleRegistry {
51 unary: HashMap<String, Rule>,
52}
53
54impl Default for RuleRegistry {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60fn rule(f: impl Fn(&Expr) -> Expr + Send + Sync + 'static) -> Rule {
62 Arc::new(move |u| Ok(f(u)))
63}
64
65impl RuleRegistry {
66 pub fn new() -> Self {
70 let mut unary: HashMap<String, Rule> = HashMap::new();
71
72 unary.insert("sin".into(), rule(|u| func1("cos", u.clone())));
74 unary.insert("cos".into(), rule(|u| neg(func1("sin", u.clone()))));
75 unary.insert(
76 "tan".into(),
77 rule(|u| div(one(), square(func1("cos", u.clone())))),
78 );
79 unary.insert(
81 "asin".into(),
82 rule(|u| div(one(), func1("sqrt", sub(one(), square(u.clone()))))),
83 );
84 unary.insert(
85 "acos".into(),
86 rule(|u| neg(div(one(), func1("sqrt", sub(one(), square(u.clone())))))),
87 );
88 unary.insert(
89 "atan".into(),
90 rule(|u| div(one(), add(one(), square(u.clone())))),
91 );
92 unary.insert("exp".into(), rule(|u| func1("exp", u.clone())));
94 unary.insert("ln".into(), rule(|u| div(one(), u.clone())));
95 unary.insert(
96 "log2".into(),
97 rule(|u| div(one(), mul(u.clone(), num(LN_2)))),
98 );
99 unary.insert(
100 "log10".into(),
101 rule(|u| div(one(), mul(u.clone(), num(LN_10)))),
102 );
103 unary.insert(
104 "sqrt".into(),
105 rule(|u| div(one(), mul(num(2.0), func1("sqrt", u.clone())))),
106 );
107 unary.insert("sinh".into(), rule(|u| func1("cosh", u.clone())));
109 unary.insert("cosh".into(), rule(|u| func1("sinh", u.clone())));
110 unary.insert(
111 "tanh".into(),
112 rule(|u| sub(one(), square(func1("tanh", u.clone())))),
113 );
114 unary.insert("abs".into(), rule(|u| sign(u.clone())));
121
122 RuleRegistry { unary }
123 }
124
125 pub fn unary_names(&self) -> Vec<String> {
132 let mut names: Vec<String> = self.unary.keys().cloned().collect();
133 names.sort();
134 names
135 }
136
137 pub fn register(&mut self, name: &str, rule: Rule) {
140 self.unary.insert(name.to_ascii_lowercase(), rule);
141 }
142
143 fn lookup(&self, name: &str) -> Option<&Rule> {
144 self.unary.get(name)
145 }
146}
147
148type Leaf<'a> = dyn Fn(&ColRef) -> Result<Expr> + 'a;
152
153pub fn differentiate(
157 expr: &Expr,
158 wrt: &ColRef,
159 casing: IdentCasing,
160 reg: &RuleRegistry,
161) -> Result<Expr> {
162 let leaf = |c: &ColRef| match c.classify(wrt, casing) {
163 Match::Is => Ok(one()),
164 Match::Not => Ok(zero()),
165 Match::Ambiguous => Err(DiffError::AmbiguousColumn(format!(
166 "occurrence of `{}` cannot be matched against differentiation \
167 variable `{}` — fully qualify it",
168 c.display(),
169 wrt.display()
170 ))),
171 };
172 linearize(expr, &leaf, reg)
173}
174
175pub fn jvp(
181 expr: &Expr,
182 seeds: &[(ColRef, Expr)],
183 casing: IdentCasing,
184 reg: &RuleRegistry,
185) -> Result<Expr> {
186 let leaf = |c: &ColRef| {
187 for (col, tangent) in seeds {
188 match c.classify(col, casing) {
189 Match::Is => return Ok(tangent.clone()),
190 Match::Ambiguous => {
191 return Err(DiffError::AmbiguousColumn(format!(
192 "occurrence of `{}` cannot be matched against seeded \
193 column `{}` — fully qualify it",
194 c.display(),
195 col.display()
196 )))
197 }
198 Match::Not => continue,
199 }
200 }
201 Ok(zero())
202 };
203 linearize(expr, &leaf, reg)
204}
205
206fn linearize(expr: &Expr, leaf: &Leaf, reg: &RuleRegistry) -> Result<Expr> {
208 match expr {
209 Expr::Identifier(_) | Expr::CompoundIdentifier(_) => {
211 let cr = ColRef::from_expr(expr)
212 .ok_or_else(|| DiffError::Internal("column expr yielded no ColRef".into()))?;
213 leaf(&cr)
214 }
215
216 Expr::Value(_) => Ok(zero()),
218
219 Expr::Nested(inner) => linearize(inner, leaf, reg),
222
223 Expr::Cast {
229 kind,
230 expr: inner,
231 data_type,
232 array,
233 format,
234 } => {
235 if !is_numeric_type(data_type) {
236 return Err(DiffError::NotImplemented(format!(
237 "differentiation through a cast to non-numeric type `{data_type}` \
238 is not supported"
239 )));
240 }
241 let du = linearize(inner, leaf, reg)?;
242 Ok(Expr::Cast {
243 kind: kind.clone(),
244 expr: Box::new(du),
245 data_type: data_type.clone(),
246 array: *array,
247 format: format.clone(),
248 })
249 }
250
251 Expr::UnaryOp {
253 op: UnaryOperator::Minus,
254 expr: inner,
255 } => Ok(neg(linearize(inner, leaf, reg)?)),
256 Expr::UnaryOp {
257 op: UnaryOperator::Plus,
258 expr: inner,
259 } => linearize(inner, leaf, reg),
260
261 Expr::BinaryOp { left, op, right } => linearize_binary(left, op, right, leaf, reg),
262
263 Expr::Function(f) => linearize_function(f, leaf, reg),
264
265 other => Err(DiffError::NotImplemented(format!(
266 "this expression cannot be differentiated: `{other}`. {SUPPORTED}; CASE, \
267 comparisons, subqueries, window functions, and string/temporal expressions are \
268 not differentiable"
269 ))),
270 }
271}
272
273fn linearize_binary(
275 left: &Expr,
276 op: &BinaryOperator,
277 right: &Expr,
278 leaf: &Leaf,
279 reg: &RuleRegistry,
280) -> Result<Expr> {
281 let da = linearize(left, leaf, reg)?;
282 let db = linearize(right, leaf, reg)?;
283 match op {
284 BinaryOperator::Plus => Ok(add(da, db)),
286 BinaryOperator::Minus => Ok(sub(da, db)),
288 BinaryOperator::Multiply => Ok(add(mul(da, right.clone()), mul(left.clone(), db))),
290 BinaryOperator::Divide => {
292 let numerator = sub(mul(da, right.clone()), mul(left.clone(), db));
293 Ok(div(numerator, square(right.clone())))
294 }
295 other => Err(DiffError::NotImplemented(format!(
296 "the operator `{other}` is not differentiable. {SUPPORTED}"
297 ))),
298 }
299}
300
301fn linearize_function(f: &Function, leaf: &Leaf, reg: &RuleRegistry) -> Result<Expr> {
303 let name = simple_func_name(f).ok_or_else(|| {
304 DiffError::NotImplemented(format!(
305 "cannot differentiate the call `{f}`: only an unqualified function name has a \
306 differentiation rule (a schema-qualified or otherwise complex name is left alone)"
307 ))
308 })?;
309 let args = positional_args(f).ok_or_else(|| {
310 DiffError::NotImplemented(format!(
311 "function `{name}` has non-positional arguments, which are not differentiable"
312 ))
313 })?;
314
315 if name == "power" || name == "pow" {
317 return linearize_power(&name, &args, leaf, reg);
318 }
319
320 if args.len() != 1 {
321 return Err(DiffError::NotImplemented(format!(
322 "no differentiation rule for `{name}` with {} arguments: the built-in function \
323 rules are unary, and `power` is the only two-argument rule",
324 args.len()
325 )));
326 }
327 let u = args[0];
328 let du = linearize(u, leaf, reg)?;
329 if is_zero(&du) {
331 return Ok(zero());
332 }
333 let outer = reg.lookup(&name).ok_or_else(|| {
334 DiffError::NotImplemented(format!(
335 "no differentiation rule for function `{name}`. {SUPPORTED}. Register a custom \
336 rule with `Ddx::register(\"{name}\", ...)`"
337 ))
338 })?(u)?;
339 Ok(mul(outer, du))
340}
341
342fn linearize_power(name: &str, args: &[&Expr], leaf: &Leaf, reg: &RuleRegistry) -> Result<Expr> {
348 if args.len() != 2 {
349 return Err(DiffError::NotImplemented(format!(
350 "{name}() expects exactly two arguments"
351 )));
352 }
353 let base = args[0];
354 let exponent = args[1];
355 match (as_const(base), as_const(exponent)) {
356 (_, Some(c)) => {
358 let dbase = linearize(base, leaf, reg)?;
359 if is_zero(&dbase) {
360 return Ok(zero());
361 }
362 let outer = mul(
367 finite_num(c)?,
368 func("power", vec![base.clone(), finite_num(c - 1.0)?]),
369 );
370 Ok(mul(outer, dbase))
371 }
372 (Some(a), None) => {
374 let dexp = linearize(exponent, leaf, reg)?;
375 if is_zero(&dexp) {
376 return Ok(zero());
377 }
378 let outer = mul(
382 func("power", vec![base.clone(), exponent.clone()]),
383 finite_num(a.ln())?,
384 );
385 Ok(mul(outer, dexp))
386 }
387 (None, None) => Err(DiffError::NotImplemented(
389 "cannot differentiate `power(base, exponent)` when both the base and the exponent \
390 depend on the differentiation variable; ddx handles it only when one side is a \
391 constant (e.g. `power(x, 2)` or `power(2, x)`). For the general u^v case, rewrite \
392 it as `exp(exponent * ln(base))` when the base is positive"
393 .into(),
394 )),
395 }
396}
397
398fn simple_func_name(f: &Function) -> Option<String> {
404 match f.name.0.as_slice() {
405 [ObjectNamePart::Identifier(id)] => Some(id.value.to_ascii_lowercase()),
406 _ => None,
407 }
408}
409
410pub(crate) fn positional_args(f: &Function) -> Option<Vec<&Expr>> {
414 match &f.args {
415 FunctionArguments::List(list) => {
416 let mut out = Vec::with_capacity(list.args.len());
417 for arg in &list.args {
418 match arg {
419 FunctionArg::Unnamed(FunctionArgExpr::Expr(e)) => out.push(e),
420 _ => return None,
421 }
422 }
423 Some(out)
424 }
425 _ => None,
426 }
427}
428
429pub(crate) fn is_numeric_type(dt: &DataType) -> bool {
434 matches!(
435 dt,
436 DataType::Numeric(_)
438 | DataType::Decimal(_)
439 | DataType::BigNumeric(_)
440 | DataType::BigDecimal(_)
441 | DataType::Dec(_)
442 | DataType::Float(_)
443 | DataType::FloatUnsigned(_)
444 | DataType::Float4
445 | DataType::Float32
446 | DataType::Float64
447 | DataType::Real
448 | DataType::RealUnsigned
449 | DataType::Float8
450 | DataType::Double(_)
451 | DataType::DoubleUnsigned(_)
452 | DataType::DoublePrecision
453 | DataType::DoublePrecisionUnsigned
454 | DataType::TinyInt(_)
456 | DataType::TinyIntUnsigned(_)
457 | DataType::UTinyInt
458 | DataType::Int2(_)
459 | DataType::Int2Unsigned(_)
460 | DataType::SmallInt(_)
461 | DataType::SmallIntUnsigned(_)
462 | DataType::USmallInt
463 | DataType::MediumInt(_)
464 | DataType::MediumIntUnsigned(_)
465 | DataType::Int(_)
466 | DataType::Int4(_)
467 | DataType::Int8(_)
468 | DataType::Int16
469 | DataType::Int32
470 | DataType::Int64
471 | DataType::Int128
472 | DataType::Int256
473 | DataType::Integer(_)
474 | DataType::IntUnsigned(_)
475 | DataType::Int4Unsigned(_)
476 | DataType::IntegerUnsigned(_)
477 | DataType::HugeInt
478 | DataType::UHugeInt
479 | DataType::UInt8
480 | DataType::UInt16
481 | DataType::UInt32
482 | DataType::UInt64
483 | DataType::UInt128
484 | DataType::UInt256
485 | DataType::BigInt(_)
486 | DataType::BigIntUnsigned(_)
487 | DataType::UBigInt
488 | DataType::Int8Unsigned(_)
489 | DataType::Signed
490 | DataType::SignedInteger
491 | DataType::Unsigned
492 | DataType::UnsignedInteger
493 )
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499
500 #[test]
501 fn the_error_message_lists_exactly_the_rules_that_exist() {
502 let registry = RuleRegistry::new();
514 let listed: Vec<String> = SUPPORTED
515 .split("unary calls to ")
516 .nth(1)
517 .expect("SUPPORTED must describe the unary rules")
518 .split(',')
519 .next()
520 .expect("the unary list is comma-delimited from the rest")
521 .split('/')
522 .map(|s| s.trim().to_string())
523 .collect();
524
525 let mut expected = registry.unary_names();
526 expected.sort();
527 let mut actual = listed;
528 actual.sort();
529 assert_eq!(
530 actual, expected,
531 "the rule set and the sentence users are shown have diverged; \
532 update SUPPORTED in this file to match the registry"
533 );
534 }
535}