1use std::fmt;
18
19use num_bigint::BigInt;
20use num_rational::Ratio;
21use num_traits::Signed;
22use smallvec::SmallVec;
23
24use crate::base::arena::Arena;
25use crate::base::node::{ExprId, ExprNode};
26use crate::prelude::*;
27
28use super::common::{display_sort_key, extract_negative_power, is_neg_coeff_mul, is_neg_one_mul};
29
30const GREEK_LETTERS: &[&str] = &[
35 "alpha",
36 "beta",
37 "gamma",
38 "delta",
39 "epsilon",
40 "zeta",
41 "eta",
42 "theta",
43 "iota",
44 "kappa",
45 "lambda",
46 "mu",
47 "nu",
48 "xi",
49 "omicron",
50 "pi",
51 "rho",
52 "sigma",
53 "tau",
54 "upsilon",
55 "phi",
56 "chi",
57 "psi",
58 "omega",
59 "Alpha",
61 "Beta",
62 "Gamma",
63 "Delta",
64 "Epsilon",
65 "Zeta",
66 "Eta",
67 "Theta",
68 "Iota",
69 "Kappa",
70 "Lambda",
71 "Mu",
72 "Nu",
73 "Xi",
74 "Omicron",
75 "Pi",
76 "Rho",
77 "Sigma",
78 "Tau",
79 "Upsilon",
80 "Phi",
81 "Chi",
82 "Psi",
83 "Omega",
84 "varepsilon",
86 "varphi",
87 "vartheta",
88 "varrho",
89 "varsigma",
90];
91
92fn symbol_to_latex(name: &str) -> String {
94 if let Some(idx) = name.find('_') {
96 let base = &name[..idx];
97 let sub = &name[idx + 1..];
98 let latex_base = greek_base(base).unwrap_or_else(|| base.to_string());
99 return format!("{}_{{{}}}", latex_base, sub);
100 }
101
102 if let Some(g) = greek_base(name) {
103 return g;
104 }
105
106 name.to_string()
107}
108
109fn greek_base(name: &str) -> Option<String> {
111 for &g in GREEK_LETTERS {
112 if name == g {
113 return Some(format!("\\{}", g));
114 }
115 }
116 None
117}
118
119enum LatexItem {
125 Lit(&'static str),
127 Owned(String),
129 Expr(ExprId),
131}
132
133pub(crate) fn fmt_latex(arena: &Arena, f: &mut fmt::Formatter<'_>, id: ExprId) -> fmt::Result {
141 let mut stack: Vec<LatexItem> = Vec::with_capacity(32);
142 stack.push(LatexItem::Expr(id));
143
144 while let Some(item) = stack.pop() {
145 match item {
146 LatexItem::Lit(s) => write!(f, "{s}")?,
147 LatexItem::Owned(s) => write!(f, "{s}")?,
148 LatexItem::Expr(eid) => expand_latex(arena, eid, &mut stack),
149 }
150 }
151
152 Ok(())
153}
154
155fn latex_to_string(arena: &Arena, id: ExprId) -> String {
157 let w = LatexWriter { arena, id };
158 format!("{}", w)
159}
160
161struct LatexWriter<'a> {
163 arena: &'a Arena,
164 id: ExprId,
165}
166
167impl fmt::Display for LatexWriter<'_> {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 fmt_latex(self.arena, f, self.id)
170 }
171}
172
173fn looks_like_number(s: &str) -> bool {
179 if s.is_empty() {
180 return false;
181 }
182 let s = s.strip_prefix('-').unwrap_or(s);
183 s.chars().all(|c| c.is_ascii_digit())
184}
185
186fn join_mul_factors(factors: &[String]) -> String {
188 if factors.len() == 1 {
189 return factors[0].clone();
190 }
191
192 let mut result = String::new();
193 for (i, f) in factors.iter().enumerate() {
194 if i > 0 {
195 let prev = &factors[i - 1];
196 if looks_like_number(prev) && looks_like_number(f) {
197 result.push_str(r" \cdot ");
199 } else if looks_like_number(prev) {
200 } else {
203 result.push(' ');
204 }
205 }
206 result.push_str(f);
207 }
208 result
209}
210
211fn render_mul_factor(arena: &Arena, id: ExprId) -> String {
213 let s = latex_to_string(arena, id);
214 if matches!(arena.node(id), ExprNode::Add(_)) {
215 format!("\\left({}\\right)", s)
216 } else {
217 s
218 }
219}
220
221fn render_pow_base(arena: &Arena, base: ExprId) -> String {
228 let s = latex_to_string(arena, base);
229 let wrap = match arena.node(base) {
230 ExprNode::Add(_) | ExprNode::Mul(_) | ExprNode::Neg(_) | ExprNode::Pow(_, _) => true,
231 ExprNode::Num(nid) => {
232 let r = arena.num(*nid);
233 r.is_negative() || !r.is_integer()
234 }
235 _ => false,
236 };
237 if wrap {
238 format!("\\left({}\\right)", s)
239 } else {
240 s
241 }
242}
243
244fn render_mul(arena: &Arena, children: &[ExprId]) -> String {
249 if children.is_empty() {
250 return "1".to_string();
251 }
252
253 let first_node = arena.node(children[0]);
255 let is_neg_one = if let ExprNode::Num(nid) = first_node {
256 *arena.num(*nid) == Ratio::from(BigInt::from(-1))
257 } else {
258 false
259 };
260 let is_pos_one = if let ExprNode::Num(nid) = first_node {
261 *arena.num(*nid) == Ratio::from(BigInt::from(1))
262 } else {
263 false
264 };
265
266 let (skip_first, prefix) = if children.len() > 1 && is_neg_one {
267 (true, "-")
268 } else if children.len() > 1 && is_pos_one {
269 (true, "")
270 } else {
271 (false, "")
272 };
273
274 let start_idx = if skip_first { 1 } else { 0 };
275 let factors = &children[start_idx..];
276
277 let mut numer_factors: Vec<String> = Vec::new();
279 let mut denom_factors: Vec<String> = Vec::new();
280
281 for &factor in factors {
282 if let Some((base_id, pos_exp_str)) = extract_negative_power(arena, factor) {
283 let base_latex = render_pow_base(arena, base_id);
284 if pos_exp_str == "1" {
285 denom_factors.push(base_latex);
286 } else {
287 denom_factors.push(format!("{}^{{{}}}", base_latex, pos_exp_str));
288 }
289 } else {
290 numer_factors.push(render_mul_factor(arena, factor));
291 }
292 }
293
294 if denom_factors.is_empty() {
295 let body = if numer_factors.is_empty() {
297 "1".to_string()
298 } else {
299 join_mul_factors(&numer_factors)
300 };
301 format!("{}{}", prefix, body)
302 } else {
303 let numer_str = if numer_factors.is_empty() {
305 "1".to_string()
306 } else {
307 join_mul_factors(&numer_factors)
308 };
309 let denom_str = join_mul_factors(&denom_factors);
310 if prefix == "-" {
311 format!("-\\frac{{{}}}{{{}}}", numer_str, denom_str)
312 } else {
313 format!("\\frac{{{}}}{{{}}}", numer_str, denom_str)
314 }
315 }
316}
317
318fn render_mul_without_neg_one(arena: &Arena, id: ExprId) -> String {
325 if let ExprNode::Mul(children) = arena.node(id) {
326 let rest = &children[1..];
327 if rest.len() == 1 {
328 return latex_to_string(arena, rest[0]);
329 }
330 render_mul(arena, rest)
332 } else {
333 latex_to_string(arena, id)
334 }
335}
336
337fn render_neg_coeff_mul_as_subtraction(arena: &Arena, id: ExprId) -> String {
340 if let ExprNode::Mul(children) = arena.node(id)
341 && let Some(&first) = children.first()
342 && let ExprNode::Num(nid) = arena.node(first)
343 {
344 let r = arena.num(*nid);
345 let pos_r = -r.clone();
346
347 let coeff_str = render_num_value(&pos_r);
350
351 let rest_factors: Vec<String> = children[1..]
352 .iter()
353 .map(|&c| render_mul_factor(arena, c))
354 .collect();
355
356 if pos_r == Ratio::from(BigInt::from(1)) {
357 if rest_factors.is_empty() {
359 return "1".to_string();
360 }
361 return join_mul_factors(&rest_factors);
362 }
363
364 let mut all_factors = vec![coeff_str];
365 all_factors.extend(rest_factors);
366
367 return join_mul_factors(&all_factors);
371 }
372 latex_to_string(arena, id)
373}
374
375fn render_num_value(r: &Ratio<BigInt>) -> String {
381 if r.is_integer() {
382 format!("{}", r.numer())
383 } else if r.numer().is_negative() {
384 format!("-\\frac{{{}}}{{{}}}", -r.numer(), r.denom())
385 } else {
386 format!("\\frac{{{}}}{{{}}}", r.numer(), r.denom())
387 }
388}
389
390fn trig_func_parts(arena: &Arena, id: ExprId) -> Option<(&'static str, ExprId)> {
397 match arena.node(id) {
398 ExprNode::Sin(x) => Some((r"\sin", *x)),
399 ExprNode::Cos(x) => Some((r"\cos", *x)),
400 ExprNode::Tan(x) => Some((r"\tan", *x)),
401 ExprNode::Sinh(x) => Some((r"\sinh", *x)),
402 ExprNode::Cosh(x) => Some((r"\cosh", *x)),
403 ExprNode::Tanh(x) => Some((r"\tanh", *x)),
404 ExprNode::Asin(x) => Some((r"\arcsin", *x)),
405 ExprNode::Acos(x) => Some((r"\arccos", *x)),
406 ExprNode::Atan(x) => Some((r"\arctan", *x)),
407 ExprNode::Asinh(x) => Some((r"\operatorname{asinh}", *x)),
408 ExprNode::Acosh(x) => Some((r"\operatorname{acosh}", *x)),
409 ExprNode::Atanh(x) => Some((r"\operatorname{atanh}", *x)),
410 _ => None,
411 }
412}
413
414fn push_latex_func(name: &'static str, arg: ExprId, stack: &mut Vec<LatexItem>) {
415 stack.push(LatexItem::Lit(r"\right)"));
416 stack.push(LatexItem::Expr(arg));
417 stack.push(LatexItem::Owned(format!("{}\\left(", name)));
418}
419
420fn push_latex_call(head: &str, args: &[ExprId], stack: &mut Vec<LatexItem>) {
422 stack.push(LatexItem::Lit(r"\right)"));
423 for (i, &arg) in args.iter().enumerate().rev() {
424 stack.push(LatexItem::Expr(arg));
425 if i > 0 {
426 stack.push(LatexItem::Lit(", "));
427 }
428 }
429 stack.push(LatexItem::Owned(format!("{head}\\left(")));
430}
431
432fn push_special_09_latex(name: &str, args: &[ExprId], stack: &mut Vec<LatexItem>) -> bool {
440 use crate::base::arena::{
441 FN_AIRYAI, FN_AIRYAIPRIME, FN_AIRYBI, FN_AIRYBIPRIME, FN_ASSOC_LAGUERRE, FN_ASSOC_LEGENDRE,
442 FN_BETAINC, FN_BETAINC_REGULARIZED, FN_CHI, FN_DIRICHLET_ETA, FN_ELLIPTIC_E, FN_ELLIPTIC_F,
443 FN_ELLIPTIC_K, FN_ELLIPTIC_PI, FN_ERFCINV, FN_ERFI, FN_ERFINV, FN_EXPINT, FN_FRESNELC,
444 FN_FRESNELS, FN_GEGENBAUER, FN_JACOBI, FN_LOWERGAMMA, FN_POLYLOG, FN_SHI, FN_UPPERGAMMA,
445 };
446 let head: Option<&str> = match (name, args.len()) {
448 (FN_ERFI, 1) => Some(r"\operatorname{erfi}"),
449 (FN_ERFINV, 1) => Some(r"\operatorname{erf}^{-1}"),
450 (FN_ERFCINV, 1) => Some(r"\operatorname{erfc}^{-1}"),
451 (FN_SHI, 1) => Some(r"\operatorname{Shi}"),
452 (FN_CHI, 1) => Some(r"\operatorname{Chi}"),
453 (FN_FRESNELS, 1) => Some("S"),
454 (FN_FRESNELC, 1) => Some("C"),
455 (FN_LOWERGAMMA, 2) => Some(r"\gamma"),
456 (FN_UPPERGAMMA, 2) => Some(r"\Gamma"),
457 (FN_DIRICHLET_ETA, 1) => Some(r"\eta"),
458 (FN_AIRYAI, 1) => Some(r"\operatorname{Ai}"),
459 (FN_AIRYBI, 1) => Some(r"\operatorname{Bi}"),
460 (FN_AIRYAIPRIME, 1) => Some(r"\operatorname{Ai}^\prime"),
461 (FN_AIRYBIPRIME, 1) => Some(r"\operatorname{Bi}^\prime"),
462 (FN_ELLIPTIC_K, 1) => Some("K"),
463 (FN_ELLIPTIC_E, 1) => Some("E"),
464 _ => None,
465 };
466 if let Some(head) = head {
467 push_latex_call(head, args, stack);
468 return true;
469 }
470 match (name, args.len()) {
471 (FN_EXPINT, 2) | (FN_POLYLOG, 2) => {
473 let head = if name == FN_EXPINT {
474 r"\operatorname{E}_{"
475 } else {
476 r"\operatorname{Li}_{"
477 };
478 stack.push(LatexItem::Lit(r"\right)"));
479 stack.push(LatexItem::Expr(args[1]));
480 stack.push(LatexItem::Lit(r"}\left("));
481 stack.push(LatexItem::Expr(args[0]));
482 stack.push(LatexItem::Lit(head));
483 true
484 }
485 (FN_ELLIPTIC_F, 2) | (FN_ELLIPTIC_PI, 2) => {
487 stack.push(LatexItem::Lit(r"\right)"));
488 stack.push(LatexItem::Expr(args[1]));
489 stack.push(LatexItem::Lit(r"\middle| "));
490 stack.push(LatexItem::Expr(args[0]));
491 stack.push(LatexItem::Lit(if name == FN_ELLIPTIC_F {
492 r"F\left("
493 } else {
494 r"\Pi\left("
495 }));
496 true
497 }
498 (FN_GEGENBAUER, 3) | (FN_ASSOC_LEGENDRE, 3) | (FN_ASSOC_LAGUERRE, 3) => {
500 let letter = match name {
501 FN_GEGENBAUER => r"C_{",
502 FN_ASSOC_LEGENDRE => r"P_{",
503 _ => r"L_{",
504 };
505 stack.push(LatexItem::Lit(r"\right)"));
506 stack.push(LatexItem::Expr(args[2]));
507 stack.push(LatexItem::Lit(r"\right)}\left("));
508 stack.push(LatexItem::Expr(args[1]));
509 stack.push(LatexItem::Lit(r"}^{\left("));
510 stack.push(LatexItem::Expr(args[0]));
511 stack.push(LatexItem::Lit(letter));
512 true
513 }
514 (FN_JACOBI, 4) => {
516 stack.push(LatexItem::Lit(r"\right)"));
517 stack.push(LatexItem::Expr(args[3]));
518 stack.push(LatexItem::Lit(r"\right)}\left("));
519 stack.push(LatexItem::Expr(args[2]));
520 stack.push(LatexItem::Lit(","));
521 stack.push(LatexItem::Expr(args[1]));
522 stack.push(LatexItem::Lit(r"}^{\left("));
523 stack.push(LatexItem::Expr(args[0]));
524 stack.push(LatexItem::Lit(r"P_{"));
525 true
526 }
527 (FN_BETAINC, 4) | (FN_BETAINC_REGULARIZED, 4) => {
529 stack.push(LatexItem::Lit(r"\right)"));
530 stack.push(LatexItem::Expr(args[1]));
531 stack.push(LatexItem::Lit(", "));
532 stack.push(LatexItem::Expr(args[0]));
533 stack.push(LatexItem::Lit(r")}\left("));
534 stack.push(LatexItem::Expr(args[3]));
535 stack.push(LatexItem::Lit(", "));
536 stack.push(LatexItem::Expr(args[2]));
537 stack.push(LatexItem::Lit(if name == FN_BETAINC {
538 r"\operatorname{B}_{("
539 } else {
540 r"\operatorname{I}_{("
541 }));
542 true
543 }
544 _ => false,
545 }
546}
547
548fn expand_latex(arena: &Arena, id: ExprId, stack: &mut Vec<LatexItem>) {
553 let node = arena.node(id).clone();
554
555 match node {
556 ExprNode::Num(nid) => {
558 let r = arena.num(nid);
559 stack.push(LatexItem::Owned(render_num_value(r)));
560 }
561
562 ExprNode::Symbol(sid) => {
563 let name = arena.symbol_name(sid);
564 stack.push(LatexItem::Owned(symbol_to_latex(name)));
565 }
566
567 ExprNode::Pi => stack.push(LatexItem::Lit(r"\pi")),
568 ExprNode::E => stack.push(LatexItem::Lit("e")),
569 ExprNode::ImaginaryUnit => stack.push(LatexItem::Lit("i")),
570 ExprNode::EulerGamma => stack.push(LatexItem::Lit(r"\gamma")),
571 ExprNode::Catalan => stack.push(LatexItem::Lit("G")),
572 ExprNode::GoldenRatio => stack.push(LatexItem::Lit(r"\phi")),
573 ExprNode::PhysicalConstant(name_id, _) => {
574 stack.push(LatexItem::Owned(symbol_to_latex(
575 arena.symbol_name(name_id),
576 )));
577 }
578 ExprNode::Infinity => stack.push(LatexItem::Lit(r"\infty")),
579 ExprNode::NegInfinity => stack.push(LatexItem::Lit(r"-\infty")),
580 ExprNode::ComplexInfinity => stack.push(LatexItem::Lit(r"\tilde{\infty}")),
581 ExprNode::NaN => stack.push(LatexItem::Lit(r"\text{NaN}")),
582
583 ExprNode::Add(ref children) => {
588 let children = children.clone();
589 if children.is_empty() {
590 stack.push(LatexItem::Lit("0"));
591 return;
592 }
593
594 let mut display_order: SmallVec<[ExprId; 6]> = children;
596 display_order.sort_by_key(|a| display_sort_key(arena, *a));
597
598 let mut result = String::new();
599
600 for (i, &child) in display_order.iter().enumerate() {
601 let child_node = arena.node(child);
602
603 if let ExprNode::Neg(inner) = child_node {
604 let inner = *inner;
606 let inner_latex = latex_to_string(arena, inner);
607 if i == 0 {
608 if matches!(arena.node(inner), ExprNode::Add(_)) {
610 result.push_str(&format!("-\\left({}\\right)", inner_latex));
611 } else {
612 result.push_str(&format!("-{}", inner_latex));
613 }
614 } else if matches!(arena.node(inner), ExprNode::Add(_)) {
615 result.push_str(&format!(" - \\left({}\\right)", inner_latex));
616 } else {
617 result.push_str(&format!(" - {}", inner_latex));
618 }
619 } else if is_neg_one_mul(arena, child) {
620 let rest_latex = render_mul_without_neg_one(arena, child);
622 if i == 0 {
623 result.push_str(&format!("-{}", rest_latex));
624 } else {
625 result.push_str(&format!(" - {}", rest_latex));
626 }
627 } else if is_neg_coeff_mul(arena, child) {
628 let sub_latex = render_neg_coeff_mul_as_subtraction(arena, child);
630 if i == 0 {
631 result.push_str(&format!("-{}", sub_latex));
632 } else {
633 result.push_str(&format!(" - {}", sub_latex));
634 }
635 } else if i == 0 {
636 if let ExprNode::Num(nid) = arena.node(child) {
638 let r = arena.num(*nid);
639 if r.is_negative() && !r.is_integer() {
640 result.push_str(&render_num_value(r));
642 } else {
643 result.push_str(&latex_to_string(arena, child));
644 }
645 } else {
646 result.push_str(&latex_to_string(arena, child));
647 }
648 } else {
649 if let ExprNode::Num(nid) = arena.node(child) {
651 let r = arena.num(*nid);
652 if r.is_negative() {
653 let pos_r = -r.clone();
654 let pos_str = render_num_value(&pos_r);
655 result.push_str(&format!(" - {}", pos_str));
656 } else {
657 result.push_str(&format!(" + {}", latex_to_string(arena, child)));
658 }
659 } else {
660 result.push_str(&format!(" + {}", latex_to_string(arena, child)));
661 }
662 }
663 }
664
665 stack.push(LatexItem::Owned(result));
666 }
667
668 ExprNode::Mul(ref children) => {
673 let children_vec: Vec<ExprId> = children.iter().copied().collect();
674 stack.push(LatexItem::Owned(render_mul(arena, &children_vec)));
675 }
676
677 ExprNode::Pow(base, exp) => {
679 if let ExprNode::Num(nid) = arena.node(exp) {
681 let r = arena.num(*nid);
682 if *r == Ratio::new(BigInt::from(1), BigInt::from(2)) {
683 stack.push(LatexItem::Lit("}"));
684 stack.push(LatexItem::Expr(base));
685 stack.push(LatexItem::Lit(r"\sqrt{"));
686 return;
687 }
688 if *r == Ratio::new(BigInt::from(1), BigInt::from(3)) {
690 stack.push(LatexItem::Lit("}"));
691 stack.push(LatexItem::Expr(base));
692 stack.push(LatexItem::Lit(r"\sqrt[3]{"));
693 return;
694 }
695 if !r.is_integer() && !r.is_negative() && *r.numer() == BigInt::from(1) {
697 let n = r.denom();
698 stack.push(LatexItem::Lit("}"));
699 stack.push(LatexItem::Expr(base));
700 stack.push(LatexItem::Owned(format!("\\sqrt[{}]{{", n)));
701 return;
702 }
703 if *r == Ratio::from(BigInt::from(-1)) {
705 let base_latex = render_pow_base(arena, base);
706 stack.push(LatexItem::Owned(format!("\\frac{{1}}{{{}}}", base_latex)));
707 return;
708 }
709 if r.is_integer() && r.is_negative() {
711 let pos_n = -r.numer();
712 let base_latex = render_pow_base(arena, base);
713 stack.push(LatexItem::Owned(format!(
714 "\\frac{{1}}{{{}^{{{}}}}}",
715 base_latex, pos_n
716 )));
717 return;
718 }
719 }
720
721 if let Some((func_name, arg)) = trig_func_parts(arena, base) {
723 stack.push(LatexItem::Lit(r"\right)"));
724 stack.push(LatexItem::Expr(arg));
725 stack.push(LatexItem::Lit(r"}\left("));
726 stack.push(LatexItem::Expr(exp));
727 stack.push(LatexItem::Owned(format!("{}^{{", func_name)));
728 return;
729 }
730
731 let base_latex = render_pow_base(arena, base);
733 stack.push(LatexItem::Lit("}"));
734 stack.push(LatexItem::Expr(exp));
735 stack.push(LatexItem::Owned(format!("{}^{{", base_latex)));
736 }
737
738 ExprNode::Neg(inner) => {
740 if matches!(arena.node(inner), ExprNode::Add(_)) {
741 stack.push(LatexItem::Lit(r"\right)"));
742 stack.push(LatexItem::Expr(inner));
743 stack.push(LatexItem::Lit(r"-\left("));
744 } else {
745 stack.push(LatexItem::Expr(inner));
746 stack.push(LatexItem::Lit("-"));
747 }
748 }
749
750 ExprNode::Sin(x) => push_latex_func(r"\sin", x, stack),
752 ExprNode::Cos(x) => push_latex_func(r"\cos", x, stack),
753 ExprNode::Tan(x) => push_latex_func(r"\tan", x, stack),
754 ExprNode::Asin(x) => push_latex_func(r"\arcsin", x, stack),
755 ExprNode::Acos(x) => push_latex_func(r"\arccos", x, stack),
756 ExprNode::Atan(x) => push_latex_func(r"\arctan", x, stack),
757 ExprNode::Sinh(x) => push_latex_func(r"\sinh", x, stack),
758 ExprNode::Cosh(x) => push_latex_func(r"\cosh", x, stack),
759 ExprNode::Tanh(x) => push_latex_func(r"\tanh", x, stack),
760 ExprNode::Asinh(x) => push_latex_func(r"\operatorname{asinh}", x, stack),
761 ExprNode::Acosh(x) => push_latex_func(r"\operatorname{acosh}", x, stack),
762 ExprNode::Atanh(x) => push_latex_func(r"\operatorname{atanh}", x, stack),
763
764 ExprNode::Exp(x) => push_latex_func(r"\exp", x, stack),
766 ExprNode::Ln(x) => push_latex_func(r"\ln", x, stack),
767
768 ExprNode::Sign(x) => push_latex_func(r"\operatorname{sgn}", x, stack),
770 ExprNode::Heaviside(x) => push_latex_func(r"\operatorname{H}", x, stack),
771 ExprNode::DiracDelta(x) => push_latex_func(r"\delta", x, stack),
772 ExprNode::Gamma(x) => push_latex_func(r"\Gamma", x, stack),
773 ExprNode::LogGamma(x) => push_latex_func(r"\ln \Gamma", x, stack),
774 ExprNode::Digamma(x) => push_latex_func(r"\psi", x, stack),
775 ExprNode::Erf(x) => push_latex_func(r"\operatorname{erf}", x, stack),
776 ExprNode::Erfc(x) => push_latex_func(r"\operatorname{erfc}", x, stack),
777 ExprNode::LambertW(x) => push_latex_func(r"\operatorname{W}", x, stack),
778
779 ExprNode::Re(x) => push_latex_func(r"\Re", x, stack),
781 ExprNode::Im(x) => push_latex_func(r"\Im", x, stack),
782 ExprNode::Conjugate(x) => {
783 stack.push(LatexItem::Lit("}"));
784 stack.push(LatexItem::Expr(x));
785 stack.push(LatexItem::Lit(r"\overline{"));
786 }
787 ExprNode::Arg(x) => push_latex_func(r"\arg", x, stack),
788
789 ExprNode::Si(x) => push_latex_func(r"\operatorname{Si}", x, stack),
791 ExprNode::Ci(x) => push_latex_func(r"\operatorname{Ci}", x, stack),
792 ExprNode::Ei(x) => push_latex_func(r"\operatorname{Ei}", x, stack),
793 ExprNode::Li(x) => push_latex_func(r"\operatorname{li}", x, stack),
794 ExprNode::Zeta(x) => push_latex_func(r"\zeta", x, stack),
795 ExprNode::Polygamma(n, x) => {
796 stack.push(LatexItem::Lit(r"\right)"));
798 stack.push(LatexItem::Expr(x));
799 stack.push(LatexItem::Lit(r")}\left("));
800 stack.push(LatexItem::Expr(n));
801 stack.push(LatexItem::Lit(r"\psi^{("));
802 }
803 ExprNode::KroneckerDelta(i, j) => {
804 stack.push(LatexItem::Lit("}"));
806 stack.push(LatexItem::Expr(j));
807 stack.push(LatexItem::Lit(" "));
808 stack.push(LatexItem::Expr(i));
809 stack.push(LatexItem::Lit(r"\delta_{"));
810 }
811
812 ExprNode::Abs(x) => {
814 stack.push(LatexItem::Lit(r"\right|"));
815 stack.push(LatexItem::Expr(x));
816 stack.push(LatexItem::Lit(r"\left|"));
817 }
818
819 ExprNode::Floor(x) => {
821 stack.push(LatexItem::Lit(r"\rfloor"));
822 stack.push(LatexItem::Expr(x));
823 stack.push(LatexItem::Lit(r"\lfloor "));
824 }
825 ExprNode::Ceiling(x) => {
826 stack.push(LatexItem::Lit(r"\rceil"));
827 stack.push(LatexItem::Expr(x));
828 stack.push(LatexItem::Lit(r"\lceil "));
829 }
830
831 ExprNode::Factorial(x) => {
833 if arena.node(x).is_atom() {
834 stack.push(LatexItem::Lit("!"));
835 stack.push(LatexItem::Expr(x));
836 } else {
837 stack.push(LatexItem::Lit(r"\right)!"));
838 stack.push(LatexItem::Expr(x));
839 stack.push(LatexItem::Lit(r"\left("));
840 }
841 }
842
843 ExprNode::Binomial(n, k) => {
845 stack.push(LatexItem::Lit("}"));
846 stack.push(LatexItem::Expr(k));
847 stack.push(LatexItem::Lit("}{"));
848 stack.push(LatexItem::Expr(n));
849 stack.push(LatexItem::Lit(r"\binom{"));
850 }
851
852 ExprNode::Beta(a, b) => {
854 stack.push(LatexItem::Lit(r"\right)"));
855 stack.push(LatexItem::Expr(b));
856 stack.push(LatexItem::Lit(", "));
857 stack.push(LatexItem::Expr(a));
858 stack.push(LatexItem::Lit(r"\mathrm{B}\left("));
859 }
860
861 ExprNode::Atan2(y, x) => {
863 stack.push(LatexItem::Lit(r"\right)"));
864 stack.push(LatexItem::Expr(x));
865 stack.push(LatexItem::Lit(", "));
866 stack.push(LatexItem::Expr(y));
867 stack.push(LatexItem::Lit(r"\operatorname{atan2}\left("));
868 }
869
870 ExprNode::Min(ref args) => {
872 let args = args.clone();
873 stack.push(LatexItem::Lit(r"\right)"));
874 for (i, &arg) in args.iter().enumerate().rev() {
875 stack.push(LatexItem::Expr(arg));
876 if i > 0 {
877 stack.push(LatexItem::Lit(", "));
878 }
879 }
880 stack.push(LatexItem::Lit(r"\min\left("));
881 }
882 ExprNode::Max(ref args) => {
883 let args = args.clone();
884 stack.push(LatexItem::Lit(r"\right)"));
885 for (i, &arg) in args.iter().enumerate().rev() {
886 stack.push(LatexItem::Expr(arg));
887 if i > 0 {
888 stack.push(LatexItem::Lit(", "));
889 }
890 }
891 stack.push(LatexItem::Lit(r"\max\left("));
892 }
893
894 ExprNode::Sum(body, var, lo, hi) => {
896 stack.push(LatexItem::Expr(body));
897 stack.push(LatexItem::Lit("} "));
898 stack.push(LatexItem::Expr(hi));
899 stack.push(LatexItem::Lit("^{"));
900 stack.push(LatexItem::Expr(lo));
901 stack.push(LatexItem::Lit("="));
902 stack.push(LatexItem::Expr(var));
903 stack.push(LatexItem::Lit(r"\sum_{"));
904 }
905 ExprNode::Product_(body, var, lo, hi) => {
906 stack.push(LatexItem::Expr(body));
907 stack.push(LatexItem::Lit("} "));
908 stack.push(LatexItem::Expr(hi));
909 stack.push(LatexItem::Lit("^{"));
910 stack.push(LatexItem::Expr(lo));
911 stack.push(LatexItem::Lit("="));
912 stack.push(LatexItem::Expr(var));
913 stack.push(LatexItem::Lit(r"\prod_{"));
914 }
915
916 ExprNode::Derivative(body, var) => {
918 stack.push(LatexItem::Expr(body));
919 stack.push(LatexItem::Lit("} "));
920 stack.push(LatexItem::Expr(var));
921 stack.push(LatexItem::Lit(r"\frac{d}{d"));
922 }
923
924 ExprNode::Integral(body, var) => {
926 stack.push(LatexItem::Expr(var));
927 stack.push(LatexItem::Lit(r"\, d"));
928 stack.push(LatexItem::Expr(body));
929 stack.push(LatexItem::Lit(r"\int "));
930 }
931
932 ExprNode::DefiniteIntegral(body, var, lo, hi) => {
934 stack.push(LatexItem::Expr(var));
935 stack.push(LatexItem::Lit(r"\, d"));
936 stack.push(LatexItem::Expr(body));
937 stack.push(LatexItem::Lit("} "));
938 stack.push(LatexItem::Expr(hi));
939 stack.push(LatexItem::Lit("}^{"));
940 stack.push(LatexItem::Expr(lo));
941 stack.push(LatexItem::Lit(r"\int_{"));
942 }
943
944 ExprNode::Limit(body, var, point) => {
946 stack.push(LatexItem::Expr(body));
947 stack.push(LatexItem::Lit("} "));
948 stack.push(LatexItem::Expr(point));
949 stack.push(LatexItem::Lit(r" \to "));
950 stack.push(LatexItem::Expr(var));
951 stack.push(LatexItem::Lit(r"\lim_{"));
952 }
953
954 ExprNode::Series(body, var, point, order) => {
956 stack.push(LatexItem::Lit(r"\right)"));
957 stack.push(LatexItem::Expr(order));
958 stack.push(LatexItem::Lit(", "));
959 stack.push(LatexItem::Expr(point));
960 stack.push(LatexItem::Lit(", "));
961 stack.push(LatexItem::Expr(var));
962 stack.push(LatexItem::Lit(", "));
963 stack.push(LatexItem::Expr(body));
964 stack.push(LatexItem::Lit(r"\operatorname{Series}\left("));
965 }
966
967 ExprNode::LaplaceTransform(body, _t, _s) => {
969 stack.push(LatexItem::Lit(r"\right\}"));
970 stack.push(LatexItem::Expr(body));
971 stack.push(LatexItem::Lit(r"\mathcal{L}\left\{"));
972 }
973
974 ExprNode::InverseLaplaceTransform(body, _s, _t) => {
976 stack.push(LatexItem::Lit(r"\right\}"));
977 stack.push(LatexItem::Expr(body));
978 stack.push(LatexItem::Lit(r"\mathcal{L}^{-1}\left\{"));
979 }
980
981 ExprNode::Residue(body, var, point) => {
983 stack.push(LatexItem::Expr(body));
984 stack.push(LatexItem::Lit("} "));
985 stack.push(LatexItem::Expr(point));
986 stack.push(LatexItem::Lit("="));
987 stack.push(LatexItem::Expr(var));
988 stack.push(LatexItem::Lit(r"\operatorname{Res}_{"));
989 }
990
991 ExprNode::RootOf(poly, index) => {
993 stack.push(LatexItem::Lit(r"\right)"));
994 stack.push(LatexItem::Expr(index));
995 stack.push(LatexItem::Lit(", "));
996 stack.push(LatexItem::Expr(poly));
997 stack.push(LatexItem::Lit(r"\operatorname{RootOf}\left("));
998 }
999
1000 ExprNode::DSolve(expr, func, var) => {
1002 stack.push(LatexItem::Lit(r"\right)"));
1003 stack.push(LatexItem::Expr(var));
1004 stack.push(LatexItem::Lit(", "));
1005 stack.push(LatexItem::Expr(func));
1006 stack.push(LatexItem::Lit(", "));
1007 stack.push(LatexItem::Expr(expr));
1008 stack.push(LatexItem::Lit(r"\operatorname{DSolve}\left("));
1009 }
1010
1011 ExprNode::RootSum(poly, body, sumvar) => {
1014 stack.push(LatexItem::Lit(r"\right)"));
1015 stack.push(LatexItem::Expr(body));
1016 stack.push(LatexItem::Lit(r" \mapsto "));
1017 stack.push(LatexItem::Expr(sumvar));
1018 stack.push(LatexItem::Lit(r",\, "));
1019 stack.push(LatexItem::Expr(poly));
1020 stack.push(LatexItem::Lit(r"\operatorname{RootSum}\left("));
1021 }
1022
1023 ExprNode::ConditionSet(var, condition) => {
1025 stack.push(LatexItem::Lit(r"\right\}"));
1026 stack.push(LatexItem::Expr(condition));
1027 stack.push(LatexItem::Lit(r" \mid "));
1028 stack.push(LatexItem::Expr(var));
1029 stack.push(LatexItem::Lit(r"\left\{"));
1030 }
1031
1032 ExprNode::Apply(sym_id, ref args) => {
1034 let name = arena.symbol_name(sym_id).to_owned();
1035 let args = args.clone();
1036 if push_special_09_latex(&name, &args, stack) {
1037 return;
1038 }
1039 stack.push(LatexItem::Lit(r"\right)"));
1040 for (i, &arg) in args.iter().enumerate().rev() {
1041 stack.push(LatexItem::Expr(arg));
1042 if i > 0 {
1043 stack.push(LatexItem::Lit(", "));
1044 }
1045 }
1046 stack.push(LatexItem::Owned(format!("{}\\left(", name)));
1047 }
1048
1049 ExprNode::Piecewise(ref pieces) => {
1051 let pieces = pieces.clone();
1052 let mut result = String::from("\\begin{cases}\n");
1053 for (i, &(val, cond)) in pieces.iter().enumerate() {
1054 let val_latex = latex_to_string(arena, val);
1055 let cond_latex = latex_to_string(arena, cond);
1056 result.push_str(&format!(" {} & \\text{{if }} {}", val_latex, cond_latex));
1057 if i + 1 < pieces.len() {
1058 result.push_str(" \\\\\n");
1059 } else {
1060 result.push('\n');
1061 }
1062 }
1063 result.push_str("\\end{cases}");
1064 stack.push(LatexItem::Owned(result));
1065 }
1066
1067 ExprNode::BoolTrue => stack.push(LatexItem::Lit(r"\text{True}")),
1069 ExprNode::BoolFalse => stack.push(LatexItem::Lit(r"\text{False}")),
1070
1071 ExprNode::Gt(a, b) => {
1073 stack.push(LatexItem::Expr(b));
1074 stack.push(LatexItem::Lit(" > "));
1075 stack.push(LatexItem::Expr(a));
1076 }
1077 ExprNode::Ge(a, b) => {
1078 stack.push(LatexItem::Expr(b));
1079 stack.push(LatexItem::Lit(r" \geq "));
1080 stack.push(LatexItem::Expr(a));
1081 }
1082 ExprNode::Eq_(a, b) => {
1083 stack.push(LatexItem::Expr(b));
1084 stack.push(LatexItem::Lit(" = "));
1085 stack.push(LatexItem::Expr(a));
1086 }
1087 ExprNode::Ne(a, b) => {
1088 stack.push(LatexItem::Expr(b));
1089 stack.push(LatexItem::Lit(r" \neq "));
1090 stack.push(LatexItem::Expr(a));
1091 }
1092
1093 ExprNode::And(ref children) => {
1095 let children = children.clone();
1096 for (i, &child) in children.iter().enumerate().rev() {
1097 stack.push(LatexItem::Expr(child));
1098 if i > 0 {
1099 stack.push(LatexItem::Lit(r" \land "));
1100 }
1101 }
1102 }
1103 ExprNode::Or(ref children) => {
1104 let children = children.clone();
1105 for (i, &child) in children.iter().enumerate().rev() {
1106 stack.push(LatexItem::Expr(child));
1107 if i > 0 {
1108 stack.push(LatexItem::Lit(r" \lor "));
1109 }
1110 }
1111 }
1112 ExprNode::Not(x) => {
1113 stack.push(LatexItem::Expr(x));
1114 stack.push(LatexItem::Lit(r"\lnot "));
1115 }
1116
1117 ExprNode::EmptySet => stack.push(LatexItem::Lit(r"\emptyset")),
1119 ExprNode::UniversalSet => stack.push(LatexItem::Lit(r"\mathbb{R}")),
1120
1121 ExprNode::Interval(a, b, flags) => {
1123 let left = if flags & crate::base::node::INTERVAL_LEFT_OPEN != 0 {
1124 "("
1125 } else {
1126 "["
1127 };
1128 let right = if flags & crate::base::node::INTERVAL_RIGHT_OPEN != 0 {
1129 ")"
1130 } else {
1131 "]"
1132 };
1133 let a_latex = latex_to_string(arena, a);
1134 let b_latex = latex_to_string(arena, b);
1135 stack.push(LatexItem::Owned(format!(
1136 "{}{}, {}{}",
1137 left, a_latex, b_latex, right
1138 )));
1139 }
1140
1141 ExprNode::FiniteSet(ref elems) => {
1142 let elems = elems.clone();
1143 stack.push(LatexItem::Lit("\\}"));
1144 for (i, &elem) in elems.iter().enumerate().rev() {
1145 stack.push(LatexItem::Expr(elem));
1146 if i > 0 {
1147 stack.push(LatexItem::Lit(", "));
1148 }
1149 }
1150 stack.push(LatexItem::Lit("\\{"));
1151 }
1152
1153 ExprNode::SetUnion(ref sets) => {
1154 let sets = sets.clone();
1155 for (i, &set) in sets.iter().enumerate().rev() {
1156 stack.push(LatexItem::Expr(set));
1157 if i > 0 {
1158 stack.push(LatexItem::Lit(r" \cup "));
1159 }
1160 }
1161 }
1162
1163 ExprNode::SetIntersection(ref sets) => {
1164 let sets = sets.clone();
1165 for (i, &set) in sets.iter().enumerate().rev() {
1166 stack.push(LatexItem::Expr(set));
1167 if i > 0 {
1168 stack.push(LatexItem::Lit(r" \cap "));
1169 }
1170 }
1171 }
1172
1173 ExprNode::SetComplement(a, b) => {
1174 stack.push(LatexItem::Expr(b));
1175 stack.push(LatexItem::Lit(r" \setminus "));
1176 stack.push(LatexItem::Expr(a));
1177 }
1178 }
1179}
1180
1181impl<S: Sort> Expr<S> {
1186 pub fn to_latex(&self) -> String {
1201 let inner = self.inner.read();
1202 let w = LatexWriter {
1203 arena: &inner.arena,
1204 id: self.raw_id(),
1205 };
1206 format!("{}", w)
1207 }
1208
1209 pub fn to_latex_inline(&self) -> String {
1221 format!("${}$", self.to_latex())
1222 }
1223
1224 pub fn to_latex_display(&self) -> String {
1236 format!("$${}$$", self.to_latex())
1237 }
1238}
1239
1240#[cfg(test)]
1245mod tests {
1246 use crate::prelude::*;
1247
1248 #[test]
1251 fn latex_integer() {
1252 assert_eq!(crate::api::context::Context::new().int(42).to_latex(), "42");
1253 }
1254
1255 #[test]
1256 fn latex_zero() {
1257 assert_eq!(crate::api::context::Context::new().int(0).to_latex(), "0");
1258 }
1259
1260 #[test]
1261 fn latex_negative_integer() {
1262 let neg = crate::api::context::Context::new().int(-3);
1263 let latex = neg.to_latex();
1264 assert!(latex == "-3", "got: {latex}");
1265 }
1266
1267 #[test]
1268 fn latex_fraction() {
1269 let half = crate::api::context::Context::new().rational(1, 2);
1270 assert_eq!(half.to_latex(), r"\frac{1}{2}");
1271 }
1272
1273 #[test]
1274 fn latex_negative_fraction() {
1275 let neg_frac = crate::api::context::Context::new().rational(-5, 7);
1276 let latex = neg_frac.to_latex();
1277 assert!(
1278 latex == r"-\frac{5}{7}" || latex == r"\frac{-5}{7}",
1279 "got: {latex}"
1280 );
1281 }
1282
1283 #[test]
1286 fn latex_symbol() {
1287 assert_eq!(
1288 crate::api::context::Context::new().symbol("x").to_latex(),
1289 "x"
1290 );
1291 }
1292
1293 #[test]
1294 fn latex_symbol_multichar() {
1295 assert_eq!(
1296 crate::api::context::Context::new().symbol("foo").to_latex(),
1297 "foo"
1298 );
1299 }
1300
1301 #[test]
1302 fn latex_greek_theta() {
1303 assert_eq!(
1304 crate::api::context::Context::new()
1305 .symbol("theta")
1306 .to_latex(),
1307 r"\theta"
1308 );
1309 }
1310
1311 #[test]
1312 fn latex_greek_alpha() {
1313 assert_eq!(
1314 crate::api::context::Context::new()
1315 .symbol("alpha")
1316 .to_latex(),
1317 r"\alpha"
1318 );
1319 }
1320
1321 #[test]
1322 fn latex_greek_omega() {
1323 assert_eq!(
1324 crate::api::context::Context::new()
1325 .symbol("omega")
1326 .to_latex(),
1327 r"\omega"
1328 );
1329 }
1330
1331 #[test]
1332 fn latex_greek_lambda() {
1333 assert_eq!(
1334 crate::api::context::Context::new()
1335 .symbol("lambda")
1336 .to_latex(),
1337 r"\lambda"
1338 );
1339 }
1340
1341 #[test]
1342 fn latex_symbol_subscript() {
1343 let x1 = crate::api::context::Context::new().symbol("x_1");
1344 assert_eq!(x1.to_latex(), "x_{1}");
1345 }
1346
1347 #[test]
1350 fn latex_pi() {
1351 assert_eq!(crate::api::context::Context::new().pi().to_latex(), r"\pi");
1352 }
1353
1354 #[test]
1355 fn latex_e() {
1356 assert_eq!(crate::api::context::Context::new().e().to_latex(), "e");
1357 }
1358
1359 #[test]
1360 fn latex_imaginary() {
1361 assert_eq!(crate::api::context::Context::new().i_unit().to_latex(), "i");
1362 }
1363
1364 #[test]
1365 fn latex_infinity() {
1366 assert_eq!(
1367 crate::api::context::Context::new().infinity().to_latex(),
1368 r"\infty"
1369 );
1370 }
1371
1372 #[test]
1373 fn latex_neg_infinity() {
1374 assert_eq!(
1375 crate::api::context::Context::new()
1376 .neg_infinity()
1377 .to_latex(),
1378 r"-\infty"
1379 );
1380 }
1381
1382 #[test]
1385 fn latex_power() {
1386 let x = crate::api::context::Context::new().symbol("x");
1387 let expr = x.powi(2);
1388 assert_eq!(expr.to_latex(), r"x^{2}");
1389 }
1390
1391 #[test]
1392 fn latex_power_cube() {
1393 let x = crate::api::context::Context::new().symbol("x");
1394 let expr = x.powi(3);
1395 assert_eq!(expr.to_latex(), r"x^{3}");
1396 }
1397
1398 #[test]
1399 fn latex_sqrt() {
1400 let x = crate::api::context::Context::new().symbol("x");
1401 let expr = x.sqrt();
1402 assert_eq!(expr.to_latex(), r"\sqrt{x}");
1403 }
1404
1405 #[test]
1406 fn latex_cbrt() {
1407 let x = crate::api::context::Context::new().symbol("x");
1408 let expr = x.cbrt();
1409 assert_eq!(expr.to_latex(), r"\sqrt[3]{x}");
1410 }
1411
1412 #[test]
1413 fn latex_inverse() {
1414 let x = crate::api::context::Context::new().symbol("x");
1415 let expr = x.powi(-1);
1416 let latex = expr.to_latex();
1417 assert_eq!(latex, r"\frac{1}{x}");
1418 }
1419
1420 #[test]
1423 fn latex_neg_symbol() {
1424 let x = crate::api::context::Context::new().symbol("x");
1425 let expr = -&x;
1426 assert_eq!(expr.to_latex(), "-x");
1427 }
1428
1429 #[test]
1432 fn latex_add_simple() {
1433 let x = crate::api::context::Context::new().symbol("x");
1434 let expr = &x + 1;
1435 let latex = expr.to_latex();
1436 assert!(latex == "x + 1" || latex == "1 + x", "got: {latex}");
1437 }
1438
1439 #[test]
1440 fn latex_add_power_and_const() {
1441 let x = crate::api::context::Context::new().symbol("x");
1442 let expr = x.powi(2) + 1;
1443 let latex = expr.to_latex();
1444 assert!(
1445 latex == r"x^{2} + 1" || latex == r"1 + x^{2}",
1446 "got: {latex}"
1447 );
1448 }
1449
1450 #[test]
1453 fn latex_mul_coefficient() {
1454 let x = crate::api::context::Context::new().symbol("x");
1455 let expr = &x * 2;
1456 let latex = expr.to_latex();
1457 assert!(
1458 latex == "2 x" || latex == "x \\cdot 2" || latex == "2x",
1459 "got: {latex}"
1460 );
1461 }
1462
1463 #[test]
1466 fn latex_sin() {
1467 let x = crate::api::context::Context::new().symbol("x");
1468 let expr = x.sin();
1469 assert_eq!(expr.to_latex(), r"\sin\left(x\right)");
1470 }
1471
1472 #[test]
1473 fn latex_cos() {
1474 let x = crate::api::context::Context::new().symbol("x");
1475 let expr = x.cos();
1476 assert_eq!(expr.to_latex(), r"\cos\left(x\right)");
1477 }
1478
1479 #[test]
1480 fn latex_tan() {
1481 let x = crate::api::context::Context::new().symbol("x");
1482 let expr = x.tan();
1483 assert_eq!(expr.to_latex(), r"\tan\left(x\right)");
1484 }
1485
1486 #[test]
1487 fn latex_sinh() {
1488 let x = crate::api::context::Context::new().symbol("x");
1489 let expr = x.sinh();
1490 assert_eq!(expr.to_latex(), r"\sinh\left(x\right)");
1491 }
1492
1493 #[test]
1494 fn latex_cosh() {
1495 let x = crate::api::context::Context::new().symbol("x");
1496 let expr = x.cosh();
1497 assert_eq!(expr.to_latex(), r"\cosh\left(x\right)");
1498 }
1499
1500 #[test]
1503 fn latex_exp() {
1504 let x = crate::api::context::Context::new().symbol("x");
1505 let expr = x.exp();
1506 assert_eq!(expr.to_latex(), r"\exp\left(x\right)");
1507 }
1508
1509 #[test]
1510 fn latex_ln() {
1511 let x = crate::api::context::Context::new().symbol("x");
1512 let expr = x.ln();
1513 assert_eq!(expr.to_latex(), r"\ln\left(x\right)");
1514 }
1515
1516 #[test]
1519 fn latex_abs() {
1520 let x = crate::api::context::Context::new().symbol("x");
1521 let expr = x.abs();
1522 assert_eq!(expr.to_latex(), r"\left|x\right|");
1523 }
1524
1525 #[test]
1528 fn latex_asin() {
1529 let x = crate::api::context::Context::new().symbol("x");
1530 let expr = x.asin();
1531 assert_eq!(expr.to_latex(), r"\arcsin\left(x\right)");
1532 }
1533
1534 #[test]
1535 fn latex_acos() {
1536 let x = crate::api::context::Context::new().symbol("x");
1537 let expr = x.acos();
1538 assert_eq!(expr.to_latex(), r"\arccos\left(x\right)");
1539 }
1540
1541 #[test]
1542 fn latex_atan() {
1543 let x = crate::api::context::Context::new().symbol("x");
1544 let expr = x.atan();
1545 assert_eq!(expr.to_latex(), r"\arctan\left(x\right)");
1546 }
1547
1548 #[test]
1551 fn latex_derivative() {
1552 let x = crate::api::context::Context::new().symbol("x");
1553 let expr = x.powi(2).formal_diff(&x);
1554 let latex = expr.to_latex();
1555 assert_eq!(latex, r"\frac{d}{dx} x^{2}");
1556 }
1557
1558 #[test]
1561 fn latex_integral() {
1562 let x = crate::api::context::Context::new().symbol("x");
1563 let expr = x.sin().sin();
1564 let integral = expr.integrate(&x);
1565 let latex = integral.to_latex();
1566 if integral.expr_type() == crate::api::expr::ExprType::Integral {
1567 assert!(latex.starts_with(r"\int"), "got: {latex}");
1568 assert!(latex.ends_with(r"\, dx"), "got: {latex}");
1569 }
1570 assert!(!latex.is_empty());
1571 }
1572
1573 #[test]
1574 fn latex_definite_integral() {
1575 let ctx = crate::api::context::Context::new();
1576 let x = ctx.symbol("x");
1577 let node = x.sin().definite_integral_node(&x, &ctx.int(0), &ctx.pi());
1578 assert_eq!(node.to_latex(), r"\int_{0}^{\pi} \sin\left(x\right)\, dx");
1579 }
1580
1581 #[test]
1584 fn latex_sin_squared() {
1585 let x = crate::api::context::Context::new().symbol("x");
1586 let expr = x.sin().powi(2);
1587 let latex = expr.to_latex();
1588 assert_eq!(latex, r"\sin^{2}\left(x\right)");
1589 }
1590
1591 #[test]
1592 fn latex_nested_function() {
1593 let x = crate::api::context::Context::new().symbol("x");
1594 let expr = x.sin().exp();
1595 let latex = expr.to_latex();
1596 assert_eq!(latex, r"\exp\left(\sin\left(x\right)\right)");
1597 }
1598
1599 #[test]
1602 fn latex_inline_delimiters() {
1603 let x = crate::api::context::Context::new().symbol("x");
1604 assert_eq!(x.to_latex_inline(), "$x$");
1605 }
1606
1607 #[test]
1608 fn latex_display_delimiters() {
1609 let x = crate::api::context::Context::new().symbol("x");
1610 assert_eq!(x.to_latex_display(), "$$x$$");
1611 }
1612
1613 #[test]
1616 fn latex_relational_gt() {
1617 let ctx = crate::api::context::Context::new();
1618 let x = ctx.symbol("x");
1619 let y = ctx.symbol("y");
1620 let expr = x.gt(&y);
1621 assert_eq!(expr.to_latex(), "x > y");
1622 }
1623
1624 #[test]
1625 fn latex_relational_eq() {
1626 let ctx = crate::api::context::Context::new();
1627 let x = ctx.symbol("x");
1628 let expr = x.eq_expr(&ctx.int(0));
1629 assert_eq!(expr.to_latex(), "x = 0");
1630 }
1631
1632 #[test]
1635 fn latex_deep_expression_no_stack_overflow() {
1636 let x = crate::api::context::Context::new().symbol("x");
1637 let mut expr = x.clone();
1638 for _ in 0..1000 {
1639 expr = expr.sin();
1640 }
1641 let latex = expr.to_latex();
1642 assert!(latex.starts_with(r"\sin\left("));
1643 assert!(latex.ends_with(r"\right)"));
1644 }
1645
1646 #[test]
1649 fn latex_add_with_neg_term() {
1650 let ctx = crate::api::context::Context::new();
1651 let x = ctx.symbol("x");
1652 let y = ctx.symbol("y");
1653 let expr = &x - &y;
1654 let latex = expr.to_latex();
1655 assert!(latex == "x - y" || latex == "-y + x", "got: {latex}");
1656 }
1657
1658 #[test]
1661 fn latex_binomial() {
1662 let ctx = Context::new();
1663 let n = ctx.symbol("n");
1664 let k = ctx.symbol("k");
1665 let expr = n.binomial(&k);
1666 let latex = expr.to_latex();
1667 assert_eq!(latex, r"\binom{n}{k}");
1668 }
1669
1670 #[test]
1673 fn latex_factorial() {
1674 let ctx = Context::new();
1675 let n = ctx.symbol("n");
1676 let expr = n.factorial();
1677 let latex = expr.to_latex();
1678 assert_eq!(latex, "n!");
1679 }
1680
1681 #[test]
1684 fn latex_coefficient_no_space() {
1685 let x = crate::api::context::Context::new().symbol("x");
1686 let expr = &x * 3; let latex = expr.to_latex();
1688 assert!(
1690 !latex.contains(r"\cdot"),
1691 "coefficient times variable shouldn't use cdot: {latex}"
1692 );
1693 }
1694
1695 #[test]
1698 fn latex_named_constants() {
1699 let ctx = crate::api::context::Context::new();
1700 assert_eq!(ctx.euler_gamma().to_latex(), r"\gamma");
1701 assert_eq!(ctx.catalan().to_latex(), "G");
1702 assert_eq!(ctx.golden_ratio().to_latex(), r"\phi");
1703 }
1704
1705 #[test]
1706 fn latex_complex_nodes() {
1707 let ctx = crate::api::context::Context::new();
1708 let z = ctx.symbol("z");
1709 assert_eq!(z.re().to_latex(), r"\Re\left(z\right)");
1710 assert_eq!(z.im().to_latex(), r"\Im\left(z\right)");
1711 assert_eq!(z.conjugate().to_latex(), r"\overline{z}");
1712 assert_eq!(z.arg().to_latex(), r"\arg\left(z\right)");
1713 let sum = &z + 1;
1714 assert_eq!(sum.conjugate().to_latex(), r"\overline{z} + 1");
1715 }
1716
1717 #[test]
1718 fn latex_special_functions() {
1719 let ctx = crate::api::context::Context::new();
1720 let x = ctx.symbol("x");
1721 let n = ctx.symbol("n");
1722 assert_eq!(x.si().to_latex(), r"\operatorname{Si}\left(x\right)");
1723 assert_eq!(x.ci().to_latex(), r"\operatorname{Ci}\left(x\right)");
1724 assert_eq!(x.ei().to_latex(), r"\operatorname{Ei}\left(x\right)");
1725 assert_eq!(x.li().to_latex(), r"\operatorname{li}\left(x\right)");
1726 assert_eq!(x.zeta().to_latex(), r"\zeta\left(x\right)");
1727 assert_eq!(x.polygamma(&n).to_latex(), r"\psi^{(n)}\left(x\right)");
1728 assert_eq!(x.kronecker_delta(&n).to_latex(), r"\delta_{n x}");
1729 }
1730}