mod coefficient;
mod core;
pub mod representation;
use ahash::HashMap;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use numerica::domains::float::Float;
use smartstring::{LazyCompact, SmartString};
use crate::{
coefficient::Coefficient,
domains::{atom::AtomField, float::Complex, integer::Integer, rational::Rational},
evaluate::ExternalFunction,
parser::{ParseSettings, Token},
poly::series::Series,
printer::{AnsiWrap, AtomPrinter, PrintFunction, PrintOptions, PrintState},
state::{RecycledAtom, State, SymbolData, Workspace},
transformer::StatsOptions,
utils::{BorrowedOrOwned, Settable},
warn,
};
use std::{
any::{Any, TypeId},
borrow::Cow,
cmp::Ordering,
hash::Hash,
io::{Read, Write},
ops::DerefMut,
sync::OnceLock,
};
pub use self::core::AtomCore;
pub use self::representation::{
Add, AddView, Fun, InlineNum, InlineVar, KeyLookup, ListIterator, ListSlice, Mul, MulView, Num,
NumView, Pow, PowView, Var, VarView,
};
use self::representation::{FunView, RawAtom};
pub use crate::{
evaluate::EvaluationError,
poly::{PolynomialConversionError, series::SeriesError},
tensors::TensorCanonicalizationError,
};
#[derive(Clone)]
pub struct NamespacedSymbol {
pub namespace: Cow<'static, str>,
pub symbol: Cow<'static, str>,
pub file: Cow<'static, str>,
pub line: usize,
}
impl NamespacedSymbol {
pub fn parse(s: &str) -> NamespacedSymbol {
let (namespace, _partial_symbol) = s.rsplit_once("::").unwrap_or_else(|| {
panic!("Input {s} does not contain a symbol in the format `namespace::symbol`.")
});
NamespacedSymbol {
namespace: namespace.to_string().into(),
symbol: s.to_string().into(),
file: "".into(),
line: 0,
}
}
pub fn try_parse<S: AsRef<str>>(s: S) -> Option<NamespacedSymbol> {
let (namespace, _partial_symbol) = s.as_ref().rsplit_once("::")?;
Some(NamespacedSymbol {
namespace: namespace.to_string().into(),
symbol: s.as_ref().to_string().into(),
file: "".into(),
line: 0,
})
}
pub fn try_parse_lit(s: &'static str) -> Option<NamespacedSymbol> {
let (namespace, _partial_symbol) = s.rsplit_once("::")?;
Some(NamespacedSymbol {
namespace: namespace.into(),
symbol: s.into(),
file: "".into(),
line: 0,
})
}
}
impl TryFrom<&str> for NamespacedSymbol {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
NamespacedSymbol::try_parse(value).ok_or_else(|| {
format!("Input {value} does not contain a symbol in the format `namespace::symbol`.")
})
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! wrap_symbol {
($e:literal) => {{
if let Some(mut s) = $crate::atom::NamespacedSymbol::try_parse_lit($e) {
s.file = file!().into();
s.line = line!() as usize;
s
} else {
let ns = if $crate::state::State::is_builtin(&$e) {
"symbolica"
} else {
$crate::namespace!()
};
$crate::atom::NamespacedSymbol {
symbol: format!("{}::{}", ns, $e).into(),
namespace: ns.into(),
file: file!().into(),
line: line!() as usize,
}
}
}};
($e:expr) => {{
if let Some(mut s) = $crate::atom::NamespacedSymbol::try_parse($e) {
s.file = file!().into();
s.line = line!() as usize;
s
} else {
let ns = if $crate::state::State::is_builtin(&$e) {
"symbolica"
} else {
$crate::namespace!()
};
$crate::atom::NamespacedSymbol {
symbol: format!("{}::{}", ns, $e).into(),
namespace: ns.into(),
file: file!().into(),
line: line!() as usize,
}
}
}};
}
pub struct DefaultNamespace<T> {
pub namespace: Cow<'static, str>,
pub data: T,
pub file: Cow<'static, str>,
pub line: usize,
}
impl<T> DefaultNamespace<T> {
pub fn attach_namespace(&self, s: &str) -> NamespacedSymbol {
if let Some(mut s) = NamespacedSymbol::try_parse(s) {
s.file = self.file.clone();
s.line = self.line;
s
} else {
NamespacedSymbol {
symbol: format!("{}::{}", self.namespace, s).into(),
namespace: self.namespace.clone(),
file: self.file.clone(),
line: self.line,
}
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! wrap_input {
($e:expr) => {
$crate::atom::DefaultNamespace {
data: $e,
namespace: $crate::namespace!().into(),
file: file!().into(),
line: line!() as usize,
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! with_default_namespace {
($e:expr, $namespace: expr) => {
$crate::atom::DefaultNamespace {
data: $e,
namespace: $namespace.into(),
file: file!().into(),
line: line!() as usize,
}
};
}
#[macro_export]
macro_rules! namespace {
() => {{ env!("CARGO_CRATE_NAME") }};
}
#[macro_export]
macro_rules! hide_namespace {
($e:expr) => {
$crate::atom::AtomCore::printer(
&$e,
$crate::printer::PrintOptions {
hide_namespace: Some($crate::namespace!().into()),
..$crate::printer::PrintOptions::new()
},
)
};
}
pub type NormalizationFunction = Box<dyn Fn(AtomView, &mut Settable<Atom>) + Send + Sync>;
pub type DerivativeFunction = Box<dyn Fn(AtomView, usize, &mut Settable<Atom>) + Send + Sync>;
pub type SeriesExpansionFunction =
dyn for<'a> Fn(&'a [Series<AtomField>]) -> Option<(Atom, Atom)> + Send + Sync;
pub struct EvaluationInfo {
tag_count: usize,
constant_eval: Option<ErasedConstantEval>,
constant_eval_cache: OnceLock<Result<Complex<Float>, String>>,
eval_fns: HashMap<TypeId, ErasedEvalFn>,
cpp: Option<String>,
}
pub type EvalFn<T> = Box<dyn ExternalFunction<T>>;
type ErasedConstantEval =
Box<dyn Fn(&[AtomView], u32) -> Result<Complex<Float>, String> + Send + Sync>;
type ErasedTaggedEvalGen = Box<dyn Fn(&[AtomView]) -> Box<dyn Any> + Send + Sync>;
enum ErasedEvalFn {
Direct(Box<dyn Any + Send + Sync>),
Tagged(ErasedTaggedEvalGen),
}
impl EvaluationInfo {
pub fn new() -> Self {
Self {
tag_count: 0,
constant_eval: None,
constant_eval_cache: OnceLock::new(),
eval_fns: HashMap::default(),
cpp: None,
}
}
pub fn constant(
f: impl Fn(&[AtomView], u32) -> Result<Complex<Float>, String> + Send + Sync + 'static,
) -> Self {
Self {
tag_count: 0,
constant_eval: Some(Box::new(f)),
constant_eval_cache: OnceLock::new(),
eval_fns: HashMap::default(),
cpp: None,
}
}
pub fn with_tags(mut self, num_tags: usize) -> Self {
self.tag_count = num_tags;
if num_tags > 0 {
self.constant_eval_cache = OnceLock::new();
}
self
}
pub fn get_tag_count(&self) -> usize {
self.tag_count
}
pub fn with_cpp(mut self, snippet: impl Into<String>) -> Self {
self.cpp = Some(snippet.into());
self
}
pub fn get_cpp(&self) -> Option<&str> {
self.cpp.as_deref()
}
pub fn register<T: 'static>(mut self, f: impl ExternalFunction<T> + 'static) -> Self {
let f: EvalFn<T> = Box::new(f);
self.eval_fns.insert(
TypeId::of::<T>(),
ErasedEvalFn::Direct(Box::new(f) as Box<dyn Any + Send + Sync>),
);
self
}
pub fn register_tagged<T: 'static>(
mut self,
f: impl Fn(&[AtomView]) -> EvalFn<T> + Send + Sync + 'static,
) -> Self {
self.eval_fns.insert(
TypeId::of::<T>(),
ErasedEvalFn::Tagged(Box::new(move |tags| Box::new(f(tags)) as Box<dyn Any>)),
);
self
}
pub fn evaluate_constant(
&self,
tags: &[AtomView],
precision: u32,
) -> Result<Complex<Float>, String> {
if precision == 53
&& self.tag_count == 0
&& let Some(eval) = &self.constant_eval
{
return self
.constant_eval_cache
.get_or_init(|| eval(&[], 53))
.clone();
}
if let Some(eval) = &self.constant_eval {
return eval(tags, precision);
};
Err("No precision-aware constant evaluator registered".to_owned())
}
pub fn get_evaluator<T: 'static>(&self, tags: &[AtomView]) -> Option<EvalFn<T>> {
match self.eval_fns.get(&TypeId::of::<T>())? {
ErasedEvalFn::Direct(boxed) => Some(
boxed
.downcast_ref::<EvalFn<T>>()
.expect("stored eval function had the wrong concrete type")
.clone(),
),
ErasedEvalFn::Tagged(erased) => {
let boxed = erased(tags);
Some(
*boxed
.downcast::<EvalFn<T>>()
.expect("stored eval generator had the wrong concrete type"),
)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum UserDataKey {
Integer(i64),
String(String),
Atom(Atom),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UserData {
None,
Integer(i64),
String(String),
Atom(Atom),
List(Vec<UserData>),
Map(HashMap<UserDataKey, UserData>),
Serialized(Vec<u8>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum SymbolAttribute {
Symmetric,
Antisymmetric,
Cyclesymmetric,
Linear,
Scalar,
Real,
Integer,
Positive,
}
#[derive(Copy, Clone, Eq)]
pub struct Symbol {
id: u32,
wildcard_level: u8,
is_symmetric: bool,
is_antisymmetric: bool,
is_cyclesymmetric: bool,
is_linear: bool,
is_scalar: bool,
is_real: bool,
is_integer: bool,
is_positive: bool,
}
#[cfg(feature = "serde")]
impl serde::Serialize for Symbol {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut data = vec![];
self.export(&mut data);
data.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Symbol {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let mut v = <&[u8]>::deserialize(deserializer)?;
Symbol::import(&mut v)
.map_err(|e| serde::de::Error::custom(format!("Failed to deserialize Symbol: {e}")))
}
}
#[cfg(feature = "bincode")]
impl bincode::Encode for Symbol {
fn encode<E: bincode::enc::Encoder>(
&self,
encoder: &mut E,
) -> Result<(), bincode::error::EncodeError> {
let mut data = vec![];
self.export(&mut data)
.map_err(|e| bincode::error::EncodeError::OtherString(e.to_string()))?;
bincode::Encode::encode(&data, encoder)
}
}
#[cfg(feature = "bincode")]
impl<Context> bincode::Decode<Context> for Symbol {
fn decode<D: bincode::de::Decoder<Context = Context>>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
let v: Vec<u8> = bincode::Decode::decode(decoder)?;
Symbol::import(&mut v.as_slice())
.map_err(|e| bincode::error::DecodeError::OtherString(e.to_string()))
}
}
#[cfg(feature = "bincode")]
impl<'de, C> bincode::de::BorrowDecode<'de, C> for Symbol {
fn borrow_decode<D: bincode::de::BorrowDecoder<'de, Context = C>>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
let v: Vec<u8> = bincode::Decode::decode(decoder)?;
Symbol::import(&mut v.as_slice())
.map_err(|e| bincode::error::DecodeError::OtherString(e.to_string()))
}
}
impl Ord for Symbol {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl PartialOrd for Symbol {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl std::hash::Hash for Symbol {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl PartialEq for Symbol {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl std::fmt::Debug for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
let data = self.get_global_data();
write!(
f,
"Symbol(name: {}, id: {}, attributes: {:?}, tags: {:?})",
data.name,
self.id,
self.get_attributes(),
data.tags
)
} else {
self.format(&PrintOptions::file(), PrintState::default(), f)
}
}
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.format(&PrintOptions::from_fmt(f), PrintState::default(), f)
}
}
impl<T: AtomCore> PartialEq<T> for Symbol {
fn eq(&self, other: &T) -> bool {
match other.as_atom_view() {
AtomView::Var(v) => self.get_id() == v.get_symbol_id(),
_ => false,
}
}
}
pub struct SymbolBuilder {
symbol: NamespacedSymbol,
attributes: Option<Cow<'static, [SymbolAttribute]>>,
tags: Vec<String>,
aliases: Vec<String>,
normalization_function: Option<NormalizationFunction>,
print_function: Option<PrintFunction>,
derivative_function: Option<DerivativeFunction>,
series_function: Option<Box<SeriesExpansionFunction>>,
evaluation_function: Option<EvaluationInfo>,
generator: Option<Box<dyn Fn(&[Symbol], SymbolBuilder) -> SymbolBuilder + Send + Sync>>,
user_data: Option<UserData>,
}
impl SymbolBuilder {
pub fn new(symbol: NamespacedSymbol) -> Self {
SymbolBuilder {
symbol,
attributes: None,
tags: vec![],
aliases: vec![],
normalization_function: None,
print_function: None,
derivative_function: None,
series_function: None,
evaluation_function: None,
generator: None,
user_data: None,
}
}
pub fn with_attributes(
mut self,
attributes: impl Into<Cow<'static, [SymbolAttribute]>>,
) -> Self {
self.attributes = Some(attributes.into());
self
}
pub fn with_tags<T: AsRef<[U]>, U: AsRef<str>>(mut self, tags: T) -> Self {
self.tags = tags.as_ref().iter().map(|x| x.as_ref().into()).collect();
self
}
pub fn with_aliases<T: AsRef<[U]>, U: AsRef<str>>(mut self, aliases: T) -> Self {
self.aliases = aliases.as_ref().iter().map(|x| x.as_ref().into()).collect();
self
}
pub fn with_normalization_function(
mut self,
normalization_function: impl Fn(AtomView, &mut Settable<Atom>) + Send + Sync + 'static,
) -> Self {
self.normalization_function = Some(Box::new(normalization_function));
self
}
pub fn with_print_function(
mut self,
print_function: impl Fn(AtomView, &PrintOptions, &PrintState) -> Option<String>
+ Send
+ Sync
+ 'static,
) -> Self {
self.print_function = Some(Box::new(print_function));
self
}
pub fn with_derivative_function(
mut self,
derivative_function: impl Fn(AtomView, usize, &mut Settable<Atom>) + Send + Sync + 'static,
) -> Self {
self.derivative_function = Some(Box::new(derivative_function));
self
}
pub fn with_series_function(
mut self,
series_function: impl for<'a> Fn(&'a [Series<AtomField>]) -> Option<(Atom, Atom)>
+ Send
+ Sync
+ 'static,
) -> Self {
self.series_function = Some(Box::new(series_function));
self
}
pub fn with_evaluation_info(mut self, evaluation_info: EvaluationInfo) -> Self {
self.evaluation_function = Some(evaluation_info);
self
}
pub fn with_generator(
mut self,
f: impl Fn(&[Symbol], SymbolBuilder) -> SymbolBuilder + Send + Sync + 'static,
) -> Self {
self.generator = Some(Box::new(f));
self
}
pub fn build_group(
builders: Vec<SymbolBuilder>,
) -> Result<Vec<Symbol>, SmartString<LazyCompact>> {
let state = &mut State::get_state_mut();
let mut next_index = state.get_next_symbol_index();
let mut symbols = vec![];
for b in &builders {
if state.fetch_symbol(&b.symbol.symbol).is_some() {
return Err(format!("Symbol {} is already defined.", b.symbol.symbol).into());
}
if let Some(attr) = b.attributes.as_ref() {
symbols.push(Symbol::raw_fn(
next_index,
State::get_wildcard_level(&b.symbol.symbol),
attr.contains(&SymbolAttribute::Symmetric),
attr.contains(&SymbolAttribute::Antisymmetric),
attr.contains(&SymbolAttribute::Cyclesymmetric),
attr.contains(&SymbolAttribute::Linear),
attr.contains(&SymbolAttribute::Scalar),
attr.contains(&SymbolAttribute::Real),
attr.contains(&SymbolAttribute::Integer),
attr.contains(&SymbolAttribute::Positive),
));
} else {
symbols.push(Symbol::raw_var(
next_index,
State::get_wildcard_level(&b.symbol.symbol),
));
}
next_index += 1;
}
let mut result = Vec::with_capacity(builders.len());
for mut b in builders {
if let Some(f) = b.generator.take() {
b = (f)(&symbols, b);
}
result.push(b.build_with_state(state)?);
}
Ok(result)
}
pub fn with_user_data(mut self, data: UserData) -> Self {
self.user_data = Some(data);
self
}
pub fn build(self) -> Result<Symbol, SmartString<LazyCompact>> {
self.build_with_state(&mut State::get_state_mut())
}
pub(crate) fn build_with_state(
self,
state: &mut State,
) -> Result<Symbol, SmartString<LazyCompact>> {
let (namespace, partial_symbol) =
self.symbol.symbol.rsplit_once("::").ok_or_else(|| {
SmartString::from(format!(
"Input {} does not contain a symbol in the format `namespace::symbol`.",
self.symbol.symbol,
))
})?;
for tag in &self.tags {
if !tag.contains("::") {
return Err(format!("Tag {} must contain a namespace", tag).into());
}
}
Token::check_symbol_namespace(namespace)?;
Token::check_symbol_name(partial_symbol)?;
if self.attributes.is_none()
&& self.normalization_function.is_none()
&& self.print_function.is_none()
&& self.derivative_function.is_none()
&& self.series_function.is_none()
&& self.evaluation_function.is_none()
&& self.tags.is_empty()
&& self.aliases.is_empty()
&& self.user_data.is_none()
{
state.get_symbol(self.symbol)
} else {
state.get_symbol_with_attributes(
self.symbol,
self.attributes.as_ref().map(|x| x.as_ref()).unwrap_or(&[]),
self.normalization_function,
self.print_function,
self.derivative_function,
self.series_function,
self.evaluation_function,
self.tags,
self.aliases,
self.user_data,
)
}
}
}
impl Symbol {
pub const ARG: Symbol = State::ARG;
pub const COEFF: Symbol = State::COEFF;
pub const EXP: Symbol = State::EXP;
pub const LOG: Symbol = State::LOG;
pub const SIN: Symbol = State::SIN;
pub const COS: Symbol = State::COS;
pub const SQRT: Symbol = State::SQRT;
pub const CONJ: Symbol = State::CONJ;
pub const ABS: Symbol = State::ABS;
pub const IF: Symbol = State::IF;
pub const SEP: Symbol = State::SEP;
pub const DERIVATIVE: Symbol = State::DERIVATIVE;
pub const E: Symbol = State::E;
pub const PI: Symbol = State::PI;
pub const PI_STR: &'static str = "𝜋";
pub const E_STR: &'static str = "𝑒";
pub const SEP_STR: &'static str = "‖";
pub(crate) const ARG_ID: u32 = State::ARG.id;
pub(crate) const EXP_ID: u32 = State::EXP.id;
pub(crate) const LOG_ID: u32 = State::LOG.id;
pub(crate) const SIN_ID: u32 = State::SIN.id;
pub(crate) const COS_ID: u32 = State::COS.id;
pub(crate) const SQRT_ID: u32 = State::SQRT.id;
pub(crate) const CONJ_ID: u32 = State::CONJ.id;
pub(crate) const ABS_ID: u32 = State::ABS.id;
pub(crate) const IF_ID: u32 = State::IF.id;
pub(crate) const DERIVATIVE_ID: u32 = State::DERIVATIVE.id;
pub(crate) const E_ID: u32 = State::E.id;
pub(crate) const PI_ID: u32 = State::PI.id;
pub fn parse<T: AsRef<str>>(
name: T,
namespace: impl Into<Cow<'static, str>>,
) -> Result<Self, String> {
Token::parse_symbol(
name.as_ref(),
&DefaultNamespace {
namespace: namespace.into(),
data: name.as_ref(),
file: Cow::default(),
line: 0,
},
&mut HashMap::default(),
&mut State::get_state_mut(),
)
}
pub fn parse_with_default_namespace<T: AsRef<str>>(
name: DefaultNamespace<T>,
) -> Result<Self, String> {
Token::parse_symbol(
name.data.as_ref(),
&name,
&mut HashMap::default(),
&mut State::get_state_mut(),
)
}
pub fn get_symbol(name: NamespacedSymbol) -> Option<Symbol> {
State::get_global_state()
.read()
.unwrap()
.fetch_symbol(name.symbol.as_ref())
}
pub fn to_atom(self) -> Atom {
Atom::var(self)
}
pub fn call<A: FunctionArguments>(self, args: A) -> Atom {
args.add_args_to_function_builder(FunctionBuilder::new(self))
.finish()
}
pub fn call_args<'a, I, T>(self, args: I) -> Atom
where
I: IntoIterator<Item = T>,
T: Into<AtomOrView<'a>>,
{
FunctionBuilder::new(self).add_args(args).finish()
}
pub fn get_name(&self) -> &str {
State::get_name(*self)
}
pub fn get_ascii_name(&self) -> Option<String> {
if self.get_name().is_ascii() {
return Some(self.get_name().replace("::", "_"));
} else {
for x in self.get_aliases() {
if x.is_ascii() {
return Some(x.replace("::", "_"));
}
}
}
None
}
pub fn get_stripped_ascii_name(&self) -> Option<&str> {
if self.get_stripped_name().is_ascii() {
return Some(self.get_stripped_name());
} else {
for x in self.get_aliases() {
if let Some((_, name)) = x.rsplit_once("::")
&& name.is_ascii()
{
return Some(name);
} else if x.is_ascii() {
return Some(x);
}
}
}
None
}
pub fn get_stripped_name(&self) -> &str {
let d = self.get_global_data();
&d.name[d.namespace.len() + 2..]
}
#[inline(always)]
pub fn get_id(&self) -> u32 {
self.id
}
pub fn get_namespace(&self) -> &'static str {
State::get_symbol_namespace(*self)
}
pub fn get_wildcard_level(&self) -> u8 {
self.wildcard_level
}
pub fn is_symmetric(&self) -> bool {
self.is_symmetric
}
pub fn is_antisymmetric(&self) -> bool {
self.is_antisymmetric
}
pub fn is_cyclesymmetric(&self) -> bool {
self.is_cyclesymmetric
}
pub fn is_linear(&self) -> bool {
self.is_linear
}
pub fn is_scalar(&self) -> bool {
self.is_scalar
}
pub fn is_real(&self) -> bool {
self.is_real
}
pub fn is_integer(&self) -> bool {
self.is_integer
}
pub fn is_positive(&self) -> bool {
self.is_positive
}
pub(crate) fn is_fixed_builtin(self) -> bool {
State::is_fixed_builtin(self)
}
pub fn is_builtin(self) -> bool {
State::is_fixed_builtin(self) || self.get_namespace() == "symbolica"
}
pub fn get_tags(&self) -> &[String] {
&self.get_global_data().tags
}
pub fn get_aliases(&self) -> &[String] {
&self.get_global_data().aliases
}
pub fn has_tag(&self, tag: impl AsRef<str>) -> bool {
let r = tag.as_ref();
self.get_global_data().tags.iter().any(|x| x == r)
}
pub fn is_exportable(&self) -> bool {
self.get_normalization_function().is_none()
&& self.get_derivative_function().is_none()
&& self.get_series_function().is_none()
&& self.get_print_function().is_none()
&& self.get_evaluation_info().is_none()
}
pub fn get_normalization_function(&self) -> Option<&'static NormalizationFunction> {
self.get_global_data().custom_normalization.as_ref()
}
pub fn get_derivative_function(&self) -> Option<&'static DerivativeFunction> {
self.get_global_data().custom_derivative.as_ref()
}
pub fn get_series_function(&self) -> Option<&'static SeriesExpansionFunction> {
self.get_global_data().custom_series.as_deref()
}
pub fn get_print_function(&self) -> Option<&'static PrintFunction> {
self.get_global_data().custom_print.as_ref()
}
pub fn get_evaluation_info(&self) -> Option<&'static EvaluationInfo> {
self.get_global_data().custom_evaluation.as_ref()
}
pub fn get_attributes(&self) -> Vec<SymbolAttribute> {
let mut attrs = vec![];
if self.is_symmetric {
attrs.push(SymbolAttribute::Symmetric);
}
if self.is_antisymmetric {
attrs.push(SymbolAttribute::Antisymmetric);
}
if self.is_cyclesymmetric {
attrs.push(SymbolAttribute::Cyclesymmetric);
}
if self.is_linear {
attrs.push(SymbolAttribute::Linear);
}
if self.is_scalar {
attrs.push(SymbolAttribute::Scalar);
}
if self.is_real {
attrs.push(SymbolAttribute::Real);
}
if self.is_integer {
attrs.push(SymbolAttribute::Integer);
}
if self.is_positive {
attrs.push(SymbolAttribute::Positive);
}
attrs
}
pub fn has_attributes_of(&self, s: Symbol) -> bool {
for t in s.get_tags() {
if !self.has_tag(t) {
return false;
}
}
(!s.is_antisymmetric() || self.is_antisymmetric())
&& (!s.is_symmetric() || self.is_symmetric())
&& (!s.is_cyclesymmetric() || self.is_cyclesymmetric())
&& (!s.is_linear() || self.is_linear())
&& (!s.is_positive() || self.is_positive())
&& (!s.is_integer() || self.is_integer())
&& (!s.is_real() || self.is_real())
&& (!s.is_scalar() || self.is_scalar())
}
pub fn get_data(&self) -> &'static UserData {
&self.get_global_data().user_data
}
#[inline(always)]
pub fn has_attributes(&self) -> bool {
self.is_antisymmetric()
|| self.is_symmetric()
|| self.is_cyclesymmetric()
|| self.is_linear()
|| self.is_positive()
|| self.is_integer()
|| self.is_real()
|| self.is_scalar()
|| !self.get_tags().is_empty()
}
pub(crate) fn import_impl<R: Read>(
source: &mut R,
) -> Result<
(
String,
String,
Vec<SymbolAttribute>,
Vec<String>,
UserData,
Vec<String>,
bool,
),
std::io::Error,
> {
let l = source.read_u32::<LittleEndian>()?;
let mut v = vec![0; l as usize];
source.read_exact(&mut v)?;
let str: String = std::string::String::from_utf8(v)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let l = source.read_u32::<LittleEndian>()?;
let mut v = vec![0; l as usize];
source.read_exact(&mut v)?;
let namespace: String = std::string::String::from_utf8(v)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let flags = source.read_u8()?;
let extra_flags = source.read_u32::<LittleEndian>()?;
let s = Symbol::decode_flags(0, flags, extra_flags);
let mut tags = vec![];
let num_tags = source.read_u16::<LittleEndian>()?;
for _ in 0..num_tags {
let l = source.read_u32::<LittleEndian>()?;
let mut v = vec![0; l as usize];
source.read_exact(&mut v)?;
let tag: String = std::string::String::from_utf8(v)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
tags.push(tag);
}
let mut aliases = vec![];
let num_aliases = source.read_u16::<LittleEndian>()?;
for _ in 0..num_aliases {
let l = source.read_u32::<LittleEndian>()?;
let mut v = vec![0; l as usize];
source.read_exact(&mut v)?;
let alias: String = std::string::String::from_utf8(v)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
aliases.push(alias);
}
let extra_data = UserData::read(&mut *source)?;
let is_exportable = source.read_u8()? != 0;
let mut attributes = vec![];
if s.is_antisymmetric() {
attributes.push(SymbolAttribute::Antisymmetric);
}
if s.is_symmetric() {
attributes.push(SymbolAttribute::Symmetric);
}
if s.is_cyclesymmetric() {
attributes.push(SymbolAttribute::Cyclesymmetric);
}
if s.is_linear() {
attributes.push(SymbolAttribute::Linear);
}
if s.is_scalar() {
attributes.push(SymbolAttribute::Scalar);
}
if s.is_real() {
attributes.push(SymbolAttribute::Real);
}
if s.is_integer() {
attributes.push(SymbolAttribute::Integer);
}
if s.is_positive() {
attributes.push(SymbolAttribute::Positive);
}
Ok((
str.to_string(),
namespace.to_string(),
attributes,
tags,
extra_data,
aliases,
is_exportable,
))
}
pub fn import<R: Read>(source: &mut R) -> Result<Symbol, std::io::Error> {
let (name, namespace, attributes, tags, extra_data, aliases, is_exportable) =
Self::import_impl(source)?;
match SymbolBuilder::new(NamespacedSymbol {
symbol: name.clone().into(),
namespace: namespace.into(),
file: "Imported".into(),
line: 0,
})
.with_attributes(attributes.clone())
.with_tags(tags.clone())
.with_user_data(extra_data.clone())
.with_aliases(aliases.clone())
.build()
{
Ok(symbol) => {
if !is_exportable && symbol.is_exportable() {
warn!(
"Imported symbol {name} was previously defined with user-defined functions, but the imported version does not have any."
);
}
Ok(symbol)
}
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
)),
}
}
pub fn export<W: Write>(&self, dest: &mut W) -> Result<(), std::io::Error> {
let n = self.get_name();
dest.write_u32::<LittleEndian>(n.len() as u32)?;
dest.write_all(n.as_bytes())?;
let namespace = self.get_namespace();
dest.write_u32::<LittleEndian>(namespace.len() as u32)?;
dest.write_all(namespace.as_bytes())?;
let (flags, extra_flags) = self.encode_flags();
dest.write_u8(flags)?;
dest.write_u32::<LittleEndian>(extra_flags)?;
dest.write_u16::<LittleEndian>(self.get_tags().len() as u16)?;
for t in self.get_tags() {
dest.write_u32::<LittleEndian>(t.len() as u32)?;
dest.write_all(t.as_bytes())?;
}
dest.write_u16::<LittleEndian>(self.get_aliases().len() as u16)?;
for t in self.get_aliases() {
dest.write_u32::<LittleEndian>(t.len() as u32)?;
dest.write_all(t.as_bytes())?;
}
self.get_data().write(dest)?;
dest.write_u8(self.is_exportable() as u8)
}
pub(crate) const fn raw_var(id: u32, wildcard_level: u8) -> Self {
Symbol {
id,
wildcard_level,
is_symmetric: false,
is_antisymmetric: false,
is_cyclesymmetric: false,
is_linear: false,
is_scalar: false,
is_real: false,
is_integer: false,
is_positive: false,
}
}
pub(crate) const fn raw_fn(
id: u32,
wildcard_level: u8,
is_symmetric: bool,
is_antisymmetric: bool,
is_cyclesymmetric: bool,
is_linear: bool,
is_scalar: bool,
is_real: bool,
is_integer: bool,
is_positive: bool,
) -> Self {
Symbol {
id,
wildcard_level,
is_symmetric,
is_antisymmetric,
is_cyclesymmetric,
is_linear,
is_scalar,
is_real: is_real || is_integer || is_positive,
is_integer,
is_positive,
}
}
fn get_attributes_tuple_str(&self) -> [(&'static str, bool); 8] {
[
("symmetric", self.is_symmetric),
("antisymmetric", self.is_antisymmetric),
("cyclesymmetric", self.is_cyclesymmetric),
("linear", self.is_linear),
("scalar", self.is_scalar),
("real", self.is_real),
("integer", self.is_integer),
("positive", self.is_positive),
]
}
pub fn get_attributes_tuple(&self) -> [(SymbolAttribute, bool); 8] {
[
(SymbolAttribute::Symmetric, self.is_symmetric),
(SymbolAttribute::Antisymmetric, self.is_antisymmetric),
(SymbolAttribute::Cyclesymmetric, self.is_cyclesymmetric),
(SymbolAttribute::Linear, self.is_linear),
(SymbolAttribute::Scalar, self.is_scalar),
(SymbolAttribute::Real, self.is_real),
(SymbolAttribute::Integer, self.is_integer),
(SymbolAttribute::Positive, self.is_positive),
]
}
pub fn format<W: std::fmt::Write>(
&self,
opts: &PrintOptions,
state: PrintState,
f: &mut W,
) -> Result<(), std::fmt::Error> {
let data = self.get_global_data();
let (namespace, name) = (&data.namespace, &data.name[data.namespace.len() + 2..]);
if let Some(custom_print) = &data.custom_print
&& let Some(s) = custom_print(InlineVar::new(*self).as_view(), opts, &state)
{
f.write_str(&s)?;
return Ok(());
}
if opts.mode.is_latex() {
match self.get_id() {
Symbol::E_ID => f.write_char('e'),
Symbol::PI_ID => f.write_str("\\pi"),
Symbol::COS_ID => f.write_str("\\cos"),
Symbol::SIN_ID => f.write_str("\\sin"),
Symbol::EXP_ID => f.write_str("\\exp"),
Symbol::LOG_ID => f.write_str("\\log"),
_ => {
f.write_str(name)?;
if !opts.hide_all_namespaces {
f.write_fmt(format_args!("_{{\\tiny \text{{{namespace}}}}}"))
} else {
Ok(())
}
}
}
} else if opts.mode.is_typst() {
match self.get_id() {
Symbol::E_ID => f.write_char('e'),
Symbol::PI_ID => f.write_str("pi"),
Symbol::COS_ID => f.write_str("cos"),
Symbol::SIN_ID => f.write_str("sin"),
Symbol::EXP_ID => f.write_str("exp"),
Symbol::LOG_ID => f.write_str("log"),
_ => {
f.write_char('"')?;
if !opts.hide_all_namespaces {
f.write_fmt(format_args!("{}::{}", namespace, name))?;
} else {
f.write_str(name)?;
}
f.write_char('"')
}
}
} else {
if (!opts.hide_all_namespaces || opts.include_attributes)
&& !State::is_fixed_builtin(*self)
&& (opts.hide_namespace.as_deref() != Some(namespace) || opts.include_attributes)
{
if opts.color_namespace && opts.mode.is_symbolica() {
f.write_fmt(format_args!(
"{}",
AnsiWrap::new(namespace)
.dimmed()
.italic()
.color_mode(opts.color_mode)
))?;
if opts.include_attributes {
f.write_fmt(format_args!(
"{}",
AnsiWrap::new("::{").dimmed().color_mode(opts.color_mode)
))?;
let mut first = true;
for (x, t) in self.get_attributes_tuple_str() {
if t {
if !first {
f.write_fmt(format_args!(
"{}",
AnsiWrap::new(",").dimmed().color_mode(opts.color_mode)
))?;
}
first = false;
f.write_fmt(format_args!(
"{}",
AnsiWrap::new(x).dimmed().color_mode(opts.color_mode)
))?;
}
}
if !self.get_tags().is_empty() {
for tag in self.get_tags() {
if !first {
f.write_fmt(format_args!(
"{}",
AnsiWrap::new(",").dimmed().color_mode(opts.color_mode)
))?;
}
first = false;
f.write_fmt(format_args!(
"{}",
AnsiWrap::new(tag).dimmed().color_mode(opts.color_mode)
))?;
}
}
f.write_fmt(format_args!(
"{}",
AnsiWrap::new("}").dimmed().color_mode(opts.color_mode)
))?;
}
f.write_fmt(format_args!(
"{}",
AnsiWrap::new("::").dimmed().color_mode(opts.color_mode)
))?;
} else {
if opts.mode.is_mathematica() {
for part in namespace.split("::") {
let mut inside_full_form_unicode = false;
for c in part.split(Symbol::SEP_STR) {
if inside_full_form_unicode {
f.write_fmt(format_args!("\\[{}]", c))?;
} else {
f.write_str(c)?;
}
inside_full_form_unicode = !inside_full_form_unicode;
}
f.write_char('`')?;
}
} else {
f.write_fmt(format_args!("{namespace}::"))?;
}
if opts.mode.is_symbolica() && opts.include_attributes {
f.write_str("{")?;
let mut first = true;
for (x, t) in self.get_attributes_tuple_str() {
if t {
if !first {
f.write_char(',')?;
}
first = false;
f.write_str(x)?;
}
}
if !self.get_tags().is_empty() {
for tag in self.get_tags() {
if !first {
f.write_char(',')?;
}
first = false;
f.write_str(tag)?;
}
}
f.write_str("}::")?;
}
}
}
if opts.mode.is_symbolica() && opts.color_builtin_symbols && name.ends_with('_') {
f.write_fmt(format_args!(
"{}",
AnsiWrap::cyan(name).italic().color_mode(opts.color_mode)
))
} else if opts.mode.is_symbolica() && opts.color_builtin_symbols && self.is_builtin() {
f.write_fmt(format_args!(
"{}",
AnsiWrap::purple(name).color_mode(opts.color_mode)
))
} else if opts.mode.is_mathematica() {
if self.is_fixed_builtin() {
match self.get_id() {
Symbol::E_ID => f.write_str("E"),
Symbol::PI_ID => f.write_str("Pi"),
Symbol::COS_ID => f.write_str("Cos"),
Symbol::SIN_ID => f.write_str("Sin"),
Symbol::EXP_ID => f.write_str("Exp"),
Symbol::LOG_ID => f.write_str("Log"),
Symbol::SQRT_ID => f.write_str("Sqrt"),
Symbol::CONJ_ID => f.write_str("Conjugate"),
Symbol::DERIVATIVE_ID => f.write_str("Derivative"),
_ => f.write_str(name),
}
} else {
let mut inside_full_form_unicode = false;
for c in name.split(Symbol::SEP_STR) {
if inside_full_form_unicode {
f.write_fmt(format_args!("\\[{}]", c))?;
} else {
f.write_str(c)?;
}
inside_full_form_unicode = !inside_full_form_unicode;
}
Ok(())
}
} else {
f.write_str(name)
}
}
}
pub(crate) fn get_global_data(self) -> &'static SymbolData {
State::get_symbol_data(self)
}
}
#[derive(Clone, Hash, Eq, PartialOrd, Ord, Debug)]
#[cfg_attr(
feature = "bincode",
derive(bincode_trait_derive::Encode),
derive(bincode_trait_derive::Decode),
derive(bincode_trait_derive::BorrowDecodeFromDecode),
trait_decode(trait = crate::state::HasStateMap)
)]
pub enum Indeterminate {
Symbol(Symbol, InlineVar),
Function(Symbol, Atom),
}
impl std::fmt::Display for Indeterminate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Indeterminate::Symbol(v, _) => f.write_str(v.get_stripped_name()),
Indeterminate::Function(_, a) => std::fmt::Display::fmt(a, f),
}
}
}
impl From<Symbol> for Indeterminate {
fn from(i: Symbol) -> Indeterminate {
Indeterminate::Symbol(i, i.into())
}
}
impl PartialEq<Symbol> for Indeterminate {
fn eq(&self, other: &Symbol) -> bool {
match self {
Indeterminate::Symbol(s, _) => s == other,
_ => false,
}
}
}
impl From<Symbol> for BorrowedOrOwned<'_, Indeterminate> {
fn from(atom: Symbol) -> Self {
BorrowedOrOwned::Owned(Indeterminate::from(atom))
}
}
impl<T: AtomCore> PartialEq<T> for Indeterminate {
fn eq(&self, other: &T) -> bool {
self.as_view() == other.as_atom_view()
}
}
impl<T: Into<Coefficient>> From<T> for Atom {
fn from(t: T) -> Self {
Atom::num(t)
}
}
impl TryFrom<Atom> for Indeterminate {
type Error = String;
fn try_from(a: Atom) -> Result<Indeterminate, Self::Error> {
match a {
Atom::Var(v) => {
let s = v.get_symbol();
Ok(Indeterminate::Symbol(s, InlineVar::new(s)))
}
Atom::Fun(f) => Ok(Indeterminate::Function(f.get_symbol(), Atom::Fun(f))),
_ => Err(format!(
"Cannot convert {a} to a variable as it can be decomposed into a polynomial part"
)),
}
}
}
impl TryFrom<Atom> for BorrowedOrOwned<'_, Indeterminate> {
type Error = String;
fn try_from(atom: Atom) -> Result<Self, Self::Error> {
Ok(BorrowedOrOwned::Owned(Indeterminate::try_from(atom)?))
}
}
impl From<Indeterminate> for Atom {
fn from(val: Indeterminate) -> Self {
match val {
Indeterminate::Symbol(s, _) => Atom::var(s),
Indeterminate::Function(_, a) => a,
}
}
}
impl Indeterminate {
pub fn get_symbol(&self) -> Symbol {
match self {
Indeterminate::Symbol(s, _) => *s,
Indeterminate::Function(s, _) => *s,
}
}
pub fn as_view(&self) -> AtomView<'_> {
match self {
Indeterminate::Symbol(_, v) => v.as_view(),
Indeterminate::Function(_, a) => a.as_view(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AtomType {
Num,
Var,
Add,
Mul,
Pow,
Fun,
}
impl std::fmt::Display for AtomType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AtomType::Num => write!(f, "Num"),
AtomType::Var => write!(f, "Var"),
AtomType::Add => write!(f, "Add"),
AtomType::Mul => write!(f, "Mul"),
AtomType::Pow => write!(f, "Pow"),
AtomType::Fun => write!(f, "Fun"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SliceType {
Add,
Mul,
Arg,
One,
Pow,
Empty,
}
pub enum AtomView<'a> {
Num(NumView<'a>),
Var(VarView<'a>),
Fun(FunView<'a>),
Pow(PowView<'a>),
Mul(MulView<'a>),
Add(AddView<'a>),
}
impl Clone for AtomView<'_> {
fn clone(&self) -> Self {
*self
}
}
impl Copy for AtomView<'_> {}
impl Eq for AtomView<'_> {}
impl<'a, T> PartialEq<T> for AtomView<'a>
where
for<'b> &'b T: Into<AtomOrView<'a>>,
{
fn eq(&self, other: &T) -> bool {
self.get_data() == other.into().as_view().get_data()
}
}
impl<'a, T> PartialOrd<T> for AtomView<'a>
where
for<'b> &'b T: Into<AtomOrView<'a>>,
{
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
Some(self.cmp(&other.into().as_view()))
}
}
impl Ord for AtomView<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.cmp(other)
}
}
impl Hash for AtomView<'_> {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
AtomView::Num(a) => a.hash(state),
AtomView::Var(a) => a.hash(state),
AtomView::Fun(a) => a.hash(state),
AtomView::Pow(a) => a.hash(state),
AtomView::Mul(a) => a.hash(state),
AtomView::Add(a) => a.hash(state),
}
}
}
impl std::fmt::Display for AtomView<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
AtomPrinter::new(*self).fmt(f)
}
}
impl From<Symbol> for Atom {
fn from(symbol: Symbol) -> Atom {
Atom::var(symbol)
}
}
impl From<&Symbol> for Atom {
fn from(symbol: &Symbol) -> Atom {
Atom::var(*symbol)
}
}
impl From<AtomView<'_>> for Atom {
fn from(view: AtomView) -> Atom {
view.to_owned()
}
}
impl<'a> From<NumView<'a>> for AtomView<'a> {
fn from(n: NumView<'a>) -> AtomView<'a> {
AtomView::Num(n)
}
}
impl<'a> From<VarView<'a>> for AtomView<'a> {
fn from(n: VarView<'a>) -> AtomView<'a> {
AtomView::Var(n)
}
}
impl<'a> From<FunView<'a>> for AtomView<'a> {
fn from(n: FunView<'a>) -> AtomView<'a> {
AtomView::Fun(n)
}
}
impl<'a> From<MulView<'a>> for AtomView<'a> {
fn from(n: MulView<'a>) -> AtomView<'a> {
AtomView::Mul(n)
}
}
impl<'a> From<AddView<'a>> for AtomView<'a> {
fn from(n: AddView<'a>) -> AtomView<'a> {
AtomView::Add(n)
}
}
#[derive(Clone, Debug)]
pub enum AtomOrView<'a> {
Atom(Atom),
InlineVar(InlineVar),
View(AtomView<'a>),
}
impl std::fmt::Display for AtomOrView<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_view().fmt(f)
}
}
impl PartialEq for AtomOrView<'_> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_view().eq(&other.as_view())
}
}
impl Eq for AtomOrView<'_> {}
impl PartialOrd for AtomOrView<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AtomOrView<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.as_view().cmp(&other.as_view())
}
}
impl Hash for AtomOrView<'_> {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_view().hash(state)
}
}
impl<'a, T> From<T> for AtomOrView<'a>
where
T: Into<Coefficient>,
{
fn from(v: T) -> AtomOrView<'a> {
AtomOrView::Atom(Atom::num(v.into()))
}
}
impl<'a> From<Symbol> for AtomOrView<'a> {
fn from(s: Symbol) -> AtomOrView<'a> {
AtomOrView::InlineVar(InlineVar::new(s))
}
}
impl<'a> From<&'a Symbol> for AtomOrView<'a> {
fn from(s: &'a Symbol) -> AtomOrView<'a> {
(*s).into()
}
}
impl<'a> From<Atom> for AtomOrView<'a> {
fn from(a: Atom) -> AtomOrView<'a> {
AtomOrView::Atom(a)
}
}
impl<'a> From<&'a Atom> for AtomOrView<'a> {
fn from(a: &'a Atom) -> AtomOrView<'a> {
AtomOrView::View(a.as_view())
}
}
impl<'a> From<InlineVar> for AtomOrView<'a> {
fn from(a: InlineVar) -> AtomOrView<'a> {
AtomOrView::InlineVar(a)
}
}
impl<'a> From<&'a InlineVar> for AtomOrView<'a> {
fn from(a: &'a InlineVar) -> AtomOrView<'a> {
AtomOrView::InlineVar(*a)
}
}
impl<'a> From<AtomView<'a>> for AtomOrView<'a> {
fn from(a: AtomView<'a>) -> AtomOrView<'a> {
AtomOrView::View(a)
}
}
impl<'a> From<&AtomView<'a>> for AtomOrView<'a> {
fn from(a: &AtomView<'a>) -> AtomOrView<'a> {
AtomOrView::View(*a)
}
}
impl<'a> AtomOrView<'a> {
pub fn into_owned(self) -> Atom {
match self {
AtomOrView::Atom(a) => a,
AtomOrView::InlineVar(v) => Atom::var(v.get_symbol()),
AtomOrView::View(a) => a.to_owned(),
}
}
#[inline(always)]
pub fn as_view(&'a self) -> AtomView<'a> {
match self {
AtomOrView::Atom(a) => a.as_view(),
AtomOrView::InlineVar(v) => v.as_view(),
AtomOrView::View(a) => *a,
}
}
pub fn as_mut(&mut self) -> &mut Atom {
match self {
AtomOrView::Atom(a) => a,
AtomOrView::InlineVar(v) => {
let mut oa = Atom::default();
oa.set_from_view(&v.as_view());
*self = AtomOrView::Atom(oa);
match self {
AtomOrView::Atom(a) => a,
_ => unreachable!(),
}
}
AtomOrView::View(a) => {
let mut oa = Atom::default();
oa.set_from_view(a);
*self = AtomOrView::Atom(oa);
match self {
AtomOrView::Atom(a) => a,
_ => unreachable!(),
}
}
}
}
}
impl AtomView<'_> {
pub fn to_owned(&self) -> Atom {
let mut a = Atom::default();
a.set_from_view(self);
a
}
pub fn clone_into(&self, target: &mut Atom) {
target.set_from_view(self);
}
pub fn to_plain_string(&self) -> String {
format!("{}", self.printer(PrintOptions::file()))
}
pub fn nterms(&self) -> usize {
if let AtomView::Add(a) = self {
a.get_nargs()
} else {
1
}
}
pub fn with_stats<F: Fn(AtomView) -> Atom>(&self, op: F, o: &StatsOptions) -> Atom {
let start_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(std::time::Duration::from_secs(0));
let t = std::time::Instant::now();
let out = op(*self);
let dt = t.elapsed();
o.print(*self, out.as_view(), start_time, dt);
out
}
#[inline]
pub fn is_zero(&self) -> bool {
if let AtomView::Num(n) = self {
n.is_zero()
} else {
false
}
}
#[inline]
pub fn is_one(&self) -> bool {
if let AtomView::Num(n) = self {
n.is_one()
} else {
false
}
}
pub(crate) fn add_no_norm(&self, workspace: &Workspace, rhs: AtomView<'_>) -> RecycledAtom {
let mut e = workspace.new_atom();
let a = e.to_add();
a.extend(*self);
a.extend(rhs);
e
}
pub(crate) fn sub_no_norm(&self, workspace: &Workspace, rhs: AtomView<'_>) -> RecycledAtom {
let mut e = workspace.new_atom();
let a = e.to_add();
a.extend(*self);
a.extend(rhs.neg_no_norm(workspace).as_view());
e
}
pub(crate) fn mul_no_norm(&self, workspace: &Workspace, rhs: AtomView<'_>) -> RecycledAtom {
let mut e = workspace.new_atom();
let a = e.to_mul();
a.extend(*self);
a.extend(rhs);
e
}
pub(crate) fn pow_no_norm(&self, workspace: &Workspace, exp: AtomView<'_>) -> RecycledAtom {
let mut e = workspace.new_atom();
e.to_pow(*self, exp);
e
}
pub(crate) fn div_no_norm(&self, workspace: &Workspace, div: AtomView<'_>) -> RecycledAtom {
self.mul_no_norm(
workspace,
div.pow_no_norm(workspace, workspace.new_num(-1).as_view())
.as_view(),
)
}
pub(crate) fn neg_no_norm(&self, workspace: &Workspace) -> RecycledAtom {
self.mul_no_norm(workspace, workspace.new_num(-1).as_view())
}
pub fn add_with_ws_into(&self, workspace: &Workspace, rhs: AtomView<'_>, out: &mut Atom) {
self.add_normalized(rhs, workspace, out);
}
pub fn sub_with_ws_into(&self, workspace: &Workspace, rhs: AtomView<'_>, out: &mut Atom) {
self.sub_no_norm(workspace, rhs)
.as_view()
.normalize(workspace, out);
}
pub fn mul_with_ws_into(&self, workspace: &Workspace, rhs: AtomView<'_>, out: &mut Atom) {
self.mul_no_norm(workspace, rhs)
.as_view()
.normalize(workspace, out);
}
pub fn pow_with_ws_into(&self, workspace: &Workspace, exp: AtomView<'_>, out: &mut Atom) {
self.pow_no_norm(workspace, exp)
.as_view()
.normalize(workspace, out);
}
pub fn div_with_ws_into(&self, workspace: &Workspace, div: AtomView<'_>, out: &mut Atom) {
self.div_no_norm(workspace, div)
.as_view()
.normalize(workspace, out);
}
pub fn neg_with_ws_into(&self, workspace: &Workspace, out: &mut Atom) {
self.neg_no_norm(workspace)
.as_view()
.normalize(workspace, out);
}
pub fn get_byte_size(&self) -> usize {
match self {
AtomView::Num(n) => n.get_byte_size(),
AtomView::Var(v) => v.get_byte_size(),
AtomView::Fun(f) => f.get_byte_size(),
AtomView::Pow(p) => p.get_byte_size(),
AtomView::Mul(m) => m.get_byte_size(),
AtomView::Add(a) => a.get_byte_size(),
}
}
}
#[must_use]
#[derive(Clone)]
#[cfg_attr(
feature = "bincode",
derive(bincode_trait_derive::BorrowDecodeFromDecode),
trait_decode(trait = crate::state::HasStateMap)
)]
pub enum Atom {
Num(Num),
Var(Var),
Fun(Fun),
Pow(Pow),
Mul(Mul),
Add(Add),
Zero,
}
impl Atom {
pub const I_STR: &'static str = "𝑖";
pub fn i() -> Atom {
Atom::num(Complex::<Rational>::new_i())
}
pub fn zero() -> Atom {
Atom::Zero
}
pub fn one() -> Atom {
Atom::num(1)
}
}
impl Default for Atom {
#[inline]
fn default() -> Self {
Atom::Zero
}
}
impl std::fmt::Display for Atom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
AtomPrinter::new(self.as_view()).fmt(f)
}
}
impl std::fmt::Debug for Atom {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_view().fmt(fmt)
}
}
impl From<Num> for Atom {
fn from(n: Num) -> Atom {
Atom::Num(n)
}
}
impl From<Var> for Atom {
fn from(n: Var) -> Atom {
Atom::Var(n)
}
}
impl From<Add> for Atom {
fn from(n: Add) -> Atom {
Atom::Add(n)
}
}
impl From<Mul> for Atom {
fn from(n: Mul) -> Atom {
Atom::Mul(n)
}
}
impl From<Fun> for Atom {
fn from(n: Fun) -> Atom {
Atom::Fun(n)
}
}
impl Eq for Atom {}
impl Hash for Atom {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_view().hash(state)
}
}
impl Ord for Atom {
fn cmp(&self, other: &Self) -> Ordering {
self.as_view().cmp(&other.as_view())
}
}
impl<T> PartialEq<T> for Atom
where
for<'a> &'a T: Into<AtomOrView<'a>>,
{
fn eq(&self, other: &T) -> bool {
self.as_view() == other.into().as_view()
}
}
impl<T> PartialOrd<T> for Atom
where
for<'a> &'a T: Into<AtomOrView<'a>>,
{
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
Some(self.as_view().cmp(&other.into().as_view()))
}
}
impl Atom {
pub fn new() -> Atom {
Atom::default()
}
pub fn parse<T: AsRef<str>, U: Into<Cow<'static, str>>>(
input: T,
namespace: U,
settings: ParseSettings,
) -> Result<Atom, String> {
Self::parse_with_default_namespace(
DefaultNamespace {
namespace: namespace.into(),
data: input.as_ref(),
file: "".into(),
line: 0,
},
settings,
)
}
pub fn parse_with_default_namespace<T: AsRef<str>>(
input: DefaultNamespace<T>,
settings: ParseSettings,
) -> Result<Atom, String> {
let mut name_map = HashMap::default();
Workspace::get_local().with(|ws| {
let t = Token::parse_with_atom_info(
input.data.as_ref(),
settings,
Some((&input, &mut name_map, ws)),
)?;
let ns = DefaultNamespace {
data: "",
namespace: input.namespace,
file: input.file,
line: input.line,
};
drop(input.data);
t.to_atom(&ns, &mut name_map, ws)
})
}
#[inline]
pub fn var(id: Symbol) -> Atom {
Var::new(id).into()
}
#[inline]
pub fn num<T: Into<Coefficient>>(num: T) -> Atom {
let c = num.into();
if c.is_zero() {
Atom::Zero
} else {
Num::new(c).into()
}
}
#[inline]
pub fn is_zero(&self) -> bool {
self.as_view().is_zero()
}
#[inline]
pub fn is_one(&self) -> bool {
self.as_view().is_one()
}
pub fn nterms(&self) -> usize {
self.as_view().nterms()
}
pub fn to_plain_string(&self) -> String {
format!("{}", self.printer(PrintOptions::file()))
}
pub fn with_stats<F: Fn(AtomView) -> Atom>(&self, op: F, o: &StatsOptions) -> Atom {
self.as_view().with_stats(op, o)
}
pub fn repeat_map<F: Fn(AtomView) -> Atom>(&mut self, op: F) {
let mut res;
loop {
res = op(self.as_view());
if res == *self {
break;
}
std::mem::swap(self, &mut res);
}
}
#[inline]
pub fn to_num<T: Into<Coefficient>>(&mut self, coeff: T) -> &mut Num {
let buffer = std::mem::replace(self, Atom::Zero).into_raw();
*self = Atom::Num(Num::new_into(coeff.into(), buffer));
if let Atom::Num(n) = self {
n
} else {
unreachable!()
}
}
#[inline]
pub fn to_var(&mut self, id: Symbol) -> &mut Var {
let buffer = std::mem::replace(self, Atom::Zero).into_raw();
*self = Atom::Var(Var::new_into(id, buffer));
if let Atom::Var(n) = self {
n
} else {
unreachable!()
}
}
#[inline]
pub fn to_fun(&mut self, id: Symbol) -> &mut Fun {
let buffer = std::mem::replace(self, Atom::Zero).into_raw();
*self = Atom::Fun(Fun::new_into(id, buffer));
if let Atom::Fun(n) = self {
n
} else {
unreachable!()
}
}
#[inline]
pub fn to_pow(&mut self, base: AtomView, exp: AtomView) -> &mut Pow {
let buffer = std::mem::replace(self, Atom::Zero).into_raw();
*self = Atom::Pow(Pow::new_into(base, exp, buffer));
if let Atom::Pow(n) = self {
n
} else {
unreachable!()
}
}
#[inline]
pub fn to_mul(&mut self) -> &mut Mul {
let buffer = std::mem::replace(self, Atom::Zero).into_raw();
*self = Atom::Mul(Mul::new_into(buffer));
if let Atom::Mul(n) = self {
n
} else {
unreachable!()
}
}
#[inline]
pub fn to_add(&mut self) -> &mut Add {
let buffer = std::mem::replace(self, Atom::Zero).into_raw();
*self = Atom::Add(Add::new_into(buffer));
if let Atom::Add(n) = self {
n
} else {
unreachable!()
}
}
#[inline(always)]
pub fn into_raw(self) -> RawAtom {
match self {
Atom::Num(n) => n.into_raw(),
Atom::Var(v) => v.into_raw(),
Atom::Fun(f) => f.into_raw(),
Atom::Pow(p) => p.into_raw(),
Atom::Mul(m) => m.into_raw(),
Atom::Add(a) => a.into_raw(),
Atom::Zero => RawAtom::new(),
}
}
#[inline(always)]
pub fn set_from_view(&mut self, view: &AtomView) {
let buffer = std::mem::replace(self, Atom::Zero).into_raw();
match view {
AtomView::Num(n) => *self = Atom::Num(Num::from_view_into(n, buffer)),
AtomView::Var(v) => *self = Atom::Var(Var::from_view_into(v, buffer)),
AtomView::Fun(f) => *self = Atom::Fun(Fun::from_view_into(f, buffer)),
AtomView::Pow(p) => *self = Atom::Pow(Pow::from_view_into(p, buffer)),
AtomView::Mul(m) => *self = Atom::Mul(Mul::from_view_into(m, buffer)),
AtomView::Add(a) => *self = Atom::Add(Add::from_view_into(a, buffer)),
}
}
#[inline(always)]
pub fn as_view(&self) -> AtomView<'_> {
match self {
Atom::Num(n) => AtomView::Num(n.to_num_view()),
Atom::Var(v) => AtomView::Var(v.to_var_view()),
Atom::Fun(f) => AtomView::Fun(f.to_fun_view()),
Atom::Pow(p) => AtomView::Pow(p.to_pow_view()),
Atom::Mul(m) => AtomView::Mul(m.to_mul_view()),
Atom::Add(a) => AtomView::Add(a.to_add_view()),
Atom::Zero => AtomView::ZERO,
}
}
#[inline(always)]
pub(crate) fn set_normalized(&mut self, normalized: bool) {
match self {
Atom::Num(_) => {}
Atom::Var(_) => {}
Atom::Fun(a) => a.set_normalized(normalized),
Atom::Pow(a) => a.set_normalized(normalized),
Atom::Mul(a) => a.set_normalized(normalized),
Atom::Add(a) => a.set_normalized(normalized),
Atom::Zero => {}
}
}
}
#[derive(Clone)]
pub struct FunctionBuilder {
handle: RecycledAtom,
}
impl FunctionBuilder {
pub fn new(name: Symbol) -> FunctionBuilder {
let mut a = RecycledAtom::new();
a.to_fun(name);
FunctionBuilder { handle: a }
}
pub fn from_atom<'a, T: Into<AtomOrView<'a>>>(atom: T) -> FunctionBuilder {
Self::try_from_atom(atom).unwrap()
}
pub fn try_from_atom<'a, T: Into<AtomOrView<'a>>>(atom: T) -> Result<FunctionBuilder, String> {
let a = atom.into();
if let AtomView::Fun(_) = a.as_view() {
Ok(FunctionBuilder {
handle: a.into_owned().into(),
})
} else if let AtomView::Var(v) = a.as_view() {
Ok(FunctionBuilder::new(v.get_symbol()))
} else {
Err("Atom must be a function or variable".to_string())
}
}
pub fn add_arg<'a, T: Into<AtomOrView<'a>>>(mut self, arg: T) -> FunctionBuilder {
if let Atom::Fun(f) = self.handle.deref_mut() {
f.add_arg(arg.into().as_view());
}
self
}
pub fn add_args<'a, I, T>(mut self, args: I) -> FunctionBuilder
where
I: IntoIterator<Item = T>,
T: Into<AtomOrView<'a>>,
{
if let Atom::Fun(f) = self.handle.deref_mut() {
for a in args {
f.add_arg(a.into().as_view());
}
}
self
}
pub fn finish(self) -> Atom {
Workspace::get_local().with(|ws| {
let mut f = ws.new_atom();
self.handle.as_view().normalize(ws, &mut f);
f.into_inner()
})
}
}
pub trait FunctionArgument {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder;
}
pub trait FunctionArguments {
fn add_args_to_function_builder(self, f: FunctionBuilder) -> FunctionBuilder;
}
impl FunctionArguments for () {
fn add_args_to_function_builder(self, f: FunctionBuilder) -> FunctionBuilder {
f
}
}
macro_rules! impl_single_function_arguments {
($($ty:ty),+ $(,)?) => {
$(
impl FunctionArguments for $ty {
fn add_args_to_function_builder(self, f: FunctionBuilder) -> FunctionBuilder {
<$ty as FunctionArgument>::add_arg_to_function_builder(&self, f)
}
}
)+
};
}
impl_single_function_arguments!(
Atom,
&Atom,
&mut Atom,
AtomView<'_>,
&AtomView<'_>,
Symbol,
&Symbol,
Coefficient,
&Coefficient,
Integer,
Rational,
Float,
Complex<Rational>,
Complex<Float>,
i8,
&i8,
i16,
&i16,
i32,
&i32,
i64,
&i64,
i128,
&i128,
isize,
&isize,
u8,
&u8,
u16,
&u16,
u32,
&u32,
u64,
&u64,
u128,
&u128,
usize,
&usize,
f64,
&f64,
);
macro_rules! impl_function_arguments {
($(($ty:ident, $var:ident)),+) => {
impl<$($ty: FunctionArgument),+> FunctionArguments for ($($ty,)+) {
fn add_args_to_function_builder(self, f: FunctionBuilder) -> FunctionBuilder {
let ($($var,)+) = self;
$(
let f = FunctionArgument::add_arg_to_function_builder(&$var, f);
)+
f
}
}
};
}
impl_function_arguments!((A, a));
impl_function_arguments!((A, a), (B, b));
impl_function_arguments!((A, a), (B, b), (C, c));
impl_function_arguments!((A, a), (B, b), (C, c), (D, d));
impl_function_arguments!((A, a), (B, b), (C, c), (D, d), (E, e));
impl_function_arguments!((A, a), (B, b), (C, c), (D, d), (E, e), (F, f));
impl_function_arguments!((A, a), (B, b), (C, c), (D, d), (E, e), (F, f), (G, g));
impl_function_arguments!(
(A, a),
(B, b),
(C, c),
(D, d),
(E, e),
(F, f),
(G, g),
(H, h)
);
impl_function_arguments!(
(A, a),
(B, b),
(C, c),
(D, d),
(E, e),
(F, f),
(G, g),
(H, h),
(I, i)
);
impl_function_arguments!(
(A, a),
(B, b),
(C, c),
(D, d),
(E, e),
(F, f),
(G, g),
(H, h),
(I, i),
(J, j)
);
impl_function_arguments!(
(A, a),
(B, b),
(C, c),
(D, d),
(E, e),
(F, f),
(G, g),
(H, h),
(I, i),
(J, j),
(K, k)
);
impl_function_arguments!(
(A, a),
(B, b),
(C, c),
(D, d),
(E, e),
(F, f),
(G, g),
(H, h),
(I, i),
(J, j),
(K, k),
(L, l)
);
impl FunctionArgument for Atom {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder {
f.add_arg(self.as_view())
}
}
impl FunctionArgument for &Atom {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder {
f.add_arg(self.as_view())
}
}
impl FunctionArgument for &mut Atom {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder {
f.add_arg(self.as_view())
}
}
impl FunctionArgument for AtomView<'_> {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder {
f.add_arg(*self)
}
}
impl FunctionArgument for &AtomView<'_> {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder {
f.add_arg(**self)
}
}
impl FunctionArgument for Symbol {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder {
let t = InlineVar::new(*self);
f.add_arg(t.as_view())
}
}
impl FunctionArgument for &Symbol {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder {
let t = InlineVar::new(**self);
f.add_arg(t.as_view())
}
}
impl<T: Into<Coefficient> + Clone> FunctionArgument for T {
fn add_arg_to_function_builder(&self, f: FunctionBuilder) -> FunctionBuilder {
f.add_arg(Atom::num(self.clone()))
}
}
#[macro_export]
macro_rules! function {
($name: expr) => {
{
$crate::atom::FunctionBuilder::from_atom($name).finish()
}
};
($name: expr, $($id: expr),*) => {
{
let mut f = $crate::atom::FunctionBuilder::from_atom($name);
$(
f = $crate::atom::FunctionArgument::add_arg_to_function_builder(&$id, f);
)+
f.finish()
}
};
}
#[macro_export]
macro_rules! tag {
($name: expr) => {
if !$name.contains("::") {
let mut s = String::from($crate::namespace!());
s.push_str("::");
s.push_str($name.as_ref());
s
} else {
String::from($name)
}
};
}
#[macro_export]
macro_rules! symbol {
($id: expr) => {
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).build().unwrap_or_else(|e| panic!("{}", e))
};
($id: expr; $($attr: ident),*) => {
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(&[$($crate::atom::SymbolAttribute::$attr,)*]).build().unwrap_or_else(|e| panic!("{}", e))
};
($id: expr, $($a: tt = $value: expr),*) => {
{
let mut b = $crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id));
$(
b = $crate::symbol_set_attr!(b, $a = $value);
)+
b.build().unwrap_or_else(|e| panic!("{}", e))
}
};
($id: expr; $($attr: ident),+; $($a: ident = $value: expr),*) => {
{
let mut b = $crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(&[$($crate::atom::SymbolAttribute::$attr,)*]);
$(
b = $crate::symbol_set_attr!(b, $a = $value);
)+
b.build().unwrap_or_else(|e| panic!("{}", e))
}
};
($($id: expr),*) => {
{
(
$(
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).build().unwrap_or_else(|e| panic!("{}", e)),
)+
)
}
};
($($id: expr),*; tag = $tag: expr) => {
{
(
$(
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_tags(std::slice::from_ref(&$tag)).build().unwrap_or_else(|e| panic!("{}", e)),
)+
)
}
};
($($id: expr),*; tags = $tags: expr) => {
{
(
$(
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_tags($tags).build().unwrap_or_else(|e| panic!("{}", e)),
)+
)
}
};
($($id: expr),*; $($attr: ident),*) => {
{
macro_rules! gen_attr {
() => {
&[$($crate::atom::SymbolAttribute::$attr,)*]
};
}
(
$(
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(gen_attr!()).build().unwrap_or_else(|e| panic!("{}", e)),
)+
)
}
};
($($id: expr),*; $($attr: ident),*; tag = $tag: expr) => {
{
macro_rules! gen_attr {
() => {
&[$($crate::atom::SymbolAttribute::$attr,)*]
};
}
(
$(
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(gen_attr!()).with_tags(std::slice::from_ref(&$tag)).build().unwrap_or_else(|e| panic!("{}", e)),
)+
)
}
};
($($id: expr),*; $($attr: ident),*; tags = $tags: expr) => {
{
macro_rules! gen_attr {
() => {
&[$($crate::atom::SymbolAttribute::$attr,)*]
};
}
(
$(
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(gen_attr!()).with_tags($tags).build().unwrap_or_else(|e| panic!("{}", e)),
)+
)
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! symbol_set_attr {
() => {{}};
($b: expr, norm = $norm: expr) => {
$b.with_normalization_function($norm)
};
($b: expr, print = $print: expr) => {
$b.with_print_function($print)
};
($b: expr, der = $der: expr) => {
$b.with_derivative_function($der)
};
($b: expr, series = $series: expr) => {
$b.with_series_function($series)
};
($b: expr, data = $user_data: expr) => {
$b.with_user_data($user_data)
};
($b: expr, tag = $tag: expr) => {
$b.with_tags(std::slice::from_ref(&$tag))
};
($b: expr, tags = $tags: expr) => {
$b.with_tags($tags)
};
($b: expr, aliases = $aliases: expr) => {
$b.with_aliases($aliases)
};
($b: expr, eval = $eval: expr) => {
$b.with_evaluation_info($eval)
};
}
#[macro_export]
macro_rules! try_symbol {
($id: expr) => {
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).build()
};
($id: expr; $($attr: ident),*) => {
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(&[$($crate::atom::SymbolAttribute::$attr,)*]).build()
};
($id: expr, $($a: tt = $value: expr),*) => {
{
let mut b = $crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id));
$(
b = $crate::symbol_set_attr!(b, $a = $value);
)+
b.build()
}
};
($id: expr; $($attr: ident),+; $($a: ident = $value: expr),*) => {
{
let mut b = $crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(&[$($crate::atom::SymbolAttribute::$attr,)*]);
$(
b = $crate::symbol_set_attr!(b, $a = $value);
)+
b.build()
}
};
($($id: expr),*) => {
{
(
$(
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).build(),
)+
)
}
};
($($id: expr),*; $($attr: ident),*) => {
{
macro_rules! gen_attr {
() => {
&[$($crate::atom::SymbolAttribute::$attr,)*]
};
}
(
$(
$crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(gen_attr!()).build(),
)+
)
}
};
}
#[macro_export]
macro_rules! get_symbol {
($id: expr) => {
$crate::atom::Symbol::get_symbol($crate::wrap_symbol!($id))
};
($($id: expr),*) => {
{
(
$(
$crate::atom::Symbol::get_symbol($crate::wrap_symbol!($id)),
)+
)
}
};
}
#[macro_export]
macro_rules! symbol_group {
($($id: expr; $($attr: ident),*; $gen: expr),+) => {
$crate::try_symbol_group!($($id; $($attr),*; $gen),+).unwrap()
};
}
#[macro_export]
macro_rules! try_symbol_group {
($($id: expr; $($attr: ident),*; $gen: expr),+) => {
{
let mut v = vec![];
$(
let b = $crate::atom::SymbolBuilder::new($crate::wrap_symbol!($id)).with_attributes(&[$($crate::atom::SymbolAttribute::$attr,)*]).
with_generator($gen);
v.push(b);
)+
$crate::atom::SymbolBuilder::build_group(v)
}
};
}
#[macro_export]
macro_rules! parse {
($($all_args:tt)*) => {
$crate::try_parse!($($all_args)*).unwrap_or_else(|e| panic!("{}", e))
};
}
#[macro_export]
macro_rules! try_parse {
($s: expr) => {
$crate::atom::Atom::parse_with_default_namespace(
$crate::wrap_input!($s),
$crate::parser::ParseSettings::symbolica(),
)
};
($s: expr, Mathematica) => {{
$crate::atom::Atom::parse_with_default_namespace(
$crate::wrap_input!($s),
$crate::parser::ParseSettings::mathematica(),
)
}};
($s: expr, settings = $settings: expr) => {{ $crate::atom::Atom::parse_with_default_namespace($crate::wrap_input!($s), $settings) }};
($s: expr, default_namespace = $ns: expr) => {
$crate::atom::Atom::parse_with_default_namespace(
$crate::with_default_namespace!($s, $ns),
$crate::parser::ParseSettings::symbolica(),
)
};
($s: expr, Mathematica, default_namespace = $ns: expr) => {{
$crate::atom::Atom::parse_with_default_namespace(
$crate::with_default_namespace!($s, $ns),
$crate::parser::ParseSettings::mathematica(),
)
}};
($s: expr, settings = $settings: expr, default_namespace = $ns: expr) => {{
$crate::atom::Atom::parse_with_default_namespace(
$crate::with_default_namespace!($s, $ns),
$settings,
)
}};
}
#[macro_export]
macro_rules! parse_lit {
($s: expr) => {{
$crate::atom::Atom::parse_with_default_namespace(
$crate::wrap_input!(stringify!($s)),
$crate::parser::ParseSettings::symbolica(),
)
.unwrap_or_else(|e| panic!("{}", e))
}};
($s: expr, default_namespace = $ns: expr) => {{
$crate::atom::Atom::parse_with_default_namespace(
$crate::with_default_namespace!(stringify!($s), $ns),
$crate::parser::ParseSettings::symbolica(),
)
.unwrap_or_else(|e| panic!("{}", e))
}};
}
#[macro_export]
macro_rules! try_parse_lit {
($s: expr) => {{
$crate::atom::Atom::parse_with_default_namespace(
$crate::wrap_input!(stringify!($s)),
$crate::parser::ParseSettings::symbolica(),
)
}};
($s: expr, default_namespace = $ns: expr) => {{
$crate::atom::Atom::parse_with_default_namespace(
$crate::with_default_namespace!(stringify!($s), $ns),
$crate::parser::ParseSettings::symbolica(),
)
}};
}
impl Atom {
pub fn add_many<'a, I, T>(args: I) -> Atom
where
I: IntoIterator<Item = T>,
T: Into<AtomOrView<'a>>,
{
let args = args.into_iter().map(Into::into).collect::<Vec<_>>();
let mut out = Atom::new();
Workspace::get_local().with(|ws| {
AtomView::add_normalized_slice(&args, ws, &mut out);
});
out
}
pub fn mul_many<'a, I, T>(args: I) -> Atom
where
I: IntoIterator<Item = T>,
T: Into<AtomOrView<'a>>,
{
let mut out = Atom::new();
Workspace::get_local().with(|ws| {
let mut t = ws.new_atom();
let add = t.to_mul();
for a in args {
add.extend(a.into().as_atom_view());
}
t.as_view().normalize(ws, &mut out);
});
out
}
}
impl<'a, T> std::iter::FromIterator<T> for Atom
where
T: Into<AtomOrView<'a>>,
{
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Atom::add_many(iter)
}
}
impl<'a, T> std::iter::Sum<T> for Atom
where
T: Into<AtomOrView<'a>>,
{
fn sum<I: Iterator<Item = T>>(iter: I) -> Self {
Atom::add_many(iter)
}
}
impl<'a, T> std::iter::Product<T> for Atom
where
T: Into<AtomOrView<'a>>,
{
fn product<I: Iterator<Item = T>>(iter: I) -> Self {
Atom::mul_many(iter)
}
}
mod ops;
impl AsRef<Atom> for Atom {
fn as_ref(&self) -> &Atom {
self
}
}
#[cfg(test)]
mod test {
use crate::{
atom::{Atom, AtomCore, Symbol, UserData},
domains::{integer::Integer, rational::Rational},
};
use super::FunctionBuilder;
#[test]
fn parse_macro() {
assert_eq!(parse_lit!(x ^ 2 + 5 + f(x)), parse!("x ^ 2 + 5 + f(x)"));
}
#[test]
fn iterator_arithmetic() {
let (x, y, z) = symbol!("x", "y", "z");
let terms = [Atom::from(x), Atom::from(y), Atom::from(z)];
let collected: Atom = terms.iter().collect();
assert_eq!(collected, parse!("x+y+z"));
let sum: Atom = terms.iter().sum();
assert_eq!(sum, parse!("x+y+z"));
let product: Atom = terms.iter().product();
assert_eq!(product, parse!("x*y*z"));
let symbol_sum: Atom = [x, y, z].into_iter().sum();
assert_eq!(symbol_sum, parse!("x+y+z"));
let empty_sum: Atom = std::iter::empty::<Atom>().sum();
assert_eq!(empty_sum, Atom::Zero);
let empty_product: Atom = std::iter::empty::<Atom>().product();
assert_eq!(empty_product, Atom::num(1));
}
#[test]
fn debug() {
let x = parse!("v1+f1(v2)");
assert_eq!(
format!("{x:#?}"),
"AddView { data: [5, 17, 2, 13, 2, 1, 15, 3, 5, 0, 0, 0, 1, 45, 2, 1, 16] }"
);
assert_eq!(
x.get_all_symbols(true),
[symbol!("v1"), symbol!("v2"), symbol!("f1")]
.into_iter()
.collect(),
);
assert_eq!(x.as_view().get_byte_size(), 17);
}
#[test]
fn composition() {
let v1 = parse!("v1");
let v2 = parse!("v2");
let f1_id = symbol!("f1");
let f1 = function!(f1_id, v1, v2, Atom::num(2));
let r = (-(&v2 + &v1 + 2) * &v2 * 6).pow(5) / &v2.pow(&v1) * &f1 / 4;
let res = parse!("1/4*(v2^v1)^-1*(-6*v2*(v1+v2+2))^5*f1(v1,v2,2)");
assert_eq!(res, r);
}
#[test]
fn pow() {
let x = parse!("x");
let res = parse!("x^x");
assert_eq!(res, x.pow(symbol!("x")));
}
#[test]
fn building() {
let _ = FunctionBuilder::new(symbol!("a"))
.add_arg(1)
.add_args([1, 2])
.add_arg(symbol!("a"))
.add_args([symbol!("b")])
.add_args([parse!("a")])
.add_arg(parse!("a"))
.add_arg(parse!("a"))
.add_arg(parse!("a").as_view())
.finish();
}
#[test]
fn call_symbol_as_function() {
let (x, y) = symbol!("x", "y");
assert_eq!(x.call(()), function!(x));
assert_eq!(x.call(3), function!(x, 3));
assert_eq!(x.call(y), function!(x, y));
assert_eq!(x.call((1, 2, 3, y)), function!(x, 1, 2, 3, y));
assert_eq!(x.call_args([1, 2, 3]), function!(x, 1, 2, 3));
assert_eq!(x.call_args(1..=3), function!(x, 1, 2, 3));
}
#[test]
fn arithmetic_combinations() {
let (x, y) = symbol!("x", "y");
let xa = Atom::from(x);
let ya = Atom::from(y);
assert_eq!(&xa + y, parse!("x+y"));
assert_eq!(&xa - y, parse!("x-y"));
assert_eq!(&xa * y, parse!("x*y"));
assert_eq!(&xa / y, parse!("x/y"));
assert_eq!(xa.as_view() + y, parse!("x+y"));
assert_eq!(xa.as_view() - y, parse!("x-y"));
assert_eq!(xa.as_view() * y, parse!("x*y"));
assert_eq!(xa.as_view() / y, parse!("x/y"));
assert_eq!(x + ya.clone(), parse!("x+y"));
assert_eq!(x - ya.clone(), parse!("x-y"));
assert_eq!(x * ya.clone(), parse!("x*y"));
assert_eq!(x / ya.clone(), parse!("x/y"));
assert_eq!(x + &ya, parse!("x+y"));
assert_eq!(x - &ya, parse!("x-y"));
assert_eq!(x * &ya, parse!("x*y"));
assert_eq!(x / &ya, parse!("x/y"));
assert_eq!(x + ya.as_view(), parse!("x+y"));
assert_eq!(x - ya.as_view(), parse!("x-y"));
assert_eq!(x * ya.as_view(), parse!("x*y"));
assert_eq!(x / ya.as_view(), parse!("x/y"));
assert_eq!(x + 2, parse!("x+2"));
assert_eq!(x - 2, parse!("x-2"));
assert_eq!(x * 2, parse!("2*x"));
assert_eq!(x / 2, parse!("x/2"));
assert_eq!(&xa + 2, parse!("x+2"));
assert_eq!(&xa - 2, parse!("x-2"));
assert_eq!(&xa * 2, parse!("2*x"));
assert_eq!(&xa / 2, parse!("x/2"));
assert_eq!(2 + x, parse!("x+2"));
assert_eq!(2 - x, parse!("2-x"));
assert_eq!(x * y, parse!("x*y"));
assert_eq!(2 * x, parse!("2*x"));
assert_eq!(2 / x, parse!("2/x"));
assert_eq!(2 + x, parse!("x+2"));
assert_eq!(2 - &xa, parse!("2-x"));
assert_eq!(2 * &xa, parse!("2*x"));
assert_eq!(2 / &xa, parse!("2/x"));
assert_eq!(Integer::from(2) * xa.as_view(), parse!("2*x"));
assert_eq!(Rational::from((2, 3)) * xa.as_view(), parse!("2/3*x"));
assert_eq!(Integer::from(2) * xa.clone(), parse!("2*x"));
assert_eq!(Rational::from((2, 3)) * xa.clone(), parse!("2/3*x"));
let mut a = Atom::from(x);
a += y;
assert_eq!(a, parse!("x+y"));
}
#[test]
fn user_data() {
let s = crate::symbol!("user_data::test", data = UserData::Integer(42));
assert_eq!(s.get_data(), &UserData::Integer(42));
}
#[test]
fn symbol_import_export() {
let s = crate::symbol!("export_test::s", data = UserData::String("test".to_owned()));
let mut e = vec![];
s.export(&mut e).unwrap();
let imported = Symbol::import(&mut e.as_slice()).unwrap();
assert_eq!(s, imported);
assert_eq!(s.get_data(), imported.get_data());
}
}