use std::fmt::{self, Display, Write};
use colored::Colorize;
use crate::{
atom::{
representation::FunView, AddView, Atom, AtomView, MulView, NumView, PowView, Symbol,
VarView,
},
coefficient::CoefficientView,
domains::{
factorized_rational_polynomial::FactorizedRationalPolynomial,
finite_field::FiniteFieldCore, rational_polynomial::RationalPolynomial, Ring, RingPrinter,
},
poly::{polynomial::MultivariatePolynomial, Exponent, MonomialOrder},
state::State,
tensors::matrix::Matrix,
};
#[derive(Debug, Copy, Clone)]
pub struct PrintOptions {
pub terms_on_new_line: bool,
pub color_top_level_sum: bool,
pub color_builtin_symbols: bool,
pub print_finite_field: bool,
pub symmetric_representation_for_finite_field: bool,
pub explicit_rational_polynomial: bool,
pub number_thousands_separator: Option<char>,
pub multiplication_operator: char,
pub double_star_for_exponentiation: bool,
pub square_brackets_for_function: bool,
pub num_exp_as_superscript: bool,
pub latex: bool,
}
impl PrintOptions {
pub const fn mathematica() -> PrintOptions {
Self {
terms_on_new_line: false,
color_top_level_sum: false,
color_builtin_symbols: false,
print_finite_field: true,
symmetric_representation_for_finite_field: false,
explicit_rational_polynomial: false,
number_thousands_separator: None,
multiplication_operator: ' ',
double_star_for_exponentiation: false,
square_brackets_for_function: true,
num_exp_as_superscript: false,
latex: false,
}
}
pub const fn latex() -> PrintOptions {
Self {
terms_on_new_line: false,
color_top_level_sum: false,
color_builtin_symbols: false,
print_finite_field: true,
symmetric_representation_for_finite_field: false,
explicit_rational_polynomial: false,
number_thousands_separator: None,
multiplication_operator: ' ',
double_star_for_exponentiation: false,
square_brackets_for_function: false,
num_exp_as_superscript: false,
latex: true,
}
}
pub const fn file() -> PrintOptions {
Self {
terms_on_new_line: false,
color_top_level_sum: false,
color_builtin_symbols: false,
print_finite_field: false,
symmetric_representation_for_finite_field: false,
explicit_rational_polynomial: false,
number_thousands_separator: None,
multiplication_operator: '*',
double_star_for_exponentiation: false,
square_brackets_for_function: false,
num_exp_as_superscript: false,
latex: false,
}
}
pub const fn sympy() -> PrintOptions {
Self {
double_star_for_exponentiation: true,
..Self::file()
}
}
}
impl Default for PrintOptions {
fn default() -> Self {
Self {
terms_on_new_line: false,
color_top_level_sum: true,
color_builtin_symbols: true,
print_finite_field: true,
symmetric_representation_for_finite_field: false,
explicit_rational_polynomial: false,
number_thousands_separator: None,
multiplication_operator: '*',
double_star_for_exponentiation: false,
square_brackets_for_function: false,
num_exp_as_superscript: false,
latex: false,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct PrintState {
pub level: usize,
pub top_level_add_child: bool,
pub explicit_sign: bool,
pub superscript: bool,
}
macro_rules! define_formatters {
($($a:ident),*) => {
$(
trait $a {
fn fmt_debug(
&self,
f: &mut fmt::Formatter,
) -> fmt::Result;
fn fmt_output(
&self,
f: &mut fmt::Formatter,
print_opts: &PrintOptions,
print_state: PrintState,
) -> fmt::Result;
})+
};
}
define_formatters!(
FormattedPrintVar,
FormattedPrintNum,
FormattedPrintFn,
FormattedPrintPow,
FormattedPrintMul,
FormattedPrintAdd
);
pub struct AtomPrinter<'a> {
pub atom: AtomView<'a>,
pub print_opts: PrintOptions,
}
impl<'a> AtomPrinter<'a> {
pub fn new(atom: AtomView<'a>) -> AtomPrinter<'a> {
AtomPrinter {
atom,
print_opts: PrintOptions::default(),
}
}
pub fn new_with_options(atom: AtomView<'a>, print_opts: PrintOptions) -> AtomPrinter<'a> {
AtomPrinter { atom, print_opts }
}
}
impl<'a> fmt::Display for AtomPrinter<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let print_state = PrintState {
level: 0,
top_level_add_child: false,
explicit_sign: false,
superscript: false,
};
self.atom.fmt_output(f, &self.print_opts, print_state)
}
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(State::get_name(*self))
}
}
impl Atom {
pub fn printer<'a>(&'a self, opts: PrintOptions) -> AtomPrinter<'a> {
AtomPrinter::new_with_options(self.as_view(), opts)
}
pub fn to_canonical_string(&self) -> String {
self.as_view().to_canonical_string()
}
}
impl<'a> AtomView<'a> {
fn fmt_debug(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
AtomView::Num(n) => n.fmt_debug(fmt),
AtomView::Var(v) => v.fmt_debug(fmt),
AtomView::Fun(f) => f.fmt_debug(fmt),
AtomView::Pow(p) => p.fmt_debug(fmt),
AtomView::Mul(m) => m.fmt_debug(fmt),
AtomView::Add(a) => a.fmt_debug(fmt),
}
}
fn fmt_output(
&self,
fmt: &mut fmt::Formatter,
opts: &PrintOptions,
print_state: PrintState,
) -> fmt::Result {
match self {
AtomView::Num(n) => n.fmt_output(fmt, opts, print_state),
AtomView::Var(v) => v.fmt_output(fmt, opts, print_state),
AtomView::Fun(f) => f.fmt_output(fmt, opts, print_state),
AtomView::Pow(p) => p.fmt_output(fmt, opts, print_state),
AtomView::Mul(t) => t.fmt_output(fmt, opts, print_state),
AtomView::Add(e) => e.fmt_output(fmt, opts, print_state),
}
}
pub fn printer(&self, opts: PrintOptions) -> AtomPrinter {
AtomPrinter::new_with_options(*self, opts)
}
pub fn to_canonical_string(&self) -> String {
let mut s = String::new();
self.to_canonical_view_impl(&mut s);
s
}
fn to_canonical_view_impl(&self, out: &mut String) {
fn add_paren(cur: AtomView, s: AtomView) -> bool {
if let AtomView::Pow(_) = cur {
matches!(s, AtomView::Add(_) | AtomView::Mul(_))
} else if let AtomView::Mul(_) = cur {
matches!(s, AtomView::Add(_))
} else {
false
}
}
match self {
AtomView::Num(_) => write!(out, "{}", self).unwrap(),
AtomView::Var(v) => write!(out, "{}", v.get_symbol()).unwrap(),
AtomView::Fun(f) => {
write!(out, "{}(", f.get_symbol()).unwrap();
let mut args = vec![];
for x in f.iter() {
let mut arg = String::new();
x.to_canonical_view_impl(&mut arg);
args.push(arg);
}
if f.is_symmetric() {
args.sort();
}
if f.is_antisymmetric() {
unimplemented!(
"Antisymmetric functions are not supported yet for canonical view"
);
}
for (i, arg) in args.iter().enumerate() {
if i > 0 {
write!(out, ",").unwrap();
}
write!(out, "{}", arg).unwrap();
}
write!(out, ")").unwrap();
}
AtomView::Pow(p) => {
let (b, e) = p.get_base_exp();
if add_paren(*self, b) {
write!(out, "(").unwrap();
b.to_canonical_view_impl(out);
write!(out, ")").unwrap();
} else {
b.to_canonical_view_impl(out);
}
if add_paren(*self, e) {
write!(out, "^(").unwrap();
e.to_canonical_view_impl(out);
write!(out, ")").unwrap();
} else {
write!(out, "^").unwrap();
e.to_canonical_view_impl(out);
}
}
AtomView::Mul(m) => {
let mut terms = vec![];
for x in m.iter() {
let mut term = if add_paren(*self, x) {
"(".to_string()
} else {
String::new()
};
x.to_canonical_view_impl(&mut term);
if add_paren(*self, x) {
term.push(')');
}
terms.push(term);
}
terms.sort();
for (i, term) in terms.iter().enumerate() {
if i > 0 {
write!(out, "*").unwrap();
}
write!(out, "{}", term).unwrap();
}
}
AtomView::Add(a) => {
let mut terms = vec![];
for x in a.iter() {
let mut term = if add_paren(*self, x) {
"(".to_string()
} else {
String::new()
};
x.to_canonical_view_impl(&mut term);
if add_paren(*self, x) {
term.push(')');
}
terms.push(term);
}
terms.sort();
for (i, term) in terms.iter().enumerate() {
if i > 0 {
write!(out, "+").unwrap();
}
write!(out, "{}", term).unwrap();
}
}
}
}
}
impl<'a> fmt::Debug for AtomView<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.fmt_debug(fmt)
}
}
impl<'a> FormattedPrintVar for VarView<'a> {
fn fmt_output(
&self,
f: &mut fmt::Formatter,
opts: &PrintOptions,
print_state: PrintState,
) -> fmt::Result {
if print_state.explicit_sign {
if print_state.top_level_add_child && opts.color_top_level_sum {
f.write_fmt(format_args!("{}", "+".yellow()))?;
} else {
f.write_char('+')?;
}
}
let id = self.get_symbol();
let name = State::get_name(id);
if opts.latex {
match id {
State::E => f.write_char('e'),
State::PI => f.write_str("\\pi"),
State::I => f.write_char('i'),
_ => f.write_str(name),
}
} else if opts.color_builtin_symbols && name.ends_with('_') {
f.write_fmt(format_args!("{}", name.cyan().italic()))
} else if opts.color_builtin_symbols && State::is_builtin(id) {
f.write_fmt(format_args!("{}", name.purple()))
} else {
f.write_str(name)
}
}
fn fmt_debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as std::fmt::Debug>::fmt(self, f)
}
}
impl<'a> FormattedPrintNum for NumView<'a> {
fn fmt_debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as std::fmt::Debug>::fmt(self, f)
}
fn fmt_output(
&self,
f: &mut fmt::Formatter,
opts: &PrintOptions,
print_state: PrintState,
) -> fmt::Result {
fn format_num(
mut s: String,
opts: &PrintOptions,
print_state: &PrintState,
f: &mut fmt::Formatter,
) -> fmt::Result {
if print_state.superscript {
let map = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
s = s
.as_bytes()
.iter()
.map(|x| map[(x - b'0') as usize])
.collect();
return f.write_str(&s);
}
if let Some(c) = opts.number_thousands_separator {
let mut first = true;
for triplet in s.as_bytes().chunks(3) {
if !first {
f.write_char(c)?;
}
f.write_str(std::str::from_utf8(triplet).unwrap())?;
first = false;
}
Ok(())
} else {
f.write_str(&s)
}
}
let d = self.get_coeff_view();
let is_negative = match d {
CoefficientView::Natural(n, _) => n < 0,
CoefficientView::Large(r) => r.is_negative(),
_ => false,
};
if is_negative {
if print_state.top_level_add_child && opts.color_top_level_sum {
f.write_fmt(format_args!("{}", "-".yellow()))?;
} else if print_state.superscript {
f.write_char('⁻')?;
} else {
f.write_char('-')?;
}
} else if print_state.explicit_sign {
if print_state.top_level_add_child && opts.color_top_level_sum {
f.write_fmt(format_args!("{}", "+".yellow()))?;
} else {
f.write_char('+')?;
}
}
match d {
CoefficientView::Natural(num, den) => {
if !opts.latex
&& (opts.number_thousands_separator.is_some() || print_state.superscript)
{
format_num(num.unsigned_abs().to_string(), opts, &print_state, f)?;
if den != 1 {
f.write_char('/')?;
format_num(den.to_string(), opts, &print_state, f)?;
}
Ok(())
} else if den != 1 {
if opts.latex {
f.write_fmt(format_args!("\\frac{{{}}}{{{}}}", num.unsigned_abs(), den))
} else {
f.write_fmt(format_args!("{}/{}", num.unsigned_abs(), den))
}
} else {
f.write_fmt(format_args!("{}", num.unsigned_abs()))
}
}
CoefficientView::Float(fl) => {
let float = fl.to_float();
f.write_fmt(format_args!("{}", float))
}
CoefficientView::Large(r) => {
let rat = r.to_rat().abs();
if !opts.latex
&& (opts.number_thousands_separator.is_some() || print_state.superscript)
{
format_num(rat.numerator().to_string(), opts, &print_state, f)?;
if !rat.is_integer() {
f.write_char('/')?;
format_num(rat.denominator().to_string(), opts, &print_state, f)?;
}
Ok(())
} else if !rat.is_integer() {
if opts.latex {
f.write_fmt(format_args!(
"\\frac{{{}}}{{{}}}",
rat.numerator(),
rat.denominator(),
))
} else {
f.write_fmt(format_args!(
"{}/{}",
rat.numerator_ref(),
rat.denominator_ref()
))
}
} else {
f.write_fmt(format_args!("{}", rat.numerator_ref()))
}
}
CoefficientView::FiniteField(num, fi) => {
let ff = State::get_finite_field(fi);
f.write_fmt(format_args!(
"[{}%{}]",
ff.from_element(&num),
ff.get_prime()
))
}
CoefficientView::RationalPolynomial(p) => f.write_fmt(format_args!(
"[{}]",
RationalPolynomialPrinter {
poly: &p.deserialize(),
opts: *opts,
add_parentheses: false,
}
)),
}
}
}
impl<'a> FormattedPrintMul for MulView<'a> {
fn fmt_debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as std::fmt::Debug>::fmt(self, f)
}
fn fmt_output(
&self,
f: &mut fmt::Formatter,
opts: &PrintOptions,
mut print_state: PrintState,
) -> fmt::Result {
let mut first = true;
let mut skip_num = false;
if let Some(AtomView::Num(n)) = self.iter().last() {
if n.get_coeff_view() == CoefficientView::Natural(-1, 1) {
if print_state.top_level_add_child && opts.color_top_level_sum {
f.write_fmt(format_args!("{}", "-".yellow()))?;
} else {
f.write_char('-')?;
}
first = true;
} else {
n.fmt_output(f, opts, print_state)?;
first = false;
}
skip_num = true;
} else if print_state.explicit_sign {
if print_state.top_level_add_child && opts.color_top_level_sum {
f.write_fmt(format_args!("{}", "+".yellow()))?;
} else {
f.write_char('+')?;
}
}
print_state.top_level_add_child = false;
print_state.level += 1;
print_state.explicit_sign = false;
for x in self.iter().take(if skip_num {
self.get_nargs() - 1
} else {
self.get_nargs()
}) {
if !first {
if opts.latex {
f.write_char(' ')?;
} else {
f.write_char(opts.multiplication_operator)?;
}
}
first = false;
if let AtomView::Add(_) = x {
if opts.latex {
f.write_str("\\left(")?;
} else {
f.write_char('(')?;
}
x.fmt_output(f, opts, print_state)?;
if opts.latex {
f.write_str("\\right)")?;
} else {
f.write_char(')')?;
}
} else {
x.fmt_output(f, opts, print_state)?;
}
}
Ok(())
}
}
impl<'a> FormattedPrintFn for FunView<'a> {
fn fmt_output(
&self,
f: &mut fmt::Formatter,
opts: &PrintOptions,
mut print_state: PrintState,
) -> fmt::Result {
if print_state.explicit_sign {
if print_state.top_level_add_child && opts.color_top_level_sum {
f.write_fmt(format_args!("{}", "+".yellow()))?;
} else {
f.write_char('+')?;
}
}
let id = self.get_symbol();
let name = State::get_name(id);
if opts.latex {
if name == "cos" || name == "sin" || name == "exp" || name == "log" {
f.write_fmt(format_args!("\\{}\\!\\left(", name))?;
} else {
f.write_fmt(format_args!("{}\\!\\left(", name))?;
}
} else {
if opts.color_builtin_symbols && name.ends_with('_') {
f.write_fmt(format_args!("{}", name.cyan().italic()))?;
} else if opts.color_builtin_symbols && State::is_builtin(id) {
f.write_fmt(format_args!("{}", name.purple()))?;
} else {
f.write_str(name)?;
}
if opts.square_brackets_for_function {
f.write_char('[')?;
} else {
f.write_char('(')?;
}
}
print_state.top_level_add_child = false;
print_state.level += 1;
print_state.explicit_sign = false;
let mut first = true;
for x in self.iter() {
if !first {
f.write_char(',')?;
}
first = false;
x.fmt_output(f, opts, print_state)?;
}
if opts.latex {
f.write_str("\\right)")
} else if opts.square_brackets_for_function {
f.write_char(']')
} else {
f.write_char(')')
}
}
fn fmt_debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as std::fmt::Debug>::fmt(self, f)
}
}
impl<'a> FormattedPrintPow for PowView<'a> {
fn fmt_output(
&self,
f: &mut fmt::Formatter,
opts: &PrintOptions,
mut print_state: PrintState,
) -> fmt::Result {
if print_state.explicit_sign {
if print_state.top_level_add_child && opts.color_top_level_sum {
f.write_fmt(format_args!("{}", "+".yellow()))?;
} else {
f.write_char('+')?;
}
}
let b = self.get_base();
let e = self.get_exp();
print_state.top_level_add_child = false;
print_state.level += 1;
print_state.explicit_sign = false;
let mut superscript_exponent = false;
if opts.latex {
if let AtomView::Num(n) = e {
if n.get_coeff_view() == CoefficientView::Natural(-1, 1) {
f.write_str("\\frac{1}{")?;
b.fmt_output(f, opts, print_state)?;
return f.write_char('}');
}
}
} else if opts.num_exp_as_superscript {
if let AtomView::Num(n) = e {
superscript_exponent = n.get_coeff_view().is_integer()
}
}
let base_needs_parentheses =
matches!(b, AtomView::Add(_) | AtomView::Mul(_) | AtomView::Pow(_))
|| if let AtomView::Num(n) = b {
match n.get_coeff_view() {
CoefficientView::Natural(n, d) => n < 0 || d != 1,
CoefficientView::Float(_) => true, CoefficientView::Large(r) => r.is_negative() || !r.to_rat().is_integer(),
CoefficientView::FiniteField(n, i) => {
opts.symmetric_representation_for_finite_field
&& n.0 * 2 > State::get_finite_field(i).get_prime()
}
CoefficientView::RationalPolynomial(_) => true,
}
} else {
false
};
if base_needs_parentheses {
if opts.latex {
f.write_str("\\left(")?;
} else {
f.write_char('(')?;
}
b.fmt_output(f, opts, print_state)?;
if opts.latex {
f.write_str("\\right)")?;
} else {
f.write_char(')')?;
}
} else {
b.fmt_output(f, opts, print_state)?;
}
if !superscript_exponent {
if !opts.latex && opts.double_star_for_exponentiation {
f.write_str("**")?;
} else {
f.write_char('^')?;
}
}
if opts.latex {
f.write_char('{')?;
e.fmt_output(f, opts, print_state)?;
f.write_char('}')
} else {
let exp_needs_parentheses = matches!(e, AtomView::Add(_) | AtomView::Mul(_))
|| if let AtomView::Num(n) = e {
!n.get_coeff_view().is_integer()
} else {
false
};
if exp_needs_parentheses {
f.write_char('(')?;
e.fmt_output(f, opts, print_state)?;
f.write_char(')')
} else {
print_state.superscript = superscript_exponent;
e.fmt_output(f, opts, print_state)
}
}
}
fn fmt_debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as std::fmt::Debug>::fmt(self, f)
}
}
impl<'a> FormattedPrintAdd for AddView<'a> {
fn fmt_output(
&self,
f: &mut fmt::Formatter,
opts: &PrintOptions,
mut print_state: PrintState,
) -> fmt::Result {
let mut first = true;
print_state.top_level_add_child = print_state.level == 0;
print_state.level += 1;
for x in self.iter() {
if !first && print_state.top_level_add_child && opts.terms_on_new_line {
f.write_char('\n')?;
f.write_char('\t')?;
}
print_state.explicit_sign = !first;
first = false;
x.fmt_output(f, opts, print_state)?;
}
Ok(())
}
fn fmt_debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as std::fmt::Debug>::fmt(self, f)
}
}
pub struct FactorizedRationalPolynomialPrinter<'a, R: Ring, E: Exponent> {
pub poly: &'a FactorizedRationalPolynomial<R, E>,
pub opts: PrintOptions,
pub add_parentheses: bool,
}
impl<'a, R: Ring, E: Exponent> FactorizedRationalPolynomialPrinter<'a, R, E> {
pub fn new(
poly: &'a FactorizedRationalPolynomial<R, E>,
) -> FactorizedRationalPolynomialPrinter<'a, R, E> {
FactorizedRationalPolynomialPrinter {
poly,
opts: PrintOptions::default(),
add_parentheses: false,
}
}
pub fn new_with_options(
poly: &'a FactorizedRationalPolynomial<R, E>,
opts: PrintOptions,
) -> FactorizedRationalPolynomialPrinter<'a, R, E> {
FactorizedRationalPolynomialPrinter {
poly,
opts,
add_parentheses: false,
}
}
}
impl<'a, R: Ring, E: Exponent> Display for FactorizedRationalPolynomialPrinter<'a, R, E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.opts.explicit_rational_polynomial {
if !R::is_zero(&self.poly.numer_coeff)
&& !self.poly.numerator.ring.is_one(&self.poly.numer_coeff)
{
f.write_fmt(format_args!(
"[{}]*",
RingPrinter {
ring: &self.poly.numerator.ring,
element: &self.poly.numer_coeff,
opts: self.opts,
in_product: false
}
))?;
}
if self.poly.denominators.is_empty()
&& self.poly.numerator.ring.is_one(&self.poly.denom_coeff)
{
if self.poly.numerator.is_zero() {
f.write_char('0')?;
} else {
f.write_fmt(format_args!(
"[{}]",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))?;
}
} else {
f.write_fmt(format_args!(
"[{}",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
},
))?;
if !self.poly.numerator.ring.is_one(&self.poly.denom_coeff) {
f.write_fmt(format_args!(
",{},1",
RingPrinter {
ring: &self.poly.numerator.ring,
element: &self.poly.denom_coeff,
opts: self.opts,
in_product: false
},
))?;
}
for (d, p) in &self.poly.denominators {
f.write_fmt(format_args!(
",{}",
PolynomialPrinter {
poly: d,
opts: self.opts,
}
))?;
f.write_fmt(format_args!(",{}", p))?;
}
f.write_char(']')?;
}
return Ok(());
}
if R::is_zero(&self.poly.numer_coeff) {
return f.write_char('0');
}
if self.poly.denominators.is_empty()
&& self.poly.numerator.ring.is_one(&self.poly.denom_coeff)
{
if !self.poly.numerator.ring.is_one(&self.poly.numer_coeff) {
f.write_fmt(format_args!(
"{}",
RingPrinter {
ring: &self.poly.numerator.ring,
element: &self.poly.numer_coeff,
opts: self.opts,
in_product: false
}
))?;
}
if (self.poly.numerator.ring.is_one(&self.poly.numer_coeff) && !self.add_parentheses)
|| self.poly.numerator.nterms() < 2
{
if !self.poly.numerator.ring.is_one(&self.poly.numer_coeff) {
if self.poly.numerator.is_one() {
return Ok(());
}
f.write_char('*')?;
}
f.write_fmt(format_args!(
"{}",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))
} else {
if !self.poly.numerator.ring.is_one(&self.poly.numer_coeff) {
if self.poly.numerator.is_one() {
return Ok(());
}
f.write_char('*')?;
}
f.write_fmt(format_args!(
"({})",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))
}
} else {
if self.opts.latex {
if !self.poly.numerator.ring.is_one(&self.poly.numer_coeff) {
f.write_fmt(format_args!(
"{} ",
RingPrinter {
ring: &self.poly.numerator.ring,
element: &self.poly.numer_coeff,
opts: self.opts,
in_product: false
}
))?;
}
f.write_fmt(format_args!(
"\\frac{{{}}}{{",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
},
))?;
if !self.poly.numerator.ring.is_one(&self.poly.denom_coeff) {
f.write_fmt(format_args!(
"{}",
RingPrinter {
ring: &self.poly.numerator.ring,
element: &self.poly.denom_coeff,
opts: self.opts,
in_product: false
}
))?;
}
for (d, p) in &self.poly.denominators {
if *p == 1 {
f.write_fmt(format_args!(
"({})",
PolynomialPrinter {
poly: d,
opts: self.opts,
}
))?;
} else {
f.write_fmt(format_args!(
"({})^{}",
PolynomialPrinter {
poly: d,
opts: self.opts,
},
p
))?;
}
}
return f.write_str("}}");
}
if !self.poly.numerator.ring.is_one(&self.poly.numer_coeff) {
f.write_fmt(format_args!(
"{}*",
RingPrinter {
ring: &self.poly.numerator.ring,
element: &self.poly.numer_coeff,
opts: self.opts,
in_product: false
}
))?;
}
if self.poly.numerator.nterms() < 2 {
f.write_fmt(format_args!(
"{}",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))?;
} else {
f.write_fmt(format_args!(
"({})",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))?;
}
f.write_char('/')?;
if self.poly.denominators.is_empty() {
return f.write_fmt(format_args!(
"{}",
RingPrinter {
ring: &self.poly.numerator.ring,
element: &self.poly.denom_coeff,
opts: self.opts,
in_product: true
}
));
}
if self.poly.numerator.ring.is_one(&self.poly.denom_coeff)
&& self.poly.denominators.len() == 1
&& self.poly.denominators[0].0.nterms() == 1
&& self.poly.denominators[0].1 == 1
{
let (d, _) = &self.poly.denominators[0];
let var_count = d.exponents.iter().filter(|x| !x.is_zero()).count();
if var_count == 0 || d.ring.is_one(&d.coefficients[0]) && var_count == 1 {
return f.write_fmt(format_args!(
"{}",
PolynomialPrinter {
poly: d,
opts: self.opts,
}
));
}
}
f.write_char('(')?;
if !self.poly.numerator.ring.is_one(&self.poly.denom_coeff) {
f.write_fmt(format_args!(
"{}",
RingPrinter {
ring: &self.poly.numerator.ring,
element: &self.poly.denom_coeff,
opts: self.opts,
in_product: true
}
))?;
}
for (d, p) in &self.poly.denominators {
if *p == 1 {
f.write_fmt(format_args!(
"({})",
PolynomialPrinter {
poly: d,
opts: self.opts,
}
))?;
} else {
f.write_fmt(format_args!(
"({}){}{}",
PolynomialPrinter {
poly: d,
opts: self.opts,
},
if self.opts.double_star_for_exponentiation {
"**"
} else {
"^"
},
p
))?;
}
}
f.write_char(')')
}
}
}
pub struct RationalPolynomialPrinter<'a, R: Ring, E: Exponent> {
pub poly: &'a RationalPolynomial<R, E>,
pub opts: PrintOptions,
pub add_parentheses: bool,
}
impl<'a, R: Ring, E: Exponent> RationalPolynomialPrinter<'a, R, E> {
pub fn new(poly: &'a RationalPolynomial<R, E>) -> RationalPolynomialPrinter<'a, R, E> {
RationalPolynomialPrinter {
poly,
opts: PrintOptions::default(),
add_parentheses: false,
}
}
pub fn new_with_options(
poly: &'a RationalPolynomial<R, E>,
opts: PrintOptions,
) -> RationalPolynomialPrinter<'a, R, E> {
RationalPolynomialPrinter {
poly,
opts,
add_parentheses: false,
}
}
}
impl<'a, R: Ring, E: Exponent> Display for RationalPolynomialPrinter<'a, R, E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.opts.explicit_rational_polynomial {
if self.poly.denominator.is_one() {
if self.poly.numerator.is_zero() {
f.write_char('0')?;
} else {
f.write_fmt(format_args!(
"[{}]",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))?;
}
} else {
f.write_fmt(format_args!(
"[{},{}]",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
},
PolynomialPrinter {
poly: &self.poly.denominator,
opts: self.opts,
}
))?;
}
return Ok(());
}
if self.poly.denominator.is_one() {
if !self.add_parentheses || self.poly.numerator.nterms() < 2 {
f.write_fmt(format_args!(
"{}",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))
} else {
f.write_fmt(format_args!(
"({})",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))
}
} else {
if self.opts.latex {
return f.write_fmt(format_args!(
"\\frac{{{}}}{{{}}}",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
},
PolynomialPrinter {
poly: &self.poly.denominator,
opts: self.opts,
}
));
}
if self.poly.numerator.nterms() < 2 {
f.write_fmt(format_args!(
"{}",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))?;
} else {
f.write_fmt(format_args!(
"({})",
PolynomialPrinter {
poly: &self.poly.numerator,
opts: self.opts,
}
))?;
}
if self.poly.denominator.nterms() == 1 {
let var_count = self
.poly
.denominator
.exponents
.iter()
.filter(|x| !x.is_zero())
.count();
if var_count == 0
|| self
.poly
.denominator
.ring
.is_one(&self.poly.denominator.coefficients[0])
&& var_count == 1
{
return f.write_fmt(format_args!(
"/{}",
PolynomialPrinter {
poly: &self.poly.denominator,
opts: self.opts,
}
));
}
}
f.write_fmt(format_args!(
"/({})",
PolynomialPrinter {
poly: &self.poly.denominator,
opts: self.opts,
}
))
}
}
}
pub struct PolynomialPrinter<'a, F: Ring + Display, E: Exponent, O: MonomialOrder> {
pub poly: &'a MultivariatePolynomial<F, E, O>,
pub opts: PrintOptions,
}
impl<'a, R: Ring + Display, E: Exponent, O: MonomialOrder> PolynomialPrinter<'a, R, E, O> {
pub fn new(poly: &'a MultivariatePolynomial<R, E, O>) -> PolynomialPrinter<'a, R, E, O> {
PolynomialPrinter {
poly,
opts: PrintOptions::default(),
}
}
pub fn new_with_options(
poly: &'a MultivariatePolynomial<R, E, O>,
opts: PrintOptions,
) -> PolynomialPrinter<'a, R, E, O> {
PolynomialPrinter { poly, opts }
}
}
impl<'a, F: Ring + Display, E: Exponent, O: MonomialOrder> Display
for PolynomialPrinter<'a, F, E, O>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.sign_plus() {
f.write_char('+')?;
}
let var_map: Vec<String> = self
.poly
.variables
.as_ref()
.iter()
.map(|v| v.to_string())
.collect();
let mut is_first_term = true;
for monomial in self.poly {
let mut is_first_factor = true;
if self.poly.ring.is_one(monomial.coefficient) {
if !is_first_term {
write!(f, "+")?;
}
} else if monomial
.coefficient
.eq(&self.poly.ring.neg(&self.poly.ring.one()))
{
write!(f, "-")?;
} else {
if is_first_term {
self.poly
.ring
.fmt_display(monomial.coefficient, &self.opts, true, f)?;
} else {
write!(
f,
"{:+}",
RingPrinter {
ring: &self.poly.ring,
element: monomial.coefficient,
opts: self.opts,
in_product: true
}
)?;
}
is_first_factor = false;
}
is_first_term = false;
for (var_id, e) in var_map.iter().zip(monomial.exponents) {
if e.is_zero() {
continue;
}
if is_first_factor {
is_first_factor = false;
} else if !self.opts.latex {
write!(f, "*")?;
}
f.write_str(var_id)?;
if e.to_u32() != 1 {
if self.opts.latex {
write!(f, "^{{{}}}", e)?;
} else if self.opts.double_star_for_exponentiation {
write!(f, "**{}", e)?;
} else {
write!(f, "^{}", e)?;
}
}
}
if is_first_factor {
write!(f, "1")?;
}
}
if is_first_term {
write!(f, "0")?;
}
if self.opts.print_finite_field {
Display::fmt(&self.poly.ring, f)?;
}
Ok(())
}
}
pub struct MatrixPrinter<'a, F: Ring + Display> {
pub matrix: &'a Matrix<F>,
pub opts: PrintOptions,
}
impl<'a, F: Ring + Display> MatrixPrinter<'a, F> {
pub fn new(matrix: &'a Matrix<F>) -> MatrixPrinter<'a, F> {
MatrixPrinter {
matrix,
opts: PrintOptions::default(),
}
}
pub fn new_with_options(matrix: &'a Matrix<F>, opts: PrintOptions) -> MatrixPrinter<'a, F> {
MatrixPrinter { matrix, opts }
}
}
impl<'a, F: Ring + Display> Display for MatrixPrinter<'a, F> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.opts.latex {
f.write_str("\\begin{pmatrix}")?;
for (ri, r) in self.matrix.row_iter().enumerate() {
for (ci, c) in r.iter().enumerate() {
f.write_fmt(format_args!(
"{}",
RingPrinter {
ring: &self.matrix.field,
element: c,
opts: self.opts,
in_product: false,
},
))?;
if ci + 1 < self.matrix.ncols as usize {
f.write_str(" & ")?;
}
}
if ri + 1 < self.matrix.nrows as usize {
f.write_str(r" \\ ")?;
}
}
f.write_str("\\end{pmatrix}")
} else {
f.write_char('{')?;
for (ri, r) in self.matrix.row_iter().enumerate() {
f.write_char('{')?;
for (ci, c) in r.iter().enumerate() {
f.write_fmt(format_args!(
"{}",
RingPrinter {
ring: &self.matrix.field,
element: c,
opts: self.opts,
in_product: false,
},
))?;
if ci + 1 < self.matrix.ncols as usize {
f.write_char(',')?;
}
}
f.write_char('}')?;
if ri + 1 < self.matrix.nrows as usize {
f.write_char(',')?;
}
}
f.write_char('}')
}
}
}
#[cfg(test)]
mod test {
use colored::control::ShouldColorize;
use crate::{
atom::Atom,
domains::{finite_field::Zp, integer::Z},
printer::{AtomPrinter, PolynomialPrinter, PrintOptions},
state::{FunctionAttribute, State},
};
#[test]
fn atoms() {
let a = Atom::parse("f(x,y^2)^(x+z)/5+3").unwrap();
if ShouldColorize::from_env().should_colorize() {
assert_eq!(format!("{}", a), "1/5*f(x,y^2)^(x+z)\u{1b}[33m+\u{1b}[0m3");
} else {
assert_eq!(format!("{}", a), "1/5*f(x,y^2)^(x+z)+3");
}
assert_eq!(
format!(
"{}",
AtomPrinter::new_with_options(a.as_view(), PrintOptions::latex())
),
"\\frac{1}{5} f\\!\\left(x,y^{2}\\right)^{x+z}+3"
);
assert_eq!(
format!(
"{}",
AtomPrinter::new_with_options(a.as_view(), PrintOptions::mathematica())
),
"1/5 f[x,y^2]^(x+z)+3"
);
let a = Atom::parse("8127389217 x^2").unwrap();
assert_eq!(
format!(
"{}",
AtomPrinter::new_with_options(
a.as_view(),
PrintOptions {
number_thousands_separator: Some('_'),
multiplication_operator: ' ',
num_exp_as_superscript: true,
..PrintOptions::file()
}
)
),
"812_738_921_7 x²"
);
}
#[test]
fn polynomials() {
let a = Atom::parse("15 x^2")
.unwrap()
.to_polynomial::<_, u8>(&Zp::new(17), None);
assert_eq!(
format!(
"{}",
PolynomialPrinter::new_with_options(
&a,
PrintOptions {
print_finite_field: true,
symmetric_representation_for_finite_field: true,
..PrintOptions::file()
}
)
),
"-2*x^2 % 17"
);
}
#[test]
fn rational_polynomials() {
let a = Atom::parse("15 x^2 / (1+x)")
.unwrap()
.to_rational_polynomial::<_, _, u8>(&Z, &Z, None);
assert_eq!(format!("{}", a), "15*x^2/(1+x)");
let a = Atom::parse("(15 x^2 + 6) / (1+x)")
.unwrap()
.to_rational_polynomial::<_, _, u8>(&Z, &Z, None);
assert_eq!(format!("{}", a), "(6+15*x^2)/(1+x)");
}
#[test]
fn factorized_rational_polynomials() {
let a = Atom::parse("15 x^2 / ((1+x)(x+2))")
.unwrap()
.to_factorized_rational_polynomial::<_, _, u8>(&Z, &Z, None);
assert!(
format!("{}", a) == "15*x^2/((1+x)(2+x))" || format!("{}", a) == "15*x^2/((2+x)(1+x))"
);
let a = Atom::parse("(15 x^2 + 6) / ((1+x)(x+2))")
.unwrap()
.to_factorized_rational_polynomial::<_, _, u8>(&Z, &Z, None);
assert!(
format!("{}", a) == "3*(2+5*x^2)/((1+x)(2+x))"
|| format!("{}", a) == "3*(2+5*x^2)/((2+x)(1+x))"
);
}
#[test]
fn base_parentheses() {
let a = Atom::parse("(-1)^(x+1)-(1/2)^x").unwrap();
assert_eq!(
format!(
"{}",
AtomPrinter::new_with_options(a.as_view(), PrintOptions::file())
),
"(-1)^(x+1)-(1/2)^x"
)
}
#[test]
fn canon() {
let _ =
State::get_symbol_with_attributes("canon_f", &[FunctionAttribute::Symmetric]).unwrap();
let _ = State::get_symbol("canon_y");
let _ = State::get_symbol("canon_x");
let a = Atom::parse("canon_x^2 + 2*canon_x*canon_y + canon_y^2*(canon_x+canon_y) + canon_f(canon_x,canon_y)").unwrap();
assert_eq!(
a.to_canonical_string(),
"(canon_x+canon_y)*canon_y^2+2*canon_x*canon_y+canon_f(canon_x,canon_y)+canon_x^2"
);
}
}