#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{
collections::HashMap,
env,
io::{Read, Write},
net::{TcpListener, TcpStream, ToSocketAddrs},
process::abort,
sync::atomic::{AtomicBool, Ordering::Relaxed},
thread::ThreadId,
time::{Duration, SystemTime},
};
use once_cell::sync::OnceCell;
use tinyjson::JsonValue;
#[cfg(feature = "python_export")]
pub mod api;
#[cfg(not(feature = "python_export"))]
mod api;
pub mod atom;
pub mod coefficient;
mod collect;
mod derivative;
pub mod domains;
pub mod evaluate;
mod expand;
pub mod id;
mod normalize;
pub mod parser;
pub mod poly;
pub mod printer;
pub mod solve;
pub mod state;
pub mod streaming;
pub mod tensors;
pub mod transcendental;
pub mod transformer;
pub mod utils;
pub mod prelude {
pub use crate::{
LicenseManager, OperationCount, create_hyperdual_from_components,
create_hyperdual_single_derivative, function, get_symbol, hide_namespace, initialize,
namespace, parse, parse_lit, symbol, symbol_group, tag, try_parse, try_parse_lit,
try_symbol, try_symbol_group,
};
pub use crate::atom::{
Atom, AtomCore, AtomOrView, AtomType, AtomView, EvaluationError, EvaluationInfo,
FunctionArgument, FunctionBuilder, Indeterminate, InlineNum, InlineVar,
PolynomialConversionError, SeriesError, Symbol, TensorCanonicalizationError, UserData,
UserDataKey,
};
pub use crate::coefficient::{Coefficient, CoefficientView, ConvertToRing};
pub use crate::domains::{
EuclideanDomain, Field, Ring, RingOps, Set,
algebraic_number::{AlgebraicExtension, AlgebraicNumber},
atom::AtomField,
factorized_rational_polynomial::FactorizedRationalPolynomial,
finite_field::{FiniteField, FiniteFieldCore, FiniteFieldElement, Z2, Zp, Zp64},
float::{
Complex, Constructible, DoubleFloat, ErrorPropagatingFloat, F64, Float, FloatLike,
Real, RealLike, SingleFloat,
},
integer::{Integer, IntegerRing, Z},
rational::{Q, Rational},
rational_polynomial::{
LogarithmicIntegralTerm, RationalIntegral, RationalPolynomial, RationalPolynomialField,
},
};
pub use crate::evaluate::{
BatchEvaluator, CompileOptions, CompiledCode, CompiledComplexEvaluator, CompiledNumber,
CompiledRealEvaluator, CompiledSimdComplexEvaluator, CompiledSimdRealEvaluator, Dualizer,
EvaluationDomain, EvaluationFn, EvaluatorBuilder, EvaluatorLoader, ExportNumber,
ExportSettings, ExportedCode, ExportedInstructions, ExpressionEvaluator, ExternalFunction,
FunctionMap, InlineASM, JITCompilationSettings, OptimizationSettings, Vectorize,
};
pub use crate::id::{
AtomTreeIterator, BorrowReplacement, Condition, ConditionResult, Match, MatchError,
MatchSettings, MatchStack, Pattern, PatternAtomTreeIterator, PatternRestriction, Relation,
ReplaceBuilder, ReplaceIterator, ReplaceSettings, ReplaceWith, Replacement,
WildcardRestriction,
};
pub use crate::numerical_integration::{
ContinuousGrid, DiscreteGrid, Grid, MonteCarloRng, Sample,
};
pub use crate::parser::{ParseMode, ParseSettings, Token};
pub use crate::poly::{
Exponent, GrevLexOrder, IntoVariableMap, LexOrder, MonomialOrder, PolyVariable,
PositiveExponent,
factor::Factorize,
gcd::PolynomialGCD,
groebner::GroebnerBasis,
polynomial::{MultivariatePolynomial, PolynomialRing},
series::{Series, SeriesDepth},
univariate::{UnivariatePolynomial, UnivariatePolynomialRing},
};
pub use crate::printer::{
AtomPrinter, CanonicalOrderingSettings, PrintMode, PrintOptions, PrintState,
};
pub use crate::solve::SolveError;
pub use crate::state::State;
pub use crate::streaming::{TermStreamer, TermStreamerConfig};
pub use crate::tensors::{
CanonicalTensor,
matrix::{Matrix, Vector},
};
pub use crate::transcendental::TranscendentalFunctions;
pub use crate::transformer::Transformer;
}
pub use graphica as graph; #[doc(hidden)]
pub use inventory as _inventory;
pub use numerica::*;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct OperationCount {
pub additions: usize,
pub multiplications: usize,
pub inversions: usize,
pub function_calls: usize,
}
impl OperationCount {
pub fn new(
additions: usize,
multiplications: usize,
inversions: usize,
function_calls: usize,
) -> Self {
Self {
additions,
multiplications,
inversions,
function_calls,
}
}
pub fn add_integer_power(&mut self, exponent: i64) {
if exponent < 0 {
self.inversions += 1;
}
self.multiplications += exponent.unsigned_abs().saturating_sub(1) as usize;
}
pub fn add_function_call(&mut self) {
self.function_calls += 1;
}
}
impl std::ops::Add for OperationCount {
type Output = OperationCount;
fn add(self, rhs: Self) -> Self::Output {
OperationCount {
additions: self.additions + rhs.additions,
multiplications: self.multiplications + rhs.multiplications,
inversions: self.inversions + rhs.inversions,
function_calls: self.function_calls + rhs.function_calls,
}
}
}
impl std::ops::AddAssign for OperationCount {
fn add_assign(&mut self, rhs: Self) {
self.additions += rhs.additions;
self.multiplications += rhs.multiplications;
self.inversions += rhs.inversions;
self.function_calls += rhs.function_calls;
}
}
impl std::fmt::Display for OperationCount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} +, {} ×, {} x⁻¹, {} f(·)",
self.additions, self.multiplications, self.inversions, self.function_calls
)
}
}
use crate::printer::AnsiWrap;
#[cfg(feature = "faster_alloc")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
static LICENSE_KEY: OnceCell<String> = OnceCell::new();
static LICENSE_MANAGER: OnceCell<LicenseManager> = OnceCell::new();
static LICENSED: AtomicBool = LicenseManager::init();
pub struct GlobalSettings {
pub initialize_tracing: AtomicBool,
pub use_hu_monagan_poly_gcd: AtomicBool,
pub force_hu_monagan_poly_gcd: AtomicBool,
}
pub static GLOBAL_SETTINGS: GlobalSettings = GlobalSettings {
initialize_tracing: AtomicBool::new(true),
use_hu_monagan_poly_gcd: AtomicBool::new(true),
force_hu_monagan_poly_gcd: AtomicBool::new(false),
};
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
if $crate::GLOBAL_SETTINGS.initialize_tracing.load(std::sync::atomic::Ordering::Relaxed) {
let _ = tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.from_env_lossy(),
)
.try_init();
$crate::GLOBAL_SETTINGS.initialize_tracing.store(false, std::sync::atomic::Ordering::Relaxed);
}
tracing::error!($($arg)*);
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
if $crate::GLOBAL_SETTINGS.initialize_tracing.load(std::sync::atomic::Ordering::Relaxed) {
let _ = tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.from_env_lossy(),
)
.try_init();
$crate::GLOBAL_SETTINGS.initialize_tracing.store(false, std::sync::atomic::Ordering::Relaxed);
}
tracing::warn!($($arg)*);
};
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
if $crate::GLOBAL_SETTINGS.initialize_tracing.load(std::sync::atomic::Ordering::Relaxed) {
let _ = tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.from_env_lossy(),
)
.try_init();
$crate::GLOBAL_SETTINGS.initialize_tracing.store(false, std::sync::atomic::Ordering::Relaxed);
}
tracing::info!($($arg)*);
};
}
#[allow(dead_code)]
pub struct LicenseManager {
lock: Option<TcpListener>,
core_limit: Option<usize>,
pid: u32,
thread_id: ThreadId,
has_license: bool,
}
const MULTIPLE_INSTANCE_WARNING: &str = "┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cannot start new unlicensed Symbolica instance since there is already another one running on the machine. │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────┘"
;
const RESOLVE_ERROR: &str = "
┌───────────────────────────────────────────────────────────┐
│ Could not resolve the IP of the Symbolica license server. │
│ │
│ Please check your DNS configuration. │
└───────────────────────────────────────────────────────────┘";
const CONNECTION_ERROR: &str = "
┌────────────────────────────────────────────────┐
│ Could not connect to Symbolica license server. │
│ │
│ Some networks block traffic to uncommon ports. │
│ Consider switching networks or using a VPN. │
└────────────────────────────────────────────────┘";
const NETWORK_ERROR: &str = "
┌───────────────────────────────────────────────────┐
│ Connection to Symbolica license server timed out. │
│ │
│ Please check your network configuration. │
└───────────────────────────────────────────────────┘";
const ACTIVATION_ERROR: &str = "
┌──────────────────────────────────────────┐
│ Could not activate the Symbolica license │
└──────────────────────────────────────────┘";
const MISSING_LICENSE_ERROR: &str = "
┌───────────────────────────────┐
│ Symbolica license key missing │
└───────────────────────────────┘";
impl Default for LicenseManager {
fn default() -> Self {
Self::new()
}
}
const OEM_LICENSE_KEY: Option<&str> = option_env!("SYMBOLICA_OEM_LICENSE");
#[macro_export]
macro_rules! activate_oem_license {
($key: literal) => {{
const KEY2: [u32; 6] = {
let mut h: u32 = 5381;
let b = env!("CARGO_CRATE_NAME").as_bytes();
let mut i = 0;
while i < b.len() {
h = h.wrapping_mul(33).wrapping_add(b[i] as u32);
i += 1;
}
[124124564, 26352342, 63345, 3812471234, 23523, h]
};
symbolica::LicenseManager::set_oem_license_key($key, &KEY2).unwrap_or_else(|e| {
panic!("{}", e);
});
}};
}
impl LicenseManager {
pub(crate) fn new() -> LicenseManager {
let pid = std::process::id();
let thread_id = std::thread::current().id();
match Self::check_license_key() {
Ok(()) => {
return LicenseManager {
lock: None,
core_limit: None,
pid,
thread_id,
has_license: true,
};
}
Err(e) => {
if !e.contains("missing") {
eprintln!("{e}");
}
}
}
if env::var("SYMBOLICA_HIDE_BANNER").is_err() {
println!(
"┌────────────────────────────────────────────────────────┐
│ You are running a restricted Symbolica instance. │
│ │
│ This mode is only permitted for non-commercial use and │
│ is limited to one instance and core per machine. │
│ │
│ {} can easily acquire a {} license key │
│ that unlocks all cores and removes this banner: │
│ │
│ from symbolica import * │
│ request_hobbyist_license('YOUR_NAME', 'YOUR_EMAIL') │
│ │
│ All other users can obtain a free 30-day trial key: │
│ │
│ from symbolica import * │
│ request_trial_license('NAME', 'EMAIL', 'EMPLOYER') │
│ │
│ See https://symbolica.io/docs/get_started.html#license │
└────────────────────────────────────────────────────────┘",
AnsiWrap::new("Hobbyists").bold(),
AnsiWrap::new("free").bold(),
);
}
let port = env::var("SYMBOLICA_PORT").unwrap_or_else(|_| "12011".to_owned());
match TcpListener::bind(format!("127.0.0.1:{port}")) {
Ok(o) => {
rayon::ThreadPoolBuilder::new()
.num_threads(1)
.build_global()
.unwrap();
drop(o);
std::thread::spawn(move || {
loop {
let new_port =
env::var("SYMBOLICA_PORT").unwrap_or_else(|_| "12011".to_owned());
if port != new_port {
println!("{MULTIPLE_INSTANCE_WARNING}");
abort();
}
match TcpListener::bind(format!("127.0.0.1:{port}")) {
Ok(_) => {
std::thread::sleep(Duration::from_secs(1));
}
Err(_) => {
println!("{MULTIPLE_INSTANCE_WARNING}");
abort();
}
}
}
});
LicenseManager {
lock: None,
core_limit: Some(1),
pid,
thread_id,
has_license: false,
}
}
Err(_) => {
println!("{MULTIPLE_INSTANCE_WARNING}");
abort();
}
}
}
const fn init() -> AtomicBool {
AtomicBool::new(false)
}
fn check_license_key() -> Result<(), String> {
let key = LICENSE_KEY
.get()
.cloned()
.or(env::var("SYMBOLICA_LICENSE").ok());
let Some(mut key) = key else {
std::thread::spawn(|| {
let mut m: HashMap<String, JsonValue> = HashMap::default();
m.insert(
"version".to_owned(),
env!("CARGO_PKG_VERSION").to_owned().into(),
);
let mut v = JsonValue::from(m).stringify().unwrap();
v.push('\n');
if let Ok(mut stream) = Self::connect() {
let _ = stream.write_all(v.as_bytes());
};
});
return Err(MISSING_LICENSE_ERROR.to_owned());
};
if key.contains('#') {
let mut a = key.split('#');
let f1 = a.next().ok_or_else(|| ACTIVATION_ERROR.to_owned())?;
let f2 = a.next().ok_or_else(|| ACTIVATION_ERROR.to_owned())?;
let f3 = a.next().ok_or_else(|| ACTIVATION_ERROR.to_owned())?;
let mut h: u32 = 5381;
for b in f2.as_bytes() {
h = h.wrapping_mul(33).wrapping_add(*b as u32);
}
for b in f3.as_bytes() {
h = h.wrapping_mul(33).wrapping_add(*b as u32);
}
let h = format!("{h:x}");
if f1 != h {
Err(ACTIVATION_ERROR.to_owned())?;
}
let t = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let t2 = u64::from_str_radix(f2, 16)
.map_err(|_| ACTIVATION_ERROR.to_owned())
.unwrap();
if t > t2 {
Err("┌───────────────────────────────────┐
│ The Symbolica license has expired │
└───────────────────────────────────┘"
.to_owned())?;
}
key = f3.to_owned();
std::thread::spawn(|| {
if let Err(e) = Self::check_registration(key)
&& e.contains("expired")
{
println!("{e}");
abort();
}
});
} else {
Self::check_registration(key)?;
}
LICENSED.store(true, Relaxed);
Ok(())
}
fn connect() -> Result<TcpStream, String> {
let mut ip = ("symbolica.io", 12012)
.to_socket_addrs()
.map_err(|e| format!("{RESOLVE_ERROR}\nError: {e}"))?;
let Some(n) = ip.next() else {
return Err(RESOLVE_ERROR.to_owned());
};
let stream = match TcpStream::connect_timeout(&n, Duration::from_secs(5)) {
Ok(stream) => stream,
Err(_) => {
return Err(CONNECTION_ERROR.to_owned());
}
};
stream
.set_read_timeout(Some(Duration::from_secs(5)))
.map_err(|e| e.to_string())?;
stream
.set_write_timeout(Some(Duration::from_secs(5)))
.map_err(|e| e.to_string())?;
Ok(stream)
}
fn check_registration(key: String) -> Result<(), String> {
let mut stream = Self::connect()?;
let mut m: HashMap<String, JsonValue> = HashMap::default();
m.insert(
"version".to_owned(),
env!("CARGO_PKG_VERSION").to_owned().into(),
);
m.insert("license".to_owned(), key.into());
let mut v = JsonValue::from(m).stringify().unwrap();
v.push('\n');
stream
.write_all(v.as_bytes())
.map_err(|e| format!("{NETWORK_ERROR}\nError: {e}"))?;
let mut buf = Vec::new();
stream
.read_to_end(&mut buf)
.map_err(|e| format!("{NETWORK_ERROR}\nError: {e}"))?;
let read_str =
std::str::from_utf8(&buf).map_err(|e| format!("{NETWORK_ERROR}\nError: {e}"))?;
if read_str == "{\"status\":\"ok\"}\n" {
Ok(())
} else if read_str.is_empty() {
Err("┌──────────────────────────────────────────┐
│ Could not activate the Symbolica license │
└──────────────────────────────────────────┘"
.to_owned())
} else {
let message: JsonValue = read_str[..read_str.len() - 1]
.parse()
.map_err(|e| format!("{NETWORK_ERROR}\nError: {e}"))?;
let message_parsed: &HashMap<_, _> = message
.get()
.ok_or_else(|| format!("{NETWORK_ERROR}\nError: Empty response"))?;
let status: &String = message_parsed
.get("status")
.unwrap()
.get()
.ok_or_else(|| format!("{NETWORK_ERROR}\nError: missing status"))?;
Err(format!(
"┌──────────────────────────────────────────┐
│ Could not activate the Symbolica license │
└──────────────────────────────────────────┘
Error: {status}",
))
}
}
#[inline(always)]
fn check() {
if LICENSED.load(Relaxed) {
return;
}
Self::check_impl();
}
fn check_impl() {
let manager = LICENSE_MANAGER.get_or_init(LicenseManager::new);
if manager.has_license {
return;
}
let pid = std::process::id();
let thread_id = std::thread::current().id();
if manager.pid != pid || manager.thread_id != thread_id {
println!("{MULTIPLE_INSTANCE_WARNING}");
abort();
}
}
pub fn set_license_key(key: &str) -> Result<(), String> {
if LICENSE_KEY.get_or_init(|| key.to_owned()) != key {
Err("Different license key cannot be set in same session")?;
}
Self::check_license_key()
}
pub fn set_oem_license_key(
key1: &'static str,
key2: &'static [u32; 6],
) -> Result<(), &'static str> {
let Some(oom_key) = OEM_LICENSE_KEY else {
return Err("OEM license key not set");
};
if !oom_key.starts_with("SYMBOLICA_OEM_") {
return Err("Invalid OEM license key");
}
if !key1.starts_with("SYMBOLICA_OEM_KEY_") {
return Err("Invalid OEM license key part");
}
let mut h: u32 = 5381;
for b in oom_key.as_bytes() {
h = h.wrapping_mul(33).wrapping_add(*b as u32);
}
for b in key2 {
h = h.wrapping_mul(33).wrapping_add(*b);
}
if key1 == format!("SYMBOLICA_OEM_KEY_{h:x}") {
LICENSED.store(true, Relaxed);
std::thread::spawn(|| {
if let Err(e) = Self::check_registration(oom_key.to_owned())
&& e.contains("Unknown license")
{
println!("{e}");
abort();
}
});
Ok(())
} else {
Err("Invalid OEM license key: key does not match")
}
}
pub fn is_licensed() -> bool {
LICENSED.load(Relaxed) || Self::check_license_key().is_ok()
}
pub fn get_version() -> &'static str {
env!("SYMBOLICA_VERSION")
}
fn request_license_email(data: HashMap<String, JsonValue>) -> Result<(), String> {
let mut stream = Self::connect()?;
let mut v = JsonValue::from(data).stringify().unwrap();
v.push('\n');
stream
.write_all(v.as_bytes())
.map_err(|e| format!("{NETWORK_ERROR}\nError: {e}"))?;
let mut buf = Vec::new();
stream
.read_to_end(&mut buf)
.map_err(|e| format!("{NETWORK_ERROR}\nError: {e}"))?;
let read_str = std::str::from_utf8(&buf).map_err(|_| "Bad server response".to_string())?;
if read_str == "{\"status\":\"email sent\"}\n" {
Ok(())
} else if read_str.is_empty() {
Err("Empty response".to_owned())
} else {
let message: JsonValue = read_str[..read_str.len() - 1]
.parse()
.map_err(|_| "Bad server response".to_string())?;
let message_parsed: &HashMap<_, _> = message
.get()
.ok_or_else(|| "Bad server response".to_string())?;
let status: &String = message_parsed
.get("status")
.unwrap()
.get()
.ok_or_else(|| "Bad server response".to_string())?;
Err(status.clone())
}
}
pub fn request_hobbyist_license(name: &str, email: &str) -> Result<(), String> {
let mut m: HashMap<String, JsonValue> = HashMap::default();
m.insert("name".to_owned(), name.to_owned().into());
m.insert("email".to_owned(), email.to_owned().into());
m.insert("type".to_owned(), "hobbyist".to_owned().into());
Self::request_license_email(m)
}
pub fn request_trial_license(name: &str, email: &str, company: &str) -> Result<(), String> {
let mut m: HashMap<String, JsonValue> = HashMap::default();
m.insert("name".to_owned(), name.to_owned().into());
m.insert("email".to_owned(), email.to_owned().into());
m.insert("company".to_owned(), company.to_owned().into());
m.insert("type".to_owned(), "trial".to_owned().into());
Self::request_license_email(m)
}
pub fn request_sublicense(
name: &str,
email: &str,
company: &str,
super_license: &str,
) -> Result<(), String> {
let mut m: HashMap<String, JsonValue> = HashMap::default();
m.insert("name".to_owned(), name.to_owned().into());
m.insert("email".to_owned(), email.to_owned().into());
m.insert("company".to_owned(), company.to_owned().into());
m.insert("type".to_owned(), "sublicense".to_owned().into());
m.insert("super_license".to_owned(), super_license.to_owned().into());
Self::request_license_email(m)
}
pub fn get_license_key(email: &str) -> Result<(), String> {
let mut m: HashMap<String, JsonValue> = HashMap::default();
m.insert("email".to_owned(), email.to_owned().into());
Self::request_license_email(m)
}
}