use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::array::{Array, Buf, Data, Layout};
use crate::complex::{self as cx, Cx};
use crate::dtype::DType;
use crate::error::{Error, ErrorKind, Result, Span};
use crate::exact::{self, Ext, Rat};
use crate::fmt::FmtOpts;
use crate::frontend::{ComplexOrder, Rules};
use crate::par;
use crate::simd::multiversioned;
pub const RANK_INF: i64 = i64::MAX;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Agreement {
LeadingPrefix,
ExactOrScalar,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Tol {
pub ct: f64,
pub by_smaller: bool,
}
impl Tol {
pub const EXACT: Tol = Tol { ct: 0.0, by_smaller: true };
pub const J: Tol = Tol { ct: 5.684_341_886_080_802e-14, by_smaller: true };
pub const APL: Tol = Tol { ct: 1e-13, by_smaller: false };
#[inline(always)]
pub fn eq(self, a: f64, b: f64) -> bool {
if a == b {
return true;
}
let s = if self.by_smaller {
a.abs().min(b.abs())
} else {
a.abs().max(b.abs())
};
(a - b).abs() < self.ct * s
}
#[inline(always)]
pub fn is_j(self) -> bool {
self.by_smaller
}
#[inline(always)]
pub fn is_zero(self, y: f64) -> bool {
self.is_j() && y.abs() < self.ct
}
#[inline(always)]
pub fn lt(self, a: f64, b: f64) -> bool {
a < b && !self.eq(a, b)
}
#[inline(always)]
pub fn le(self, a: f64, b: f64) -> bool {
a <= b || self.eq(a, b)
}
#[inline]
pub fn eq_cx(self, a: Cx, b: Cx) -> bool {
if a == b {
return true;
}
let (ma, mb) = (cx::abs(a), cx::abs(b));
let s = if self.by_smaller { ma.min(mb) } else { ma.max(mb) };
cx::abs(cx::sub(a, b)) < self.ct * s
}
#[inline(always)]
pub fn floor(self, y: f64) -> f64 {
let c = y.ceil();
if self.eq(y, c) {
c
} else {
y.floor()
}
}
#[inline(always)]
pub fn ceil(self, y: f64) -> f64 {
let f = y.floor();
if self.eq(y, f) {
f
} else {
y.ceil()
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct EvalCfg {
pub agreement: Agreement,
pub fmt: FmtOpts,
pub tol: Tol,
pub rules: Rules,
}
impl EvalCfg {
pub(crate) fn pure<R>(self, f: impl FnOnce(&mut Ctx<'_>) -> R) -> R {
let mut sink = |_: &str| debug_assert!(false, "a pure verb wrote to the output sink");
let mut env = Env::new(Vec::new());
f(&mut Ctx { cfg: self, out: &mut sink, inp: None, env: &mut env, device: None })
}
}
pub const RECURSION_LIMIT: usize = 64;
pub struct Env {
globals: HashMap<String, Array>,
frames: Vec<HashMap<String, Array>>,
running: Vec<std::sync::Arc<crate::ir::ExplicitDef>>,
verbs: HashMap<String, Verb>,
args: Vec<Array>,
}
impl Env {
pub fn new(args: Vec<Array>) -> Env {
Env {
globals: HashMap::new(),
frames: Vec::new(),
running: Vec::new(),
verbs: HashMap::new(),
args,
}
}
pub fn get(&self, name: &str) -> Option<Array> {
if let Some(frame) = self.frames.last() && let Some(v) = frame.get(name) {
return Some(v.clone());
}
self.globals.get(name).cloned()
}
pub fn assign(&mut self, name: String, value: Array, scope: crate::ir::Scope) {
if scope == crate::ir::Scope::LocalDefault && self.get(&name).is_some() {
return;
}
let target = match (scope, self.frames.last_mut()) {
(crate::ir::Scope::Local | crate::ir::Scope::LocalDefault, Some(frame)) => frame,
_ => &mut self.globals,
};
target.insert(name, value);
}
pub fn define(&mut self, name: String, verb: Verb) {
self.verbs.insert(name, verb);
}
pub fn undefine(&mut self, name: &str) {
self.verbs.remove(name);
}
pub fn verb(&self, name: &str) -> Option<&Verb> {
self.verbs.get(name)
}
pub fn arg(&self, i: usize) -> Result<Array> {
self.args
.get(i)
.cloned()
.ok_or_else(|| Error::internal("a parameter was read where none is bound"))
}
pub fn enter(
&mut self,
frame: HashMap<String, Array>,
def: std::sync::Arc<crate::ir::ExplicitDef>,
span: Span,
) -> Result<()> {
if self.frames.len() >= RECURSION_LIMIT {
return Err(Error::new(
ErrorKind::Domain,
format!("explicit definitions called each other more than {RECURSION_LIMIT} deep"),
Some(span),
)
.note("a definition that recurses needs a case that stops"));
}
self.frames.push(frame);
self.running.push(def);
Ok(())
}
pub fn leave(&mut self) -> HashMap<String, Array> {
self.running.pop();
self.frames.pop().unwrap_or_default()
}
pub fn current_def(&self) -> Option<std::sync::Arc<crate::ir::ExplicitDef>> {
self.running.last().cloned()
}
}
pub type InputFn<'a> = Option<&'a mut dyn FnMut() -> Option<String>>;
pub fn reborrow_input<'s, 'a: 's>(inp: &'s mut InputFn<'a>) -> InputFn<'s> {
match inp {
Some(f) => Some(&mut **f),
None => None,
}
}
pub struct Ctx<'a> {
pub cfg: EvalCfg,
pub out: &'a mut dyn FnMut(&str),
pub inp: InputFn<'a>,
pub env: &'a mut Env,
pub device: Option<&'a crate::device::Device>,
}
const MAX_NESTING: usize = 400;
thread_local! {
static NESTING: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
pub(crate) fn check_nesting(depth: usize, span: Span) -> Result<()> {
if depth > MAX_NESTING {
return Err(Error::new(
ErrorKind::Limit,
format!("this program nests more than {MAX_NESTING} applications deep"),
Some(span),
));
}
Ok(())
}
pub(crate) struct Nesting;
impl Nesting {
pub(crate) fn enter(span: Span) -> Result<Nesting> {
let depth = NESTING.with(|c| {
let d = c.get() + 1;
c.set(d);
d
});
if depth > MAX_NESTING {
NESTING.with(|c| c.set(c.get() - 1));
return Err(Error::new(
ErrorKind::Limit,
format!("this program nests more than {MAX_NESTING} applications deep"),
Some(span),
));
}
Ok(Nesting)
}
}
impl Drop for Nesting {
fn drop(&mut self) {
NESTING.with(|c| c.set(c.get().saturating_sub(1)));
}
}
impl Ctx<'_> {
fn with_tol<R>(&mut self, tol: Tol, f: impl FnOnce(&mut Ctx<'_>) -> R) -> R {
let cfg = EvalCfg { tol, ..self.cfg };
f(&mut Ctx {
cfg,
out: &mut *self.out,
inp: reborrow_input(&mut self.inp),
env: &mut *self.env,
device: self.device,
})
}
pub(crate) fn read_line(&mut self, span: Span) -> Result<String> {
let Some(read) = self.inp.as_deref_mut() else {
return Err(Error::new(
ErrorKind::Value,
"this expression reads input, and this run has no input source attached",
Some(span),
)
.note("attach one with Program::run_io (Rust), input= (Python), or jay_run_io (C)"));
};
read().ok_or_else(|| {
Error::new(ErrorKind::Value, "the input has ended: there is no line to read", Some(span))
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScalarMonad {
Conj,
Neg,
Signum,
Recip,
Sqrt,
Exp,
Abs,
Floor,
Ceil,
Not,
OneMinus,
Inc,
Dec,
Double,
Halve,
Square,
Ln,
Pi,
Factorial,
Imaginary,
Polar,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScalarDyad {
Add,
Sub,
Mul,
DivJ,
DivApl,
Min,
Max,
Pow,
Residue,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
Lcm,
Gcd,
Log,
Root,
Circle,
Binomial,
MakeComplex,
PolarBy,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Enclose {
Always,
ExceptSimpleScalar,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MonadOp {
Scalar(ScalarMonad),
ShapeOf,
Tally,
Ravel,
TransposeAxes,
Catalogue,
AtomicRep,
RazeIn,
Head,
Behead,
Tail,
Curtail,
Reverse,
Nub,
GradeUp { origin: i64 },
GradeDown { origin: i64 },
IotaJ,
IotaApl { origin: i64 },
Echo,
ReadStream,
TypeCode,
Same,
Format,
DecodeBits,
EncodeBits,
Itemize,
TableOf,
Enclose(Enclose),
Open,
Raze,
First,
Enlist,
Depth,
Indices { origin: i64, boxed_coords: bool },
Steps,
ToExact,
NthPrime,
PrimeFactors,
MatrixInverse,
Roll { origin: i64, fixed: bool, float_at_zero: bool },
ComplexParts { polar: bool },
SelfClassify,
NubSieve,
Unicode { pass_chars: bool },
Symbols,
Words,
Nest,
LevelOf,
MapPaths,
PolyRoots,
PolyDeriv,
AnagramIndex,
CycleForm,
Split,
Execute { apl: bool },
NotYet(&'static str),
None,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DyadOp {
Scalar(ScalarDyad),
Reshape,
Take,
Drop,
Right,
Left,
Rotate,
AppendLeading,
AppendLast,
IndexOf { origin: i64 },
MemberJ,
MemberApl,
From,
Match,
NotMatch,
GradeSelect { down: bool },
Copy,
Decode,
Encode,
CollateGrade { down: bool, origin: i64 },
TransposeJ,
TransposeApl,
DecodeApl,
EncodeApl,
Laminate,
Link,
Strand,
IntervalIndex { offset: i64, closed: bool },
IndexOfLast { origin: i64 },
MatrixDivide,
PartitionEnclose,
Squad { origin: i64 },
SelectAxis { axis: usize, rank: usize, origin: i64 },
Fetch,
PolyEval,
PolyIntegral,
FormatSpec,
FormatSpecJ,
ParseNumbers,
SequentialMachine,
TruthTable(u8),
ExactForm,
Deal { origin: i64, fixed: bool },
Boolean(BoolDyad),
Less,
Union,
Intersect,
AnagramFrom,
Permute,
FindSeq,
UnicodeForm,
PrimeMeta,
PrimeExponents,
SymbolForm,
Pick { origin: i64 },
Expand,
WriteStream,
NotYet(&'static str),
None,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BoolDyad {
Nor,
Nand,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Prim {
pub name: &'static str,
pub monad: MonadOp,
pub dyad: DyadOp,
pub ranks: [i64; 3],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WindowKind {
Prefix,
Suffix,
Scan,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Power {
Times(u64),
Converge,
Each(Vec<u64>),
ConvergeTrace,
}
const CONVERGE_LIMIT: usize = 1 << 20;
pub type MemoCache = Arc<std::sync::Mutex<HashMap<Vec<u64>, Array>>>;
#[derive(Clone, Debug)]
pub enum Verb {
Prim(Prim),
Rank(Box<Verb>, [i64; 3]),
Reduce(Box<Verb>),
Windowed(Box<Verb>, WindowKind),
Commute(Box<Verb>),
PowerN(Box<Verb>, Power),
Fork(Box<Verb>, Box<Verb>, Box<Verb>),
NounFork(Array, Box<Verb>, Box<Verb>),
Hook(Box<Verb>, Box<Verb>),
Atop(Box<Verb>, Box<Verb>),
Compose(Box<Verb>, Box<Verb>),
BondLeft(Array, Box<Verb>),
BondRight(Box<Verb>, Array),
Each(Box<Verb>, Enclose),
Fit(Box<Verb>, f64),
Amend(Array),
AmendVerb(Box<Verb>),
ShiftFill(Array),
Memo(Box<Verb>, MemoCache),
Level { u: Box<Verb>, level: i64, spread: bool },
Characteristics(Box<Verb>),
Before(Box<Verb>, Box<Verb>),
UserDerived { def: Box<Verb>, alpha: Box<Verb>, omega: Option<Box<Verb>> },
KeyPairs(Box<Verb>),
Key(Box<Verb>),
Cut(Box<Verb>, i64),
PowerV(Box<Verb>, Box<Verb>),
PowerUntil(Box<Verb>, Box<Verb>),
AlongAxis(Box<Verb>, usize),
Explicit(Arc<crate::ir::ExplicitDef>),
SelfRef,
Named(String),
WithObverse(Box<Verb>, Box<Verb>),
Agenda(Vec<Verb>, Box<Verb>),
Adverse(Box<Verb>, Box<Verb>),
Hypergeometric { num: Vec<crate::complex::Cx>, den: Vec<crate::complex::Cx> },
Beside(Box<Verb>, Box<Verb>),
Stencil(Box<Verb>, Vec<i64>),
Evoke(Vec<Verb>, i64),
InnerProduct { u: Box<Verb>, v: Box<Verb>, apl: bool },
}
impl Verb {
pub fn ranks(&self) -> [i64; 3] {
match self {
Verb::Prim(p) => p.ranks,
Verb::Rank(_, r) => *r,
Verb::Windowed(_, WindowKind::Prefix) => [RANK_INF, 0, RANK_INF],
Verb::Each(..) => [0, 0, 0],
Verb::Fit(v, _) => v.ranks(),
Verb::Amend(_)
| Verb::AmendVerb(_)
| Verb::ShiftFill(_)
| Verb::Level { .. }
| Verb::Characteristics(_)
| Verb::UserDerived { .. }
| Verb::KeyPairs(_)
| Verb::Key(_)
| Verb::Cut(..)
| Verb::PowerV(..)
| Verb::PowerUntil(..)
| Verb::AlongAxis(..) => [RANK_INF, RANK_INF, RANK_INF],
Verb::Memo(v, _) => v.ranks(),
Verb::WithObverse(v, _) | Verb::Adverse(v, _) => v.ranks(),
Verb::Beside(..) => [RANK_INF, RANK_INF, RANK_INF],
Verb::Hypergeometric { .. } => [0, 0, 0],
Verb::InnerProduct { .. } => [2, RANK_INF, RANK_INF],
_ => [RANK_INF, RANK_INF, RANK_INF],
}
}
pub fn name(&self) -> String {
match self {
Verb::Prim(p) => p.name.to_string(),
Verb::Rank(v, r) => format!("{}\"{}", v.name(), rank_str(*r)),
Verb::Reduce(v) => format!("{}/", v.name()),
Verb::Windowed(v, WindowKind::Suffix) => format!("{}\\.", v.name()),
Verb::Windowed(v, _) => format!("{}\\", v.name()),
Verb::Commute(v) => format!("{}~", v.name()),
Verb::PowerN(v, Power::Converge) => format!("{}^:_", v.name()),
Verb::PowerN(v, Power::Times(n)) => format!("{}^:{n}", v.name()),
Verb::PowerN(v, Power::Each(_)) => format!("{}^:n", v.name()),
Verb::PowerN(v, Power::ConvergeTrace) => format!("{}^:a:", v.name()),
Verb::Fork(f, g, h) => format!("({} {} {})", f.name(), g.name(), h.name()),
Verb::NounFork(_, g, h) => format!("(n {} {})", g.name(), h.name()),
Verb::Hook(f, g) => format!("({} {})", f.name(), g.name()),
Verb::Atop(f, g) => format!("({}@:{})", f.name(), g.name()),
Verb::Compose(f, g) => format!("({}&:{})", f.name(), g.name()),
Verb::BondLeft(_, v) => format!("(n&{})", v.name()),
Verb::BondRight(v, _) => format!("({}&n)", v.name()),
Verb::Each(v, Enclose::Always) => format!("({}&.>)", v.name()),
Verb::Each(v, _) => format!("({}¨)", v.name()),
Verb::Fit(v, n) => format!("{}!.{n}", v.name()),
Verb::Amend(_) => "(m})".to_string(),
Verb::AmendVerb(v) => format!("({}}})", v.name()),
Verb::ShiftFill(_) => "|.!.n".to_string(),
Verb::Characteristics(v) => format!("{} b.", v.name()),
Verb::Before(f, g) => format!("({}⍛{})", f.name(), g.name()),
Verb::KeyPairs(v) => format!("{}⌸", v.name()),
Verb::UserDerived { def, alpha, omega } => match omega {
Some(g) => format!("({} {} {})", alpha.name(), def.name(), g.name()),
None => format!("({} {})", alpha.name(), def.name()),
},
Verb::Memo(v, _) => format!("{} M.", v.name()),
Verb::Level { u, level, spread } => {
format!("{} {} {level}", u.name(), if *spread { "S:" } else { "L:" })
}
Verb::Key(v) => format!("{}/.", v.name()),
Verb::Cut(v, n) => format!("{};.{n}", v.name()),
Verb::PowerV(v, w) => format!("{}^:{}", v.name(), w.name()),
Verb::PowerUntil(v, w) => format!("{}⍣{}", v.name(), w.name()),
Verb::AlongAxis(v, k) => format!("{}[{k}]", v.name()),
Verb::Explicit(d) => d.name.clone(),
Verb::SelfRef => "$:".to_string(),
Verb::Named(n) => n.clone(),
Verb::WithObverse(v, w) => format!("({}:.{})", v.name(), w.name()),
Verb::Adverse(v, w) => format!("({}::{})", v.name(), w.name()),
Verb::Beside(f, g) => format!("({}∘{})", f.name(), g.name()),
Verb::Hypergeometric { num, den } => {
format!("({} H. {})", cx_list(num), cx_list(den))
}
Verb::Agenda(vs, w) => {
let names: Vec<String> = vs.iter().map(Verb::name).collect();
format!("({}@.{})", names.join("`"), w.name())
}
Verb::Evoke(vs, n) => {
let names: Vec<String> = vs.iter().map(Verb::name).collect();
format!("({}`:{n})", names.join("`"))
}
Verb::Stencil(u, w) => {
let sizes: Vec<String> = w.iter().map(i64::to_string).collect();
format!("({}⌺{})", u.name(), sizes.join(" "))
}
Verb::InnerProduct { u, v, .. } => format!("({} . {})", u.name(), v.name()),
}
}
pub fn uses_tolerance(&self) -> bool {
match self {
Verb::Prim(p) => {
matches!(
p.monad,
MonadOp::Scalar(ScalarMonad::Floor)
| MonadOp::Scalar(ScalarMonad::Ceil)
| MonadOp::Nub
) || matches!(
p.dyad,
DyadOp::Scalar(
ScalarDyad::Eq
| ScalarDyad::Ne
| ScalarDyad::Lt
| ScalarDyad::Le
| ScalarDyad::Gt
| ScalarDyad::Ge
) | DyadOp::Match
| DyadOp::NotMatch
| DyadOp::MemberJ
| DyadOp::MemberApl
| DyadOp::IndexOf { .. }
| DyadOp::IndexOfLast { .. }
)
}
Verb::Rank(v, _)
| Verb::Reduce(v)
| Verb::Windowed(v, _)
| Verb::Commute(v)
| Verb::PowerN(v, _)
| Verb::BondLeft(_, v)
| Verb::BondRight(v, _)
| Verb::Each(v, _)
| Verb::Fit(v, _)
| Verb::Key(v)
| Verb::Cut(v, _)
| Verb::AlongAxis(v, _) => v.uses_tolerance(),
Verb::PowerV(v, w) | Verb::PowerUntil(v, w) => {
v.uses_tolerance() || w.uses_tolerance()
}
Verb::Amend(_)
| Verb::AmendVerb(_)
| Verb::ShiftFill(_)
| Verb::Characteristics(_)
| Verb::Explicit(_)
| Verb::SelfRef
| Verb::Named(_)
| Verb::Hypergeometric { .. } => false,
Verb::Memo(v, _) | Verb::Level { u: v, .. } => v.uses_tolerance(),
Verb::WithObverse(v, _) => v.uses_tolerance(),
Verb::Adverse(v, w) | Verb::Beside(v, w) | Verb::Before(v, w) => {
v.uses_tolerance() || w.uses_tolerance()
}
Verb::KeyPairs(v) => v.uses_tolerance(),
Verb::UserDerived { def, alpha, omega } => {
def.uses_tolerance()
|| alpha.uses_tolerance()
|| omega.as_ref().is_some_and(|g| g.uses_tolerance())
}
Verb::Agenda(vs, w) => {
w.uses_tolerance() || vs.iter().any(Verb::uses_tolerance)
}
Verb::Evoke(vs, _) => vs.iter().any(Verb::uses_tolerance),
Verb::Stencil(u, _) => u.uses_tolerance(),
Verb::InnerProduct { u, v, .. } => u.uses_tolerance() || v.uses_tolerance(),
Verb::Fork(f, g, h) => {
f.uses_tolerance() || g.uses_tolerance() || h.uses_tolerance()
}
Verb::NounFork(_, g, h)
| Verb::Hook(g, h)
| Verb::Atop(g, h)
| Verb::Compose(g, h) => g.uses_tolerance() || h.uses_tolerance(),
}
}
pub fn is_pure(&self) -> bool {
match self {
Verb::Prim(p) => {
!matches!(
p.monad,
MonadOp::Echo | MonadOp::Roll { .. } | MonadOp::ReadStream
) && !matches!(p.dyad, DyadOp::Deal { .. } | DyadOp::WriteStream)
}
Verb::Rank(v, _)
| Verb::Reduce(v)
| Verb::Windowed(v, _)
| Verb::Commute(v)
| Verb::PowerN(v, _) => v.is_pure(),
Verb::Fork(f, g, h) => f.is_pure() && g.is_pure() && h.is_pure(),
Verb::NounFork(_, g, h)
| Verb::Hook(g, h)
| Verb::Atop(g, h)
| Verb::Compose(g, h) => g.is_pure() && h.is_pure(),
Verb::BondLeft(_, v) | Verb::BondRight(v, _) | Verb::Each(v, _) | Verb::Fit(v, _) => {
v.is_pure()
}
Verb::Key(v) | Verb::Cut(v, _) | Verb::AlongAxis(v, _) => v.is_pure(),
Verb::Hypergeometric { .. } => true,
Verb::PowerV(v, w) | Verb::PowerUntil(v, w) => v.is_pure() && w.is_pure(),
Verb::WithObverse(v, _) => v.is_pure(),
Verb::Adverse(v, w) | Verb::Beside(v, w) | Verb::Before(v, w) => {
v.is_pure() && w.is_pure()
}
Verb::KeyPairs(v) => v.is_pure(),
Verb::UserDerived { .. } => false,
Verb::Agenda(vs, w) => w.is_pure() && vs.iter().all(Verb::is_pure),
Verb::Evoke(vs, _) => vs.iter().all(Verb::is_pure),
Verb::Stencil(u, _) => u.is_pure(),
Verb::InnerProduct { u, v, .. } => u.is_pure() && v.is_pure(),
Verb::Amend(_) | Verb::ShiftFill(_) | Verb::Characteristics(_) => true,
Verb::AmendVerb(v) | Verb::Level { u: v, .. } => v.is_pure(),
Verb::Memo(v, _) => v.is_pure(),
Verb::Explicit(_) | Verb::SelfRef | Verb::Named(_) => false,
}
}
pub fn monad(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let _depth = Nesting::enter(span)?;
if y.is_row_major() {
return self.monad_rows(y, ctx, span);
}
match self.monad_columns(y, ctx, span) {
Some(r) => r,
None => self.monad_rows(&y.to_row_major(), ctx, span),
}
}
fn monad_rows(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
debug_assert!(y.is_row_major());
match self {
Verb::Prim(p) => {
if let MonadOp::Scalar(op) = p.monad {
return scalar_monad(op, y, ctx.cfg, span);
}
if p.monad == MonadOp::Open && is_mixed_simple(y) {
return Ok(y.clone());
}
let frame_rank = y.rank() - effective_rank(p.ranks[0], y.rank());
if frame_rank == 0 {
return monad_op(p, y, ctx, span);
}
let frame = y.shape[..frame_rank].to_vec();
let n: usize = frame.iter().product();
let cells = each_cell(n, y.count(), self.is_pure(), ctx, |i, c| {
monad_op(p, &y.cell_at(frame_rank, i), c, span)
})?;
assemble(&frame, cells, span)
}
Verb::Rank(v, r) => {
let frame_rank = y.rank() - effective_rank(r[0], y.rank());
if frame_rank == 0 {
return v.monad(y, ctx, span);
}
if let Some(a) = reduce_vector_cells(v, y, frame_rank) {
return Ok(a);
}
let frame = y.shape[..frame_rank].to_vec();
let n: usize = frame.iter().product();
let cells = each_cell(n, y.count(), self.is_pure(), ctx, |i, c| {
v.monad(&y.cell_at(frame_rank, i), c, span)
})?;
assemble(&frame, cells, span)
}
Verb::Reduce(v) => reduce(v, y, ctx, span),
Verb::Windowed(v, kind) => {
runs(v, y, *kind == WindowKind::Suffix, ctx, span)
}
Verb::Commute(v) => v.dyad(y, y, ctx, span),
Verb::PowerN(v, p) => power(v, p.clone(), None, y, ctx, span),
Verb::Fork(f, g, h) => {
let l = f.monad(y, ctx, span)?;
let r = h.monad(y, ctx, span)?;
g.dyad(&l, &r, ctx, span)
}
Verb::NounFork(n, g, h) => {
let r = h.monad(y, ctx, span)?;
g.dyad(n, &r, ctx, span)
}
Verb::Hook(f, g) => {
let r = g.monad(y, ctx, span)?;
f.dyad(y, &r, ctx, span)
}
Verb::Atop(f, g) | Verb::Compose(f, g) => {
let r = g.monad(y, ctx, span)?;
f.monad(&r, ctx, span)
}
Verb::BondLeft(m, v) => v.dyad(m, y, ctx, span),
Verb::BondRight(v, n) => v.dyad(y, n, ctx, span),
Verb::Each(u, rule) => {
let n = y.count();
let cells = each_cell(n, n, self.is_pure(), ctx, |i, c| {
let opened = open_cell(&atom(y, i));
Ok(enclose(&u.monad(&opened, c, span)?, *rule))
})?;
assemble(&y.shape, cells, span)
}
Verb::Fit(v, n) => {
let tol = Tol { ct: *n, ..ctx.cfg.tol };
ctx.with_tol(tol, |c| v.monad(y, c, span))
}
Verb::Amend(m) => {
if m.rank() != 0 || y.rank() > 1 {
return Err(Error::new(
ErrorKind::Rank,
"selecting with m} takes one index into a list",
Some(span),
));
}
from_index(m, y, span)
}
Verb::AmendVerb(u) => {
let m = u.monad(y, ctx, span)?;
Verb::Amend(m).monad(y, ctx, span)
}
Verb::ShiftFill(fill) => shift_fill(&Array::scalar_i64(-1), y, fill, span),
Verb::Memo(u, cache) => memoised(u, cache, None, y, ctx, span),
Verb::Characteristics(u) => characteristics(u, y, span),
Verb::Before(f, g) => {
let l = f.monad(y, ctx, span)?;
g.dyad(&l, y, ctx, span)
}
Verb::KeyPairs(u) => key_pairs(u, y, None, ctx, span),
Verb::UserDerived { def, alpha, omega } => {
with_operands(alpha, omega.as_deref(), ctx, |c| def.monad(y, c, span))
}
Verb::Level { u, level, spread } => {
at_level(u, *level, *spread, y, ctx, span)
}
Verb::Key(u) => oblique(u, y, ctx, span),
Verb::Cut(u, n) => cut(u, None, y, *n, ctx, span),
Verb::PowerV(u, v) => power_v(u, v, None, y, ctx, span),
Verb::PowerUntil(u, v) => power_until(u, v, y, ctx, span),
Verb::AlongAxis(u, k) => along_axis(u, None, y, *k, ctx, span),
Verb::Explicit(d) => crate::ir::call_explicit(d, None, y, ctx, span),
Verb::SelfRef => {
let d = self_ref(ctx, span)?;
crate::ir::call_explicit(&d, None, y, ctx, span)
}
Verb::Named(n) => named_verb(ctx, n, span)?.monad(y, ctx, span),
Verb::WithObverse(v, _) => v.monad(y, ctx, span),
Verb::Adverse(v, w) => match v.monad(y, ctx, span) {
Err(e) if e.kind != ErrorKind::NotYet => w.monad(y, ctx, span),
other => other,
},
Verb::Beside(f, g) => {
let r = g.monad(y, ctx, span)?;
f.monad(&r, ctx, span)
}
Verb::Hypergeometric { num, den } => hypergeometric(num, den, y, span),
Verb::Agenda(vs, w) => {
agenda_pick(vs, w, None, y, ctx, span)?.monad(y, ctx, span)
}
Verb::Evoke(vs, n) => evoke(vs, *n, None, y, ctx, span),
Verb::Stencil(u, w) => stencil(u, w, y, ctx, span),
Verb::InnerProduct { u, v, apl } => determinant(u, v, *apl, y, ctx, span),
}
}
fn monad_columns(&self, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Option<Result<Array>> {
debug_assert!(!y.is_row_major());
match self {
Verb::Prim(p) => match p.monad {
MonadOp::Scalar(op) => Some(scalar_monad(op, y, ctx.cfg, span)),
MonadOp::ShapeOf | MonadOp::Tally => Some(monad_op(p, y, ctx, span)),
MonadOp::TransposeAxes => Some(Ok(transpose_axes(y))),
_ => None,
},
Verb::Reduce(v) => reduce_columns(v, y).map(Ok),
Verb::Rank(v, r) => {
if y.rank() != effective_rank(r[0], y.rank()) + 1 {
return None;
}
reduce_rows_columns(v, y).map(Ok)
}
_ => None,
}
}
pub fn dyad(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let _depth = Nesting::enter(span)?;
if x.is_row_major() && y.is_row_major() {
return self.dyad_rows(x, y, ctx, span);
}
if let Some(layout) = self.elementwise_layout(x, y) {
return Ok(self.dyad_rows(x, y, ctx, span)?.with_layout(layout));
}
self.dyad_rows(&x.to_row_major(), &y.to_row_major(), ctx, span)
}
fn elementwise_layout(&self, x: &Array, y: &Array) -> Option<Layout> {
let Verb::Prim(p) = self else { return None };
if !matches!(p.dyad, DyadOp::Scalar(_)) {
return None;
}
if x.rank() == 0 {
return Some(y.layout());
}
if y.rank() == 0 {
return Some(x.layout());
}
(x.shape == y.shape && x.layout() == y.layout()).then(|| x.layout())
}
fn dyad_rows(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
match self {
Verb::Prim(_) | Verb::Rank(_, _) | Verb::Each(..) => {
self.dyad_ranked(x, y, ctx, span)
}
Verb::Windowed(_, WindowKind::Prefix) => self.dyad_ranked(x, y, ctx, span),
Verb::Windowed(u, WindowKind::Suffix) => outfix(u, x, y, ctx, span),
Verb::Windowed(_, WindowKind::Scan) => {
Err(Error::not_yet("dyadic scan (x f\\ y)", span))
}
Verb::Commute(v) => v.dyad(y, x, ctx, span),
Verb::PowerN(v, p) => power(v, p.clone(), Some(x), y, ctx, span),
Verb::Reduce(v) => table(v, x, y, ctx, span),
Verb::Fork(f, g, h) => {
let l = f.dyad(x, y, ctx, span)?;
let r = h.dyad(x, y, ctx, span)?;
g.dyad(&l, &r, ctx, span)
}
Verb::NounFork(n, g, h) => {
let r = h.dyad(x, y, ctx, span)?;
g.dyad(n, &r, ctx, span)
}
Verb::Hook(f, g) => {
let r = g.monad(y, ctx, span)?;
f.dyad(x, &r, ctx, span)
}
Verb::Atop(f, g) => {
let r = g.dyad(x, y, ctx, span)?;
f.monad(&r, ctx, span)
}
Verb::Compose(f, g) => {
let l = g.monad(x, ctx, span)?;
let r = g.monad(y, ctx, span)?;
f.dyad(&l, &r, ctx, span)
}
Verb::Fit(v, n) => {
let tol = Tol { ct: *n, ..ctx.cfg.tol };
ctx.with_tol(tol, |c| v.dyad(x, y, c, span))
}
Verb::Amend(m) => amend(m, x, y, span),
Verb::AmendVerb(u) => {
let m = u.dyad(x, y, ctx, span)?;
amend(&m, x, y, span)
}
Verb::ShiftFill(fill) => shift_fill(x, y, fill, span),
Verb::Memo(u, cache) => memoised(u, cache, Some(x), y, ctx, span),
Verb::Characteristics(_) => {
Err(Error::domain("u b. has no dyadic meaning", span))
}
Verb::Before(f, g) => {
let l = f.monad(x, ctx, span)?;
g.dyad(&l, y, ctx, span)
}
Verb::KeyPairs(u) => key_pairs(u, x, Some(y), ctx, span),
Verb::UserDerived { def, alpha, omega } => {
with_operands(alpha, omega.as_deref(), ctx, |c| def.dyad(x, y, c, span))
}
Verb::Level { u, level, spread } => {
at_level_dyad(u, *level, *spread, x, y, ctx, span)
}
Verb::Key(u) => key(u, x, y, ctx, span),
Verb::Cut(u, n) => cut(u, Some(x), y, *n, ctx, span),
Verb::PowerV(u, v) => power_v(u, v, Some(x), y, ctx, span),
Verb::PowerUntil(..) => {
Err(Error::not_yet("dyadic power with a function operand (x f⍣g y)", span))
}
Verb::AlongAxis(u, k) => along_axis(u, Some(x), y, *k, ctx, span),
Verb::Explicit(d) => crate::ir::call_explicit(d, Some(x), y, ctx, span),
Verb::SelfRef => {
let d = self_ref(ctx, span)?;
crate::ir::call_explicit(&d, Some(x), y, ctx, span)
}
Verb::Named(n) => named_verb(ctx, n, span)?.dyad(x, y, ctx, span),
Verb::WithObverse(v, _) => v.dyad(x, y, ctx, span),
Verb::Adverse(v, w) => match v.dyad(x, y, ctx, span) {
Err(e) if e.kind != ErrorKind::NotYet => w.dyad(x, y, ctx, span),
other => other,
},
Verb::Beside(f, g) => {
let r = g.monad(y, ctx, span)?;
f.dyad(x, &r, ctx, span)
}
Verb::Hypergeometric { .. } => {
Err(Error::domain("m H. n has no dyadic meaning", span))
}
Verb::Agenda(vs, w) => {
agenda_pick(vs, w, Some(x), y, ctx, span)?.dyad(x, y, ctx, span)
}
Verb::Evoke(vs, n) => evoke(vs, *n, Some(x), y, ctx, span),
Verb::InnerProduct { u, v, apl } => inner_product(u, v, *apl, x, y, ctx, span),
Verb::Stencil(..) => {
Err(Error::domain("f⌺w has no dyadic meaning", span))
}
Verb::BondLeft(..) | Verb::BondRight(..) => {
Err(Error::domain(format!("{} has no dyadic meaning", self.name()), span))
}
}
}
fn dyad_ranked(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let ranks = self.ranks();
let er_l = effective_rank(ranks[1], x.rank());
let er_r = effective_rank(ranks[2], y.rank());
if er_l == 0 && er_r == 0 {
if let Some(op) = self.scalar_dyad_op() {
return scalar_dyad(op, x, y, ctx.cfg, span);
}
}
let fxl = x.rank() - er_l;
let fyl = y.rank() - er_r;
let p = agree(&x.shape[..fxl], &y.shape[..fyl], &x.shape, &y.shape, ctx.cfg.agreement, span)?;
if p.frame.is_empty() {
return self.dyad_cell(x, y, ctx, span);
}
let work = x.count().max(y.count());
let cells = each_cell(p.n, work, self.is_pure(), ctx, |i, c| {
let xc = x.cell_at(fxl, i / p.x_div);
let yc = y.cell_at(fyl, i / p.y_div);
self.dyad_cell(&xc, &yc, c, span)
})?;
assemble(&p.frame, cells, span)
}
fn dyad_cell(&self, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
match self {
Verb::Prim(p) if p.dyad == DyadOp::WriteStream => {
stream_number(y, 2, "1!:2 writes", span)?;
(ctx.out)(&format!("{}\n", crate::fmt::format_array(x, &ctx.cfg.fmt)));
Ok(x.clone())
}
Verb::Prim(p) => dyad_op(p, x, y, ctx.cfg, span),
Verb::Rank(v, _) => v.dyad(x, y, ctx, span),
Verb::Windowed(v, _) => infix(v, x, y, ctx, span),
Verb::Each(u, rule) => {
let r = u.dyad(&open_cell(x), &open_cell(y), ctx, span)?;
Ok(enclose(&r, *rule))
}
_ => Err(Error::internal("dyad_cell on a verb without cell ranks")),
}
}
fn scalar_dyad_op(&self) -> Option<ScalarDyad> {
match self {
Verb::Prim(p) => match p.dyad {
DyadOp::Scalar(op) => Some(op),
_ => None,
},
Verb::Rank(v, _) => v.scalar_dyad_op(),
_ => None,
}
}
}
pub fn effective_rank(r: i64, arg_rank: usize) -> usize {
if r >= 0 {
(r as usize).min(arg_rank)
} else {
arg_rank.saturating_sub(r.unsigned_abs() as usize)
}
}
fn self_ref(ctx: &Ctx<'_>, span: Span) -> Result<Arc<crate::ir::ExplicitDef>> {
ctx.env.current_def().ok_or_else(|| {
Error::new(
ErrorKind::Value,
"self-reference outside an explicit definition",
Some(span),
)
})
}
fn named_verb(ctx: &Ctx<'_>, name: &str, span: Span) -> Result<Verb> {
ctx.env.verb(name).cloned().ok_or_else(|| {
Error::new(ErrorKind::Value, format!("undefined verb: {name}"), Some(span))
})
}
fn each_cell<F>(
n: usize,
work: usize,
pure: bool,
ctx: &mut Ctx<'_>,
f: F,
) -> Result<Vec<Array>>
where
F: Fn(usize, &mut Ctx<'_>) -> Result<Array> + Sync + Send,
{
if pure && n > 1 && par::worth_it(work) {
let cfg = ctx.cfg;
return par::map_indexed(n, |i| cfg.pure(|c| f(i, c))).into_iter().collect();
}
(0..n).map(|i| f(i, ctx)).collect()
}
fn one_rank(r: i64) -> String {
if r == RANK_INF { "_".to_string() } else { r.to_string() }
}
fn rank_str(r: [i64; 3]) -> String {
if r[0] == r[1] && r[1] == r[2] {
one_rank(r[0])
} else {
format!("{} {} {}", one_rank(r[0]), one_rank(r[1]), one_rank(r[2]))
}
}
fn show_shape(shape: &[usize]) -> String {
if shape.is_empty() {
return "(scalar)".to_string();
}
shape.iter().map(|n| n.to_string()).collect::<Vec<_>>().join(" ")
}
fn strides(shape: &[usize]) -> Vec<usize> {
let mut s = vec![1usize; shape.len()];
for k in (0..shape.len().saturating_sub(1)).rev() {
s[k] = s[k + 1] * shape[k + 1];
}
s
}
fn odometer(coord: &mut [usize], shape: &[usize]) {
for k in (0..coord.len()).rev() {
coord[k] += 1;
if coord[k] < shape[k] {
return;
}
coord[k] = 0;
}
}
fn push_elem(dst: &mut Data, src: &Data, i: usize) {
match (dst, src) {
(Data::Bool(a), Data::Bool(b)) => a.push(b[i]),
(Data::I64(a), Data::I64(b)) => a.push(b[i]),
(Data::Ext(a), Data::Ext(b)) => a.push(b[i].clone()),
(Data::Rat(a), Data::Rat(b)) => a.push(b[i].clone()),
(Data::F64(a), Data::F64(b)) => a.push(b[i]),
(Data::Complex(a), Data::Complex(b)) => a.push(b[i]),
(Data::Char(a), Data::Char(b)) => a.push(b[i]),
(Data::Symbol(a), Data::Symbol(b)) => a.push(b[i]),
(Data::Box(a), Data::Box(b)) => a.push(b[i].clone()),
_ => debug_assert!(false, "push_elem across dtypes"),
}
}
fn fill_data(dtype: DType, n: usize) -> Data {
let mut d = Data::empty(dtype);
for _ in 0..n {
d.push_fill();
}
d
}
struct Pairing {
frame: Vec<usize>,
n: usize,
x_div: usize,
y_div: usize,
}
fn frame_mismatch(
xs: &[usize],
ys: &[usize],
fx: &[usize],
fy: &[usize],
axis: usize,
span: Span,
) -> Error {
let kind = if fx.len() == 1 && fy.len() == 1 { ErrorKind::Length } else { ErrorKind::Shape };
let note = if axis < fx.len() && axis < fy.len() {
format!("frames first differ at axis {axis}: {} vs {}", fx[axis], fy[axis])
} else {
format!(
"frames have different numbers of axes: {} vs {}, diverging at axis {axis}",
fx.len(),
fy.len()
)
};
Error::new(
kind,
format!(
"arguments do not agree: left shape {}, right shape {}",
show_shape(xs),
show_shape(ys)
),
Some(span),
)
.note(note)
}
fn agree(
fx: &[usize],
fy: &[usize],
xs: &[usize],
ys: &[usize],
mode: Agreement,
span: Span,
) -> Result<Pairing> {
let common = fx.len().min(fy.len());
match mode {
Agreement::LeadingPrefix => {
for i in 0..common {
if fx[i] != fy[i] {
return Err(frame_mismatch(xs, ys, fx, fy, i, span));
}
}
let (long, short) = if fx.len() >= fy.len() { (fx, fy) } else { (fy, fx) };
let n: usize = long.iter().product();
let surplus: usize = long[short.len()..].iter().product();
let (x_div, y_div) =
if fx.len() >= fy.len() { (1, surplus.max(1)) } else { (surplus.max(1), 1) };
Ok(Pairing { frame: long.to_vec(), n, x_div, y_div })
}
Agreement::ExactOrScalar => {
if fx == fy {
let n: usize = fx.iter().product();
return Ok(Pairing { frame: fx.to_vec(), n, x_div: 1, y_div: 1 });
}
let one = |f: &[usize]| f.iter().product::<usize>() == 1;
if fx.is_empty() || (one(fx) && !fy.is_empty()) {
let n: usize = fy.iter().product();
return Ok(Pairing { frame: fy.to_vec(), n, x_div: n.max(1), y_div: 1 });
}
if fy.is_empty() || one(fy) {
let n: usize = fx.iter().product();
return Ok(Pairing { frame: fx.to_vec(), n, x_div: 1, y_div: n.max(1) });
}
let axis = (0..common).find(|&i| fx[i] != fy[i]).unwrap_or(common);
Err(frame_mismatch(xs, ys, fx, fy, axis, span))
}
}
}
fn assemble(frame: &[usize], cells: Vec<Array>, span: Span) -> Result<Array> {
if cells.is_empty() {
return Ok(Array::new(frame.to_vec(), Data::empty(DType::I64)));
}
let mut dt = cells[0].dtype();
for c in &cells[1..] {
dt = DType::promote(dt, c.dtype()).ok_or_else(|| {
let boxed = dt == DType::Box || c.dtype() == DType::Box;
let what = if boxed {
"cannot frame boxed and unboxed results into one array"
} else {
"cannot frame character and numeric results into one array"
};
Error::new(ErrorKind::Type, what, Some(span))
})?;
}
let widen = |c: &Array| -> Result<Data> {
c.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening while framing"))
};
if cells[1..].iter().all(|c| c.shape == cells[0].shape) {
let mut data = Data::empty(dt);
for c in &cells {
if c.dtype() == dt {
data.extend_from(&c.data);
} else {
data.extend_from(&widen(c)?);
}
}
let mut shape = frame.to_vec();
shape.extend_from_slice(&cells[0].shape);
return Ok(Array::new(shape, data));
}
let crank = cells.iter().map(|c| c.rank()).max().unwrap_or(0);
let padded: Vec<Vec<usize>> = cells
.iter()
.map(|c| {
let mut s = vec![1usize; crank - c.rank()];
s.extend_from_slice(&c.shape);
s
})
.collect();
let mut common = vec![0usize; crank];
for s in &padded {
for k in 0..crank {
common[k] = common[k].max(s[k]);
}
}
let cell_n: usize = common.iter().product();
let mut data = Data::empty(dt);
for (c, ps) in cells.iter().zip(&padded) {
let cd = if c.dtype() == dt { c.data.clone() } else { widen(c)? };
let st = strides(ps);
let mut coord = vec![0usize; crank];
for _ in 0..cell_n {
let mut idx = 0usize;
let mut inside = true;
for k in 0..crank {
if coord[k] >= ps[k] {
inside = false;
break;
}
idx += coord[k] * st[k];
}
if inside {
push_elem(&mut data, &cd, idx);
} else {
data.push_fill();
}
odometer(&mut coord, &common);
}
}
let mut shape = frame.to_vec();
shape.extend_from_slice(&common);
Ok(Array::new(shape, data))
}
fn atom(a: &Array, i: usize) -> Array {
debug_assert!(a.is_row_major(), "an atom out of a column-major buffer");
Array::new(Vec::new(), a.data.slice(i, i + 1))
}
fn enclose(y: &Array, rule: Enclose) -> Array {
if rule == Enclose::ExceptSimpleScalar && y.rank() == 0 && y.dtype() != DType::Box {
return y.clone();
}
Array::boxed(y.clone())
}
fn open_cell(y: &Array) -> Array {
match &y.data {
Data::Box(v) if !v.is_empty() => v[0].clone(),
_ => y.clone(),
}
}
fn first(y: &Array) -> Array {
if y.count() == 0 {
let mut d = Data::empty(y.dtype());
d.push_fill();
return open_cell(&Array::new(Vec::new(), d));
}
open_cell(&atom(y, 0))
}
fn depth(y: &Array) -> i64 {
match &y.data {
Data::Box(v) => 1 + v.iter().map(depth).max().unwrap_or(0),
_ => i64::from(y.rank() > 0),
}
}
fn leaves(a: &Array, out: &mut Vec<Array>) {
match &a.data {
Data::Box(v) => {
for b in v.iter() {
leaves(b, out);
}
}
_ => out.push(a.clone()),
}
}
fn enlist(y: &Array, span: Span) -> Result<Array> {
let mut parts = Vec::new();
leaves(y, &mut parts);
let mut dt = None;
for p in parts.iter().filter(|p| p.count() > 0) {
dt = Some(match dt {
None => p.dtype(),
Some(t) => DType::promote(t, p.dtype()).ok_or_else(|| {
Error::new(
ErrorKind::Type,
"cannot enlist character and numeric data into one vector",
Some(span),
)
})?,
});
}
let dt = dt.unwrap_or(DType::I64);
let mut data = Data::empty(dt);
for p in &parts {
let cast = p.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening in enlist"))?;
data.extend_from(&cast);
}
Ok(Array::new(vec![data.len()], data))
}
fn spread(a: &Array, shape: &[usize]) -> Array {
let n: usize = shape.iter().product();
let mut data = Data::empty(a.dtype());
for _ in 0..n {
push_elem(&mut data, &a.data, 0);
}
Array::new(shape.to_vec(), data)
}
fn wider_shape(a: &[usize], b: &[usize]) -> Vec<usize> {
let r = a.len().max(b.len());
let pad = |s: &[usize]| {
let mut v = vec![1usize; r - s.len()];
v.extend_from_slice(s);
v
};
let (pa, pb) = (pad(a), pad(b));
(0..r).map(|k| pa[k].max(pb[k])).collect()
}
fn raze(y: &Array, span: Span) -> Result<Array> {
let opened: Vec<Array> = (0..y.count()).map(|i| open_cell(&atom(y, i))).collect();
let mut common: Option<Vec<usize>> = None;
for a in opened.iter().filter(|a| a.rank() > 0) {
common = Some(match common {
None => a.shape[1..].to_vec(),
Some(c) => wider_shape(&c, &a.shape[1..]),
});
}
let common = common.unwrap_or_default();
let mut cells: Vec<Array> = Vec::new();
for a in &opened {
if a.rank() == 0 {
cells.push(spread(a, &common));
continue;
}
for i in 0..a.items() {
cells.push(a.item(i));
}
}
if cells.is_empty() {
return Ok(Array::new(vec![0], Data::empty(DType::I64)));
}
let n = cells.len();
assemble(&[n], cells, span)
}
fn link(x: &Array, y: &Array, span: Span) -> Result<Array> {
let head = Array::boxed(x.clone());
let tail = if y.dtype() == DType::Box { y.clone() } else { Array::boxed(y.clone()) };
catenate(&head, &tail, true, false, span)
}
fn nest_like(a: &Array, other: &Array) -> Array {
if a.dtype() == DType::Box || other.dtype() != DType::Box {
return a.clone();
}
let cells: Vec<Array> = (0..a.count()).map(|i| atom(a, i)).collect();
Array::new(a.shape.clone(), Data::Box(cells.into()))
}
fn box_items(y: &Array) -> Array {
if y.dtype() == DType::Box {
return y.clone();
}
let n = y.items();
let boxes: Vec<Array> = (0..n).map(|i| item_or_self(y, i)).collect();
Array::new(vec![n], Data::Box(boxes.into()))
}
fn strand(x: &Array, y: &Array, span: Span) -> Result<Array> {
let item = enclose(x, Enclose::ExceptSimpleScalar);
let one = |a: &Array| Array::new(vec![1], a.data.clone());
if item.dtype() != DType::Box
&& y.dtype() != DType::Box
&& DType::promote(item.dtype(), y.dtype()).is_some()
{
return catenate(&one(&item), y, true, false, span);
}
let head = if item.dtype() == DType::Box { item } else { Array::boxed(item) };
catenate(&one(&head), &box_items(y), true, false, span)
}
fn char_arith(span: Span) -> Error {
Error::new(ErrorKind::Type, "cannot do arithmetic on characters", Some(span))
}
fn symbol_arith(span: Span) -> Error {
Error::new(
ErrorKind::Type,
"cannot do arithmetic on symbols; `5 s:` gives their names back",
Some(span),
)
}
fn box_arith(span: Span) -> Error {
Error::new(
ErrorKind::Type,
"cannot do arithmetic on boxed values; open them first (J `>`, APL `⊃`)",
Some(span),
)
}
fn wrong_type(d: DType, span: Span) -> Error {
match d {
DType::Box => box_arith(span),
DType::Symbol => symbol_arith(span),
_ => char_arith(span),
}
}
fn borrow_i64<'a>(d: &'a Data, tmp: &'a mut Vec<i64>) -> &'a [i64] {
match d {
Data::I64(v) => v,
Data::Bool(v) => {
*tmp = par::map(v, |&b| b as i64);
&tmp[..]
}
_ => &[],
}
}
fn borrow_f64<'a>(d: &'a Data, tmp: &'a mut Vec<f64>) -> &'a [f64] {
match d {
Data::F64(v) => v,
Data::I64(v) => {
*tmp = par::map(v, |&x| x as f64);
&tmp[..]
}
Data::Bool(v) => {
*tmp = par::map(v, |&x| x as f64);
&tmp[..]
}
Data::Ext(v) => {
*tmp = par::map(v, exact::ext_to_f64);
&tmp[..]
}
Data::Rat(v) => {
*tmp = par::map(v, Rat::to_f64);
&tmp[..]
}
_ => &[],
}
}
fn borrow_cx<'a>(d: &'a Data, tmp: &'a mut Vec<Cx>) -> &'a [Cx] {
match d {
Data::Complex(v) => v,
Data::Ext(v) => {
*tmp = par::map(v, |x| [exact::ext_to_f64(x), 0.0]);
&tmp[..]
}
Data::Rat(v) => {
*tmp = par::map(v, |x| [x.to_f64(), 0.0]);
&tmp[..]
}
Data::F64(v) => {
*tmp = par::map(v, |&x| [x, 0.0]);
&tmp[..]
}
Data::I64(v) => {
*tmp = par::map(v, |&x| [x as f64, 0.0]);
&tmp[..]
}
Data::Bool(v) => {
*tmp = v.iter().map(|&x| [x as f64, 0.0]).collect();
&tmp[..]
}
_ => &[],
}
}
pub(crate) trait Widen<T>: Copy + Send + Sync {
fn widen(self) -> T;
}
macro_rules! widens {
($($from:ty => $to:ty : |$v:ident| $e:expr;)*) => {
$(impl Widen<$to> for $from {
#[inline(always)]
fn widen(self) -> $to {
let $v = self;
$e
}
})*
};
}
widens! {
u8 => i64: |v| v as i64;
i64 => i64: |v| v;
u8 => f64: |v| v as f64;
i64 => f64: |v| v as f64;
f64 => f64: |v| v;
u8 => Cx: |v| [v as f64, 0.0];
i64 => Cx: |v| [v as f64, 0.0];
f64 => Cx: |v| [v, 0.0];
Cx => Cx: |v| v;
}
macro_rules! i64_source {
($d:expr, $tmp:ident, $s:ident, $body:expr) => {
match $d {
Data::I64(v) => {
let $s: &[i64] = v;
$body
}
Data::Bool(v) => {
let $s: &[u8] = v;
$body
}
other => {
let $s: &[i64] = borrow_i64(other, &mut $tmp);
$body
}
}
};
}
macro_rules! f64_source {
($d:expr, $tmp:ident, $s:ident, $body:expr) => {
match $d {
Data::F64(v) => {
let $s: &[f64] = v;
$body
}
Data::I64(v) => {
let $s: &[i64] = v;
$body
}
Data::Bool(v) => {
let $s: &[u8] = v;
$body
}
other => {
let $s: &[f64] = borrow_f64(other, &mut $tmp);
$body
}
}
};
}
macro_rules! cx_source {
($d:expr, $tmp:ident, $s:ident, $body:expr) => {
match $d {
Data::Complex(v) => {
let $s: &[Cx] = v;
$body
}
Data::F64(v) => {
let $s: &[f64] = v;
$body
}
Data::I64(v) => {
let $s: &[i64] = v;
$body
}
Data::Bool(v) => {
let $s: &[u8] = v;
$body
}
other => {
let $s: &[Cx] = borrow_cx(other, &mut $tmp);
$body
}
}
};
}
fn as_f64<'a>(d: &'a Data, tmp: &'a mut Vec<f64>, span: Span) -> Result<&'a [f64]> {
if !d.dtype().is_numeric() {
return Err(wrong_type(d.dtype(), span));
}
Ok(borrow_f64(d, tmp))
}
fn arith_type(a: DType, b: DType, span: Span) -> Result<DType> {
if a == DType::Box || b == DType::Box {
return Err(box_arith(span));
}
if a == DType::Symbol || b == DType::Symbol {
return Err(symbol_arith(span));
}
match DType::promote(a, b) {
Some(DType::Char) => Err(char_arith(span)),
None => Err(Error::new(
ErrorKind::Type,
"cannot mix character and numeric data",
Some(span),
)),
Some(DType::Bool) => Ok(DType::I64),
Some(t) => Ok(t),
}
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn zip_chunk<A, B, U, F>(
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
start: usize,
out: &mut [U],
mut f: F,
) -> bool
where
A: Copy,
B: Copy,
F: FnMut(A, B, &mut U) -> bool,
{
let len = out.len();
if len == 0 {
return true;
}
let last = start + len - 1;
let one_x = xdiv > 1 && start / xdiv == last / xdiv;
let one_y = ydiv > 1 && start / ydiv == last / ydiv;
if xdiv == 1 && ydiv == 1 {
let xc = &xs[xoff + start..xoff + start + len];
let yc = &ys[yoff + start..yoff + start + len];
for ((slot, &a), &b) in out.iter_mut().zip(xc).zip(yc) {
if !f(a, b, slot) {
return false;
}
}
} else if xdiv == 1 && one_y {
let b = ys[yoff + start / ydiv];
let xc = &xs[xoff + start..xoff + start + len];
for (slot, &a) in out.iter_mut().zip(xc) {
if !f(a, b, slot) {
return false;
}
}
} else if one_x && ydiv == 1 {
let a = xs[xoff + start / xdiv];
let yc = &ys[yoff + start..yoff + start + len];
for (slot, &b) in out.iter_mut().zip(yc) {
if !f(a, b, slot) {
return false;
}
}
} else {
for (k, slot) in out.iter_mut().enumerate() {
let i = start + k;
if !f(xs[xoff + i / xdiv], ys[yoff + i / ydiv], slot) {
return false;
}
}
}
true
}
const LANCZOS: [f64; 9] = [
0.999_999_999_999_809_9,
676.520_368_121_885_1,
-1_259.139_216_722_402_8,
771.323_428_777_653_1,
-176.615_029_162_140_6,
12.507_343_278_686_905,
-0.138_571_095_265_720_12,
9.984_369_578_019_572e-6,
1.505_632_735_149_311_6e-7,
];
fn gamma(x: f64) -> f64 {
use std::f64::consts::PI;
if x < 0.5 {
return PI / ((PI * x).sin() * gamma(1.0 - x));
}
let z = x - 1.0;
let mut a = LANCZOS[0];
for (i, &c) in LANCZOS.iter().enumerate().skip(1) {
a += c / (z + i as f64);
}
let t = z + 7.5;
(2.0 * PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * a
}
fn factorial(y: f64) -> f64 {
if y.fract() == 0.0 && y.abs() < 1e17 {
let n = y as i64;
if n < 0 {
return if n % 2 == -1 { f64::INFINITY } else { f64::NEG_INFINITY };
}
if n > 170 {
return f64::INFINITY;
}
let mut c = 1.0f64;
for i in 2..=n {
c *= i as f64;
}
return c;
}
gamma(y + 1.0)
}
const BINOMIAL_PRODUCT_LIMIT: i64 = 4096;
fn binomial_product(x: i64, y: f64) -> f64 {
let mut c = 1.0f64;
for i in 1..=x {
c = c * (y - i as f64 + 1.0) / i as f64;
if c == 0.0 {
break;
}
}
c
}
fn binomial_i64(x: i64, y: i64) -> Option<i64> {
if x < 0 {
if y >= 0 || y < x {
return Some(0);
}
let v = binomial_exact(-y - 1, -x - 1)?;
return if (y - x) % 2 == 0 { Some(v) } else { v.checked_neg() };
}
binomial_exact(x, y)
}
fn binomial_exact(x: i64, y: i64) -> Option<i64> {
if x > BINOMIAL_PRODUCT_LIMIT {
return None;
}
let mut c: i128 = 1;
for i in 1..=x as i128 {
c = c.checked_mul(y as i128 - i + 1)? / i;
if c == 0 {
break;
}
}
i64::try_from(c).ok()
}
fn binomial(x: f64, y: f64) -> f64 {
if x.fract() == 0.0 && x.abs() < 1e17 {
let xi = x as i64;
if xi < 0 {
if y.fract() == 0.0 && y < 0.0 && y >= x {
let sign = if (y as i64 - xi) % 2 == 0 { 1.0 } else { -1.0 };
return sign * binomial_product(-y as i64 - 1, -x - 1.0);
}
return 0.0;
}
if xi <= BINOMIAL_PRODUCT_LIMIT {
return binomial_product(xi, y);
}
}
gamma(y + 1.0) / (gamma(x + 1.0) * gamma(y - x + 1.0))
}
#[inline]
fn i64_op(op: ScalarDyad, a: i64, b: i64) -> Option<i64> {
use ScalarDyad::*;
Some(match op {
Add => a.checked_add(b)?,
Sub => a.checked_sub(b)?,
Mul => a.checked_mul(b)?,
Min => a.min(b),
Max => a.max(b),
Residue => {
if a == 0 {
b
} else {
let mut r = b.wrapping_rem(a);
if r != 0 && (r < 0) != (a < 0) {
r += a;
}
r
}
}
Pow => {
if b < 0 {
return None;
}
a.checked_pow(u32::try_from(b).ok()?)?
}
Binomial => binomial_i64(a, b)?,
_ => return None,
})
}
#[inline]
fn f64_op(op: ScalarDyad, a: f64, b: f64, span: Span) -> Result<f64> {
use ScalarDyad::*;
Ok(match op {
Add => a + b,
Sub => a - b,
Mul => a * b,
Min => a.min(b),
Max => a.max(b),
DivJ => {
if b == 0.0 {
if a == 0.0 { 0.0 } else { f64::INFINITY.copysign(a) }
} else {
a / b
}
}
DivApl => {
if b == 0.0 {
if a == 0.0 {
1.0
} else {
return Err(Error::domain("division by zero", span));
}
} else {
a / b
}
}
Pow => {
if a == 0.0 && b == 0.0 {
1.0
} else {
a.powf(b)
}
}
Residue => {
if a.is_infinite() {
if b == 0.0 || (b > 0.0) == (a > 0.0) { b } else { a }
} else if a == 0.0 {
b
} else {
b - a * (b / a).floor()
}
}
Log => {
if a < 0.0 || b < 0.0 {
return Err(Error::not_yet("complex numbers", span));
}
b.ln() / a.ln()
}
Root => {
if b < 0.0 {
return Err(Error::not_yet("complex numbers", span));
}
b.powf(1.0 / a)
}
Circle => return circle(a, b, span),
Binomial => binomial(a, b),
_ => return Err(Error::internal("non-arithmetic op in the float path")),
})
}
#[inline]
fn escapes_reals(op: ScalarDyad, a: f64, b: f64) -> bool {
use ScalarDyad::*;
match op {
Pow => a < 0.0 && b.fract() != 0.0,
Log => a < 0.0 || b < 0.0,
Root => b < 0.0,
Circle => circle_escapes(a, b),
_ => false,
}
}
#[inline]
fn circle_escapes(k: f64, y: f64) -> bool {
if k.fract() != 0.0 {
return false;
}
match k as i64 {
0 | -1 | -2 | -7 => y.abs() > 1.0,
-4 => y.abs() < 1.0,
-6 => y < 1.0,
8 | -8 | -11 | -12 => true,
_ => false,
}
}
#[inline]
fn circle(k: f64, y: f64, span: Span) -> Result<f64> {
if k.fract() != 0.0 {
return Err(Error::domain("the circle function needs an integer left argument", span));
}
let complex = || Error::internal("a circle function left the reals on the real path");
Ok(match k as i64 {
0 => {
if y.abs() > 1.0 {
return Err(complex());
}
(1.0 - y * y).max(0.0).sqrt()
}
1 => y.sin(),
2 => y.cos(),
3 => y.tan(),
4 => (1.0 + y * y).sqrt(),
5 => y.sinh(),
6 => y.cosh(),
7 => y.tanh(),
-1 => {
if y.abs() > 1.0 {
return Err(complex());
}
y.asin()
}
-2 => {
if y.abs() > 1.0 {
return Err(complex());
}
y.acos()
}
-3 => y.atan(),
-4 => {
if y.abs() < 1.0 {
return Err(complex());
}
y.signum() * (y * y - 1.0).max(0.0).sqrt()
}
-5 => y.asinh(),
-6 => {
if y < 1.0 {
return Err(complex());
}
y.acosh()
}
-7 => {
if y.abs() > 1.0 {
return Err(complex());
}
y.atanh()
}
9 | -9 | -10 => y,
10 => y.abs(),
11 => 0.0,
12 => {
if y < 0.0 {
std::f64::consts::PI
} else {
0.0
}
}
8 | -8 | -11 | -12 => return Err(complex()),
_ => {
return Err(Error::domain(
"the circle functions run from _12 to 12",
span,
));
}
})
}
#[inline]
fn cx_op(op: ScalarDyad, a: Cx, b: Cx, span: Span) -> Result<Cx> {
use ScalarDyad::*;
Ok(match op {
Add => cx::add(a, b),
Sub => cx::sub(a, b),
Mul => cx::mul(a, b),
DivJ => cx::div(a, b),
DivApl => {
if b == cx::ZERO {
if a == cx::ZERO {
cx::ONE
} else {
return Err(Error::domain("division by zero", span));
}
} else {
cx::div(a, b)
}
}
Pow => cx::pow(a, b),
Log => cx::log(a, b),
Root => cx::root(a, b),
Residue => cx::residue(a, b),
Lcm => cx::lcm(a, b),
Gcd => cx::gcd(a, b),
MakeComplex => cx::add(a, cx::mul(cx::I, b)),
PolarBy => cx::mul(a, cx::exp(cx::mul(cx::I, b))),
Circle => {
if a[1] != 0.0 || a[0].fract() != 0.0 {
return Err(Error::domain(
"the circle function needs an integer left argument",
span,
));
}
cx::circle(a[0] as i64, b).ok_or_else(|| {
Error::domain("the circle functions run from _12 to 12", span)
})?
}
Min | Max => return Err(no_complex_order(span)),
Binomial => {
return Err(Error::not_yet("the binomial function on complex numbers", span));
}
Eq | Ne | Lt | Le | Gt | Ge => {
return Err(Error::internal("a comparison in the complex arithmetic path"));
}
})
}
fn no_complex_order(span: Span) -> Error {
Error::new(
ErrorKind::Domain,
"complex numbers have no order; only equality (=, ~:) applies to them",
Some(span),
)
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn dyad_cx_chunk_body<A: Widen<Cx>, B: Widen<Cx>>(
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
start: usize,
out: &mut [Cx],
span: Span,
) -> Result<()> {
use ScalarDyad::*;
macro_rules! plain {
($step:expr) => {{
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut Cx| {
*slot = $step(a.widen(), b.widen());
true
});
return Ok(());
}};
}
match op {
Add => plain!(cx::add),
Sub => plain!(cx::sub),
Mul => plain!(cx::mul),
DivJ => plain!(cx::div),
_ => {}
}
let mut err = None;
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut Cx| {
match cx_op(op, a.widen(), b.widen(), span) {
Ok(v) => {
*slot = v;
true
}
Err(e) => {
err = Some(e);
false
}
}
});
match err {
Some(e) => Err(e),
None => Ok(()),
}
}
multiversioned! {
#[allow(clippy::too_many_arguments)]
fn dyad_cx_chunk[A: Widen<Cx>, B: Widen<Cx>](
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
start: usize,
out: &mut [Cx],
span: Span,
) -> Result<()> = dyad_cx_chunk_body;
}
#[allow(clippy::too_many_arguments)]
fn dyad_cx<A: Widen<Cx>, B: Widen<Cx>>(
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
n: usize,
span: Span,
) -> Result<Vec<Cx>> {
par::try_fill(n, |start, part| {
dyad_cx_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part, span)
})
}
#[allow(clippy::too_many_arguments)]
fn complex_dyad_data(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
span: Span,
) -> Result<Data> {
let (mut tx, mut ty) = (Vec::new(), Vec::new());
macro_rules! pass {
($xs:expr, $ys:expr) => {
Data::Complex(dyad_cx(op, $xs, xoff, xdiv, $ys, yoff, ydiv, n, span)?.into())
};
}
Ok(match (x, y) {
(Data::Complex(a), _) => {
let xs: &[Cx] = a;
cx_source!(y, ty, ys, pass!(xs, ys))
}
(_, Data::Complex(b)) => {
let ys: &[Cx] = b;
cx_source!(x, tx, xs, pass!(xs, ys))
}
_ => pass!(borrow_cx(x, &mut tx), borrow_cx(y, &mut ty)),
})
}
fn circle_reads_a_part(x: &Data, xoff: usize, xdiv: usize, n: usize) -> bool {
if x.dtype() == DType::Complex {
return false;
}
let mut tmp = Vec::new();
let xs = borrow_f64(x, &mut tmp);
(0..n).all(|i| {
let k = xs[xoff + i / xdiv];
k.fract() == 0.0 && (9.0..=12.0).contains(&k)
})
}
#[allow(clippy::too_many_arguments)]
fn pass_leaves_reals(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
) -> bool {
use ScalarDyad::*;
if !matches!(op, Pow | Log | Root | Circle) {
return false;
}
let (mut tx, mut ty) = (Vec::new(), Vec::new());
let xs = borrow_f64(x, &mut tx);
let ys = borrow_f64(y, &mut ty);
(0..n).any(|i| escapes_reals(op, xs[xoff + i / xdiv], ys[yoff + i / ydiv]))
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn dyad_i64_chunk_body<A: Widen<i64>, B: Widen<i64>>(
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
start: usize,
out: &mut [i64],
) -> bool {
use ScalarDyad::*;
macro_rules! overflowing {
($m:ident) => {{
let mut over = false;
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
let (v, o) = i64::$m(a.widen(), b.widen());
*slot = v;
over |= o;
true
});
!over
}};
}
macro_rules! plain {
($step:expr) => {{
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
*slot = $step(a.widen(), b.widen());
true
})
}};
}
match op {
Add => overflowing!(overflowing_add),
Sub => overflowing!(overflowing_sub),
Mul => overflowing!(overflowing_mul),
Min => plain!(i64::min),
Max => plain!(i64::max),
_ => zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut i64| {
match i64_op(op, a.widen(), b.widen()) {
Some(v) => {
*slot = v;
true
}
None => false,
}
}),
}
}
multiversioned! {
#[allow(clippy::too_many_arguments)]
fn dyad_i64_chunk[A: Widen<i64>, B: Widen<i64>](
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
start: usize,
out: &mut [i64],
) -> bool = dyad_i64_chunk_body;
}
#[allow(clippy::too_many_arguments)]
fn dyad_i64<A: Widen<i64>, B: Widen<i64>>(
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
n: usize,
) -> Option<Vec<i64>> {
let (out, ok) = par::fill(n, |start, part| {
dyad_i64_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part)
});
ok.then_some(out)
}
#[allow(clippy::too_many_arguments)]
fn int_dyad_data(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
) -> Option<Data> {
let (mut tx, mut ty) = (Vec::new(), Vec::new());
let out = i64_source!(x, tx, xs, {
i64_source!(y, ty, ys, dyad_i64(op, xs, xoff, xdiv, ys, yoff, ydiv, n))
})?;
Some(Data::I64(out.into()))
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn dyad_f64_chunk_body<A: Widen<f64>, B: Widen<f64>>(
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
start: usize,
out: &mut [f64],
span: Span,
) -> Result<()> {
use ScalarDyad::*;
macro_rules! plain {
($step:expr) => {{
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut f64| {
*slot = $step(a.widen(), b.widen());
true
});
return Ok(());
}};
}
match op {
Add => plain!(|a: f64, b: f64| a + b),
Sub => plain!(|a: f64, b: f64| a - b),
Mul => plain!(|a: f64, b: f64| a * b),
Min => plain!(f64::min),
Max => plain!(f64::max),
_ => {}
}
let mut err = None;
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, out, |a, b, slot: &mut f64| {
match f64_op(op, a.widen(), b.widen(), span) {
Ok(v) => {
*slot = v;
true
}
Err(e) => {
err = Some(e);
false
}
}
});
match err {
Some(e) => Err(e),
None => Ok(()),
}
}
multiversioned! {
#[allow(clippy::too_many_arguments)]
fn dyad_f64_chunk[A: Widen<f64>, B: Widen<f64>](
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
start: usize,
out: &mut [f64],
span: Span,
) -> Result<()> = dyad_f64_chunk_body;
}
#[allow(clippy::too_many_arguments)]
fn dyad_f64<A: Widen<f64>, B: Widen<f64>>(
op: ScalarDyad,
xs: &[A],
xoff: usize,
xdiv: usize,
ys: &[B],
yoff: usize,
ydiv: usize,
n: usize,
span: Span,
) -> Result<Vec<f64>> {
par::try_fill(n, |start, part| {
dyad_f64_chunk(op, xs, xoff, xdiv, ys, yoff, ydiv, start, part, span)
})
}
#[allow(clippy::too_many_arguments)]
fn float_dyad_data(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
span: Span,
) -> Result<Data> {
let (mut tx, mut ty) = (Vec::new(), Vec::new());
let out = f64_source!(x, tx, xs, {
f64_source!(y, ty, ys, dyad_f64(op, xs, xoff, xdiv, ys, yoff, ydiv, n, span)?)
});
Ok(Data::F64(out.into()))
}
fn crossed_types(a: DType, b: DType) -> bool {
let class = |d: DType| match d {
DType::Box => 3,
DType::Symbol => 2,
DType::Char => 1,
_ => 0,
};
class(a) != class(b)
}
#[allow(clippy::too_many_arguments)]
fn symbol_min_max(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
span: Span,
) -> Result<Data> {
let (Data::Symbol(a), Data::Symbol(b)) = (x, y) else {
return Err(symbol_arith(span));
};
let down = op == ScalarDyad::Min;
let (out, _) = par::fill(n, |start, part: &mut [crate::symbol::Id]| {
zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
*slot = if crate::symbol::cmp(p, q).is_le() == down { p } else { q };
true
})
});
Ok(Data::Symbol(out.into()))
}
#[allow(clippy::too_many_arguments)]
fn compare_data(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
tol: Tol,
span: Span,
) -> Result<Data> {
use ScalarDyad::*;
let (dx, dy) = (x.dtype(), y.dtype());
let equality = matches!(op, Eq | Ne);
let boxed = dx == DType::Box || dy == DType::Box;
if equality && crossed_types(dx, dy) && (!boxed || tol.is_j()) {
let unequal = op == Ne;
return Ok(Data::Bool(vec![u8::from(unequal); n].into()));
}
if boxed {
if !equality {
return Err(box_arith(span));
}
let (Data::Box(a), Data::Box(b)) = (x, y) else {
return Err(Error::not_yet("a scalar function inside a nested array", span));
};
let (out, _) = par::fill(n, |start, part: &mut [u8]| {
for (k, slot) in part.iter_mut().enumerate() {
let i = start + k;
let e = arrays_match(&a[xoff + i / xdiv], &b[yoff + i / ydiv], tol);
*slot = u8::from(if op == Eq { e } else { !e });
}
true
});
return Ok(Data::Bool(out.into()));
}
if dx == DType::Symbol || dy == DType::Symbol {
if dx != dy {
return Err(Error::new(
ErrorKind::Type,
"cannot compare a symbol with data that is not a symbol",
Some(span),
));
}
let (Data::Symbol(a), Data::Symbol(b)) = (x, y) else {
return Err(Error::internal("symbol comparison on non-symbol data"));
};
let (out, _) = par::fill(n, |start, part: &mut [u8]| {
zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
*slot = u8::from(match op {
Eq => p == q,
Ne => p != q,
_ => {
let o = crate::symbol::cmp(p, q);
match op {
Lt => o.is_lt(),
Le => o.is_le(),
Gt => o.is_gt(),
_ => o.is_ge(),
}
}
});
true
})
});
return Ok(Data::Bool(out.into()));
}
if dx == DType::Char || dy == DType::Char {
if dx != dy {
return Err(Error::new(
ErrorKind::Type,
"cannot compare character and numeric data",
Some(span),
));
}
if !equality {
return Err(Error::new(
ErrorKind::Type,
"cannot order character data; only equality applies",
Some(span),
));
}
let (Data::Char(a), Data::Char(b)) = (x, y) else {
return Err(Error::internal("character comparison on non-character data"));
};
let (out, _) = par::fill(n, |start, part: &mut [u8]| {
zip_chunk(a, xoff, xdiv, b, yoff, ydiv, start, part, |p, q, slot| {
let e = p == q;
*slot = if op == Eq { e as u8 } else { !e as u8 };
true
})
});
return Ok(Data::Bool(out.into()));
}
if DType::promote(dx, dy).is_some_and(DType::is_exact)
&& let Some(d) = exact_compare_data(op, x, xoff, xdiv, y, yoff, ydiv, n)
{
return Ok(d);
}
if dx == DType::Complex || dy == DType::Complex {
if !equality {
return Err(no_complex_order(span));
}
let (mut tx, mut ty) = (Vec::new(), Vec::new());
let out = cx_source!(x, tx, xs, {
cx_source!(y, ty, ys, {
par::fill(n, |start, part: &mut [u8]| {
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, part, |a, b, slot| {
let e = tol.eq_cx(a.widen(), b.widen());
*slot = if op == Eq { e as u8 } else { !e as u8 };
true
})
})
.0
})
});
return Ok(Data::Bool(out.into()));
}
let out = if DType::promote(dx, dy) == Some(DType::F64) {
let (mut tx, mut ty) = (Vec::<f64>::new(), Vec::<f64>::new());
f64_source!(x, tx, xs, {
f64_source!(y, ty, ys, {
par::fill(n, |start, part: &mut [u8]| {
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, part, |a, b, slot| {
*slot = tol_cmp(op, a.widen(), b.widen(), tol) as u8;
true
})
})
.0
})
})
} else {
let (mut tx, mut ty) = (Vec::<i64>::new(), Vec::<i64>::new());
i64_source!(x, tx, xs, {
i64_source!(y, ty, ys, {
par::fill(n, |start, part: &mut [u8]| {
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, start, part, |a, b, slot| {
let (a, b): (i64, i64) = (a.widen(), b.widen());
*slot = cmp_result(op, Some(i64::cmp(&a, &b))) as u8;
true
})
})
.0
})
})
};
Ok(Data::Bool(out.into()))
}
#[inline(always)]
pub(crate) fn tol_cmp(op: ScalarDyad, a: f64, b: f64, tol: Tol) -> bool {
use ScalarDyad::*;
match op {
Eq => tol.eq(a, b),
Ne => !tol.eq(a, b),
Lt => tol.lt(a, b),
Le => tol.le(a, b),
Gt => tol.lt(b, a),
Ge => tol.le(b, a),
_ => false,
}
}
fn cmp_result(op: ScalarDyad, ord: Option<std::cmp::Ordering>) -> bool {
use std::cmp::Ordering::*;
use ScalarDyad::*;
match ord {
None => matches!(op, Ne),
Some(o) => match op {
Eq => o == Equal,
Ne => o != Equal,
Lt => o == Less,
Le => o != Greater,
Gt => o == Greater,
Ge => o != Less,
_ => false,
},
}
}
fn gcd_i128(a: i128, b: i128) -> i128 {
let (mut a, mut b) = (a.abs(), b.abs());
while b != 0 {
let t = a % b;
a = b;
b = t;
}
a
}
fn decimal_parts(v: f64) -> Option<(i128, u32)> {
if !v.is_finite() {
return None;
}
let text = format!("{v:e}");
let (mantissa, exponent) = text.split_once('e')?;
let exponent: i32 = exponent.parse().ok()?;
let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
let mut digits: i128 = format!("{whole}{fraction}").parse().ok()?;
let mut scale = fraction.len() as i32 - exponent;
while scale < 0 {
digits = digits.checked_mul(10)?;
scale += 1;
}
(scale <= 34).then_some((digits, scale as u32))
}
fn gcd_decimal(a: f64, b: f64) -> Option<f64> {
let (pa, sa) = decimal_parts(a)?;
let (pb, sb) = decimal_parts(b)?;
let scale = sa.max(sb);
let lift = |p: i128, s: u32| 10i128.checked_pow(scale - s).and_then(|k| p.checked_mul(k));
let g = gcd_i128(lift(pa, sa)?, lift(pb, sb)?);
format!("{g}e-{scale}").parse().ok()
}
fn gcd_f64(a: f64, b: f64, tol: Tol) -> Option<f64> {
let (mut a, mut b) = (a.abs(), b.abs());
if !a.is_finite() || !b.is_finite() {
return None;
}
for _ in 0..1000 {
if b == 0.0 {
return Some(a);
}
if a == 0.0 {
return Some(b);
}
let q = a / b;
let mut k = q.floor();
if tol.eq(q, k + 1.0) {
k += 1.0;
}
let mut r = a - b * k;
if r <= 0.0 || tol.eq(r, b) {
r = 0.0;
}
a = b;
b = r;
}
Some(a)
}
#[allow(clippy::too_many_arguments)]
fn real_lcm_gcd(
op: ScalarDyad,
xs: &[f64],
xoff: usize,
xdiv: usize,
ys: &[f64],
yoff: usize,
ydiv: usize,
n: usize,
tol: Tol,
span: Span,
) -> Result<Data> {
let mut out = vec![0.0f64; n];
let mut ok = true;
zip_chunk(xs, xoff, xdiv, ys, yoff, ydiv, 0, &mut out, |a, b, slot| {
let Some(g) = gcd_decimal(a, b).or_else(|| gcd_f64(a, b, tol)) else {
ok = false;
return false;
};
*slot = if op == ScalarDyad::Gcd {
g
} else if g == 0.0 {
0.0
} else {
a / g * b
};
true
});
if !ok {
return Err(Error::domain("LCM/GCD needs finite values", span));
}
Ok(Data::F64(out.into()))
}
#[allow(clippy::too_many_arguments)]
fn lcm_gcd_data(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
tol: Tol,
span: Span,
) -> Result<Data> {
let t = arith_type(x.dtype(), y.dtype(), span)?;
if t == DType::Complex {
return complex_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, span);
}
if t.is_exact()
&& let Some(d) = exact_dyad_data(op, t, x, xoff, xdiv, y, yoff, ydiv, n, span)?
{
return Ok(d);
}
let both_bool = x.dtype() == DType::Bool && y.dtype() == DType::Bool;
let float = t == DType::F64;
let (xs, ys) = if float {
let (mut tx, mut ty) = (Vec::new(), Vec::new());
let xf = borrow_f64(x, &mut tx);
let yf = borrow_f64(y, &mut ty);
let integral = |v: &[f64]| v.iter().all(|&a| a.fract() == 0.0 && fits_i64(a));
if !integral(xf) || !integral(yf) {
return real_lcm_gcd(op, xf, xoff, xdiv, yf, yoff, ydiv, n, tol, span);
}
(
xf.iter().map(|&a| a as i64).collect::<Vec<_>>(),
yf.iter().map(|&a| a as i64).collect::<Vec<_>>(),
)
} else {
let (mut tx, mut ty) = (Vec::new(), Vec::new());
(borrow_i64(x, &mut tx).to_vec(), borrow_i64(y, &mut ty).to_vec())
};
let (out, fits) = par::fill(n, |start, part: &mut [i128]| {
let mut fits = true;
zip_chunk(&xs, xoff, xdiv, &ys, yoff, ydiv, start, part, |a, b, slot| {
let (a, b) = (a as i128, b as i128);
let g = gcd_i128(a, b);
let v = if op == ScalarDyad::Gcd {
g
} else if g == 0 {
0
} else {
a / g * b
};
fits &= i64::try_from(v).is_ok();
*slot = v;
true
});
fits
});
if !fits || float {
return Ok(Data::F64(par::map(&out, |&v| v as f64).into()));
}
if both_bool {
return Ok(Data::Bool(par::map(&out, |&v| v as u8).into()));
}
Ok(Data::I64(par::map(&out, |&v| v as i64).into()))
}
fn to_rat_vec(d: &Data) -> Option<Vec<Rat>> {
Some(match d {
Data::Bool(v) => v.iter().map(|&b| Rat::from_int(Ext::from(b))).collect(),
Data::I64(v) => v.iter().map(|&x| Rat::from_int(Ext::from(x))).collect(),
Data::Ext(v) => v.iter().map(|x| Rat::from_int(x.clone())).collect(),
Data::Rat(v) => v.to_vec(),
Data::F64(_) | Data::Complex(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => {
return None;
}
})
}
fn rat_window(d: &Data, off: usize, div: usize, n: usize) -> Option<Vec<Rat>> {
if n == 0 {
return Some(Vec::new());
}
let end = off + (n - 1) / div + 1;
if off == 0 && end == d.len() {
return to_rat_vec(d);
}
to_rat_vec(&d.slice(off, end))
}
fn exact_data(t: DType, out: Vec<Rat>) -> Data {
if t == DType::Ext && out.iter().all(Rat::is_integer) {
return Data::Ext(out.iter().map(|r| r.to_int().expect("whole")).collect());
}
Data::Rat(out.into())
}
fn too_large(span: Span) -> Error {
Error::domain(
format!(
"the exact result needs more than {} bits; use floats for a value this large",
exact::MAX_BITS
),
span,
)
}
fn exact_pow(a: &Rat, b: &Rat, span: Span) -> Result<Option<Rat>> {
let Some(e) = b.to_int().as_ref().and_then(exact::ext_to_i64) else {
return Ok(None);
};
if let Some(v) = a.pow(e) {
return Ok(Some(v));
}
if a.is_zero() && e < 0 { Ok(None) } else { Err(too_large(span)) }
}
#[allow(clippy::too_many_arguments)]
fn exact_dyad_data(
op: ScalarDyad,
t: DType,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
span: Span,
) -> Result<Option<Data>> {
use ScalarDyad::*;
let (Some(xs), Some(ys)) = (rat_window(x, xoff, xdiv, n), rat_window(y, yoff, ydiv, n))
else {
return Ok(None);
};
let mut out = Vec::with_capacity(n);
for i in 0..n {
let a = &xs[i / xdiv];
let b = &ys[i / ydiv];
let v = match op {
Add => a.add(b),
Sub => a.sub(b),
Mul => a.mul(b),
DivJ | DivApl => match a.div(b) {
Some(v) => v,
None => return Ok(None),
},
Min => a.min(b).clone(),
Max => a.max(b).clone(),
Residue => exact::rat_residue(a, b),
Gcd => exact::rat_gcd(a, b),
Lcm => exact::rat_lcm(a, b),
Pow => match exact_pow(a, b, span)? {
Some(v) => v,
None => return Ok(None),
},
Binomial => match (a.to_int(), b.to_int()) {
(Some(k), Some(m)) => match exact::ext_binomial(&k, &m) {
Some(v) => Rat::from_int(v),
None => return Ok(None),
},
_ => return Ok(None),
},
Root if t == DType::Ext => {
let (Some(k), Some(m)) = (a.to_int(), b.to_int()) else {
return Ok(None);
};
let Some(k) = exact::ext_to_i64(&k).and_then(|k| u32::try_from(k).ok()) else {
return Ok(None);
};
match exact::exact_root(k, &m) {
Some(v) => Rat::from_int(v),
None => return Ok(None),
}
}
Root | Log | Circle | MakeComplex | PolarBy => return Ok(None),
Eq | Ne | Lt | Le | Gt | Ge => return Ok(None),
};
out.push(v);
}
Ok(Some(exact_data(t, out)))
}
fn exact_monad(op: ScalarMonad, y: &Array) -> Option<Array> {
use ScalarMonad::*;
let v = to_rat_vec(&y.data)?;
let shape = y.shape.clone();
if matches!(op, Floor | Ceil | Signum) {
let out: Vec<Ext> = v
.iter()
.map(|r| match op {
Floor => r.floor(),
Ceil => r.ceil(),
_ => r.signum(),
})
.collect();
return Some(Array::new(shape, Data::Ext(out.into())).with_layout(y.layout()));
}
let two = Rat::from_int(Ext::from(2));
let mut out = Vec::with_capacity(v.len());
for r in &v {
let value = match op {
Conj => r.clone(),
Neg => r.neg(),
Abs => r.abs(),
Recip => r.recip()?,
Inc => r.add(&Rat::one()),
Dec => r.sub(&Rat::one()),
OneMinus => Rat::one().sub(r),
Double => r.add(r),
Halve => r.div(&two).expect("two is not zero"),
Square => r.mul(r),
Sqrt => r.sqrt()?,
Factorial => Rat::from_int(r.to_int().as_ref().and_then(exact::ext_factorial)?),
Exp | Ln | Pi | Imaginary | Polar | Not => return None,
Floor | Ceil | Signum => unreachable!("handled above"),
};
out.push(value);
}
Some(Array::new(shape, exact_data(y.dtype(), out)).with_layout(y.layout()))
}
fn to_exact(y: &Array, span: Span) -> Result<Array> {
let data = match &y.data {
Data::Ext(_) | Data::Rat(_) => return Ok(y.clone()),
Data::Bool(v) => Data::Ext(v.iter().map(|&b| Ext::from(b)).collect()),
Data::I64(v) => Data::Ext(v.iter().map(|&x| Ext::from(x)).collect()),
Data::F64(v) => {
let mut out = Vec::with_capacity(v.len());
for &x in v.iter() {
out.push(exact::f64_to_rat(x).ok_or_else(|| {
Error::domain("an infinity has no exact value", span)
})?);
}
exact_data(DType::Ext, out)
}
Data::Complex(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => {
return Err(Error::domain(
format!("x: needs real numbers, not {} data", y.dtype().name()),
span,
));
}
};
Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
}
fn from_exact(y: &Array) -> Array {
let shape = y.shape.clone();
match &y.data {
Data::Ext(v) => match v.iter().map(exact::ext_to_i64).collect::<Option<Vec<i64>>>() {
Some(out) => Array::new(shape, Data::I64(out.into())).with_layout(y.layout()),
None => Array::new(shape, Data::F64(v.iter().map(exact::ext_to_f64).collect()))
.with_layout(y.layout()),
},
Data::Rat(v) => Array::new(shape, Data::F64(v.iter().map(Rat::to_f64).collect()))
.with_layout(y.layout()),
_ => y.clone(),
}
}
fn exact_form(x: &Array, y: &Array, span: Span) -> Result<Array> {
match one_whole(x, "the form x: converts to", span)? {
1 => {
let e = to_exact(y, span)?;
e.cast(DType::Rat).ok_or_else(|| Error::internal("an exact value has no rational form"))
}
2 => {
let e = to_exact(y, span)?;
let v = to_rat_vec(&e.data).ok_or_else(|| Error::internal("x: gave an inexact value"))?;
let mut out = Vec::with_capacity(2 * v.len());
for r in &v {
out.push(r.numer().clone());
out.push(r.denom().clone());
}
let mut shape = y.shape.clone();
shape.push(2);
Ok(Array::new(shape, Data::Ext(out.into())))
}
-1 => Ok(from_exact(y)),
-2 => {
if !y.dtype().is_numeric() {
return Err(Error::domain(
format!("x: needs real numbers, not {} data", y.dtype().name()),
span,
));
}
Ok(y.clone())
}
n => Err(Error::domain(
format!("x: converts to form 1, 2, _1 or _2, not {n}"),
span,
)),
}
}
#[allow(clippy::too_many_arguments)]
fn exact_compare_data(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
) -> Option<Data> {
let (xs, ys) = (rat_window(x, xoff, xdiv, n)?, rat_window(y, yoff, ydiv, n)?);
let out: Vec<u8> = (0..n)
.map(|i| {
let ord = xs[i / xdiv].cmp(&ys[i / ydiv]);
cmp_result(op, Some(ord)) as u8
})
.collect();
Some(Data::Bool(out.into()))
}
#[allow(clippy::too_many_arguments)]
fn scalar_dyad_data(
op: ScalarDyad,
x: &Data,
xoff: usize,
xdiv: usize,
y: &Data,
yoff: usize,
ydiv: usize,
n: usize,
tol: Tol,
span: Span,
) -> Result<Data> {
use ScalarDyad::*;
if x.dtype() == DType::Symbol || y.dtype() == DType::Symbol {
match op {
Eq | Ne | Lt | Le | Gt | Ge => {}
Min | Max => {
return symbol_min_max(op, x, xoff, xdiv, y, yoff, ydiv, n, span);
}
_ => return Err(symbol_arith(span)),
}
}
if matches!(op, Eq | Ne | Lt | Le | Gt | Ge) {
return compare_data(op, x, xoff, xdiv, y, yoff, ydiv, n, tol, span);
}
if matches!(op, Lcm | Gcd) {
return lcm_gcd_data(op, x, xoff, xdiv, y, yoff, ydiv, n, tol, span);
}
let t = arith_type(x.dtype(), y.dtype(), span)?;
if t.is_exact()
&& let Some(d) = exact_dyad_data(op, t, x, xoff, xdiv, y, yoff, ydiv, n, span)?
{
return Ok(d);
}
if t == DType::I64 && !matches!(op, DivJ | DivApl | Log | Root | Circle) {
if let Some(d) = int_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n) {
return Ok(d);
}
}
if t == DType::Complex
|| matches!(op, MakeComplex | PolarBy)
|| pass_leaves_reals(op, x, xoff, xdiv, y, yoff, ydiv, n)
{
let data = complex_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, span)?;
if op == Circle && circle_reads_a_part(x, xoff, xdiv, n) && let Data::Complex(v) = &data {
return Ok(Data::F64(v.iter().map(|z| z[0]).collect()));
}
return Ok(data);
}
float_dyad_data(op, x, xoff, xdiv, y, yoff, ydiv, n, span)
}
fn frame_pervaded(frame: Vec<usize>, cells: Vec<Array>, span: Span) -> Result<Array> {
if cells.iter().all(|c| c.rank() == 0 && c.dtype() != DType::Box) {
return assemble(&frame, cells, span);
}
let boxes: Vec<Array> = cells.into_iter().collect();
Ok(Array::new(frame, Data::Box(boxes.into())))
}
fn pervade_dyad(
op: ScalarDyad,
x: &Array,
y: &Array,
cfg: EvalCfg,
span: Span,
) -> Result<Array> {
let p = agree(&x.shape, &y.shape, &x.shape, &y.shape, cfg.agreement, span)?;
if p.n == 0 {
return Ok(Array::new(p.frame, Data::empty(DType::Box)));
}
let (xr, yr) = (x.to_row_major(), y.to_row_major());
let mut cells = Vec::with_capacity(p.n);
for i in 0..p.n {
let a = open_cell(&atom(&xr, i / p.x_div));
let b = open_cell(&atom(&yr, i / p.y_div));
cells.push(scalar_dyad(op, &a, &b, cfg, span)?);
}
frame_pervaded(p.frame, cells, span)
}
fn pervade_monad(op: ScalarMonad, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
if y.count() == 0 {
return Ok(Array::new(y.shape.clone(), Data::empty(DType::Box)));
}
let yr = y.to_row_major();
let mut cells = Vec::with_capacity(y.count());
for i in 0..y.count() {
let a = open_cell(&atom(&yr, i));
cells.push(scalar_monad(op, &a, cfg, span)?);
}
frame_pervaded(y.shape.clone(), cells, span)
}
fn scalar_dyad(
op: ScalarDyad,
x: &Array,
y: &Array,
cfg: EvalCfg,
span: Span,
) -> Result<Array> {
if cfg.rules.lang == crate::Lang::Apl
&& (x.dtype() == DType::Box || y.dtype() == DType::Box)
{
return pervade_dyad(op, x, y, cfg, span);
}
let p = agree(&x.shape, &y.shape, &x.shape, &y.shape, cfg.agreement, span)?;
if p.n == 0 {
return Ok(Array::new(p.frame, Data::empty(empty_result_type(x, y))));
}
let data =
scalar_dyad_data(op, &x.data, 0, p.x_div, &y.data, 0, p.y_div, p.n, cfg.tol, span)?;
Ok(Array::new(p.frame, data))
}
fn empty_result_type(x: &Array, y: &Array) -> DType {
for a in [x, y] {
if a.dtype().is_numeric() {
return a.dtype();
}
}
DType::I64
}
fn fits_i64(v: f64) -> bool {
v.is_finite() && v >= i64::MIN as f64 && v < i64::MAX as f64
}
fn monad_leaves_reals(op: ScalarMonad, d: &Data) -> bool {
use ScalarMonad::*;
match op {
Imaginary | Polar => d.dtype().is_numeric(),
Sqrt | Ln => match d {
Data::I64(v) => par::any(v, |&x| x < 0),
Data::F64(v) => par::any(v, |&x| x < 0.0),
Data::Ext(v) => v.iter().any(|x| x.sign() == num_bigint::Sign::Minus),
Data::Rat(v) => v.iter().any(|x| x < &Rat::zero()),
_ => false,
},
_ => false,
}
}
fn complex_monad(op: ScalarMonad, y: &Array, span: Span) -> Result<Array> {
use ScalarMonad::*;
let mut tmp = Vec::new();
let v = borrow_cx(&y.data, &mut tmp);
if y.count() > 0 && v.is_empty() {
return Err(wrong_type(y.dtype(), span));
}
let data = match op {
Abs => Data::F64(par::map(v, |&z| cx::abs(z)).into()),
Not => return Err(Error::domain("logical negation needs values of 0 or 1", span)),
Factorial => {
return Err(Error::not_yet("the factorial of a complex number", span));
}
_ => {
let step: fn(Cx) -> Cx = match op {
Conj => cx::conj,
Neg => cx::neg,
Signum => cx::signum,
Recip => cx::recip,
Sqrt => cx::sqrt,
Exp => cx::exp,
Ln => cx::ln,
Floor => cx::floor,
Ceil => cx::ceil,
OneMinus => |z| cx::sub(cx::ONE, z),
Inc => |z| cx::add(z, cx::ONE),
Dec => |z| cx::sub(z, cx::ONE),
Double => |z| cx::add(z, z),
Halve => |z| [z[0] / 2.0, z[1] / 2.0],
Square => |z| cx::mul(z, z),
Pi => |z| [std::f64::consts::PI * z[0], std::f64::consts::PI * z[1]],
Imaginary => |z| cx::mul(cx::I, z),
Polar => |z| cx::exp(cx::mul(cx::I, z)),
Abs | Not | Factorial => unreachable!("handled above"),
};
Data::Complex(par::map(v, |&z| step(z)).into())
}
};
Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
}
fn scalar_monad(op: ScalarMonad, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
use ScalarMonad::*;
if cfg.rules.lang == crate::Lang::Apl && y.dtype() == DType::Box {
return pervade_monad(op, y, cfg, span);
}
let tol = cfg.tol;
let d = &y.data;
if y.count() == 0 && !d.dtype().is_numeric() {
return Ok(Array::new(y.shape.clone(), Data::empty(DType::I64)));
}
if d.dtype() == DType::Complex || monad_leaves_reals(op, d) {
return complex_monad(op, y, span);
}
if d.dtype().is_exact() && let Some(a) = exact_monad(op, y) {
return Ok(a);
}
let mut tmp = Vec::new();
let data = match op {
Conj if d.dtype().is_numeric() => d.clone(),
Conj => return Err(wrong_type(d.dtype(), span)),
Imaginary | Polar => return Err(Error::internal("a complex monad on the real path")),
Neg => match d {
Data::Bool(v) => Data::I64(par::map(v, |&b| -(b as i64)).into()),
Data::I64(v) => match par::try_map(v, i64::checked_neg) {
Some(out) => Data::I64(out.into()),
None => Data::F64(par::map(v, |&x| -(x as f64)).into()),
},
Data::F64(v) => Data::F64(par::map(v, |&x| -x).into()),
_ => return Err(wrong_type(d.dtype(), span)),
},
Signum => match d {
Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64).into()),
Data::I64(v) => Data::I64(par::map(v, |&x| x.signum()).into()),
Data::F64(v) => Data::F64(
par::map(v, |&x| {
if tol.is_zero(x) {
0.0
} else if x > 0.0 {
1.0
} else if x < 0.0 {
-1.0
} else {
0.0
}
})
.into(),
),
_ => return Err(wrong_type(d.dtype(), span)),
},
Recip => {
let v = as_f64(d, &mut tmp, span)?;
Data::F64(par::map(v, |&x| if x == 0.0 { f64::INFINITY } else { 1.0 / x }).into())
}
Sqrt => {
let v = as_f64(d, &mut tmp, span)?;
Data::F64(par::map(v, |&x| x.sqrt()).into())
}
Exp => {
let v = as_f64(d, &mut tmp, span)?;
Data::F64(par::map(v, |&x| x.exp()).into())
}
Abs => match d {
Data::Bool(_) => d.clone(),
Data::I64(v) => match par::try_map(v, i64::checked_abs) {
Some(out) => Data::I64(out.into()),
None => Data::F64(par::map(v, |&x| (x as f64).abs()).into()),
},
Data::F64(v) => Data::F64(par::map(v, |&x| x.abs()).into()),
_ => return Err(wrong_type(d.dtype(), span)),
},
Floor | Ceil => match d {
Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64).into()),
Data::I64(_) => d.clone(),
Data::F64(v) => {
let round = |x: f64| if op == Floor { tol.floor(x) } else { tol.ceil(x) };
match par::try_map(v, |x| {
let r = round(x);
fits_i64(r).then_some(r as i64)
}) {
Some(out) => Data::I64(out.into()),
None => Data::F64(par::map(v, |&x| round(x)).into()),
}
}
_ => return Err(wrong_type(d.dtype(), span)),
},
Inc | Dec => {
let step = if op == Inc { 1i64 } else { -1 };
match d {
Data::Bool(v) => Data::I64(par::map(v, |&b| b as i64 + step).into()),
Data::I64(v) => match par::try_map(v, |x: i64| x.checked_add(step)) {
Some(out) => Data::I64(out.into()),
None => Data::F64(par::map(v, |&x| x as f64 + step as f64).into()),
},
Data::F64(v) => Data::F64(par::map(v, |&x| x + step as f64).into()),
_ => return Err(wrong_type(d.dtype(), span)),
}
}
Double | Square => match d {
Data::Bool(v) => {
Data::I64(par::map(v, |&b| if op == Double { 2 * b as i64 } else { b as i64 }).into())
}
Data::I64(v) => {
let f = |x: i64| if op == Double { x.checked_mul(2) } else { x.checked_mul(x) };
match par::try_map(v, f) {
Some(out) => Data::I64(out.into()),
None => Data::F64(
par::map(v, |&x| {
let x = x as f64;
if op == Double { x + x } else { x * x }
})
.into(),
),
}
}
Data::F64(v) => {
Data::F64(par::map(v, |&x| if op == Double { x + x } else { x * x }).into())
}
_ => return Err(wrong_type(d.dtype(), span)),
},
Halve => {
let v = as_f64(d, &mut tmp, span)?;
Data::F64(par::map(v, |&x| x / 2.0).into())
}
Pi => {
let v = as_f64(d, &mut tmp, span)?;
Data::F64(par::map(v, |&x| std::f64::consts::PI * x).into())
}
Factorial => {
let v = as_f64(d, &mut tmp, span)?;
Data::F64(par::map(v, |&x| factorial(x)).into())
}
Ln => {
let v = as_f64(d, &mut tmp, span)?;
Data::F64(par::map(v, |&x| x.ln()).into())
}
OneMinus => match d {
Data::Bool(v) => Data::Bool(par::map(v, |&b| 1 - b).into()),
Data::I64(v) => match par::try_map(v, |x: i64| 1i64.checked_sub(x)) {
Some(out) => Data::I64(out.into()),
None => Data::F64(par::map(v, |&x| 1.0 - x as f64).into()),
},
Data::F64(v) => Data::F64(par::map(v, |&x| 1.0 - x).into()),
_ => return Err(wrong_type(d.dtype(), span)),
},
Not => {
let bad = || Error::domain("logical negation needs values of 0 or 1", span);
match d {
Data::Bool(v) => Data::Bool(par::map(v, |&b| 1 - b).into()),
Data::I64(v) => {
let out = par::try_map(v, |x: i64| match x {
0 => Some(1u8),
1 => Some(0u8),
_ => None,
})
.ok_or_else(bad)?;
Data::Bool(out.into())
}
Data::F64(v) => {
let out = par::try_map(v, |x: f64| {
if x == 0.0 {
Some(1u8)
} else if x == 1.0 {
Some(0u8)
} else {
None
}
})
.ok_or_else(bad)?;
Data::Bool(out.into())
}
_ => return Err(bad()),
}
}
};
Ok(Array::new(y.shape.clone(), data).with_layout(y.layout()))
}
fn transpose_axes(y: &Array) -> Array {
if y.rank() < 2 {
return y.clone();
}
let out_shape: Vec<usize> = y.shape.iter().rev().copied().collect();
let flipped = match y.layout() {
Layout::RowMajor => Layout::ColMajor,
Layout::ColMajor => Layout::RowMajor,
};
Array::new(out_shape, y.data.clone()).with_layout(flipped)
}
fn iota_j(y: &Array, span: Span) -> Result<Array> {
if y.rank() > 1 {
return Err(Error::new(
ErrorKind::Rank,
"index generator needs a scalar or vector argument",
Some(span),
));
}
let dims = y
.to_i64_vec()
.ok_or_else(|| Error::domain("index generator needs integer lengths", span))?;
let shape: Vec<usize> = dims.iter().map(|d| d.unsigned_abs() as usize).collect();
let n = crate::limits::elements(&shape, span)?;
let st = strides(&shape);
let mut out = Vec::with_capacity(n);
let mut coord = vec![0usize; shape.len()];
for _ in 0..n {
let mut v = 0usize;
for k in 0..shape.len() {
let c = if dims[k] < 0 { shape[k] - 1 - coord[k] } else { coord[k] };
v += c * st[k];
}
out.push(v as i64);
odometer(&mut coord, &shape);
}
let data = Data::I64(out.into());
let data = if y.dtype() == DType::Ext {
data.cast(DType::Ext).ok_or_else(|| Error::internal("integers have no extended form"))?
} else {
data
};
Ok(Array::new(shape, data))
}
fn head(y: &Array) -> Array {
if y.rank() == 0 {
return y.clone();
}
if y.items() == 0 {
let cell_shape = y.shape[1..].to_vec();
let n: usize = cell_shape.iter().product();
return Array::new(cell_shape, fill_data(y.dtype(), n));
}
y.item(0)
}
fn behead(y: &Array, span: Span) -> Result<Array> {
if y.rank() == 0 {
return Err(Error::domain("cannot drop the first item of a scalar", span));
}
if y.items() == 0 {
return Ok(y.clone());
}
let m = y.item_size();
let mut shape = y.shape.clone();
shape[0] -= 1;
Ok(Array::new(shape, y.data.slice(m, y.count())))
}
fn tail(y: &Array) -> Array {
if y.rank() == 0 {
return y.clone();
}
let n = y.items();
if n == 0 {
let cell_shape = y.shape[1..].to_vec();
let m: usize = cell_shape.iter().product();
return Array::new(cell_shape, fill_data(y.dtype(), m));
}
y.item(n - 1)
}
fn curtail(y: &Array) -> Array {
if y.rank() == 0 {
return Array::empty(y.dtype());
}
let n = y.items();
if n == 0 {
return y.clone();
}
let m = y.item_size();
let mut shape = y.shape.clone();
shape[0] = n - 1;
Array::new(shape, y.data.slice(0, (n - 1) * m))
}
fn reverse(y: &Array) -> Array {
if y.rank() == 0 {
return y.clone();
}
let n = y.items();
let m = y.item_size();
let mut data = Data::empty(y.dtype());
for i in (0..n).rev() {
for k in 0..m {
push_elem(&mut data, &y.data, i * m + k);
}
}
Array::new(y.shape.clone(), data)
}
fn rotate(x: &Array, y: &Array, span: Span) -> Result<Array> {
let counts = axis_counts(x, "rotate", span)?;
if y.rank() == 0 {
return Ok(y.clone());
}
if counts.len() > y.rank() {
return Err(Error::new(
ErrorKind::Length,
format!(
"rotate has {} amounts for an argument of rank {}",
counts.len(),
y.rank()
),
Some(span),
));
}
let st = strides(&y.shape);
let n = y.count();
let r = y.rank();
let mut data = Data::empty(y.dtype());
let mut coord = vec![0usize; r];
for _ in 0..n {
let mut idx = 0usize;
for k in 0..r {
let len = y.shape[k] as i64;
let s = counts.get(k).copied().unwrap_or(0);
idx += (coord[k] as i64 + s).rem_euclid(len) as usize * st[k];
}
push_elem(&mut data, &y.data, idx);
odometer(&mut coord, &y.shape);
}
Ok(Array::new(y.shape.clone(), data))
}
fn elem_key(d: &Data, i: usize) -> u64 {
match d {
Data::Bool(v) => v[i] as u64,
Data::I64(v) => v[i] as u64,
Data::F64(v) => {
let x = v[i];
if x == 0.0 { 0 } else { x.to_bits() }
}
Data::Complex(v) => cx_key(v[i]),
Data::Char(v) => v[i] as u64,
Data::Symbol(v) => v[i] as u64,
Data::Ext(_) | Data::Rat(_) | Data::Box(_) => 0,
}
}
fn num_key(d: &Data, i: usize) -> u64 {
match d {
Data::Bool(v) => (v[i] as f64).to_bits(),
Data::I64(v) => (v[i] as f64).to_bits(),
Data::F64(v) => {
let x = v[i];
if x == 0.0 { 0.0f64.to_bits() } else { x.to_bits() }
}
Data::Complex(v) => cx_key(v[i]),
Data::Char(v) => v[i] as u64,
Data::Symbol(v) => v[i] as u64,
Data::Ext(_) | Data::Rat(_) | Data::Box(_) => 0,
}
}
fn cx_key(z: Cx) -> u64 {
let bits = |x: f64| if x == 0.0 { 0u64 } else { x.to_bits() };
bits(z[0]) ^ bits(z[1]).rotate_left(32)
}
fn nub(y: &Array, tol: Tol) -> Array {
if y.rank() == 0 {
return Array::new(vec![1], y.data.clone());
}
let n = y.items();
let m = y.item_size();
let mut keep = Vec::new();
if y.dtype() == DType::Box || y.dtype().is_exact() {
for i in 0..n {
if !keep.iter().any(|&j| arrays_match(&y.item(i), &y.item(j), tol)) {
keep.push(i);
}
}
} else if y.dtype() == DType::F64 && tol.ct != 0.0 {
let mut tv = Vec::new();
let v = borrow_f64(&y.data, &mut tv);
for i in 0..n {
if !keep.iter().any(|&j| (0..m).all(|k| tol.eq(v[i * m + k], v[j * m + k]))) {
keep.push(i);
}
}
} else {
let mut seen: HashSet<Vec<u64>> = HashSet::with_capacity(n);
for i in 0..n {
let key: Vec<u64> = (0..m).map(|k| elem_key(&y.data, i * m + k)).collect();
if seen.insert(key) {
keep.push(i);
}
}
}
let mut data = Data::empty(y.dtype());
for &i in &keep {
for k in 0..m {
push_elem(&mut data, &y.data, i * m + k);
}
}
let mut shape = y.shape.clone();
shape[0] = keep.len();
Array::new(shape, data)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Tao {
J,
Apl2,
}
impl Tao {
fn of(rules: Rules) -> Tao {
match rules.lang {
crate::Lang::J => Tao::J,
crate::Lang::Apl => Tao::Apl2,
}
}
fn class(self, dt: DType) -> u8 {
match self {
Tao::J => match dt {
DType::Symbol => 1,
DType::Char => 2,
DType::Box => 3,
_ => 0,
},
Tao::Apl2 => match dt {
DType::Char | DType::Symbol => 0,
DType::Box => 2,
_ => 1,
},
}
}
}
fn cmp_items_total(x: &Array, y: &Array, tao: Tao) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
match tao {
Tao::J => {
let class = |a: &Array| if a.count() == 0 { 0 } else { tao.class(a.dtype()) };
class(x)
.cmp(&class(y))
.then_with(|| x.rank().cmp(&y.rank()))
.then_with(|| x.shape.iter().rev().cmp(y.shape.iter().rev()))
.then_with(|| cmp_atoms(x, y, tao))
}
Tao::Apl2 => x
.rank()
.cmp(&y.rank())
.then_with(|| x.shape.iter().cmp(y.shape.iter()))
.then_with(|| cmp_atoms(x, y, tao))
.then_with(|| {
if x.count() == 0 {
tao.class(x.dtype()).cmp(&tao.class(y.dtype()))
} else {
Equal
}
}),
}
}
fn cmp_atoms(x: &Array, y: &Array, tao: Tao) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
let n = x.count();
if n == 0 {
return Equal;
}
let (xr, yr) = (x.to_row_major(), y.to_row_major());
let (dx, dy) = (xr.row_major_data(), yr.row_major_data());
let opened = |d: &Data, i: usize| -> Array {
match d {
Data::Box(v) => v[i].clone(),
_ => {
let mut one = Data::empty(d.dtype());
push_elem(&mut one, d, i);
Array::new(vec![], one)
}
}
};
if matches!(dx, Data::Box(_)) || matches!(dy, Data::Box(_)) {
return (0..n)
.map(|i| cmp_items_total(&opened(dx, i), &opened(dy, i), tao))
.find(|o| *o != Equal)
.unwrap_or(Equal);
}
let classes = tao.class(dx.dtype()).cmp(&tao.class(dy.dtype()));
if classes != Equal {
return classes;
}
match (dx, dy) {
(Data::Char(a), Data::Char(b)) => a[..n].cmp(&b[..n]),
_ => cmp_numbers(dx, dy, n),
}
}
fn cmp_numbers(dx: &Data, dy: &Data, n: usize) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
let seek = |f: &dyn Fn(usize) -> std::cmp::Ordering| {
(0..n).map(f).find(|o| *o != Equal).unwrap_or(Equal)
};
match DType::promote(dx.dtype(), dy.dtype()) {
Some(DType::Complex) => {
let (mut ta, mut tb) = (Vec::new(), Vec::new());
let (a, b) = (borrow_cx(dx, &mut ta), borrow_cx(dy, &mut tb));
seek(&|k| {
a[k][0]
.partial_cmp(&b[k][0])
.unwrap_or(Equal)
.then_with(|| a[k][1].partial_cmp(&b[k][1]).unwrap_or(Equal))
})
}
Some(DType::F64) => {
let (mut ta, mut tb) = (Vec::new(), Vec::new());
let (a, b) = (borrow_f64(dx, &mut ta), borrow_f64(dy, &mut tb));
seek(&|k| a[k].partial_cmp(&b[k]).unwrap_or(Equal))
}
Some(t) if t.is_exact() => match (to_rat_vec(dx), to_rat_vec(dy)) {
(Some(a), Some(b)) => seek(&|k| a[k].cmp(&b[k])),
_ => Equal,
},
None => Equal,
Some(_) => {
let (mut ta, mut tb) = (Vec::new(), Vec::new());
let (a, b) = (borrow_i64(dx, &mut ta), borrow_i64(dy, &mut tb));
seek(&|k| a[k].cmp(&b[k]))
}
}
}
fn cmp_items(d: &Data, i: usize, j: usize, m: usize, tao: Tao) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
let (a, b) = (i * m, j * m);
let ord = |k: usize| match d {
Data::Bool(v) => v[a + k].cmp(&v[b + k]),
Data::I64(v) => v[a + k].cmp(&v[b + k]),
Data::F64(v) => v[a + k].partial_cmp(&v[b + k]).unwrap_or(Equal),
Data::Complex(v) => v[a + k][0]
.partial_cmp(&v[b + k][0])
.unwrap_or(Equal)
.then_with(|| v[a + k][1].partial_cmp(&v[b + k][1]).unwrap_or(Equal)),
Data::Char(v) => v[a + k].cmp(&v[b + k]),
Data::Symbol(v) => crate::symbol::cmp(v[a + k], v[b + k]),
Data::Ext(v) => v[a + k].cmp(&v[b + k]),
Data::Rat(v) => v[a + k].cmp(&v[b + k]),
Data::Box(v) => cmp_items_total(&v[a + k], &v[b + k], tao),
};
(0..m).map(ord).find(|o| *o != Equal).unwrap_or(Equal)
}
fn grade_order(y: &Array, down: bool, tao: Tao) -> Vec<usize> {
if y.rank() == 0 {
return vec![0];
}
let n = y.items();
let m = y.item_size();
let mut idx: Vec<usize> = (0..n).collect();
if down {
idx.sort_by(|&a, &b| cmp_items(&y.data, b, a, m, tao));
} else {
idx.sort_by(|&a, &b| cmp_items(&y.data, a, b, m, tao));
}
idx
}
fn collate_grade(x: &Array, y: &Array, down: bool, origin: i64, span: Span) -> Result<Array> {
let chars_of = |a: &Array| -> Result<Vec<char>> {
match a.row_major_data() {
Data::Char(v) => Ok(v.as_slice().to_vec()),
_ => Err(Error::domain("a collating grade takes characters", span)),
}
};
let (xs, ys) = (chars_of(x)?, chars_of(y)?);
let xshape = if x.rank() == 0 { vec![1] } else { x.shape.clone() };
let width = xshape.len();
let absent: Vec<usize> = xshape.iter().rev().copied().collect();
let mut keys: std::collections::HashMap<char, Vec<usize>> =
std::collections::HashMap::new();
let xst = strides(&xshape);
for (i, &c) in xs.iter().enumerate() {
keys.entry(c).or_insert_with(|| {
(0..width).map(|a| (i / xst[a]) % xshape[a]).rev().collect()
});
}
let key_of = |c: char| keys.get(&c).unwrap_or(&absent).clone();
let n = if y.rank() == 0 { 1 } else { y.items() };
let m = if n == 0 { 0 } else { ys.len() / n };
let item_keys: Vec<Vec<usize>> = (0..n)
.map(|i| ys[i * m..(i + 1) * m].iter().flat_map(|&c| key_of(c)).collect())
.collect();
let mut idx: Vec<usize> = (0..n).collect();
if down {
idx.sort_by(|&a, &b| item_keys[b].cmp(&item_keys[a]));
} else {
idx.sort_by(|&a, &b| item_keys[a].cmp(&item_keys[b]));
}
Ok(Array::from_i64(idx.into_iter().map(|i| origin + i as i64).collect()))
}
fn atomic_rep(y: &Array, ctx: &Ctx<'_>, span: Span) -> Result<Array> {
let name = match y.as_boxes() {
Some([b]) if y.rank() == 0 => crate::gerund::text_of(b),
_ => None,
};
let Some(name) = name else {
return Err(Error::domain("5!:1 takes a boxed name", span));
};
if let Some(v) = ctx.env.verb(&name) {
let ar = crate::gerund::verb_ar(v).ok_or_else(|| {
Error::not_yet(
format!("the atomic representation of {}", v.name()),
span,
)
})?;
return Ok(Array::boxed(ar.to_array()));
}
match ctx.env.get(&name) {
Some(a) => Ok(Array::boxed(crate::gerund::Ar::Noun(a).to_array())),
None => Err(Error::new(
ErrorKind::Value,
format!("undefined name: {name}"),
Some(span),
)),
}
}
fn catalogue(y: &Array, span: Span) -> Result<Array> {
let items = if y.rank() == 0 { vec![y.clone()] } else { y.cells(1) };
let opened: Vec<Array> = items
.iter()
.map(|it| match it.as_boxes() {
Some(bs) if it.rank() == 0 => bs[0].clone(),
_ => it.clone(),
})
.collect();
let mut shape: Vec<usize> = Vec::new();
for o in &opened {
shape.extend_from_slice(&o.shape);
}
let total: usize = shape.iter().product();
let mut out = Vec::with_capacity(total);
let mut coord = vec![0usize; shape.len()];
for _ in 0..total {
let mut at = 0usize;
let mut picks = Vec::with_capacity(opened.len());
for o in &opened {
let st = strides(&o.shape);
let idx: usize = (0..o.rank()).map(|a| coord[at + a] * st[a]).sum();
at += o.rank();
let mut data = Data::empty(o.dtype());
push_elem(&mut data, o.row_major_data(), idx);
picks.push(Array::new(vec![], data));
}
out.push(assemble(&[picks.len()], picks, span)?);
odometer(&mut coord, &shape);
}
Ok(Array::new(shape, Data::Box(out.into())))
}
fn raze_in(y: &Array, tol: Tol, span: Span) -> Result<Array> {
let all = raze(y, span)?;
let n = if all.rank() == 0 { 1 } else { all.items() };
let elements: Vec<Array> = (0..y.count())
.map(|i| {
let mut data = Data::empty(y.dtype());
push_elem(&mut data, y.row_major_data(), i);
let one = Array::new(vec![], data);
match one.as_boxes() {
Some(bs) => bs[0].clone(),
None => one,
}
})
.collect();
let mut out = Vec::with_capacity(elements.len() * n);
for e in &elements {
let row = member_j(&all, e, tol);
out.extend_from_slice(row.to_i64_vec().unwrap_or_default().as_slice());
}
let mut shape = y.shape.clone();
shape.push(n);
Ok(Array::new(shape, Data::Bool(out.into_iter().map(|v| v as u8).collect::<Vec<u8>>().into())))
}
fn select_items(y: &Array, order: &[usize]) -> Array {
let m = y.item_size();
let mut data = Data::empty(y.dtype());
for &i in order {
for k in 0..m {
push_elem(&mut data, &y.data, i * m + k);
}
}
let mut shape = y.shape.clone();
shape[0] = order.len();
Array::new(shape, data)
}
fn check_gradable(y: &Array, rules: Rules, span: Span) -> Result<()> {
if y.dtype() == DType::Complex && rules.complex_order != ComplexOrder::RealThenImaginary {
return Err(Error::not_yet("grading complex values by magnitude and angle", span));
}
Ok(())
}
fn grade_select(x: &Array, y: &Array, down: bool, rules: Rules, span: Span) -> Result<Array> {
check_gradable(y, rules, span)?;
let order = grade_order(y, down, Tao::of(rules));
if x.rank() == 0 {
return Ok(x.clone());
}
if let Some(&past) = order.iter().find(|&&i| i >= x.items()) {
return Err(Error::domain(
format!("index {past} is out of range: the argument has {} items", x.items()),
span,
));
}
Ok(select_items(x, &order))
}
pub(crate) fn arrays_match(x: &Array, y: &Array, tol: Tol) -> bool {
if x.shape != y.shape {
return false;
}
if x.layout() != y.layout() {
return arrays_match(&x.to_row_major(), &y.to_row_major(), tol);
}
if x.count() == 0 {
return true;
}
if let (Data::Box(a), Data::Box(b)) = (&x.data, &y.data) {
return a.iter().zip(b.iter()).all(|(p, q)| arrays_match(p, q, tol));
}
let (dx, dy) = (x.dtype(), y.dtype());
match DType::promote(dx, dy) {
None => false,
Some(DType::Char) => match (&x.data, &y.data) {
(Data::Char(a), Data::Char(b)) => a.as_slice() == b.as_slice(),
_ => false,
},
Some(DType::Symbol) => match (&x.data, &y.data) {
(Data::Symbol(a), Data::Symbol(b)) => a.as_slice() == b.as_slice(),
_ => false,
},
Some(DType::F64) => {
let (mut ta, mut tb) = (Vec::new(), Vec::new());
let a = borrow_f64(&x.data, &mut ta);
let b = borrow_f64(&y.data, &mut tb);
a.iter().zip(b).all(|(p, q)| tol.eq(*p, *q))
}
Some(DType::Complex) => {
let (mut ta, mut tb) = (Vec::new(), Vec::new());
let a = borrow_cx(&x.data, &mut ta);
let b = borrow_cx(&y.data, &mut tb);
a.iter().zip(b).all(|(p, q)| tol.eq_cx(*p, *q))
}
Some(t) if t.is_exact() => match (to_rat_vec(&x.data), to_rat_vec(&y.data)) {
(Some(a), Some(b)) => a == b,
_ => false,
},
Some(_) => {
let (mut ta, mut tb) = (Vec::new(), Vec::new());
let a = borrow_i64(&x.data, &mut ta);
let b = borrow_i64(&y.data, &mut tb);
a.iter().zip(b).all(|(p, q)| p == q)
}
}
}
fn item_or_self(a: &Array, i: usize) -> Array {
if a.rank() == 0 { a.clone() } else { a.item(i) }
}
fn member_j(x: &Array, y: &Array, tol: Tol) -> Array {
let cell_rank = y.rank().saturating_sub(1).min(x.rank());
let frame_rank = x.rank() - cell_rank;
let frame: Vec<usize> = x.shape[..frame_rank].to_vec();
let nf: usize = frame.iter().product();
let items = y.items();
let mut out = Vec::with_capacity(nf);
for i in 0..nf {
let cell = x.cell_at(frame_rank, i);
out.push((0..items).any(|j| arrays_match(&cell, &item_or_self(y, j), tol)) as u8);
}
Array::new(frame, Data::Bool(out.into()))
}
fn member_apl(x: &Array, y: &Array, tol: Tol) -> Array {
let n = x.count();
if x.dtype() == DType::Box
|| y.dtype() == DType::Box
|| x.dtype().is_exact()
|| y.dtype().is_exact()
{
let opened = |a: &Array, i: usize| -> Array {
let e = atom(a, i);
match e.as_boxes() {
Some([b]) if b.rank() == 0 && b.dtype() != DType::Box => b.clone(),
_ => e,
}
};
let out: Vec<u8> = (0..n)
.map(|i| {
let e = opened(x, i);
u8::from((0..y.count()).any(|j| arrays_match(&e, &opened(y, j), tol)))
})
.collect();
return Array::new(x.shape.clone(), Data::Bool(out.into()));
}
if x.dtype() != y.dtype()
&& [x.dtype(), y.dtype()].iter().any(|&d| matches!(d, DType::Char | DType::Symbol))
{
return Array::new(x.shape.clone(), Data::Bool(vec![0u8; n].into()));
}
if tol.ct != 0.0
&& (x.dtype() == DType::F64 || y.dtype() == DType::F64)
&& x.dtype() != DType::Char
{
let (mut tx, mut ty) = (Vec::new(), Vec::new());
let xs = borrow_f64(&x.data, &mut tx);
let ys = borrow_f64(&y.data, &mut ty);
let out: Vec<u8> =
xs.iter().map(|a| ys.iter().any(|b| tol.eq(*a, *b)) as u8).collect();
return Array::new(x.shape.clone(), Data::Bool(out.into()));
}
let seen: HashSet<u64> = (0..y.count()).map(|i| num_key(&y.data, i)).collect();
let out: Vec<u8> =
(0..n).map(|i| seen.contains(&num_key(&x.data, i)) as u8).collect();
Array::new(x.shape.clone(), Data::Bool(out.into()))
}
fn index_of(x: &Array, y: &Array, origin: i64, tol: Tol) -> Array {
let cell_rank = x.rank().saturating_sub(1).min(y.rank());
let frame_rank = y.rank() - cell_rank;
let frame: Vec<usize> = y.shape[..frame_rank].to_vec();
let nf: usize = frame.iter().product();
let items = x.items();
let mut out = Vec::with_capacity(nf);
for i in 0..nf {
let cell = y.cell_at(frame_rank, i);
let at = (0..items)
.find(|&j| arrays_match(&cell, &item_or_self(x, j), tol))
.unwrap_or(items);
out.push(origin + at as i64);
}
Array::new(frame, Data::I64(out.into()))
}
fn from_index(x: &Array, y: &Array, span: Span) -> Result<Array> {
if let Some(spec) = x.as_boxes().and_then(<[Array]>::first) {
let spec = index_spec(spec, y, span)?;
return Ok(select_spec(&spec, y));
}
let idx = x
.to_i64_vec()
.ok_or_else(|| Error::domain("index must be an integer", span))?;
let Some(&i) = idx.first() else {
return Err(Error::internal("from_index with no index"));
};
let n = y.items() as i64;
let k = if i < 0 { i + n } else { i };
if k < 0 || k >= n {
return Err(Error::domain(
format!("index {i} is out of range: the argument has {n} items"),
span,
));
}
Ok(item_or_self(y, k as usize))
}
fn cat_promote(a: &Array, other: &Array, rank: usize, axis: usize, span: Span) -> Result<Array> {
if a.rank() == rank {
return Ok(a.clone());
}
if a.rank() == 0 {
let mut shape =
if other.rank() == rank { other.shape.clone() } else { vec![1usize; rank] };
shape[axis] = 1;
let n: usize = shape.iter().product();
let mut data = Data::empty(a.dtype());
for _ in 0..n {
push_elem(&mut data, &a.data, 0);
}
return Ok(Array::new(shape, data));
}
if a.rank() + 1 == rank {
let mut shape = a.shape.clone();
shape.insert(axis, 1);
return Ok(Array::new(shape, a.data.clone()));
}
Err(Error::new(
ErrorKind::Rank,
format!("cannot catenate rank {} with rank {}", a.rank(), other.rank()),
Some(span),
))
}
pub(crate) fn catenate(
x: &Array,
y: &Array,
leading: bool,
fill: bool,
span: Span,
) -> Result<Array> {
let rank = x.rank().max(y.rank()).max(1);
let axis = if leading { 0 } else { rank - 1 };
let xa = cat_promote(x, y, rank, axis, span)?;
let ya = cat_promote(y, x, rank, axis, span)?;
let mut ragged = false;
let want: Vec<i64> = (0..rank)
.map(|k| {
ragged |= k != axis && xa.shape[k] != ya.shape[k];
xa.shape[k].max(ya.shape[k]) as i64
})
.collect();
if ragged && !fill {
return Err(Error::new(
ErrorKind::Length,
format!(
"cannot catenate: left shape {}, right shape {}",
show_shape(&xa.shape),
show_shape(&ya.shape)
),
Some(span),
));
}
let (xa, ya) = if ragged {
let fit = |a: &Array| -> Result<Array> {
let mut to = want.clone();
to[axis] = a.shape[axis] as i64;
take(&Array::from_i64(to), a, false, false, span)
};
(fit(&xa)?, fit(&ya)?)
} else {
(xa, ya)
};
let (xa, ya) = if !fill && (xa.dtype() == DType::Box) != (ya.dtype() == DType::Box) {
(nest_like(&xa, &ya), nest_like(&ya, &xa))
} else {
(xa, ya)
};
let dt = DType::promote(xa.dtype(), ya.dtype()).ok_or_else(|| {
let boxed = xa.dtype() == DType::Box || ya.dtype() == DType::Box;
let what = if boxed {
"cannot catenate boxed and unboxed data; box the other side first"
} else {
"cannot catenate character and numeric data"
};
Error::new(ErrorKind::Type, what, Some(span))
})?;
let widen = |a: &Array| -> Result<Data> {
if a.dtype() == dt {
Ok(a.data.clone())
} else {
a.data.cast(dt).ok_or_else(|| Error::internal("unsupported widening in catenate"))
}
};
let xd = widen(&xa)?;
let yd = widen(&ya)?;
let outer: usize = xa.shape[..axis].iter().product();
let ix: usize = xa.shape[axis..].iter().product();
let iy: usize = ya.shape[axis..].iter().product();
let mut data = Data::empty(dt);
for o in 0..outer {
for k in 0..ix {
push_elem(&mut data, &xd, o * ix + k);
}
for k in 0..iy {
push_elem(&mut data, &yd, o * iy + k);
}
}
let mut shape = xa.shape.clone();
shape[axis] = xa.shape[axis] + ya.shape[axis];
Ok(Array::new(shape, data))
}
fn copy_items(x: &Array, y: &Array, apl: bool, span: Span) -> Result<Array> {
let counts = x
.to_i64_vec()
.ok_or_else(|| Error::domain("replication counts must be integers", span))?;
if !apl && counts.iter().any(|&c| c < 0) {
return Err(Error::domain("replication counts must be nonnegative", span));
}
let one_item = apl && x.rank() > 0 && y.rank() > 0 && y.items() == 1 && counts.len() != 1;
let scalar_y = y.rank() == 0 || one_item;
let m = y.item_size();
let n = if x.rank() == 0 || !scalar_y { y.items() } else { counts.len() };
let per = if x.rank() == 0 { vec![counts[0]; n] } else { counts };
if per.len() != n {
return Err(Error::new(
ErrorKind::Length,
format!("{} replication count(s) for {n} item(s)", per.len()),
Some(span),
));
}
let items: u128 = per.iter().map(|&c| c.unsigned_abs() as u128).sum();
let total = crate::limits::count(items * m.max(1) as u128, span)? / m.max(1);
let mut data = Data::empty(y.dtype());
for (i, &c) in per.iter().enumerate() {
let src = if scalar_y { 0 } else { i };
for _ in 0..c.unsigned_abs() {
for k in 0..m {
if c < 0 {
data.push_fill();
} else {
push_elem(&mut data, &y.data, src * m + k);
}
}
}
}
let mut shape = if y.rank() == 0 { vec![1] } else { y.shape.clone() };
shape[0] = total;
Ok(Array::new(shape, data))
}
fn format_chars(y: &Array, opts: &FmtOpts) -> Array {
if y.dtype() == DType::Char {
return y.clone();
}
if y.count() == 0 {
return Array::new(y.shape.clone(), Data::empty(DType::Char));
}
let text = crate::fmt::format_array(y, opts);
if y.dtype() == DType::Box {
let lines = text.lines().filter(|l| !l.is_empty()).count();
let rows: usize =
if y.rank() == 0 { 1 } else { y.shape[..y.rank() - 1].iter().product() };
if lines != rows {
return text_planes(&text, &y.shape[..y.rank().saturating_sub(2)]);
}
}
if y.rank() < 2 {
let chars: Vec<char> = text.chars().collect();
return Array::new(vec![chars.len()], Data::Char(chars.into()));
}
let lines: Vec<&str> = text.lines().filter(|l| !l.is_empty()).collect();
let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
let mut chars: Vec<char> = Vec::with_capacity(lines.len() * width);
for line in &lines {
chars.extend(line.chars());
chars.resize(chars.len() + width - line.chars().count(), ' ');
}
let mut shape = y.shape[..y.rank() - 1].to_vec();
shape.push(width);
debug_assert_eq!(lines.len(), shape[..shape.len() - 1].iter().product::<usize>());
Array::new(shape, Data::Char(chars.into()))
}
fn text_planes(text: &str, frame: &[usize]) -> Array {
let lines: Vec<&str> = text.lines().filter(|l| !l.is_empty()).collect();
let width = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
let planes: usize = frame.iter().product::<usize>().max(1);
let per = lines.len() / planes;
let mut chars: Vec<char> = Vec::with_capacity(lines.len() * width);
for line in &lines {
chars.extend(line.chars());
chars.resize(chars.len() + width - line.chars().count(), ' ');
}
let mut shape = frame.to_vec();
shape.push(per);
shape.push(width);
Array::new(shape, Data::Char(chars.into()))
}
fn digits_of(a: &Array, what: &str, span: Span) -> Result<Vec<f64>> {
a.to_f64_vec().ok_or_else(|| Error::domain(format!("{what} needs numeric data"), span))
}
fn narrow(values: Vec<f64>, integral: bool) -> Data {
if integral && values.iter().all(|&v| v.fract() == 0.0 && fits_i64(v)) {
return Data::I64(values.iter().map(|&v| v as i64).collect::<Vec<_>>().into());
}
Data::F64(values.into())
}
fn is_integral(a: &Array) -> bool {
!matches!(a.dtype(), DType::F64 | DType::Rat | DType::Char | DType::Symbol)
}
fn decode_exact(x: Option<&Array>, y: &Array) -> Option<Array> {
let yr = y.to_row_major();
let digits = to_rat_vec(&yr.data)?;
let two = Rat::from_int(Ext::from(2));
let radix: Vec<Rat> = match x {
None => vec![two; digits.len()],
Some(x) => {
let r = to_rat_vec(&x.to_row_major().data)?;
match r.len() {
1 => vec![r[0].clone(); digits.len()],
n if n == digits.len() => r,
_ => return None,
}
}
};
let mut acc = Rat::from_int(Ext::from(0));
for (d, b) in digits.iter().zip(&radix) {
acc = acc.mul(b).add(d);
}
let exact_in = |a: &Array| matches!(a.dtype(), DType::Ext | DType::Rat);
if exact_in(y) || x.is_some_and(exact_in) {
return Some(Array::new(Vec::new(), exact_data(DType::Ext, vec![acc])));
}
let whole = acc.to_int()?;
Some(Array::scalar_i64(exact::ext_to_i64(&whole)?))
}
fn decode(x: Option<&Array>, y: &Array, span: Span) -> Result<Array> {
if let Some(exact) = decode_exact(x, y) {
return Ok(exact);
}
let digits = digits_of(y, "decode", span)?;
let radix: Vec<f64> = match x {
None => vec![2.0; digits.len()],
Some(x) => {
let r = digits_of(x, "decode", span)?;
match r.len() {
1 => vec![r[0]; digits.len()],
n if n == digits.len() => r,
n => {
return Err(Error::new(
ErrorKind::Length,
format!("{n} radices for {} digits", digits.len()),
Some(span),
));
}
}
}
};
let mut acc = 0.0f64;
for (d, b) in digits.iter().zip(&radix) {
acc = acc * b + d;
}
let integral = is_integral(y) && x.is_none_or(is_integral);
Ok(Array::new(vec![], narrow(vec![acc], integral)))
}
fn decode_apl(x: &Array, y: &Array, span: Span) -> Result<Array> {
let digits = digits_of(y, "decode", span)?;
let radices = digits_of(x, "decode", span)?;
let k = if y.rank() == 0 { 1 } else { y.shape[0] };
let n: usize = if y.rank() == 0 { 1 } else { y.shape[1..].iter().product() };
let (rows, width) = match x.rank() {
0 => (1usize, 0usize),
r => (x.shape[..r - 1].iter().product(), x.shape[r - 1]),
};
if width != 0 && width != k {
return Err(Error::new(
ErrorKind::Length,
format!("{width} radices for {k} digits"),
Some(span),
));
}
let per_row = if x.rank() > 0 && width == 0 { 0 } else { k };
let mut out = vec![0.0f64; rows * n];
for i in 0..rows {
for j in 0..n {
let mut acc = 0.0f64;
for d in 0..per_row {
let b = if width == 0 { radices[0] } else { radices[i * width + d] };
acc = acc * b + digits[d * n + j];
}
out[i * n + j] = acc;
}
}
let mut shape: Vec<usize> = if x.rank() == 0 {
Vec::new()
} else {
x.shape[..x.rank() - 1].to_vec()
};
if y.rank() > 0 {
shape.extend_from_slice(&y.shape[1..]);
}
let integral = is_integral(y) && is_integral(x);
Ok(Array::new(shape, narrow(out, integral)))
}
fn encode_apl(x: &Array, y: &Array, span: Span) -> Result<Array> {
let radices = digits_of(x, "encode", span)?;
let values = digits_of(y, "encode", span)?;
let k = if x.rank() == 0 { 1 } else { x.shape[0] };
let frames = if k == 0 { 0 } else { radices.len() / k };
let n = values.len();
let mut out = vec![0.0f64; k * frames * n];
let mut radix = vec![0.0f64; k];
let mut cell = vec![0.0f64; k];
for p in 0..frames {
for (i, r) in radix.iter_mut().enumerate() {
*r = radices[i * frames + p];
}
for (j, &v) in values.iter().enumerate() {
encode_one(&radix, v, &mut cell);
for i in 0..k {
out[(i * frames + p) * n + j] = cell[i];
}
}
}
let mut shape = x.shape.clone();
shape.extend_from_slice(&y.shape);
Ok(Array::new(shape, narrow(out, is_integral(x) && is_integral(y))))
}
fn bit_width(values: &[f64], span: Span) -> Result<usize> {
if values.is_empty() {
return Ok(0);
}
let mut m = 0.0f64;
for &v in values {
if !v.is_finite() {
return Err(Error::domain("cannot encode an infinite value", span));
}
m = m.max(v.abs());
}
let whole = m.floor();
if whole >= 1e15 {
return Err(Error::domain("the value is too large to encode in binary", span));
}
let mut w = 1usize;
let mut n = whole as i64;
while n > 1 {
n /= 2;
w += 1;
}
Ok(w)
}
fn encode_one(radix: &[f64], v: f64, out: &mut [f64]) {
let mut rem = v;
for i in (0..radix.len()).rev() {
let b = radix[i];
if b == 0.0 {
out[i] = rem;
rem = 0.0;
} else {
let r = rem - b * (rem / b).floor();
out[i] = r;
rem = (rem - r) / b;
}
}
}
fn encode(x: &Array, y: &Array, span: Span) -> Result<Array> {
let radix = digits_of(x, "encode", span)?;
let values = digits_of(y, "encode", span)?;
let k = radix.len();
let n = values.len();
let mut out = vec![0.0f64; k * n];
let mut cell = vec![0.0f64; k];
for (j, &v) in values.iter().enumerate() {
encode_one(&radix, v, &mut cell);
for i in 0..k {
out[i * n + j] = cell[i];
}
}
let mut shape = if x.rank() == 0 { Vec::new() } else { vec![k] };
shape.extend_from_slice(&y.shape);
Ok(Array::new(shape, narrow(out, is_integral(x) && is_integral(y))))
}
fn encode_bits(y: &Array, span: Span) -> Result<Array> {
let values = digits_of(y, "encode", span)?;
let k = bit_width(&values, span)?;
let radix = vec![2.0; k];
let mut out = vec![0.0f64; values.len() * k];
for (j, &v) in values.iter().enumerate() {
encode_one(&radix, v, &mut out[j * k..(j + 1) * k]);
}
let mut shape = y.shape.clone();
shape.push(k);
Ok(Array::new(shape, narrow(out, is_integral(y))))
}
fn laminate(x: &Array, y: &Array, span: Span) -> Result<Array> {
let spread = |a: &Array, other: &Array| -> Array {
if a.rank() != 0 {
return a.clone();
}
let shape = if other.rank() == 0 { vec![1] } else { other.shape.clone() };
let n: usize = shape.iter().product();
let mut data = Data::empty(a.dtype());
for _ in 0..n {
push_elem(&mut data, &a.data, 0);
}
Array::new(shape, data)
};
assemble(&[2], vec![spread(x, y), spread(y, x)], span)
}
fn table_of(y: &Array) -> Array {
let shape = match y.rank() {
0 => vec![1, 1],
_ => vec![y.items(), y.item_size()],
};
Array::new(shape, y.data.clone())
}
fn table(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let ranks = u.ranks();
let fxl = x.rank() - effective_rank(ranks[1], x.rank());
let fyl = y.rank() - effective_rank(ranks[2], y.rank());
let mut frame = x.shape[..fxl].to_vec();
frame.extend_from_slice(&y.shape[..fyl]);
let nx: usize = x.shape[..fxl].iter().product();
let ny: usize = y.shape[..fyl].iter().product();
let n = nx * ny;
if n == 0 {
return assemble(&frame, Vec::new(), span);
}
if frame.is_empty() {
return u.dyad(x, y, ctx, span);
}
let work = x.count().max(y.count()).max(n);
let cells = each_cell(n, work, u.is_pure(), ctx, |i, c| {
u.dyad(&x.cell_at(fxl, i / ny), &y.cell_at(fyl, i % ny), c, span)
})?;
assemble(&frame, cells, span)
}
pub(crate) fn with_origin(v: &Verb, origin: i64) -> Option<Verb> {
match v {
Verb::Prim(p) => {
let mut out = *p;
let mut changed = false;
out.monad = match p.monad {
MonadOp::GradeUp { .. } => {
changed = true;
MonadOp::GradeUp { origin }
}
MonadOp::GradeDown { .. } => {
changed = true;
MonadOp::GradeDown { origin }
}
MonadOp::IotaApl { .. } => {
changed = true;
MonadOp::IotaApl { origin }
}
MonadOp::Indices { boxed_coords, .. } => {
changed = true;
MonadOp::Indices { origin, boxed_coords }
}
MonadOp::Roll { fixed, float_at_zero, .. } => {
changed = true;
MonadOp::Roll { origin, fixed, float_at_zero }
}
other => other,
};
out.dyad = match p.dyad {
DyadOp::IndexOf { .. } => {
changed = true;
DyadOp::IndexOf { origin }
}
DyadOp::IndexOfLast { .. } => {
changed = true;
DyadOp::IndexOfLast { origin }
}
DyadOp::CollateGrade { down, .. } => {
changed = true;
DyadOp::CollateGrade { down, origin }
}
DyadOp::Squad { .. } => {
changed = true;
DyadOp::Squad { origin }
}
DyadOp::Pick { .. } => {
changed = true;
DyadOp::Pick { origin }
}
DyadOp::SelectAxis { axis, rank, .. } => {
changed = true;
DyadOp::SelectAxis { axis, rank, origin }
}
DyadOp::Deal { fixed, .. } => {
changed = true;
DyadOp::Deal { origin, fixed }
}
other => other,
};
changed.then_some(Verb::Prim(out))
}
Verb::Rank(u, r) => Some(Verb::Rank(Box::new(with_origin(u, origin)?), *r)),
Verb::Reduce(u) => Some(Verb::Reduce(Box::new(with_origin(u, origin)?))),
Verb::Windowed(u, k) => Some(Verb::Windowed(Box::new(with_origin(u, origin)?), *k)),
Verb::Commute(u) => Some(Verb::Commute(Box::new(with_origin(u, origin)?))),
Verb::Each(u, e) => Some(Verb::Each(Box::new(with_origin(u, origin)?), *e)),
Verb::Fit(u, n) => Some(Verb::Fit(Box::new(with_origin(u, origin)?), *n)),
Verb::AlongAxis(u, k) => Some(Verb::AlongAxis(Box::new(with_origin(u, origin)?), *k)),
_ => None,
}
}
fn scalar_dyad_of(v: &Verb) -> Option<ScalarDyad> {
match v {
Verb::Prim(p) => match p.dyad {
DyadOp::Scalar(op) => Some(op),
_ => None,
},
_ => None,
}
}
fn folds_with(u: &Verb, op: ScalarDyad) -> bool {
matches!(u, Verb::Reduce(inner) if scalar_dyad_of(inner) == Some(op))
}
fn inner_product(
u: &Verb,
v: &Verb,
apl: bool,
x: &Array,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
if let Some(a) = matrix_product(u, v, x, y, span) {
return Ok(a);
}
if apl && scalar_dyad_of(v).is_none() {
return apl_inner_product(u, v, x, y, ctx, span);
}
if !apl {
return inner_cells(u, v, x, y, ctx, span);
}
let saved = ctx.cfg.agreement;
ctx.cfg.agreement = Agreement::LeadingPrefix;
let out = inner_cells(u, v, x, y, ctx, span);
ctx.cfg.agreement = saved;
out
}
fn inner_cells(
u: &Verb,
v: &Verb,
x: &Array,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let cell_rank = effective_rank(v.ranks()[1].max(1), x.rank());
let frame_rank = x.rank() - cell_rank;
if frame_rank == 0 {
let inner = v.dyad(x, y, ctx, span)?;
return u.monad(&inner, ctx, span);
}
let frame = x.shape[..frame_rank].to_vec();
let n: usize = frame.iter().product();
if n == 0 {
return assemble(&frame, Vec::new(), span);
}
let work = x.count().max(y.count());
let pure = u.is_pure() && v.is_pure();
let cells = each_cell(n, work, pure, ctx, |i, c| {
let inner = v.dyad(&x.cell_at(frame_rank, i), y, c, span)?;
u.monad(&inner, c, span)
})?;
assemble(&frame, cells, span)
}
fn apl_inner_product(
u: &Verb,
v: &Verb,
x: &Array,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let k = match (x.rank(), y.rank()) {
(0, 0) => 1,
(0, _) => y.shape[0],
_ => x.shape[x.rank() - 1],
};
if x.rank() > 0 && y.rank() > 0 && x.shape[x.rank() - 1] != y.shape[0] {
return Err(Error::new(
ErrorKind::Length,
format!("inner product over {} and {} elements", x.shape[x.rank() - 1], y.shape[0]),
Some(span),
));
}
let lead: &[usize] = if x.rank() > 0 { &x.shape[..x.rank() - 1] } else { &[] };
let trail: &[usize] = if y.rank() > 0 { &y.shape[1..] } else { &[] };
let rows: usize = lead.iter().product();
let cols: usize = trail.iter().product();
let mut frame = lead.to_vec();
frame.extend_from_slice(trail);
let n = rows * cols;
if n == 0 {
return assemble(&frame, Vec::new(), span);
}
let vector = |d: &Data, at: &dyn Fn(usize) -> usize| {
let mut out = Data::empty(d.dtype());
for t in 0..k {
out.push_from(d, at(t));
}
Array::new(vec![k], out)
};
let pure = u.is_pure() && v.is_pure();
let cells = each_cell(n, x.count().max(y.count()), pure, ctx, |i, c| {
let (r, col) = (i / cols, i % cols);
let left = vector(&x.data, &|t| if x.rank() > 0 { r * k + t } else { 0 });
let right = vector(&y.data, &|t| if y.rank() > 0 { t * cols + col } else { 0 });
let inner = v.dyad(&left, &right, c, span)?;
u.monad(&inner, c, span)
})?;
assemble(&frame, cells, span)
}
fn matrix_product(u: &Verb, v: &Verb, x: &Array, y: &Array, span: Span) -> Option<Array> {
if !folds_with(u, ScalarDyad::Add) || scalar_dyad_of(v) != Some(ScalarDyad::Mul) {
return None;
}
if x.rank() == 0 || y.rank() == 0 {
return None;
}
let k = x.shape[x.rank() - 1];
if k != y.shape[0] {
return None;
}
let rows: usize = x.shape[..x.rank() - 1].iter().product();
let cols: usize = y.shape[1..].iter().product();
let mut shape = x.shape[..x.rank() - 1].to_vec();
shape.extend_from_slice(&y.shape[1..]);
if crate::limits::elements(&shape, span).is_err() {
return None;
}
let whole = matches!(x.dtype(), DType::Bool | DType::I64)
&& matches!(y.dtype(), DType::Bool | DType::I64);
if whole
&& let (Some(xs), Some(ys)) = (x.to_i64_vec(), y.to_i64_vec())
&& let Some(out) = matmul_whole(&xs, &ys, rows, k, cols)
{
return Some(Array::new(shape, Data::I64(out.into())));
}
let (xs, ys) = (x.to_f64_vec()?, y.to_f64_vec()?);
let out = par::fill_rows(rows, cols, rows * k * cols, |r0, part| {
matmul_f64(&xs, &ys, k, cols, r0, part);
});
Some(Array::new(shape, Data::F64(out.into())))
}
const MATMUL_BLOCK: usize = 128;
#[inline(always)]
fn matmul_f64_body(xs: &[f64], ys: &[f64], k: usize, n: usize, r0: usize, out: &mut [f64]) {
if n == 0 {
return;
}
let rows = out.len() / n;
for k0 in (0..k).step_by(MATMUL_BLOCK) {
let k1 = (k0 + MATMUL_BLOCK).min(k);
for r in 0..rows {
let left = &xs[(r0 + r) * k..(r0 + r + 1) * k];
let dst = &mut out[r * n..(r + 1) * n];
for (t, &a) in left.iter().enumerate().take(k1).skip(k0) {
let row = &ys[t * n..(t + 1) * n];
for (o, &b) in dst.iter_mut().zip(row) {
*o += a * b;
}
}
}
}
}
multiversioned! {
fn matmul_f64(
xs: &[f64],
ys: &[f64],
k: usize,
n: usize,
r0: usize,
out: &mut [f64],
) -> () = matmul_f64_body;
}
#[inline(always)]
fn matmul_i64_body(xs: &[i64], ys: &[i64], k: usize, n: usize, r0: usize, out: &mut [i64]) {
if n == 0 {
return;
}
let rows = out.len() / n;
for k0 in (0..k).step_by(MATMUL_BLOCK) {
let k1 = (k0 + MATMUL_BLOCK).min(k);
for r in 0..rows {
let left = &xs[(r0 + r) * k..(r0 + r + 1) * k];
let dst = &mut out[r * n..(r + 1) * n];
for (t, &a) in left.iter().enumerate().take(k1).skip(k0) {
let row = &ys[t * n..(t + 1) * n];
for (o, &b) in dst.iter_mut().zip(row) {
*o = o.wrapping_add(a.wrapping_mul(b));
}
}
}
}
}
multiversioned! {
fn matmul_i64(
xs: &[i64],
ys: &[i64],
k: usize,
n: usize,
r0: usize,
out: &mut [i64],
) -> () = matmul_i64_body;
}
fn matmul_whole(xs: &[i64], ys: &[i64], rows: usize, k: usize, n: usize) -> Option<Vec<i64>> {
let bound = |v: &[i64]| v.iter().map(|&a| (a as i128).abs()).max().unwrap_or(0);
if bound(xs).saturating_mul(bound(ys)).saturating_mul(k as i128) <= i64::MAX as i128 {
return Some(par::fill_rows(rows, n, rows * k * n, |r0, part| {
matmul_i64(xs, ys, k, n, r0, part);
}));
}
let mut out = vec![0i64; rows * n];
for r in 0..rows {
let left = &xs[r * k..(r + 1) * k];
let dst = &mut out[r * n..(r + 1) * n];
for (t, &a) in left.iter().enumerate() {
for (o, &b) in dst.iter_mut().zip(&ys[t * n..(t + 1) * n]) {
*o = a.checked_mul(b).and_then(|p| o.checked_add(p))?;
}
}
}
Some(out)
}
const DETERMINANT_MINORS_MAX: usize = 16;
fn determinant(
u: &Verb,
v: &Verb,
apl: bool,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
if apl {
return Err(Error::domain("an inner product has no monadic meaning in APL", span));
}
if y.rank() > 2 {
let frame = y.shape[..y.rank() - 2].to_vec();
let n: usize = frame.iter().product();
let pure = u.is_pure() && v.is_pure();
let cells = each_cell(n, y.count(), pure, ctx, |i, c| {
determinant(u, v, apl, &y.cell_at(y.rank() - 2, i), c, span)
})?;
return assemble(&frame, cells, span);
}
let rows = y.items();
let cols = y.item_size();
if folds_with(u, ScalarDyad::Sub)
&& scalar_dyad_of(v) == Some(ScalarDyad::Mul)
&& rows == cols
&& rows >= 3
&& matches!(y.dtype(), DType::Bool | DType::I64 | DType::F64)
&& let Some(values) = y.to_f64_vec()
{
return Ok(Array::scalar_f64(determinant_lu(values, rows)));
}
if rows > DETERMINANT_MINORS_MAX {
return Err(Error::not_yet(
format!(
"a determinant of more than {DETERMINANT_MINORS_MAX} rows by minors \
(only -/ . * over machine numbers has a direct method)"
),
span,
));
}
let mut seen: HashMap<u64, Array> = HashMap::new();
let all = if rows == 64 { u64::MAX } else { (1u64 << rows) - 1 };
minors(u, v, y, cols, rows, all, &mut seen, ctx, span)
}
#[allow(clippy::too_many_arguments)]
fn minors(
u: &Verb,
v: &Verb,
y: &Array,
cols: usize,
rows: usize,
left: u64,
seen: &mut HashMap<u64, Array>,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
if let Some(a) = seen.get(&left) {
return Ok(a.clone());
}
let column = rows - left.count_ones() as usize;
let value = if column >= cols {
let data = reduce_identity(v, 1).ok_or_else(|| {
Error::not_yet(
format!("the identity element of {} (a determinant with no columns)", v.name()),
span,
)
})?;
Array::new(Vec::new(), data)
} else if left == 0 {
u.monad(&Array::new(vec![0], Data::empty(DType::I64)), ctx, span)?
} else {
let mut terms = Vec::with_capacity(left.count_ones() as usize);
for r in 0..rows {
if left & (1 << r) == 0 {
continue;
}
let minor = minors(u, v, y, cols, rows, left & !(1 << r), seen, ctx, span)?;
let head = Array::new(Vec::new(), y.data.slice(r * cols + column, r * cols + column + 1));
terms.push(v.dyad(&head, &minor, ctx, span)?);
}
let n = terms.len();
u.monad(&assemble(&[n], terms, span)?, ctx, span)?
};
seen.insert(left, value.clone());
Ok(value)
}
fn determinant_lu(mut a: Vec<f64>, n: usize) -> f64 {
let mut det = 1.0f64;
for c in 0..n {
let mut pivot = c;
for r in c + 1..n {
if a[r * n + c].abs() > a[pivot * n + c].abs() {
pivot = r;
}
}
if a[pivot * n + c] == 0.0 {
return 0.0;
}
if pivot != c {
for j in 0..n {
a.swap(c * n + j, pivot * n + j);
}
det = -det;
}
let head = a[c * n + c];
det *= head;
for r in c + 1..n {
let factor = a[r * n + c] / head;
if factor == 0.0 {
continue;
}
for j in c..n {
a[r * n + j] -= factor * a[c * n + j];
}
}
}
det
}
fn monad_op(p: &Prim, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
match p.monad {
MonadOp::Scalar(op) => scalar_monad(op, y, ctx.cfg, span),
MonadOp::ShapeOf => {
Ok(carry_exact(Array::from_i64(y.shape.iter().map(|&n| n as i64).collect()), y))
}
MonadOp::Tally => Ok(carry_exact(Array::scalar_i64(y.items() as i64), y)),
MonadOp::Ravel => Ok(Array::new(vec![y.count()], y.data.clone())),
MonadOp::TransposeAxes => Ok(transpose_axes(y)),
MonadOp::Head => Ok(head(y)),
MonadOp::Behead => behead(y, span),
MonadOp::Tail => Ok(tail(y)),
MonadOp::Curtail => Ok(curtail(y)),
MonadOp::Reverse => Ok(reverse(y)),
MonadOp::Nub => Ok(nub(y, ctx.cfg.tol)),
MonadOp::GradeUp { origin } | MonadOp::GradeDown { origin } => {
check_gradable(y, ctx.cfg.rules, span)?;
if ctx.cfg.rules.lang == crate::Lang::Apl && y.rank() == 0 {
return Err(Error::domain("a grade needs an array, not a scalar", span));
}
let down = matches!(p.monad, MonadOp::GradeDown { .. });
let order = grade_order(y, down, Tao::of(ctx.cfg.rules));
Ok(Array::from_i64(order.iter().map(|&i| origin + i as i64).collect()))
}
MonadOp::IotaJ => iota_j(y, span),
MonadOp::IotaApl { origin } => iota_apl(y, origin, span),
MonadOp::Echo => {
(ctx.out)(&format!("{}\n", crate::fmt::format_array(y, &ctx.cfg.fmt)));
Ok(Array::empty(DType::I64))
}
MonadOp::ReadStream => {
stream_number(y, 1, "1!:1 reads", span)?;
let line = ctx.read_line(span)?;
Ok(Array::from_chars(line.chars().collect()))
}
MonadOp::TypeCode => Ok(Array::scalar_i64(type_code(y))),
MonadOp::Same => Ok(y.clone()),
MonadOp::Format => Ok(format_chars(y, &ctx.cfg.fmt)),
MonadOp::DecodeBits => decode(None, y, span).map(|r| carry_exact(r, y)),
MonadOp::EncodeBits => encode_bits(y, span).map(|r| carry_exact(r, y)),
MonadOp::Itemize => {
let mut shape = vec![1usize];
shape.extend_from_slice(&y.shape);
Ok(Array::new(shape, y.data.clone()))
}
MonadOp::TableOf => Ok(table_of(y)),
MonadOp::Enclose(rule) => Ok(enclose(y, rule)),
MonadOp::Open => Ok(open_cell(y)),
MonadOp::Raze => raze(y, span),
MonadOp::Catalogue => catalogue(y, span),
MonadOp::AtomicRep => atomic_rep(y, ctx, span),
MonadOp::RazeIn => raze_in(y, ctx.cfg.tol, span),
MonadOp::First => Ok(first(y)),
MonadOp::Enlist => enlist(y, span),
MonadOp::Depth => Ok(Array::scalar_i64(depth(y))),
MonadOp::Indices { origin, boxed_coords } => {
where_indices(y, origin, boxed_coords, span)
}
MonadOp::Steps => steps(y, span),
MonadOp::ToExact => to_exact(y, span),
MonadOp::NthPrime => {
let n = y
.to_i64_vec()
.ok_or_else(|| Error::domain("the prime index must be an integer", span))?;
let v = n.first().copied().unwrap_or(0);
Ok(carry_exact(Array::scalar_i64(nth_prime(v, span)?), y))
}
MonadOp::PrimeFactors => {
let n = y
.to_i64_vec()
.ok_or_else(|| Error::domain("prime factors need an integer", span))?;
let v = n.first().copied().unwrap_or(0);
Ok(carry_exact(Array::from_i64(prime_factors(v, span)?), y))
}
MonadOp::MatrixInverse => matrix_inverse(y, span),
MonadOp::Roll { origin, fixed, float_at_zero } => {
roll(y, origin, fixed, float_at_zero, span)
}
MonadOp::ComplexParts { polar } => complex_parts(y, polar, span),
MonadOp::SelfClassify => Ok(self_classify(y, ctx.cfg.tol)),
MonadOp::NubSieve => Ok(nub_sieve(y, ctx.cfg.tol)),
MonadOp::Unicode { pass_chars } => unicode(y, pass_chars, span),
MonadOp::Symbols => to_symbols(y, span),
MonadOp::Words => words(y, span),
MonadOp::LevelOf => Ok(Array::scalar_i64(boxing_level(y))),
MonadOp::MapPaths => Ok(map_paths(y)),
MonadOp::Nest => Ok(nest(y)),
MonadOp::PolyRoots => poly_roots(y, span),
MonadOp::PolyDeriv => poly_deriv(y, span),
MonadOp::AnagramIndex => anagram_index(y, ctx.cfg.rules, span),
MonadOp::CycleForm => cycle_form(y, span),
MonadOp::Split => Ok(split_items(y)),
MonadOp::Execute { apl } => execute(y, apl, ctx, span),
MonadOp::NotYet(what) => Err(Error::not_yet(what, span)),
MonadOp::None => {
Err(Error::domain(format!("{} has no monadic meaning", p.name), span))
}
}
}
fn complex_parts(y: &Array, polar: bool, span: Span) -> Result<Array> {
let Some(v) = y.to_complex_vec() else {
return Err(wrong_type(y.dtype(), span));
};
let z = v.first().copied().unwrap_or(cx::ZERO);
let pair = if polar { vec![cx::abs(z), cx::arg(z)] } else { vec![z[0], z[1]] };
Ok(Array::from_f64(pair))
}
fn axis_counts(x: &Array, what: &str, span: Span) -> Result<Vec<i64>> {
if x.rank() > 1 {
return Err(Error::new(
ErrorKind::Rank,
format!("{what} needs a scalar or vector left argument"),
Some(span),
));
}
if x.count() == 0 {
return Ok(Vec::new());
}
x.to_i64_vec()
.ok_or_else(|| Error::domain(format!("{what} needs integer lengths"), span))
}
fn reshape(x: &Array, y: &Array, by_items: bool, span: Span) -> Result<Array> {
let dims = axis_counts(x, "reshape", span)?;
if dims.iter().any(|&d| d < 0) {
return Err(Error::domain("reshape lengths must be nonnegative", span));
}
let mut shape: Vec<usize> = dims.iter().map(|&d| d as usize).collect();
let (unit, src) = if by_items {
let item_shape = if y.rank() == 0 { &[][..] } else { &y.shape[1..] };
shape.extend_from_slice(item_shape);
(item_shape.iter().product::<usize>(), y.items().max(usize::from(y.rank() == 0)))
} else {
(1, y.count())
};
let n = crate::limits::elements(&shape, span)?;
let mut data = Data::empty(y.dtype());
if n > 0 && src == 0 {
if by_items {
return Err(Error::new(ErrorKind::Length, "reshape of an empty array", Some(span)));
}
return Ok(Array::new(shape, fill_data(y.dtype(), n)));
}
if y.is_row_major() && n <= unit.saturating_mul(src) && n <= y.data.len() {
return Ok(Array::new(shape, y.data.slice(0, n)));
}
for i in 0..n {
push_elem(&mut data, &y.data, (i / unit) % src * unit + i % unit);
}
Ok(Array::new(shape, data))
}
fn leading_run(y: &Array, counts: &[i64], drop: bool) -> Option<Array> {
if y.rank() == 0 || counts.is_empty() {
return None;
}
let trailing_untouched = counts[1..].iter().enumerate().all(|(a, &c)| {
if drop { c == 0 } else { c.unsigned_abs() as usize == y.shape[a + 1] }
});
if !trailing_untouched {
return None;
}
let n = y.items();
let k = counts[0];
let a = k.unsigned_abs() as usize;
let (lo, keep) = if drop {
let a = a.min(n);
if k >= 0 { (a, n - a) } else { (0, n - a) }
} else {
if a > n {
return None;
}
if k >= 0 { (0, a) } else { (n - a, a) }
};
Some(section(y, lo, lo + keep))
}
fn count_rank(verb: &str, counts: usize, rank: usize, span: Span) -> Error {
Error::new(
ErrorKind::Length,
format!("{counts} {verb} counts for a rank-{rank} argument"),
Some(span),
)
}
fn take(x: &Array, y: &Array, prototype_fill: bool, apl: bool, span: Span) -> Result<Array> {
let counts = axis_counts(x, "take", span)?;
let fill = if prototype_fill { prototype_of(y) } else { None };
let promoted;
let base = if y.rank() == 0 {
promoted = Array::new(vec![1; counts.len()], y.data.clone());
&promoted
} else {
y
};
let wrong = if apl {
counts.len() != base.rank()
} else {
counts.len() > base.rank() || (counts.is_empty() && base.rank() > 0)
};
if wrong {
return Err(count_rank("take", counts.len(), base.rank(), span));
}
if let Some(run) = leading_run(base, &counts, false) {
return Ok(run);
}
let mut out_shape = base.shape.clone();
for (a, &k) in counts.iter().enumerate() {
out_shape[a] = k.unsigned_abs() as usize;
}
let n = crate::limits::elements(&out_shape, span)?;
let st = strides(&base.shape);
let mut data = Data::empty(base.dtype());
let mut coord = vec![0usize; out_shape.len()];
for _ in 0..n {
let mut idx = 0usize;
let mut inside = true;
for a in 0..out_shape.len() {
let len = base.shape[a] as i64;
let c = coord[a] as i64;
let s = match counts.get(a) {
Some(&k) if k < 0 => c + len - k.unsigned_abs() as i64,
_ => c,
};
if s < 0 || s >= len {
inside = false;
break;
}
idx += s as usize * st[a];
}
if inside {
push_elem(&mut data, &base.data, idx);
} else if let (Data::Box(v), Some(p)) = (&mut data, &fill) {
v.push(p.clone());
} else {
data.push_fill();
}
odometer(&mut coord, &out_shape);
}
Ok(Array::new(out_shape, data))
}
fn prototype_of(y: &Array) -> Option<Array> {
fn zeroed(a: &Array) -> Array {
if let Some(items) = a.as_boxes() {
let inner: Vec<Array> = items.iter().map(zeroed).collect();
return Array::new(a.shape.clone(), Data::Box(inner.into()));
}
let dtype = match a.dtype() {
DType::Char | DType::Symbol => a.dtype(),
_ => DType::I64,
};
Array::new(a.shape.clone(), fill_data(dtype, a.count()))
}
let first = y.as_boxes()?.first()?;
Some(zeroed(first))
}
fn drop_(x: &Array, y: &Array, apl: bool, span: Span) -> Result<Array> {
let counts = axis_counts(x, "drop", span)?;
let promoted;
let base = if y.rank() == 0 {
promoted = Array::new(vec![1; counts.len()], y.data.clone());
&promoted
} else {
y
};
let wrong =
if apl { counts.len() != base.rank() } else { counts.len() > base.rank() };
if wrong {
return Err(count_rank("drop", counts.len(), base.rank(), span));
}
if let Some(run) = leading_run(base, &counts, true) {
return Ok(run);
}
let mut out_shape = base.shape.clone();
let mut offset = vec![0usize; base.rank()];
for (a, &k) in counts.iter().enumerate() {
let len = base.shape[a];
let d = (k.unsigned_abs() as usize).min(len);
out_shape[a] = len - d;
if k > 0 {
offset[a] = d;
}
}
let n: usize = out_shape.iter().product();
let st = strides(&base.shape);
let mut data = Data::empty(base.dtype());
let mut coord = vec![0usize; out_shape.len()];
for _ in 0..n {
let idx: usize = (0..out_shape.len()).map(|a| (coord[a] + offset[a]) * st[a]).sum();
push_elem(&mut data, &base.data, idx);
odometer(&mut coord, &out_shape);
}
Ok(Array::new(out_shape, data))
}
fn dyad_op(p: &Prim, x: &Array, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
let tol = cfg.tol;
match p.dyad {
DyadOp::Scalar(op) => scalar_dyad(op, x, y, cfg, span),
DyadOp::Reshape => reshape(x, y, cfg.agreement == Agreement::LeadingPrefix, span),
DyadOp::Take => {
let apl = cfg.rules.lang == crate::Lang::Apl;
take(x, y, cfg.agreement == Agreement::ExactOrScalar, apl, span)
}
DyadOp::Drop => drop_(x, y, cfg.rules.lang == crate::Lang::Apl, span),
DyadOp::Right => Ok(y.clone()),
DyadOp::Left => Ok(x.clone()),
DyadOp::Rotate => rotate(x, y, span),
DyadOp::AppendLeading => {
catenate(x, y, true, cfg.agreement == Agreement::LeadingPrefix, span)
}
DyadOp::AppendLast => {
catenate(x, y, false, cfg.agreement == Agreement::LeadingPrefix, span)
}
DyadOp::IndexOf { origin } => Ok(index_of(x, y, origin, tol)),
DyadOp::MemberJ => Ok(member_j(x, y, tol)),
DyadOp::MemberApl => Ok(member_apl(x, y, tol)),
DyadOp::From => from_index(x, y, span),
DyadOp::Match => {
let empties_differ = cfg.rules.lang == crate::Lang::Apl
&& x.count() == 0
&& y.count() == 0
&& (x.dtype() == DType::Char) != (y.dtype() == DType::Char);
Ok(Array::scalar_bool(!empties_differ && arrays_match(x, y, tol)))
}
DyadOp::NotMatch => Ok(Array::scalar_bool(!arrays_match(x, y, tol))),
DyadOp::GradeSelect { down } => grade_select(x, y, down, cfg.rules, span),
DyadOp::Copy => copy_items(x, y, cfg.agreement == Agreement::ExactOrScalar, span),
DyadOp::CollateGrade { down, origin } => collate_grade(x, y, down, origin, span),
DyadOp::TransposeJ => transpose_j(x, y, span),
DyadOp::TransposeApl => transpose_apl(x, y, cfg.rules.origin, span),
DyadOp::DecodeApl => decode_apl(x, y, span).map(|r| carry_exact2(r, x, y)),
DyadOp::EncodeApl => encode_apl(x, y, span).map(|r| carry_exact2(r, x, y)),
DyadOp::Decode => decode(Some(x), y, span).map(|r| carry_exact2(r, x, y)),
DyadOp::Encode => encode(x, y, span).map(|r| carry_exact2(r, x, y)),
DyadOp::Laminate => laminate(x, y, span),
DyadOp::Link => link(x, y, span),
DyadOp::Strand => strand(x, y, span),
DyadOp::IntervalIndex { offset, closed } => {
interval_index(x, y, offset, closed, tol, span)
}
DyadOp::IndexOfLast { origin } => Ok(index_of_last(x, y, origin, tol)),
DyadOp::MatrixDivide => matrix_divide(x, y, span),
DyadOp::PartitionEnclose => partition_enclose(x, y, span),
DyadOp::Squad { origin } => squad(x, y, origin, span),
DyadOp::SelectAxis { axis, rank, origin } => {
select_axis(x, y, axis, rank, origin, span)
}
DyadOp::Fetch => fetch(x, y, span),
DyadOp::PolyEval => poly_eval(x, y, span),
DyadOp::PolyIntegral => poly_integral(x, y, span),
DyadOp::TruthTable(m) => truth_table(m, x, y, span),
DyadOp::FormatSpec => format_spec(x, y, &cfg.fmt, span),
DyadOp::FormatSpecJ => format_spec_j(x, y, &cfg.fmt, span),
DyadOp::ParseNumbers => parse_numbers(x, y, span),
DyadOp::SequentialMachine => sequential_machine(x, y, span),
DyadOp::Deal { origin, fixed } => deal(x, y, origin, fixed, span),
DyadOp::ExactForm => exact_form(x, y, span),
DyadOp::Boolean(op) => bool_dyad(op, x, y, cfg, span),
DyadOp::Less => {
set_rank(cfg, "without", x, y, span)?;
Ok(set_less(x, y, tol))
}
DyadOp::Union => {
set_rank(cfg, "union", x, y, span)?;
union_items(x, y, tol, span)
}
DyadOp::Intersect => {
set_rank(cfg, "intersection", x, y, span)?;
Ok(intersect_items(x, y, tol))
}
DyadOp::AnagramFrom => anagram_from(x, y, span),
DyadOp::Permute => permute(x, y, span),
DyadOp::FindSeq => {
find_seq(x, y, tol, cfg.rules.lang == crate::Lang::Apl, span)
}
DyadOp::UnicodeForm => unicode_form(x, y, span),
DyadOp::SymbolForm => symbol_form(x, y, span),
DyadOp::PrimeMeta => prime_meta(x, y, span).map(|r| carry_exact2(r, x, y)),
DyadOp::PrimeExponents => prime_exponents(x, y, span).map(|r| carry_exact2(r, x, y)),
DyadOp::Pick { origin } => pick(x, y, origin, span),
DyadOp::Expand => expand(x, y, span),
DyadOp::WriteStream => Err(Error::internal("1!:2 reached the pure dyad dispatcher")),
DyadOp::NotYet(what) => Err(Error::not_yet(what, span)),
DyadOp::None => Err(Error::domain(format!("{} has no dyadic meaning", p.name), span)),
}
}
fn reduce_identity(v: &Verb, n: usize) -> Option<Data> {
let Verb::Prim(p) = v else { return None };
let DyadOp::Scalar(op) = p.dyad else { return None };
let ints = |k: i64| Data::I64(vec![k; n].into());
let bits = |k: u8| Data::Bool(vec![k; n].into());
Some(match op {
ScalarDyad::Add | ScalarDyad::Sub | ScalarDyad::Gcd | ScalarDyad::Residue => ints(0),
ScalarDyad::Mul
| ScalarDyad::DivJ
| ScalarDyad::DivApl
| ScalarDyad::Pow
| ScalarDyad::Lcm
| ScalarDyad::Root
| ScalarDyad::Binomial => ints(1),
ScalarDyad::Min => Data::F64(vec![f64::INFINITY; n].into()),
ScalarDyad::Max => Data::F64(vec![f64::NEG_INFINITY; n].into()),
ScalarDyad::Eq | ScalarDyad::Le | ScalarDyad::Ge => bits(1),
ScalarDyad::Ne | ScalarDyad::Lt | ScalarDyad::Gt => bits(0),
ScalarDyad::MakeComplex | ScalarDyad::PolarBy => return None,
ScalarDyad::Log | ScalarDyad::Circle => return None,
})
}
fn is_associative(op: ScalarDyad) -> bool {
use ScalarDyad::*;
matches!(op, Add | Mul | Min | Max)
}
#[inline(always)]
fn fold_range_body<S, T, F>(
v: &[S],
m: usize,
lo: usize,
hi: usize,
j0: usize,
acc: &mut [T],
step: &F,
) -> bool
where
S: Widen<T>,
T: Copy,
F: Fn(T, T) -> (T, bool),
{
let w = acc.len();
let base = (hi - 1) * m + j0;
for (slot, &x) in acc.iter_mut().zip(&v[base..base + w]) {
*slot = x.widen();
}
let mut over = false;
for i in (lo..hi - 1).rev() {
let row = &v[i * m + j0..i * m + j0 + w];
for (slot, &x) in acc.iter_mut().zip(row) {
let (r, o) = step(x.widen(), *slot);
*slot = r;
over |= o;
}
}
!over
}
multiversioned! {
#[allow(clippy::too_many_arguments)]
fn fold_range_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
v: &[S],
m: usize,
lo: usize,
hi: usize,
j0: usize,
acc: &mut [T],
step: &F,
) -> bool = fold_range_body;
}
const VECTOR_COLUMNS: usize = 16;
#[allow(clippy::too_many_arguments)]
#[inline]
fn fold_range<S, T, F>(
v: &[S],
m: usize,
lo: usize,
hi: usize,
j0: usize,
acc: &mut [T],
step: &F,
) -> bool
where
S: Widen<T>,
T: Copy,
F: Fn(T, T) -> (T, bool),
{
if acc.len() < VECTOR_COLUMNS {
fold_range_body(v, m, lo, hi, j0, acc, step)
} else {
fold_range_vectorised(v, m, lo, hi, j0, acc, step)
}
}
const FOLD_LANES: usize = 8;
const MIN_LANE_WORK: usize = 8 * FOLD_LANES;
#[inline(always)]
fn fold_lanes_body<S, T, F>(v: &[S], step: &F) -> Option<T>
where
S: Widen<T>,
T: Copy,
F: Fn(T, T) -> (T, bool),
{
let n = v.len();
let mut over = false;
if n < MIN_LANE_WORK {
let mut acc = v[n - 1].widen();
for &x in v[..n - 1].iter().rev() {
let (r, o) = step(x.widen(), acc);
acc = r;
over |= o;
}
return (!over).then_some(acc);
}
let rows = n / FOLD_LANES;
let head = n - rows * FOLD_LANES;
let last = head + (rows - 1) * FOLD_LANES;
let mut acc = [v[last].widen(); FOLD_LANES];
for (slot, &x) in acc.iter_mut().zip(&v[last..last + FOLD_LANES]) {
*slot = x.widen();
}
for r in (0..rows - 1).rev() {
let row = &v[head + r * FOLD_LANES..head + (r + 1) * FOLD_LANES];
for (slot, &x) in acc.iter_mut().zip(row) {
let (r, o) = step(x.widen(), *slot);
*slot = r;
over |= o;
}
}
let mut a = acc[FOLD_LANES - 1];
for &x in acc[..FOLD_LANES - 1].iter().rev() {
let (r, o) = step(x, a);
a = r;
over |= o;
}
for &x in v[..head].iter().rev() {
let (r, o) = step(x.widen(), a);
a = r;
over |= o;
}
(!over).then_some(a)
}
multiversioned! {
fn fold_lanes_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
v: &[S],
step: &F,
) -> Option<T> = fold_lanes_body;
}
#[inline]
fn fold_lanes<S, T, F>(v: &[S], step: &F) -> Option<T>
where
S: Widen<T>,
T: Copy,
F: Fn(T, T) -> (T, bool),
{
if v.len() < MIN_LANE_WORK {
fold_lanes_body(v, step)
} else {
fold_lanes_vectorised(v, step)
}
}
fn fold_flat<S, T, F>(v: &[S], n: usize, assoc: bool, step: &F) -> Option<T>
where
S: Widen<T>,
T: Copy + Send + Sync,
F: Fn(T, T) -> (T, bool) + Sync + Send,
{
if assoc {
return par::try_fold_chunks(
&v[..n],
|part| fold_lanes(part, step),
|a, b| {
let (r, o) = step(a, b);
(!o).then_some(r)
},
);
}
let mut acc = v[n - 1].widen();
let mut over = false;
for &x in v[..n - 1].iter().rev() {
let (r, o) = step(x.widen(), acc);
acc = r;
over |= o;
}
(!over).then_some(acc)
}
fn fold_items<S, T, F>(v: &[S], n: usize, m: usize, assoc: bool, step: F) -> Option<Vec<T>>
where
S: Widen<T>,
T: Copy + Default + Send + Sync,
F: Fn(T, T) -> (T, bool) + Sync + Send,
{
if m >= par::WIDE_ITEM {
let (out, ok) = par::fill_wide(m, n * m, |j0, acc: &mut [T]| {
fold_range(v, m, 0, n, j0, acc, &step)
});
return ok.then_some(out);
}
if m == 1 {
return fold_flat(v, n, assoc, &step).map(|x| vec![x]);
}
let chunks = if assoc { par::chunks(n, n * m) } else { 1 };
if chunks < 2 {
let mut acc = vec![T::default(); m];
return fold_range(v, m, 0, n, 0, &mut acc, &step).then_some(acc);
}
let per = n.div_ceil(chunks);
let parts = par::map_indexed(n.div_ceil(per), |c| {
let mut acc = vec![T::default(); m];
let ok = fold_range(v, m, c * per, ((c + 1) * per).min(n), 0, &mut acc, &step);
ok.then_some(acc)
});
let mut it = parts.into_iter().rev();
let mut acc = it.next()??;
for part in it {
let part = part?;
let mut over = false;
for (slot, &x) in acc.iter_mut().zip(&part) {
let (r, o) = step(x, *slot);
*slot = r;
over |= o;
}
if over {
return None;
}
}
Some(acc)
}
fn fold_i64<S: Widen<i64>>(op: ScalarDyad, v: &[S], n: usize, m: usize) -> Option<Vec<i64>> {
use ScalarDyad::*;
let assoc = is_associative(op);
match op {
Add => fold_items(v, n, m, assoc, i64::overflowing_add),
Sub => fold_items(v, n, m, assoc, i64::overflowing_sub),
Mul => fold_items(v, n, m, assoc, i64::overflowing_mul),
Min => fold_items(v, n, m, assoc, |a: i64, b: i64| (a.min(b), false)),
Max => fold_items(v, n, m, assoc, |a: i64, b: i64| (a.max(b), false)),
_ => None,
}
}
fn fold_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize) -> Option<Vec<Cx>> {
use ScalarDyad::*;
let assoc = is_associative(op);
match op {
Add => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::add(a, b), false)),
Sub => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::sub(a, b), false)),
Mul => fold_items(v, n, m, assoc, |a: Cx, b: Cx| (cx::mul(a, b), false)),
_ => None,
}
}
fn fold_f64(op: ScalarDyad, v: &[f64], n: usize, m: usize) -> Option<Vec<f64>> {
use ScalarDyad::*;
let assoc = is_associative(op);
match op {
Add => fold_items(v, n, m, assoc, |a: f64, b: f64| (a + b, false)),
Sub => fold_items(v, n, m, assoc, |a: f64, b: f64| (a - b, false)),
Mul => fold_items(v, n, m, assoc, |a: f64, b: f64| (a * b, false)),
Min => fold_items(v, n, m, assoc, |a: f64, b: f64| (a.min(b), false)),
Max => fold_items(v, n, m, assoc, |a: f64, b: f64| (a.max(b), false)),
_ => None,
}
}
fn reduce_typed(op: ScalarDyad, d: &Data, n: usize, m: usize) -> Option<Data> {
use ScalarDyad::*;
if !matches!(op, Add | Sub | Mul | Min | Max) {
return None;
}
match d {
Data::F64(v) => Some(Data::F64(fold_f64(op, v, n, m)?.into())),
Data::Complex(v) => Some(Data::Complex(fold_cx(op, v, n, m)?.into())),
Data::I64(v) => Some(Data::I64(fold_i64(op, v, n, m)?.into())),
Data::Bool(v) => Some(Data::I64(fold_i64(op, v.as_slice(), n, m)?.into())),
Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
}
}
#[inline(always)]
fn fold_runs_body<S, T, F>(v: &[S], start: usize, m: usize, out: &mut [T], step: &F) -> bool
where
S: Widen<T>,
T: Copy,
F: Fn(T, T) -> (T, bool),
{
let mut over = false;
for (k, slot) in out.iter_mut().enumerate() {
let run = &v[(start + k) * m..(start + k + 1) * m];
let mut acc = run[m - 1].widen();
for &x in run[..m - 1].iter().rev() {
let (r, o) = step(x.widen(), acc);
acc = r;
over |= o;
}
*slot = acc;
}
!over
}
multiversioned! {
fn fold_runs_vectorised[S: Widen<T>, T: Copy, F: Fn(T, T) -> (T, bool)](
v: &[S],
start: usize,
m: usize,
out: &mut [T],
step: &F,
) -> bool = fold_runs_body;
}
fn fold_runs<S, T, F>(v: &[S], n: usize, m: usize, step: F) -> Option<Vec<T>>
where
S: Widen<T>,
T: Copy + Default + Send + Sync,
F: Fn(T, T) -> (T, bool) + Sync + Send,
{
let wide = m >= VECTOR_COLUMNS;
let (out, ok) = par::fill_wide(n, n * m, |start, part: &mut [T]| {
if wide {
fold_runs_vectorised(v, start, m, part, &step)
} else {
fold_runs_body(v, start, m, part, &step)
}
});
ok.then_some(out)
}
fn fold_runs_data(op: ScalarDyad, d: &Data, n: usize, m: usize) -> Option<Data> {
use ScalarDyad::*;
match d {
Data::F64(v) => Some(Data::F64(
match op {
Add => fold_runs(v, n, m, |a: f64, b: f64| (a + b, false)),
Sub => fold_runs(v, n, m, |a: f64, b: f64| (a - b, false)),
Mul => fold_runs(v, n, m, |a: f64, b: f64| (a * b, false)),
Min => fold_runs(v, n, m, |a: f64, b: f64| (a.min(b), false)),
Max => fold_runs(v, n, m, |a: f64, b: f64| (a.max(b), false)),
_ => None,
}?
.into(),
)),
Data::I64(v) => Some(Data::I64(fold_runs_i64(op, v.as_slice(), n, m)?.into())),
Data::Complex(v) => Some(Data::Complex(
match op {
Add => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::add(a, b), false)),
Sub => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::sub(a, b), false)),
Mul => fold_runs(v, n, m, |a: Cx, b: Cx| (cx::mul(a, b), false)),
_ => None,
}?
.into(),
)),
Data::Bool(v) => Some(Data::I64(fold_runs_i64(op, v.as_slice(), n, m)?.into())),
Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
}
}
fn fold_runs_i64<S: Widen<i64>>(op: ScalarDyad, v: &[S], n: usize, m: usize) -> Option<Vec<i64>> {
use ScalarDyad::*;
match op {
Add => fold_runs(v, n, m, i64::overflowing_add),
Sub => fold_runs(v, n, m, i64::overflowing_sub),
Mul => fold_runs(v, n, m, i64::overflowing_mul),
Min => fold_runs(v, n, m, |a: i64, b: i64| (a.min(b), false)),
Max => fold_runs(v, n, m, |a: i64, b: i64| (a.max(b), false)),
_ => None,
}
}
fn run_slices<T: Clone>(b: &Buf<T>, runs: usize, len: usize) -> Vec<&[T]> {
if let Some(parts) = b.parts() && parts.len() == runs && parts.iter().all(|p| p.len() == len) {
return parts.iter().map(Buf::as_slice).collect();
}
let flat = b.as_slice();
(0..runs).map(|c| &flat[c * len..(c + 1) * len]).collect()
}
fn fold_columns<S, T, F>(cols: &[&[S]], len: usize, assoc: bool, step: F) -> Option<Vec<T>>
where
S: Widen<T>,
T: Copy + Default + Send + Sync,
F: Fn(T, T) -> (T, bool) + Sync + Send,
{
if par::worth_it(len) {
let mut out = Vec::with_capacity(cols.len());
for c in cols {
out.push(fold_flat(c, len, assoc, &step)?);
}
return Some(out);
}
let (out, ok) = par::fill_wide(cols.len(), cols.len() * len, |start, part: &mut [T]| {
let mut ok = true;
for (k, slot) in part.iter_mut().enumerate() {
match fold_flat(cols[start + k], len, assoc, &step) {
Some(v) => *slot = v,
None => ok = false,
}
}
ok
});
ok.then_some(out)
}
fn fold_columns_data(op: ScalarDyad, d: &Data, runs: usize, len: usize) -> Option<Data> {
use ScalarDyad::*;
if !matches!(op, Add | Sub | Mul | Min | Max) {
return None;
}
let assoc = is_associative(op);
macro_rules! by {
($v:expr, $add:expr, $sub:expr, $mul:expr, $min:expr, $max:expr) => {{
let cols = run_slices($v, runs, len);
match op {
Add => fold_columns(&cols, len, assoc, $add),
Sub => fold_columns(&cols, len, assoc, $sub),
Mul => fold_columns(&cols, len, assoc, $mul),
Min => fold_columns(&cols, len, assoc, $min),
Max => fold_columns(&cols, len, assoc, $max),
_ => None,
}?
}};
}
match d {
Data::F64(v) => Some(Data::F64(
by!(
v,
|a: f64, b: f64| (a + b, false),
|a: f64, b: f64| (a - b, false),
|a: f64, b: f64| (a * b, false),
|a: f64, b: f64| (a.min(b), false),
|a: f64, b: f64| (a.max(b), false)
)
.into(),
)),
Data::I64(v) => Some(Data::I64(
by!(
v,
i64::overflowing_add,
i64::overflowing_sub,
i64::overflowing_mul,
|a: i64, b: i64| (a.min(b), false),
|a: i64, b: i64| (a.max(b), false)
)
.into(),
)),
Data::Complex(v) => {
if !matches!(op, Add | Sub | Mul) {
return None;
}
Some(Data::Complex(
by!(
v,
|a: Cx, b: Cx| (cx::add(a, b), false),
|a: Cx, b: Cx| (cx::sub(a, b), false),
|a: Cx, b: Cx| (cx::mul(a, b), false),
|_: Cx, _: Cx| unreachable!("refused above"),
|_: Cx, _: Cx| unreachable!("refused above")
)
.into(),
))
}
Data::Bool(v) => Some(Data::I64(
by!(
v,
i64::overflowing_add,
i64::overflowing_sub,
i64::overflowing_mul,
|a: i64, b: i64| (a.min(b), false),
|a: i64, b: i64| (a.max(b), false)
)
.into(),
)),
Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
}
}
fn reduce_columns(v: &Verb, y: &Array) -> Option<Array> {
let Verb::Prim(p) = v else { return None };
let DyadOp::Scalar(op) = p.dyad else { return None };
if !y.dtype().is_numeric() {
return None;
}
let n = y.shape[0];
let m: usize = y.shape[1..].iter().product();
if n == 0 || m == 0 {
return None;
}
let shape = y.shape[1..].to_vec();
if n == 1 {
return Some(Array::col_major(shape, y.data.clone()));
}
let data = fold_columns_data(op, &y.data, m, n)?;
Some(Array::col_major(shape, data))
}
fn fold_across<S, T, F>(cols: &[&[S]], rows: usize, step: F) -> Option<Vec<T>>
where
S: Widen<T>,
T: Copy + Default + Send + Sync,
F: Fn(T, T) -> (T, bool) + Sync + Send,
{
let (last, rest) = cols.split_last()?;
let (out, ok) = par::fill(rows, |start, part: &mut [T]| {
let mut over = false;
for (k, slot) in part.iter_mut().enumerate() {
let i = start + k;
let mut acc = last[i].widen();
for c in rest.iter().rev() {
let (r, o) = step(c[i].widen(), acc);
acc = r;
over |= o;
}
*slot = acc;
}
!over
});
ok.then_some(out)
}
fn fold_across_data(op: ScalarDyad, d: &Data, rows: usize, cols: usize) -> Option<Data> {
use ScalarDyad::*;
if !matches!(op, Add | Sub | Mul | Min | Max) {
return None;
}
macro_rules! by {
($v:expr, $add:expr, $sub:expr, $mul:expr, $min:expr, $max:expr) => {{
let parts = run_slices($v, cols, rows);
match op {
Add => fold_across(&parts, rows, $add),
Sub => fold_across(&parts, rows, $sub),
Mul => fold_across(&parts, rows, $mul),
Min => fold_across(&parts, rows, $min),
Max => fold_across(&parts, rows, $max),
_ => None,
}?
}};
}
match d {
Data::F64(v) => Some(Data::F64(
by!(
v,
|a: f64, b: f64| (a + b, false),
|a: f64, b: f64| (a - b, false),
|a: f64, b: f64| (a * b, false),
|a: f64, b: f64| (a.min(b), false),
|a: f64, b: f64| (a.max(b), false)
)
.into(),
)),
Data::I64(v) => Some(Data::I64(
by!(
v,
i64::overflowing_add,
i64::overflowing_sub,
i64::overflowing_mul,
|a: i64, b: i64| (a.min(b), false),
|a: i64, b: i64| (a.max(b), false)
)
.into(),
)),
Data::Complex(v) => {
if !matches!(op, Add | Sub | Mul) {
return None;
}
Some(Data::Complex(
by!(
v,
|a: Cx, b: Cx| (cx::add(a, b), false),
|a: Cx, b: Cx| (cx::sub(a, b), false),
|a: Cx, b: Cx| (cx::mul(a, b), false),
|_: Cx, _: Cx| unreachable!("refused above"),
|_: Cx, _: Cx| unreachable!("refused above")
)
.into(),
))
}
Data::Bool(v) => Some(Data::I64(
by!(
v,
i64::overflowing_add,
i64::overflowing_sub,
i64::overflowing_mul,
|a: i64, b: i64| (a.min(b), false),
|a: i64, b: i64| (a.max(b), false)
)
.into(),
)),
Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
}
}
fn reduce_rows_columns(u: &Verb, y: &Array) -> Option<Array> {
let Verb::Reduce(inner) = u else { return None };
let Verb::Prim(p) = &**inner else { return None };
let DyadOp::Scalar(op) = p.dyad else { return None };
if y.rank() != 2 || !y.dtype().is_numeric() {
return None;
}
let (rows, cols) = (y.shape[0], y.shape[1]);
if rows == 0 || cols == 0 {
return None;
}
if cols == 1 {
return Some(Array::new(vec![rows], y.data.clone()));
}
let data = fold_across_data(op, &y.data, rows, cols)?;
Some(Array::new(vec![rows], data))
}
fn reduce_vector_cells(u: &Verb, y: &Array, frame_rank: usize) -> Option<Array> {
let Verb::Reduce(inner) = u else { return None };
let Verb::Prim(p) = &**inner else { return None };
let DyadOp::Scalar(op) = p.dyad else { return None };
if y.rank() != frame_rank + 1 || !y.dtype().is_numeric() {
return None;
}
let m = y.shape[frame_rank];
if m == 0 {
return None;
}
use ScalarDyad::{Add, Max, Min, Mul, Sub};
if !matches!(op, Add | Sub | Mul | Min | Max) {
return None;
}
let frame = y.shape[..frame_rank].to_vec();
if m == 1 {
return Some(Array::new(frame, y.data.clone()));
}
let n: usize = frame.iter().product();
let data = fold_runs_data(op, &y.data, n, m)?;
Some(Array::new(frame, data))
}
fn reduce(v: &Verb, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
if y.rank() == 0 {
return Ok(y.clone());
}
let n = y.items();
if n == 1 {
return Ok(y.item(0));
}
let cell_shape = y.shape[1..].to_vec();
let m: usize = cell_shape.iter().product();
if n == 0 {
if matches!(v, Verb::Prim(p) if matches!(p.dyad, DyadOp::AppendLeading | DyadOp::AppendLast))
{
return Ok(Array::new(vec![0], Data::empty(y.dtype())));
}
return match reduce_identity(v, m) {
Some(d) => Ok(Array::new(cell_shape, d)),
None => Err(Error::domain(
format!("empty reduction has no identity for {}", v.name()),
span,
)),
};
}
if y.dtype().is_numeric() && let Verb::Prim(p) = v && let DyadOp::Scalar(op) = p.dyad {
if let Some(d) = reduce_typed(op, y.row_major_data(), n, m) {
return Ok(Array::new(cell_shape, d));
}
let mut acc = y.data.slice((n - 1) * m, n * m);
for i in (0..n - 1).rev() {
acc =
scalar_dyad_data(op, &y.data, i * m, 1, &acc, 0, 1, m, ctx.cfg.tol, span)?;
}
return Ok(Array::new(cell_shape, acc));
}
let mut acc = y.item(n - 1);
for i in (0..n - 1).rev() {
acc = v.dyad(&y.item(i), &acc, ctx, span)?;
}
Ok(acc)
}
fn folded_op(u: &Verb) -> Option<ScalarDyad> {
let Verb::Reduce(inner) = u else { return None };
let Verb::Prim(p) = &**inner else { return None };
match p.dyad {
DyadOp::Scalar(op) => Some(op),
_ => None,
}
}
fn section(y: &Array, lo: usize, hi: usize) -> Array {
let m = y.item_size();
let mut shape = y.shape.clone();
shape[0] = hi - lo;
Array::new(shape, y.data.slice(lo * m, hi * m))
}
fn as_items(y: &Array) -> Option<Array> {
(y.rank() == 0).then(|| Array::new(vec![1], y.data.clone()))
}
#[inline(always)]
fn scan_flat_body<S, T, F>(v: &[S], n: usize, m: usize, back: bool, step: F) -> Option<Vec<T>>
where
S: Widen<T>,
T: Copy + Default,
F: Fn(T, T) -> (T, bool),
{
if m == 1 {
let mut out = vec![T::default(); n];
let mut over = false;
if back {
let mut acc = v[n - 1].widen();
out[n - 1] = acc;
for (slot, &x) in out[..n - 1].iter_mut().zip(&v[..n - 1]).rev() {
let (r, o) = step(x.widen(), acc);
acc = r;
over |= o;
*slot = acc;
}
} else {
let mut acc = v[0].widen();
out[0] = acc;
for (slot, &x) in out[1..n].iter_mut().zip(&v[1..n]) {
let (r, o) = step(acc, x.widen());
acc = r;
over |= o;
*slot = acc;
}
}
return (!over).then_some(out);
}
let mut out = vec![T::default(); n * m];
let mut acc = vec![T::default(); m];
let mut over = false;
if back {
for (slot, &x) in acc.iter_mut().zip(&v[(n - 1) * m..n * m]) {
*slot = x.widen();
}
out[(n - 1) * m..n * m].copy_from_slice(&acc);
for i in (0..n - 1).rev() {
for (j, slot) in acc.iter_mut().enumerate() {
let (r, o) = step(v[i * m + j].widen(), *slot);
*slot = r;
over |= o;
}
out[i * m..i * m + m].copy_from_slice(&acc);
}
} else {
for (slot, &x) in acc.iter_mut().zip(&v[..m]) {
*slot = x.widen();
}
out[..m].copy_from_slice(&acc);
for i in 1..n {
for (j, slot) in acc.iter_mut().enumerate() {
let (r, o) = step(*slot, v[i * m + j].widen());
*slot = r;
over |= o;
}
out[i * m..i * m + m].copy_from_slice(&acc);
}
}
(!over).then_some(out)
}
multiversioned! {
fn scan_flat_vectorised[S: Widen<T>, T: Copy + Default, F: Fn(T, T) -> (T, bool)](
v: &[S],
n: usize,
m: usize,
back: bool,
step: F,
) -> Option<Vec<T>> = scan_flat_body;
}
fn scan_flat<S, T, F>(v: &[S], n: usize, m: usize, back: bool, step: F) -> Option<Vec<T>>
where
S: Widen<T>,
T: Copy + Default,
F: Fn(T, T) -> (T, bool),
{
if m < VECTOR_COLUMNS {
scan_flat_body(v, n, m, back, step)
} else {
scan_flat_vectorised(v, n, m, back, step)
}
}
fn scan_i64<S: Widen<i64>>(
op: ScalarDyad,
v: &[S],
n: usize,
m: usize,
back: bool,
) -> Option<Vec<i64>> {
use ScalarDyad::*;
match op {
Add => scan_flat(v, n, m, back, i64::overflowing_add),
Sub => scan_flat(v, n, m, back, i64::overflowing_sub),
Mul => scan_flat(v, n, m, back, i64::overflowing_mul),
Min => scan_flat(v, n, m, back, |a: i64, b: i64| (a.min(b), false)),
Max => scan_flat(v, n, m, back, |a: i64, b: i64| (a.max(b), false)),
_ => None,
}
}
fn scan_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize, back: bool) -> Option<Vec<Cx>> {
use ScalarDyad::*;
match op {
Add => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::add(a, b), false)),
Sub => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::sub(a, b), false)),
Mul => scan_flat(v, n, m, back, |a: Cx, b: Cx| (cx::mul(a, b), false)),
_ => None,
}
}
fn scan_f64<S: Widen<f64>>(
op: ScalarDyad,
v: &[S],
n: usize,
m: usize,
back: bool,
) -> Option<Vec<f64>> {
use ScalarDyad::*;
match op {
Add => scan_flat(v, n, m, back, |a: f64, b: f64| (a + b, false)),
Sub => scan_flat(v, n, m, back, |a: f64, b: f64| (a - b, false)),
Mul => scan_flat(v, n, m, back, |a: f64, b: f64| (a * b, false)),
Min => scan_flat(v, n, m, back, |a: f64, b: f64| (a.min(b), false)),
Max => scan_flat(v, n, m, back, |a: f64, b: f64| (a.max(b), false)),
_ => None,
}
}
fn scan_typed(op: ScalarDyad, d: &Data, n: usize, m: usize, back: bool) -> Option<Data> {
use ScalarDyad::*;
if !matches!(op, Add | Sub | Mul | Min | Max) {
return None;
}
fn ints<S: Widen<i64> + Widen<f64>>(
op: ScalarDyad,
v: &[S],
n: usize,
m: usize,
back: bool,
) -> Data {
match scan_i64(op, v, n, m, back) {
Some(out) => Data::I64(out.into()),
None => Data::F64(
scan_f64(op, v, n, m, back).expect("the float scan cannot overflow").into(),
),
}
}
match d {
Data::F64(v) => Some(Data::F64(scan_f64(op, v.as_slice(), n, m, back)?.into())),
Data::Complex(v) => Some(Data::Complex(scan_cx(op, v, n, m, back)?.into())),
Data::I64(v) => Some(ints(op, v.as_slice(), n, m, back)),
Data::Bool(v) => Some(ints(op, v.as_slice(), n, m, back)),
Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
}
}
fn affine_step(u: &Verb) -> Option<&Array> {
fn prim(v: &Verb, want: DyadOp, ranks: [i64; 3]) -> bool {
matches!(v, Verb::Prim(p) if p.dyad == want && p.ranks == ranks)
}
const ATOMS: [i64; 3] = [0, 0, 0];
const WHOLE: [i64; 3] = [RANK_INF; 3];
fn scaled(v: &Verb) -> Option<&Array> {
let Verb::NounFork(c, g, h) = v else { return None };
let noun = c.rank() == 0
&& matches!(c.dtype(), DType::Bool | DType::I64 | DType::F64 | DType::Complex);
let tree = prim(g, DyadOp::Scalar(ScalarDyad::Mul), ATOMS)
&& prim(h, DyadOp::Right, WHOLE);
(noun && tree).then_some(c)
}
let Verb::Fork(f, g, h) = u else { return None };
if !prim(g, DyadOp::Scalar(ScalarDyad::Add), ATOMS) {
return None;
}
if prim(f, DyadOp::Left, WHOLE) {
scaled(h)
} else if prim(h, DyadOp::Left, WHOLE) {
scaled(f)
} else {
None
}
}
struct Ring<T> {
add: fn(T, T) -> T,
mul: fn(T, T) -> T,
one: T,
finite: fn(T) -> bool,
}
fn affine_flat<T>(v: &[T], c: T, n: usize, m: usize, back: bool, r: &Ring<T>) -> Option<Vec<T>>
where
T: Copy + Default,
{
let (add, mul) = (r.add, r.mul);
let mut out = vec![T::default(); n * m];
if back {
out[(n - 1) * m..].copy_from_slice(&v[(n - 1) * m..n * m]);
for i in (0..n - 1).rev() {
for j in 0..m {
out[i * m + j] = add(v[i * m + j], mul(c, out[(i + 1) * m + j]));
}
}
} else {
out[..m].copy_from_slice(&v[..m]);
let mut pow = r.one;
for i in 1..n {
pow = mul(pow, c);
if !(r.finite)(pow) {
return None;
}
for j in 0..m {
out[i * m + j] = add(out[(i - 1) * m + j], mul(pow, v[i * m + j]));
}
}
}
Some(out)
}
fn affine_scan(c: &Array, y: &Array, back: bool) -> Option<Data> {
let (n, m) = (y.items(), y.item_size());
let machine = |t: DType| matches!(t, DType::Bool | DType::I64 | DType::F64 | DType::Complex);
if n == 0 || !machine(c.dtype()) || !machine(y.dtype()) {
return None;
}
match DType::promote(c.dtype(), y.dtype())? {
DType::F64 => {
let (mut tc, mut tv) = (Vec::new(), Vec::new());
let k = *borrow_f64(&c.data, &mut tc).first()?;
let v = borrow_f64(y.row_major_data(), &mut tv);
let r = Ring { add: |a, b| a + b, mul: |a, b| a * b, one: 1.0, finite: f64::is_finite };
Some(Data::F64(affine_flat(v, k, n, m, back, &r)?.into()))
}
DType::Complex => {
let (mut tc, mut tv) = (Vec::new(), Vec::new());
let k = *borrow_cx(&c.data, &mut tc).first()?;
let v = borrow_cx(y.row_major_data(), &mut tv);
let finite = |z: Cx| z[0].is_finite() && z[1].is_finite();
let r = Ring { add: cx::add, mul: cx::mul, one: [1.0, 0.0], finite };
Some(Data::Complex(affine_flat(v, k, n, m, back, &r)?.into()))
}
_ => None,
}
}
fn window_fold<S, T, F>(v: &[S], n: usize, m: usize, w: usize, step: F) -> Option<Vec<T>>
where
S: Widen<T>,
T: Copy + Default + Send + Sync,
F: Fn(T, T) -> (T, bool) + Sync + Send,
{
debug_assert!(w >= 1 && n >= w);
if m == 1 {
return window_fold_flat(v, n, w, step);
}
let count = n - w + 1;
let mut out = vec![T::default(); count * m];
let mut pre = vec![T::default(); w * m];
let mut suf = vec![T::default(); w * m];
let mut prev = vec![T::default(); w * m];
let mut over = false;
for b in 0..n.div_ceil(w) {
let bs = b * w;
let be = ((b + 1) * w).min(n);
for (slot, &x) in pre[..m].iter_mut().zip(&v[bs * m..bs * m + m]) {
*slot = x.widen();
}
for i in 1..be - bs {
let (o, p) = (i * m, (i - 1) * m);
for j in 0..m {
let (r, f) = step(pre[p + j], v[(bs + i) * m + j].widen());
pre[o + j] = r;
over |= f;
}
}
for e in bs.max(w - 1)..be {
let i = e + 1 - w;
let (oo, po) = (i * m, (e - bs) * m);
if i == bs {
out[oo..oo + m].copy_from_slice(&pre[po..po + m]);
} else {
let so = (i + w - bs) * m;
for j in 0..m {
let (r, f) = step(prev[so + j], pre[po + j]);
out[oo + j] = r;
over |= f;
}
}
}
let last = be - 1 - bs;
for (slot, &x) in suf[last * m..last * m + m]
.iter_mut()
.zip(&v[(be - 1) * m..be * m])
{
*slot = x.widen();
}
for i in (0..last).rev() {
let (o, p) = (i * m, (i + 1) * m);
for j in 0..m {
let (r, f) = step(v[(bs + i) * m + j].widen(), suf[p + j]);
suf[o + j] = r;
over |= f;
}
}
std::mem::swap(&mut prev, &mut suf);
}
(!over).then_some(out)
}
fn window_fold_flat<S, T, F>(v: &[S], n: usize, w: usize, step: F) -> Option<Vec<T>>
where
S: Widen<T>,
T: Copy + Default + Send + Sync,
F: Fn(T, T) -> (T, bool) + Sync + Send,
{
let (out, ok) = par::fill(n - w + 1, |lo, part: &mut [T]| {
window_fold_range(v, n, w, lo, part, &step)
});
ok.then_some(out)
}
#[inline(always)]
fn window_fold_range_body<S, T, F>(
v: &[S],
n: usize,
w: usize,
lo: usize,
out: &mut [T],
step: &F,
) -> bool
where
S: Widen<T>,
T: Copy + Default,
F: Fn(T, T) -> (T, bool),
{
if out.is_empty() {
return true;
}
let hi = lo + out.len();
let mut pre = vec![T::default(); w];
let mut suf = vec![T::default(); w];
let mut prev = vec![T::default(); w];
let mut over = false;
let mut bs = lo / w * w;
while bs < n && bs <= hi + w - 2 {
let block = &v[bs..(bs + w).min(n)];
let lb = block.len();
let mut acc = block[0].widen();
pre[0] = acc;
for (slot, &x) in pre[1..lb].iter_mut().zip(&block[1..]) {
let (r, o) = step(acc, x.widen());
acc = r;
over |= o;
*slot = acc;
}
for e in bs.max(lo + w - 1)..(bs + lb).min(hi + w - 1) {
let i = e + 1 - w;
out[i - lo] = if i == bs {
pre[e - bs]
} else {
let (r, o) = step(prev[i + w - bs], pre[e - bs]);
over |= o;
r
};
}
let mut acc = block[lb - 1].widen();
suf[lb - 1] = acc;
for (slot, &x) in suf[..lb - 1].iter_mut().zip(&block[..lb - 1]).rev() {
let (r, o) = step(x.widen(), acc);
acc = r;
over |= o;
*slot = acc;
}
std::mem::swap(&mut prev, &mut suf);
bs += w;
}
!over
}
multiversioned! {
fn window_fold_range[S: Widen<T>, T: Copy + Default, F: Fn(T, T) -> (T, bool)](
v: &[S],
n: usize,
w: usize,
lo: usize,
out: &mut [T],
step: &F,
) -> bool = window_fold_range_body;
}
pub(crate) fn windows_into<S, T, F>(v: &[S], w: usize, lo: usize, out: &mut [T], step: &F) -> bool
where
S: Widen<T>,
T: Copy + Default,
F: Fn(T, T) -> (T, bool),
{
window_fold_range(v, v.len(), w, lo, out, step)
}
fn window_i64<S: Widen<i64>>(
op: ScalarDyad,
v: &[S],
n: usize,
m: usize,
w: usize,
) -> Option<Vec<i64>> {
use ScalarDyad::*;
match op {
Add => window_fold(v, n, m, w, i64::overflowing_add),
Mul => window_fold(v, n, m, w, i64::overflowing_mul),
Min => window_fold(v, n, m, w, |a: i64, b: i64| (a.min(b), false)),
Max => window_fold(v, n, m, w, |a: i64, b: i64| (a.max(b), false)),
_ => None,
}
}
fn window_cx(op: ScalarDyad, v: &[Cx], n: usize, m: usize, w: usize) -> Option<Vec<Cx>> {
use ScalarDyad::*;
match op {
Add => window_fold(v, n, m, w, |a: Cx, b: Cx| (cx::add(a, b), false)),
Mul => window_fold(v, n, m, w, |a: Cx, b: Cx| (cx::mul(a, b), false)),
_ => None,
}
}
fn window_f64<S: Widen<f64>>(
op: ScalarDyad,
v: &[S],
n: usize,
m: usize,
w: usize,
) -> Option<Vec<f64>> {
use ScalarDyad::*;
match op {
Add => window_fold(v, n, m, w, |a: f64, b: f64| (a + b, false)),
Mul => window_fold(v, n, m, w, |a: f64, b: f64| (a * b, false)),
Min => window_fold(v, n, m, w, |a: f64, b: f64| (a.min(b), false)),
Max => window_fold(v, n, m, w, |a: f64, b: f64| (a.max(b), false)),
_ => None,
}
}
fn window_typed(op: ScalarDyad, d: &Data, n: usize, m: usize, w: usize) -> Option<Data> {
use ScalarDyad::*;
if !matches!(op, Add | Mul | Min | Max) {
return None;
}
fn ints<S: Widen<i64> + Widen<f64>>(
op: ScalarDyad,
v: &[S],
n: usize,
m: usize,
w: usize,
) -> Data {
match window_i64(op, v, n, m, w) {
Some(out) => Data::I64(out.into()),
None => {
Data::F64(window_f64(op, v, n, m, w).expect("the float fold cannot overflow").into())
}
}
}
match d {
Data::F64(v) => Some(Data::F64(window_f64(op, v.as_slice(), n, m, w)?.into())),
Data::Complex(v) => Some(Data::Complex(window_cx(op, v, n, m, w)?.into())),
Data::I64(v) => Some(ints(op, v.as_slice(), n, m, w)),
Data::Bool(v) => Some(ints(op, v.as_slice(), n, m, w)),
Data::Ext(_) | Data::Rat(_) | Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
}
}
fn runs(u: &Verb, y: &Array, back: bool, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let promoted = as_items(y);
let base = promoted.as_ref().unwrap_or(y);
let n = base.items();
let m = base.item_size();
if n > 0 && base.dtype().is_numeric() && let Some(op) = folded_op(u) {
if (back || is_associative(op))
&& let Some(d) = scan_typed(op, base.row_major_data(), n, m, back)
{
return Ok(Array::new(base.shape.clone(), d));
}
}
if n > 0 && let Verb::Reduce(inner) = u {
if base.dtype().is_numeric()
&& let Some(c) = affine_step(inner)
&& let Some(d) = affine_scan(c, base, back)
{
return Ok(Array::new(base.shape.clone(), d));
}
if back && u.is_pure() {
let mut acc = base.item(n - 1);
let mut cells = Vec::with_capacity(n);
cells.push(acc.clone());
for i in (0..n - 1).rev() {
acc = inner.dyad(&base.item(i), &acc, ctx, span)?;
cells.push(acc.clone());
}
cells.reverse();
return assemble(&[n], cells, span);
}
}
let cells = each_cell(n, n * m, u.is_pure(), ctx, |i, c| {
let part = if back { section(base, i, n) } else { section(base, 0, i + 1) };
u.monad(&part, c, span)
})?;
assemble(&[n], cells, span)
}
fn empty_windows(u: &Verb, y: &Array, w: usize, ctx: &mut Ctx<'_>, span: Span) -> Array {
let m = y.item_size();
if u.is_pure() && let Some(cells) = w.checked_mul(m).filter(|&s| s <= 1 << 20) {
let mut shape = y.shape.clone();
shape[0] = w;
let probe = Array::new(shape, fill_data(y.dtype(), cells));
if let Ok(cell) = u.monad(&probe, ctx, span) {
let mut shape = vec![0usize];
shape.extend_from_slice(&cell.shape);
return Array::new(shape, Data::empty(cell.dtype()));
}
}
Array::new(vec![0], Data::empty(DType::I64))
}
fn window_size(x: &Array, span: Span) -> Result<i64> {
let v = x
.to_i64_vec()
.ok_or_else(|| Error::domain("the window size must be an integer", span))?;
match v.as_slice() {
[k] => Ok(*k),
_ => Err(Error::new(
ErrorKind::Length,
"the window size must be a single number",
Some(span),
)),
}
}
fn infix(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let k = window_size(x, span)?;
let promoted = as_items(y);
let base = promoted.as_ref().unwrap_or(y);
let n = base.items();
let m = base.item_size();
if k < 0 {
let w = k.unsigned_abs() as usize;
let count = n.div_ceil(w);
let cells = each_cell(count, n * m, u.is_pure(), ctx, |i, c| {
u.monad(§ion(base, i * w, ((i + 1) * w).min(n)), c, span)
})?;
return assemble(&[count], cells, span);
}
let w = k as usize;
if n < w {
return Ok(empty_windows(u, base, w, ctx, span));
}
let count = n - w + 1;
if w > 0 && base.dtype().is_numeric()
&& let Some(op) = folded_op(u) && let Some(d) = window_typed(op, &base.data, n, m, w)
{
let mut shape = base.shape.clone();
shape[0] = count;
return Ok(Array::new(shape, d));
}
let work = count.saturating_mul(w).saturating_mul(m);
let cells = each_cell(count, work, u.is_pure(), ctx, |i, c| {
u.monad(§ion(base, i, i + w), c, span)
})?;
assemble(&[count], cells, span)
}
fn power(
u: &Verb,
p: Power,
x: Option<&Array>,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let step = |acc: &Array, c: &mut Ctx<'_>| match x {
Some(x) => u.dyad(x, acc, c, span),
None => u.monad(acc, c, span),
};
match p {
Power::Times(n) => {
let mut acc = y.clone();
for _ in 0..n {
acc = step(&acc, ctx)?;
}
Ok(acc)
}
Power::Converge => {
let mut acc = y.clone();
for _ in 0..CONVERGE_LIMIT {
let next = step(&acc, ctx)?;
if arrays_match(&next, &acc, ctx.cfg.tol) {
return Ok(next);
}
acc = next;
}
Err(Error::domain("the iteration did not converge", span))
}
Power::Each(ref counts) => {
let mut acc = y.clone();
let mut done = 0u64;
let mut order: Vec<usize> = (0..counts.len()).collect();
order.sort_by_key(|&i| counts[i]);
let mut cells: Vec<Option<Array>> = vec![None; counts.len()];
for i in order {
while done < counts[i] {
acc = step(&acc, ctx)?;
done += 1;
}
cells[i] = Some(acc.clone());
}
let cells: Vec<Array> = cells.into_iter().map(|c| c.expect("every count filled")).collect();
assemble(&[cells.len()], cells, span)
}
Power::ConvergeTrace => {
let mut acc = y.clone();
let mut cells = vec![acc.clone()];
for _ in 0..CONVERGE_LIMIT {
let next = step(&acc, ctx)?;
if arrays_match(&next, &acc, ctx.cfg.tol) {
return assemble(&[cells.len()], cells, span);
}
cells.push(next.clone());
acc = next;
}
Err(Error::domain("the iteration did not converge", span))
}
}
}
fn power_v(
u: &Verb,
v: &Verb,
x: Option<&Array>,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let count = match x {
Some(x) => v.dyad(x, y, ctx, span)?,
None => v.monad(y, ctx, span)?,
};
let n = count
.to_i64_vec()
.ok_or_else(|| Error::domain("the power count must be an integer", span))?;
if n.len() != 1 {
return Err(Error::not_yet("a list of power counts (u^:v with several)", span));
}
let n = n[0];
if n < 0 {
return Err(Error::not_yet("a negative power (the verb's inverse)", span));
}
power(u, Power::Times(n as u64), x, y, ctx, span)
}
fn power_until(
u: &Verb,
test: &Verb,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let mut acc = y.clone();
for _ in 0..CONVERGE_LIMIT {
let next = u.monad(&acc, ctx, span)?;
let done = test.dyad(&next, &acc, ctx, span)?;
let stop = done
.to_f64_vec()
.ok_or_else(|| Error::domain("the ⍣ test must answer with numbers", span))?;
if !stop.is_empty() && stop.iter().all(|&v| v != 0.0) {
return Ok(next);
}
acc = next;
}
Err(Error::domain("the iteration did not converge", span))
}
fn along_axis(
u: &Verb,
x: Option<&Array>,
y: &Array,
k: usize,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
if k >= y.rank().max(1) {
return Err(Error::new(
ErrorKind::Rank,
format!("axis {k} does not exist on an argument of rank {}", y.rank()),
Some(span),
));
}
let moved = axis_to_front(y, k);
let r = moved.rank();
let out = match x {
Some(x) => u.dyad(x, &moved, ctx, span)?,
None => u.monad(&moved, ctx, span)?,
};
if out.rank() == r {
return Ok(front_to_axis(&out, k));
}
Ok(out)
}
fn where_indices(y: &Array, origin: i64, boxed: bool, span: Span) -> Result<Array> {
let counts = y
.to_i64_vec()
.ok_or_else(|| Error::domain("indices needs non-negative integers", span))?;
if counts.iter().any(|&c| c < 0) {
return Err(Error::domain("indices needs non-negative integers", span));
}
if !boxed || y.rank() < 2 {
let mut out = Vec::new();
for (i, &c) in counts.iter().enumerate() {
for _ in 0..c {
out.push(origin + i as i64);
}
}
return Ok(Array::from_i64(out));
}
let r = y.rank();
let mut coord = vec![0usize; r];
let mut out: Vec<Array> = Vec::new();
for &c in &counts {
if c > 0 {
let point =
Array::from_i64(coord.iter().map(|&k| origin + k as i64).collect::<Vec<_>>());
for _ in 0..c {
out.push(point.clone());
}
}
odometer(&mut coord, &y.shape);
}
Ok(Array::new(vec![out.len()], Data::Box(out.into())))
}
fn interval_index(
x: &Array,
y: &Array,
offset: i64,
closed: bool,
tol: Tol,
span: Span,
) -> Result<Array> {
if !x.dtype().is_numeric() || !y.dtype().is_numeric() {
return ordered_interval_index(x, y, offset, closed, span);
}
let bounds = x
.to_f64_vec()
.ok_or_else(|| Error::domain("interval index needs numeric bounds", span))?;
let vals = y
.to_f64_vec()
.ok_or_else(|| Error::domain("interval index needs numeric values", span))?;
let out: Vec<i64> = vals
.iter()
.map(|&v| {
let count =
bounds.iter().filter(|&&b| if closed { !tol.lt(v, b) } else { tol.lt(b, v) });
offset + count.count() as i64
})
.collect();
Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
}
fn ordered_interval_index(
x: &Array,
y: &Array,
offset: i64,
closed: bool,
span: Span,
) -> Result<Array> {
let (xr, yr) = (x.to_row_major(), y.to_row_major());
let (bounds, vals) = (&xr.data, &yr.data);
let cmp = |i: usize, j: usize| -> Option<std::cmp::Ordering> {
match (bounds, vals) {
(Data::Char(p), Data::Char(q)) => Some(p[i].cmp(&q[j])),
(Data::Symbol(p), Data::Symbol(q)) => Some(crate::symbol::cmp(p[i], q[j])),
_ => None,
}
};
let mut out = Vec::with_capacity(y.count());
for j in 0..y.count() {
let mut count = 0i64;
for i in 0..x.count() {
let ord = cmp(i, j).ok_or_else(|| {
Error::domain(
format!(
"interval index compares {} bounds with {} values",
x.dtype().name(),
y.dtype().name()
),
span,
)
})?;
count += i64::from(if closed { ord.is_le() } else { ord.is_lt() });
}
out.push(offset + count);
}
Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
}
fn steps(y: &Array, span: Span) -> Result<Array> {
let vals = y.to_f64_vec().ok_or_else(|| Error::domain("steps needs a number", span))?;
let v = match vals.first() {
Some(&v) if v.is_finite() => v,
_ => return Err(Error::domain("steps needs a finite number", span)),
};
let n = (2.0 * v.abs()).floor();
if n > 1e7 {
return Err(Error::domain("steps would produce too many items", span));
}
let n = n as i64 + 1;
let step = if v < 0.0 { -1.0 } else { 1.0 };
let start = -v;
if v.fract() == 0.0 {
let start = start as i64;
let step = step as i64;
return Ok(Array::from_i64((0..n).map(|k| start + k * step).collect()));
}
Ok(Array::from_f64((0..n).map(|k| start + k as f64 * step).collect()))
}
fn index_of_last(x: &Array, y: &Array, origin: i64, tol: Tol) -> Array {
let cell_rank = x.rank().saturating_sub(1).min(y.rank());
let frame_rank = y.rank() - cell_rank;
let frame: Vec<usize> = y.shape[..frame_rank].to_vec();
let nf: usize = frame.iter().product();
let items = x.items();
let mut out = Vec::with_capacity(nf);
for i in 0..nf {
let cell = y.cell_at(frame_rank, i);
let at = (0..items)
.rev()
.find(|&j| arrays_match(&cell, &item_or_self(x, j), tol))
.unwrap_or(items);
out.push(origin + at as i64);
}
Array::new(frame, Data::I64(out.into()))
}
fn roll(
y: &Array,
origin: i64,
fixed: bool,
float_at_zero: bool,
span: Span,
) -> Result<Array> {
let bounds = y
.to_i64_vec()
.ok_or_else(|| Error::domain("roll needs whole numbers", span))?;
if bounds.iter().any(|&b| b < 0) {
return Err(Error::domain("roll needs non-negative numbers", span));
}
if !float_at_zero && bounds.contains(&0) {
return Err(Error::domain("? 0 has no value: the range is empty", span));
}
let any_zero = bounds.contains(&0);
crate::rng::with(fixed, |g| {
if any_zero {
let out: Vec<f64> = bounds
.iter()
.map(|&b| {
if b == 0 {
g.unit()
} else {
(origin + g.below(b as u64) as i64) as f64
}
})
.collect();
return Ok(Array::new(y.shape.clone(), Data::F64(out.into())));
}
let out: Vec<i64> =
bounds.iter().map(|&b| origin + g.below(b as u64) as i64).collect();
Ok(Array::new(y.shape.clone(), Data::I64(out.into())))
})
}
fn deal(x: &Array, y: &Array, origin: i64, fixed: bool, span: Span) -> Result<Array> {
let want = one_whole(x, "the count dealt", span)?;
let from = one_whole(y, "the range dealt from", span)?;
if want < 0 || from < 0 {
return Err(Error::domain("deal needs non-negative numbers", span));
}
if want > from {
return Err(Error::domain(
format!("cannot deal {want} distinct value(s) from {from}"),
span,
));
}
if want == 0 {
return Ok(Array::from_i64(Vec::new()));
}
let drawn = crate::rng::with(fixed, |g| g.deal(want as usize, from as u64));
Ok(Array::from_i64(drawn.into_iter().map(|v| v + origin).collect()))
}
fn one_whole(a: &Array, what: &str, span: Span) -> Result<i64> {
let v = a
.to_i64_vec()
.ok_or_else(|| Error::domain(format!("{what} must be a whole number"), span))?;
match v[..] {
[n] => Ok(n),
_ => Err(Error::new(
ErrorKind::Rank,
format!("{what} must be one number"),
Some(span),
)),
}
}
fn nth_prime(n: i64, span: Span) -> Result<i64> {
if n < 0 {
return Err(Error::domain("the prime index must not be negative", span));
}
const LIMIT: i64 = 5_000_000;
if n >= LIMIT {
return Err(Error::domain(
format!("prime index {n} is beyond the {LIMIT}th prime"),
span,
));
}
let k = (n + 1) as f64;
let bound = if n < 6 { 15.0 } else { k * (k.ln() + k.ln().ln()) };
let bound = bound.ceil() as usize + 1;
let mut sieve = vec![true; bound + 1];
sieve[0] = false;
if bound >= 1 {
sieve[1] = false;
}
let mut p = 2usize;
while p * p <= bound {
if sieve[p] {
let mut q = p * p;
while q <= bound {
sieve[q] = false;
q += p;
}
}
p += 1;
}
let mut seen = 0i64;
for (v, &is_p) in sieve.iter().enumerate() {
if is_p {
if seen == n {
return Ok(v as i64);
}
seen += 1;
}
}
Err(Error::internal("the prime sieve was too small"))
}
fn prime_factors(n: i64, span: Span) -> Result<Vec<i64>> {
if n < 1 {
return Err(Error::domain("prime factors need a positive integer", span));
}
let mut out = Vec::new();
let mut m = n;
let mut d = 2i64;
while d.saturating_mul(d) <= m {
while m % d == 0 {
out.push(d);
m /= d;
}
d += if d == 2 { 1 } else { 2 };
}
if m > 1 {
out.push(m);
}
Ok(out)
}
fn lstsq(a: &[f64], m: usize, n: usize, b: &[f64], k: usize) -> Option<Vec<f64>> {
let mut r = a.to_vec();
let mut c = b.to_vec();
let at = |i: usize, j: usize, w: usize| i * w + j;
let scale = a.iter().fold(0.0f64, |acc, v| acc.max(v.abs()));
if scale == 0.0 {
return None;
}
for j in 0..n {
let norm = (j..m).map(|i| r[at(i, j, n)] * r[at(i, j, n)]).sum::<f64>().sqrt();
if norm <= 1e-13 * scale {
return None;
}
let alpha = if r[at(j, j, n)] > 0.0 { -norm } else { norm };
let mut v = vec![0.0f64; m];
for i in j..m {
v[i] = r[at(i, j, n)];
}
v[j] -= alpha;
let vnorm2: f64 = (j..m).map(|i| v[i] * v[i]).sum();
if vnorm2 > 0.0 {
for col in j..n {
let dot: f64 = (j..m).map(|i| v[i] * r[at(i, col, n)]).sum();
let f = 2.0 * dot / vnorm2;
for i in j..m {
r[at(i, col, n)] -= f * v[i];
}
}
for col in 0..k {
let dot: f64 = (j..m).map(|i| v[i] * c[at(i, col, k)]).sum();
let f = 2.0 * dot / vnorm2;
for i in j..m {
c[at(i, col, k)] -= f * v[i];
}
}
}
}
let mut x = vec![0.0f64; n * k];
for col in 0..k {
for i in (0..n).rev() {
let mut acc = c[at(i, col, k)];
for j in i + 1..n {
acc -= r[at(i, j, n)] * x[at(j, col, k)];
}
let d = r[at(i, i, n)];
if d.abs() <= 1e-13 * scale {
return None;
}
x[at(i, col, k)] = acc / d;
}
}
Some(x)
}
fn as_matrix(a: &Array, span: Span) -> Result<(Vec<f64>, usize, usize)> {
let v = a
.to_f64_vec()
.ok_or_else(|| Error::domain("matrix division needs numeric data", span))?;
match a.rank() {
0 => Ok((v, 1, 1)),
1 => {
let m = a.shape[0];
Ok((v, m, 1))
}
2 => Ok((v, a.shape[0], a.shape[1])),
_ => Err(Error::new(
ErrorKind::Rank,
"matrix division needs an argument of rank 2 or less",
Some(span),
)),
}
}
fn matrix_inverse(y: &Array, span: Span) -> Result<Array> {
let (a, m, n) = as_matrix(y, span)?;
if m < n {
return Err(Error::new(
ErrorKind::Length,
format!("cannot invert a {m} by {n} matrix: it has more columns than rows"),
Some(span),
));
}
let mut eye = vec![0.0f64; m * m];
for i in 0..m {
eye[i * m + i] = 1.0;
}
let x = lstsq(&a, m, n, &eye, m)
.ok_or_else(|| Error::domain("the matrix is singular", span))?;
let shape = if y.rank() == 2 { vec![n, m] } else { y.shape.clone() };
Ok(Array::new(shape, Data::F64(x.into())))
}
fn matrix_divide(x: &Array, y: &Array, span: Span) -> Result<Array> {
let (a, m, n) = as_matrix(y, span)?;
let (b, bm, k) = as_matrix(x, span)?;
if bm != m {
return Err(Error::new(
ErrorKind::Length,
format!("the system has {m} rows but the right-hand side has {bm}"),
Some(span),
));
}
if m < n {
return Err(Error::new(
ErrorKind::Length,
format!("the {m} by {n} system is underdetermined"),
Some(span),
));
}
let sol = lstsq(&a, m, n, &b, k)
.ok_or_else(|| Error::domain("the system is singular", span))?;
let shape = if x.rank() == 2 { vec![n, k] } else { vec![n] };
Ok(Array::new(shape, Data::F64(sol.into())))
}
fn squad(x: &Array, y: &Array, origin: i64, span: Span) -> Result<Array> {
if x.rank() > 1 {
return Err(Error::new(
ErrorKind::Rank,
"the index of ⌷ must be a scalar or a vector",
Some(span),
));
}
let items: Vec<Array> = if x.rank() == 0 { vec![x.clone()] } else { x.cells(1) };
if items.len() != y.rank() {
return Err(Error::new(
ErrorKind::Rank,
format!("{} index(es) for an argument of rank {}", items.len(), y.rank()),
Some(span),
));
}
let mut specs = Vec::with_capacity(items.len());
let mut shape = Vec::new();
for (k, item) in items.iter().enumerate() {
let spec = match item.as_boxes() {
Some(bs) if item.rank() == 0 => bs[0].clone(),
_ => item.clone(),
};
let idx = spec
.to_i64_vec()
.ok_or_else(|| Error::domain("index must be an integer", span))?;
for &i in &idx {
let j = i - origin;
if j < 0 || j as usize >= y.shape[k] {
return Err(Error::domain(
format!("index {i} is out of range on axis {k}"),
span,
));
}
}
shape.extend_from_slice(&spec.shape);
specs.push((spec.shape.clone(), idx));
}
let y = y.to_row_major();
let st = strides(&y.shape);
let total: usize = shape.iter().product();
let mut data = Data::empty(y.dtype());
let mut coord = vec![0usize; shape.len()];
for _ in 0..total {
let mut at = 0usize;
let mut used = 0usize;
for (k, (sshape, idx)) in specs.iter().enumerate() {
let sst = strides(sshape);
let pick: usize = (0..sshape.len()).map(|a| coord[used + a] * sst[a]).sum();
used += sshape.len();
at += (idx[pick] - origin) as usize * st[k];
}
push_elem(&mut data, y.row_major_data(), at);
odometer(&mut coord, &shape);
}
Ok(Array::new(shape, data))
}
fn select_axis(
x: &Array,
y: &Array,
axis: usize,
rank: usize,
origin: i64,
span: Span,
) -> Result<Array> {
if rank != 0 && y.rank() != rank {
return Err(Error::new(
ErrorKind::Rank,
format!("{rank} index slot(s) for an argument of rank {}", y.rank()),
Some(span),
));
}
if axis >= y.rank() {
return Err(Error::new(
ErrorKind::Rank,
format!("axis {axis} does not exist on an argument of rank {}", y.rank()),
Some(span),
));
}
let idx = x
.to_i64_vec()
.ok_or_else(|| Error::domain("index must be an integer", span))?;
let len = y.shape[axis];
let mut picks = Vec::with_capacity(idx.len());
for &i in &idx {
let j = i - origin;
if j < 0 || j as usize >= len {
return Err(Error::domain(
format!("index {i} is out of range: axis {axis} has {len} items"),
span,
));
}
picks.push(j as usize);
}
let mut shape = Vec::with_capacity(y.rank() + x.rank());
shape.extend_from_slice(&y.shape[..axis]);
shape.extend_from_slice(&x.shape);
shape.extend_from_slice(&y.shape[axis + 1..]);
let outer: usize = y.shape[..axis].iter().product();
let inner: usize = y.shape[axis + 1..].iter().product();
let mut data = Data::empty(y.dtype());
for o in 0..outer {
for &p in &picks {
let base = (o * len + p) * inner;
for e in 0..inner {
push_elem(&mut data, &y.data, base + e);
}
}
}
Ok(Array::new(shape, data))
}
fn amend(m: &Array, x: &Array, y: &Array, span: Span) -> Result<Array> {
if y.rank() == 0 {
return Err(Error::new(ErrorKind::Rank, "cannot amend a scalar", Some(span)));
}
if let Some(spec) = m.as_boxes().and_then(<[Array]>::first) {
let spec = index_spec(spec, y, span)?;
return amend_spec(&spec, x, y, span);
}
let idx = m
.to_i64_vec()
.ok_or_else(|| Error::domain("amend indices must be integers", span))?;
let items = y.items() as i64;
let mut at = Vec::with_capacity(idx.len());
for &i in &idx {
let k = if i < 0 { i + items } else { i };
if k < 0 || k >= items {
return Err(Error::domain(
format!("index {i} is out of range: the argument has {items} items"),
span,
));
}
at.push(k as usize);
}
let cell = y.item_size();
let per_index = if x.count() == cell {
false
} else if x.count() == cell * at.len() {
true
} else {
return Err(Error::new(
ErrorKind::Length,
format!(
"cannot amend {} item(s) of {} element(s) each with {} element(s)",
at.len(),
cell,
x.count()
),
Some(span),
));
};
let Some(t) = DType::promote(x.dtype(), y.dtype()) else {
return Err(Error::new(
ErrorKind::Type,
"the replacement and the argument hold different kinds of value",
Some(span),
));
};
let (Some(src), Some(base)) = (x.data.cast(t), y.data.cast(t)) else {
return Err(Error::new(
ErrorKind::Type,
"the replacement and the argument hold different kinds of value",
Some(span),
));
};
let mut data = Data::empty(t);
let mut plan: Vec<Option<usize>> = vec![None; y.items()];
for (n, &k) in at.iter().enumerate() {
plan[k] = Some(if per_index { n } else { 0 });
}
for (i, slot) in plan.iter().enumerate() {
match slot {
Some(n) => {
for e in 0..cell {
push_elem(&mut data, &src, n * cell + e);
}
}
None => {
for e in 0..cell {
push_elem(&mut data, &base, i * cell + e);
}
}
}
}
Ok(Array::new(y.shape.clone(), data))
}
fn fetch(x: &Array, y: &Array, span: Span) -> Result<Array> {
let steps: Vec<Array> = match x.as_boxes() {
Some(bs) => bs.to_vec(),
None => vec![x.clone()],
};
let mut cur = y.clone();
for step in steps {
let idx = if step.count() == 0 {
Vec::new()
} else {
step.to_i64_vec()
.ok_or_else(|| Error::domain("a fetch path holds integers", span))?
};
let base =
if cur.rank() == 0 { Array::new(vec![1], cur.data.clone()) } else { cur.clone() };
if idx.len() > base.rank() {
return Err(Error::new(
ErrorKind::Length,
format!(
"a path step of {} index(es) into a value of rank {}",
idx.len(),
cur.rank()
),
Some(span),
));
}
let at = cell_index(&base, &idx, span)?;
cur = open_cell(&base.cell_at(idx.len(), at));
}
Ok(cur)
}
fn cell_index(y: &Array, idx: &[i64], span: Span) -> Result<usize> {
let mut at = 0usize;
for (k, &i) in idx.iter().enumerate() {
let len = y.shape[k] as i64;
let j = if i < 0 { i + len } else { i };
if j < 0 || j >= len {
return Err(Error::domain(
format!("index {i} is out of range: axis {k} has {len} items"),
span,
));
}
at = at * y.shape[k] + j as usize;
}
Ok(at)
}
fn partition_enclose(x: &Array, y: &Array, span: Span) -> Result<Array> {
if y.rank() > 1 {
let last = y.shape[y.rank() - 1];
let rows = y.count() / last.max(1);
let mut cells: Vec<Array> = Vec::new();
let mut width = None;
for r in 0..rows {
let row = Array::new(vec![last], y.data.slice(r * last, (r + 1) * last));
let parts = partition_enclose(x, &row, span)?;
let n = parts.count();
if *width.get_or_insert(n) != n {
return Err(Error::internal("partitions of unequal count"));
}
match parts.data {
Data::Box(v) => cells.extend(v.as_slice().iter().cloned()),
_ => return Err(Error::internal("a partition is boxed")),
}
}
let mut shape = y.shape[..y.rank() - 1].to_vec();
shape.push(width.unwrap_or(0));
return Ok(Array::new(shape, Data::Box(cells.into())));
}
if y.rank() == 0 {
return Err(Error::new(
ErrorKind::Rank,
"partitioned enclose needs an array to partition",
Some(span),
));
}
let flags = x
.to_i64_vec()
.ok_or_else(|| Error::domain("partition flags must be integers", span))?;
if flags.iter().any(|&f| f < 0) {
return Err(Error::domain("partition flags must not be negative", span));
}
if flags.len() != y.shape[0] {
return Err(Error::new(
ErrorKind::Length,
format!("{} flag(s) for {} item(s)", flags.len(), y.shape[0]),
Some(span),
));
}
let mut parts: Vec<Array> = Vec::new();
let mut cur: Option<Data> = None;
let mut prev = 0i64;
for (i, &f) in flags.iter().enumerate() {
if f > prev {
if let Some(d) = cur.take() {
parts.push(Array::new(vec![d.len()], d));
}
cur = Some(Data::empty(y.dtype()));
}
prev = f;
if f == 0 {
continue;
}
if let Some(d) = cur.as_mut() {
push_elem(d, &y.data, i);
}
}
if let Some(d) = cur.take() {
parts.push(Array::new(vec![d.len()], d));
}
Ok(Array::new(vec![parts.len()], Data::Box(parts.into())))
}
fn key(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let keys = if x.rank() == 0 { Array::new(vec![1], x.data.clone()) } else { x.clone() };
let n = keys.items();
if n != y.items() && !(y.rank() == 0 && n == 1) {
return Err(Error::new(
ErrorKind::Length,
format!("{n} key(s) for {} item(s)", y.items()),
Some(span),
));
}
let groups = group_positions(&keys, ctx.cfg.tol);
let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
let mut cells = Vec::with_capacity(groups.len());
for (_, at) in &groups {
cells.push(u.monad(&select_items(&items, at), ctx, span)?);
}
assemble(&[groups.len()], cells, span)
}
fn oblique(u: &Verb, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
if y.rank() < 2 {
let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
let n = items.items();
let mut cells = Vec::with_capacity(n);
for i in 0..n {
cells.push(u.monad(&select_items(&items, &[i]), ctx, span)?);
}
return assemble(&[n], cells, span);
}
if y.rank() > 2 {
return Err(Error::not_yet("oblique (u/.) on a rank-3 or higher argument", span));
}
let (rows, cols) = (y.shape[0], y.shape[1]);
let mut cells = Vec::with_capacity(rows + cols - 1);
for d in 0..rows + cols - 1 {
let mut data = Data::empty(y.dtype());
let mut len = 0usize;
for i in 0..rows {
if d >= i && d - i < cols {
push_elem(&mut data, &y.data, i * cols + (d - i));
len += 1;
}
}
cells.push(u.monad(&Array::new(vec![len], data), ctx, span)?);
}
assemble(&[rows + cols - 1], cells, span)
}
fn cut_ranges(frets: &[bool], mode: i64) -> Vec<(usize, usize)> {
let n = frets.len();
let mut out = Vec::new();
if mode.abs() == 1 {
let mut start: Option<usize> = None;
for (i, &fret) in frets.iter().enumerate() {
if fret {
if let Some(s) = start {
out.push((s, i));
}
start = Some(i);
}
}
if let Some(s) = start {
out.push((s, n));
}
if mode < 0 {
return out.into_iter().map(|(s, e)| (s + 1, e)).collect();
}
} else {
let mut start = 0usize;
for (i, &fret) in frets.iter().enumerate() {
if fret {
out.push((start, i + 1));
start = i + 1;
}
}
if mode < 0 {
return out.into_iter().map(|(s, e)| (s, e - 1)).collect();
}
}
out
}
fn cut(
u: &Verb,
x: Option<&Array>,
y: &Array,
mode: i64,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
if mode == 0 {
let Some(x) = x else {
return u.monad(&reverse_all_axes(y), ctx, span);
};
let (origin, size) = rectangle(x, span)?;
let origin = origin.unwrap_or_else(|| vec![0; size.len()]);
return u.monad(&subarray(y, &origin, &size, span)?, ctx, span);
}
if mode.abs() == 3 {
let Some(x) = x else {
return Err(Error::not_yet("monadic tessellation (u;.3 y)", span));
};
return tessellate(u, x, y, mode < 0, ctx, span);
}
if !matches!(mode, 1 | -1 | 2 | -2) {
return Err(Error::not_yet(format!("cut (u;.{mode})"), span));
}
let items = if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() };
let n = items.items();
let tol = ctx.cfg.tol;
let frets: Vec<bool> = match x {
Some(x) => {
let flags = x
.to_i64_vec()
.ok_or_else(|| Error::domain("cut frets must be integers", span))?;
if let Some(&bad) = flags.iter().find(|&&f| f != 0 && f != 1) {
return Err(Error::domain(format!("{bad} is not a fret: a fret is 0 or 1"), span));
}
if x.rank() == 0 {
vec![flags[0] != 0; n]
} else {
if flags.len() != n {
return Err(Error::new(
ErrorKind::Length,
format!("{} fret(s) for {n} item(s)", flags.len()),
Some(span),
));
}
flags.iter().map(|&f| f != 0).collect()
}
}
None => {
if n == 0 {
Vec::new()
} else {
let at = if mode.abs() == 1 { 0 } else { n - 1 };
let mark = items.item(at);
(0..n).map(|i| arrays_match(&items.item(i), &mark, tol)).collect()
}
}
};
let ranges = cut_ranges(&frets, mode);
let mut cells = Vec::with_capacity(ranges.len());
for (s, e) in &ranges {
cells.push(u.monad(§ion(&items, *s, *e), ctx, span)?);
}
assemble(&[ranges.len()], cells, span)
}
fn rectangle(x: &Array, span: Span) -> Result<(Option<Vec<i64>>, Vec<i64>)> {
let values = x
.to_i64_vec()
.ok_or_else(|| Error::domain("a cut rectangle is whole numbers", span))?;
match x.rank() {
0 | 1 => Ok((None, values)),
2 if x.shape[0] == 2 => {
let n = x.shape[1];
Ok((Some(values[..n].to_vec()), values[n..].to_vec()))
}
_ => Err(Error::new(
ErrorKind::Rank,
"a cut rectangle is a vector of sizes, or two rows of origins and sizes",
Some(span),
)),
}
}
fn subarray(y: &Array, origin: &[i64], size: &[i64], span: Span) -> Result<Array> {
if origin.len() > y.rank() {
return Err(Error::new(
ErrorKind::Rank,
format!("a cut of {} axis/axes into a rank-{} value", origin.len(), y.rank()),
Some(span),
));
}
let r = y.rank();
let st = strides(&y.shape);
let mut shape = y.shape.clone();
let mut start = vec![0i64; r];
let mut step = vec![1i64; r];
for k in 0..origin.len() {
let len = size[k].unsigned_abs() as usize;
let from = if origin[k] < 0 { origin[k] + y.shape[k] as i64 } else { origin[k] };
if from < 0 || from + len as i64 > y.shape[k] as i64 {
return Err(Error::domain(
format!("a cut of {len} from {from} leaves axis {k} of {}", y.shape[k]),
span,
));
}
shape[k] = len;
if size[k] < 0 {
start[k] = from + len as i64 - 1;
step[k] = -1;
} else {
start[k] = from;
}
}
Ok(gather(y, &shape, &start, &step, &st))
}
fn gather(y: &Array, shape: &[usize], start: &[i64], step: &[i64], st: &[usize]) -> Array {
let n: usize = shape.iter().product();
let mut data = Data::empty(y.dtype());
let mut coord = vec![0usize; shape.len()];
for _ in 0..n {
let idx: usize = (0..shape.len())
.map(|k| (start[k] + step[k] * coord[k] as i64) as usize * st[k])
.sum();
push_elem(&mut data, &y.data, idx);
odometer(&mut coord, shape);
}
Array::new(shape.to_vec(), data)
}
fn tessellate(
u: &Verb,
x: &Array,
y: &Array,
complete: bool,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let (movement, size) = rectangle(x, span)?;
if size.iter().any(|&s| s < 0) && movement.is_none() {
return Err(Error::not_yet(
"a negative block size without a movement row (x u;.3 y)",
span,
));
}
let movement = movement.unwrap_or_else(|| vec![1; size.len()]);
if size.len() > y.rank() {
return Err(Error::new(
ErrorKind::Rank,
format!("a tessellation of {} axis/axes into a rank-{} value", size.len(), y.rank()),
Some(span),
));
}
let mut frame = Vec::with_capacity(size.len());
for k in 0..size.len() {
let (len, step, block) = (y.shape[k] as i64, movement[k], size[k].abs());
if step <= 0 {
return Err(Error::domain("a tessellation moves by a positive step", span));
}
let count = if complete {
if len < block { 0 } else { (len - block) / step + 1 }
} else {
(len + step - 1) / step
};
frame.push(count as usize);
}
let total: usize = frame.iter().product();
let mut cells = Vec::with_capacity(total);
let mut coord = vec![0usize; frame.len()];
for _ in 0..total {
let origin: Vec<i64> = (0..frame.len()).map(|k| coord[k] as i64 * movement[k]).collect();
let block: Vec<i64> = (0..frame.len())
.map(|k| {
let len = size[k].abs().min(y.shape[k] as i64 - origin[k]);
if size[k] < 0 { -len } else { len }
})
.collect();
cells.push(u.monad(&subarray(y, &origin, &block, span)?, ctx, span)?);
odometer(&mut coord, &frame);
}
assemble(&frame, cells, span)
}
fn reverse_all_axes(y: &Array) -> Array {
if y.rank() == 0 {
return y.clone();
}
let st = strides(&y.shape);
let n = y.count();
let r = y.rank();
let mut data = Data::empty(y.dtype());
let mut coord = vec![0usize; r];
for _ in 0..n {
let idx: usize = (0..r).map(|k| (y.shape[k] - 1 - coord[k]) * st[k]).sum();
push_elem(&mut data, &y.data, idx);
odometer(&mut coord, &y.shape);
}
Array::new(y.shape.clone(), data)
}
fn axis_to_front(y: &Array, k: usize) -> Array {
if k == 0 || y.rank() < 2 {
return y.clone();
}
let r = y.rank();
let src: Vec<usize> = std::iter::once(k).chain((0..r).filter(|&a| a != k)).collect();
permute_axes(y, &src)
}
fn front_to_axis(y: &Array, k: usize) -> Array {
if k == 0 || y.rank() < 2 {
return y.clone();
}
let r = y.rank();
let mut src = Vec::with_capacity(r);
for a in 0..r {
src.push(match a.cmp(&k) {
std::cmp::Ordering::Less => a + 1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => a,
});
}
permute_axes(y, &src)
}
fn transpose_to(y: &Array, dest: &[usize], span: Span) -> Result<Array> {
let rank_out = dest.iter().copied().max().map_or(0, |m| m + 1);
let mut out_shape = vec![usize::MAX; rank_out];
for (a, &d) in dest.iter().enumerate() {
out_shape[d] = out_shape[d].min(y.shape[a]);
}
if out_shape.contains(&usize::MAX) {
return Err(Error::new(
ErrorKind::Domain,
"a transpose must name every axis of the result",
Some(span),
));
}
let y = y.to_row_major();
let st = strides(&y.shape);
let n: usize = out_shape.iter().product();
let mut data = Data::empty(y.dtype());
let mut coord = vec![0usize; rank_out];
for _ in 0..n {
let idx: usize = dest.iter().enumerate().map(|(a, &d)| coord[d] * st[a]).sum();
push_elem(&mut data, &y.data, idx);
odometer(&mut coord, &out_shape);
}
Ok(Array::new(out_shape, data))
}
fn transpose_apl(x: &Array, y: &Array, io: i64, span: Span) -> Result<Array> {
let axes = x
.to_i64_vec()
.ok_or_else(|| Error::domain("a transpose is given whole numbers", span))?;
if axes.len() != y.rank() {
return Err(Error::new(
ErrorKind::Length,
format!("{} axes for a rank-{} value", axes.len(), y.rank()),
Some(span),
));
}
let mut dest = Vec::with_capacity(axes.len());
for a in axes {
let d = a - io;
if d < 0 || d as usize >= y.rank() {
return Err(Error::new(
ErrorKind::Domain,
format!("axis {a} is outside a rank-{} value", y.rank()),
Some(span),
));
}
dest.push(d as usize);
}
transpose_to(y, &dest, span)
}
fn transpose_j(x: &Array, y: &Array, span: Span) -> Result<Array> {
let groups: Vec<Vec<i64>> = match x.as_boxes() {
Some(bs) => bs
.iter()
.map(|b| {
b.to_i64_vec().ok_or_else(|| {
Error::domain("a transpose is given whole numbers", span)
})
})
.collect::<Result<Vec<_>>>()?,
None => x
.to_i64_vec()
.ok_or_else(|| Error::domain("a transpose is given whole numbers", span))?
.into_iter()
.map(|a| vec![a])
.collect(),
};
let r = y.rank();
let mut group_of = vec![None; r];
for (g, axes) in groups.iter().enumerate() {
for &a in axes {
let k = if a < 0 { a + r as i64 } else { a };
if k < 0 || k as usize >= r {
return Err(Error::new(
ErrorKind::Domain,
format!("axis {a} is outside a rank-{r} value"),
Some(span),
));
}
if group_of[k as usize].is_some() {
return Err(Error::new(
ErrorKind::Domain,
format!("axis {a} is named twice in a transpose"),
Some(span),
));
}
group_of[k as usize] = Some(g);
}
}
let leading = group_of.iter().filter(|g| g.is_none()).count();
let mut dest = vec![0usize; r];
let mut next = 0;
for a in 0..r {
match group_of[a] {
None => {
dest[a] = next;
next += 1;
}
Some(g) => dest[a] = leading + g,
}
}
transpose_to(y, &dest, span)
}
fn permute_axes(y: &Array, src: &[usize]) -> Array {
let st = strides(&y.shape);
let out_shape: Vec<usize> = src.iter().map(|&a| y.shape[a]).collect();
let n = y.count();
let mut data = Data::empty(y.dtype());
let mut coord = vec![0usize; src.len()];
for _ in 0..n {
let idx: usize = (0..src.len()).map(|a| coord[a] * st[src[a]]).sum();
push_elem(&mut data, &y.data, idx);
odometer(&mut coord, &out_shape);
}
Array::new(out_shape, data)
}
struct Spec {
width: usize,
cells: Vec<Vec<usize>>,
shape: Vec<usize>,
}
fn axis_position(v: i64, len: usize, span: Span) -> Result<usize> {
let p = if v < 0 { v + len as i64 } else { v };
if p < 0 || p >= len as i64 {
return Err(Error::domain(
format!("index {v} is out of range: the axis has {len} element(s)"),
span,
));
}
Ok(p as usize)
}
fn index_spec(content: &Array, y: &Array, span: Span) -> Result<Spec> {
let too_deep = |n: usize| {
Error::new(
ErrorKind::Rank,
format!("an index specification of {n} axis/axes into a rank-{} value", y.rank()),
Some(span),
)
};
if let Some(items) = content.as_boxes() {
if items.len() > y.rank() {
return Err(too_deep(items.len()));
}
let mut per_axis: Vec<Vec<usize>> = Vec::with_capacity(items.len());
let mut shape: Vec<usize> = Vec::new();
for (k, c) in items.iter().enumerate() {
let len = y.shape[k];
if c.as_boxes().is_some() {
let inner = open_cell(c);
let excluded = inner.to_i64_vec().ok_or_else(|| {
Error::domain("an index complement holds integers", span)
})?;
let mut dropped = vec![false; len];
for v in excluded {
dropped[axis_position(v, len, span)?] = true;
}
let kept: Vec<usize> = (0..len).filter(|i| !dropped[*i]).collect();
shape.push(kept.len());
per_axis.push(kept);
} else {
let idx = c
.to_i64_vec()
.ok_or_else(|| Error::domain("an index holds integers", span))?;
let mut positions = Vec::with_capacity(idx.len());
for v in idx {
positions.push(axis_position(v, len, span)?);
}
shape.extend_from_slice(&c.shape);
per_axis.push(positions);
}
}
let mut cells: Vec<Vec<usize>> = vec![Vec::new()];
for positions in &per_axis {
let mut next = Vec::with_capacity(cells.len() * positions.len());
for prefix in &cells {
for &p in positions {
let mut cell = prefix.clone();
cell.push(p);
next.push(cell);
}
}
cells = next;
}
return Ok(Spec { width: per_axis.len(), cells, shape });
}
let idx = content
.to_i64_vec()
.ok_or_else(|| Error::domain("an index specification holds integers", span))?;
let rank = content.rank();
let width = if rank == 0 { 1 } else { content.shape[rank - 1] };
if width > y.rank() {
return Err(too_deep(width));
}
let shape: Vec<usize> = if rank == 0 { Vec::new() } else { content.shape[..rank - 1].to_vec() };
let count: usize = shape.iter().product();
let mut cells: Vec<Vec<usize>> = Vec::new();
if width == 0 {
cells.resize(count, Vec::new());
} else {
for chunk in idx.chunks(width) {
let mut cell = Vec::with_capacity(width);
for (k, &v) in chunk.iter().enumerate() {
cell.push(axis_position(v, y.shape[k], span)?);
}
cells.push(cell);
}
}
Ok(Spec { width, cells, shape })
}
fn spec_offset(st: &[usize], cell: &[usize]) -> usize {
cell.iter().enumerate().map(|(k, &p)| p * st[k]).sum()
}
fn select_spec(spec: &Spec, y: &Array) -> Array {
let st = strides(&y.shape);
let size: usize = y.shape[spec.width..].iter().product();
let mut data = Data::empty(y.dtype());
for cell in &spec.cells {
let base = spec_offset(&st, cell);
for e in 0..size {
push_elem(&mut data, &y.data, base + e);
}
}
let mut shape = spec.shape.clone();
shape.extend_from_slice(&y.shape[spec.width..]);
Array::new(shape, data)
}
fn amend_spec(spec: &Spec, x: &Array, y: &Array, span: Span) -> Result<Array> {
let size: usize = y.shape[spec.width..].iter().product();
let per_cell = if x.count() == size {
false
} else if x.count() == size * spec.cells.len() {
true
} else {
return Err(Error::new(
ErrorKind::Length,
format!(
"cannot amend {} cell(s) of {size} element(s) each with {} element(s)",
spec.cells.len(),
x.count()
),
Some(span),
));
};
let mismatch = || {
Error::new(
ErrorKind::Type,
"the replacement and the argument hold different kinds of value",
Some(span),
)
};
let t = DType::promote(x.dtype(), y.dtype()).ok_or_else(mismatch)?;
let (Some(src), Some(base)) = (x.data.cast(t), y.data.cast(t)) else {
return Err(mismatch());
};
let st = strides(&y.shape);
let mut plan: Vec<Option<usize>> = vec![None; y.count()];
for (n, cell) in spec.cells.iter().enumerate() {
let at = spec_offset(&st, cell);
for e in 0..size {
plan[at + e] = Some(if per_cell { n * size + e } else { e });
}
}
let mut data = Data::empty(t);
for (i, slot) in plan.iter().enumerate() {
match slot {
Some(n) => push_elem(&mut data, &src, *n),
None => push_elem(&mut data, &base, i),
}
}
Ok(Array::new(y.shape.clone(), data))
}
fn map_paths(y: &Array) -> Array {
fn coord_of(shape: &[usize], mut i: usize) -> Array {
let mut out = vec![0i64; shape.len()];
for k in (0..shape.len()).rev() {
out[k] = (i % shape[k]) as i64;
i /= shape[k];
}
Array::from_i64(out)
}
fn go(y: &Array, prefix: &[Array]) -> Array {
let Some(boxes) = y.as_boxes() else {
if prefix.is_empty() {
return Array::new(vec![0], Data::I64(Vec::new().into()));
}
return Array::new(vec![prefix.len()], Data::Box(prefix.to_vec().into()));
};
let cells: Vec<Array> = boxes
.iter()
.enumerate()
.map(|(i, b)| {
let mut path = prefix.to_vec();
path.push(coord_of(&y.shape, i));
go(b, &path)
})
.collect();
Array::new(y.shape.clone(), Data::Box(cells.into()))
}
go(y, &[])
}
fn shift_fill(x: &Array, y: &Array, fill: &Array, span: Span) -> Result<Array> {
let counts = axis_counts(x, "shift", span)?;
if y.rank() == 0 {
return Ok(y.clone());
}
if counts.len() > y.rank() {
return Err(Error::new(
ErrorKind::Length,
format!("shift has {} amounts for an argument of rank {}", counts.len(), y.rank()),
Some(span),
));
}
if fill.count() != 1 {
return Err(Error::new(ErrorKind::Length, "a fill is one atom", Some(span)));
}
let mismatch = || {
Error::new(ErrorKind::Type, "the fill and the argument differ in kind", Some(span))
};
let t = DType::promote(y.dtype(), fill.dtype()).ok_or_else(mismatch)?;
let (Some(base), Some(f)) = (y.data.cast(t), fill.data.cast(t)) else {
return Err(mismatch());
};
let st = strides(&y.shape);
let r = y.rank();
let mut data = Data::empty(t);
let mut coord = vec![0usize; r];
for _ in 0..y.count() {
let mut idx = 0usize;
let mut vacated = false;
for k in 0..r {
let from = coord[k] as i64 + counts.get(k).copied().unwrap_or(0);
if from < 0 || from >= y.shape[k] as i64 {
vacated = true;
break;
}
idx += from as usize * st[k];
}
if vacated {
push_elem(&mut data, &f, 0);
} else {
push_elem(&mut data, &base, idx);
}
odometer(&mut coord, &y.shape);
}
Ok(Array::new(y.shape.clone(), data))
}
fn memo_key(a: &Array, out: &mut Vec<u64>) -> bool {
out.push(a.rank() as u64);
out.extend(a.shape.iter().map(|&n| n as u64));
out.push(a.dtype() as u64);
match &a.data {
Data::Ext(_) | Data::Rat(_) => false,
Data::Box(items) => items.iter().all(|item| memo_key(item, out)),
d => {
for i in 0..d.len() {
out.push(elem_key(d, i));
}
true
}
}
}
fn memoised(
u: &Verb,
cache: &MemoCache,
x: Option<&Array>,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let apply = |ctx: &mut Ctx<'_>| match x {
Some(x) => u.dyad(x, y, ctx, span),
None => u.monad(y, ctx, span),
};
let mut key = vec![u64::from(x.is_some())];
let keyed = x.is_none_or(|x| memo_key(x, &mut key)) && memo_key(y, &mut key);
if !keyed {
return apply(ctx);
}
if let Ok(map) = cache.lock() && let Some(hit) = map.get(&key) {
return Ok(hit.clone());
}
let out = apply(ctx)?;
if let Ok(mut map) = cache.lock() {
map.insert(key, out.clone());
}
Ok(out)
}
fn at_level(
u: &Verb,
level: i64,
spread: bool,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let n = if level < 0 { (boxing_level(y) + level).max(0) } else { level };
if !spread {
return map_level(u, n, y, ctx, span);
}
let mut cells = Vec::new();
collect_level(u, n, y, ctx, span, &mut cells)?;
let count = cells.len();
assemble(&[count], cells, span)
}
fn map_level(u: &Verb, n: i64, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let Some(boxes) = y.as_boxes().filter(|_| boxing_level(y) > n) else {
return u.monad(y, ctx, span);
};
let boxes = boxes.to_vec();
let mut cells = Vec::with_capacity(boxes.len());
for b in &boxes {
cells.push(map_level(u, n, b, ctx, span)?);
}
Ok(Array::new(y.shape.clone(), Data::Box(cells.into())))
}
fn at_level_dyad(
u: &Verb,
level: i64,
spread: bool,
x: &Array,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let depth = |a: &Array| if level < 0 { (boxing_level(a) + level).max(0) } else { level };
let (nx, ny) = (depth(x), depth(y));
if !spread {
return map_level_dyad(u, nx, ny, x, y, ctx, span);
}
let mut cells = Vec::new();
collect_level_dyad(u, nx, ny, x, y, ctx, span, &mut cells)?;
let count = cells.len();
assemble(&[count], cells, span)
}
struct LevelPairs {
left: Vec<Array>,
right: Vec<Array>,
shape: Vec<usize>,
}
fn level_pairs(
nx: i64,
ny: i64,
x: &Array,
y: &Array,
span: Span,
) -> Result<Option<LevelPairs>> {
let bx = x.as_boxes().filter(|_| boxing_level(x) > nx);
let by = y.as_boxes().filter(|_| boxing_level(y) > ny);
Ok(match (bx, by) {
(None, None) => None,
(Some(bx), None) => {
let n = bx.len();
Some(LevelPairs {
left: bx.to_vec(),
right: vec![y.clone(); n],
shape: x.shape.clone(),
})
}
(None, Some(by)) => {
let n = by.len();
Some(LevelPairs {
left: vec![x.clone(); n],
right: by.to_vec(),
shape: y.shape.clone(),
})
}
(Some(bx), Some(by)) => {
if x.shape != y.shape {
return Err(Error::new(
ErrorKind::Length,
format!(
"the levels do not agree: left shape {}, right shape {}",
show_shape(&x.shape),
show_shape(&y.shape)
),
Some(span),
));
}
Some(LevelPairs { left: bx.to_vec(), right: by.to_vec(), shape: x.shape.clone() })
}
})
}
fn map_level_dyad(
u: &Verb,
nx: i64,
ny: i64,
x: &Array,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let Some(step) = level_pairs(nx, ny, x, y, span)? else {
return u.dyad(x, y, ctx, span);
};
let mut cells = Vec::with_capacity(step.left.len());
for (a, b) in step.left.iter().zip(step.right.iter()) {
cells.push(map_level_dyad(u, nx, ny, a, b, ctx, span)?);
}
Ok(Array::new(step.shape, Data::Box(cells.into())))
}
#[allow(clippy::too_many_arguments)]
fn collect_level_dyad(
u: &Verb,
nx: i64,
ny: i64,
x: &Array,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
out: &mut Vec<Array>,
) -> Result<()> {
let Some(step) = level_pairs(nx, ny, x, y, span)? else {
out.push(u.dyad(x, y, ctx, span)?);
return Ok(());
};
for (a, b) in step.left.iter().zip(step.right.iter()) {
collect_level_dyad(u, nx, ny, a, b, ctx, span, out)?;
}
Ok(())
}
fn collect_level(
u: &Verb,
n: i64,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
out: &mut Vec<Array>,
) -> Result<()> {
let Some(boxes) = y.as_boxes().filter(|_| boxing_level(y) > n) else {
out.push(u.monad(y, ctx, span)?);
return Ok(());
};
let boxes = boxes.to_vec();
for b in &boxes {
collect_level(u, n, b, ctx, span, out)?;
}
Ok(())
}
fn poly_coeffs(y: &Array, span: Span) -> Result<Vec<Cx>> {
let c = y
.data
.cast(DType::Complex)
.ok_or_else(|| Error::domain("a polynomial's coefficients are numbers", span))?;
match c {
Data::Complex(v) => Ok(v.as_slice().to_vec()),
_ => Err(Error::internal("coefficients did not cast to complex")),
}
}
const HYPERGEOMETRIC_TERMS: usize = 1 << 16;
fn cx_list(v: &[Cx]) -> String {
v.iter()
.map(|z| if z[1] == 0.0 { format!("{}", z[0]) } else { format!("{}j{}", z[0], z[1]) })
.collect::<Vec<_>>()
.join(" ")
}
fn hypergeometric(num: &[Cx], den: &[Cx], y: &Array, span: Span) -> Result<Array> {
let (num, den) = cancel_parameters(num, den);
let at = poly_coeffs(y, span)?;
let mut out = Vec::with_capacity(at.len());
for z in &at {
out.push(hypergeometric_at(&num, &den, *z, span)?);
}
let mut a = complex_or_real(out);
a.shape = y.shape.clone();
Ok(a)
}
fn cancel_parameters(num: &[Cx], den: &[Cx]) -> (Vec<Cx>, Vec<Cx>) {
let mut left: Vec<Cx> = Vec::with_capacity(num.len());
let mut right: Vec<Cx> = den.to_vec();
for a in num {
match right.iter().position(|b| b == a) {
Some(i) => {
right.remove(i);
}
None => left.push(*a),
}
}
(left, right)
}
fn hypergeometric_at(num: &[Cx], den: &[Cx], z: Cx, span: Span) -> Result<Cx> {
let real = |v: &[Cx]| v.iter().all(|c| c[1] == 0.0);
if z[1] == 0.0 && real(num) && real(den) {
let n: Vec<f64> = num.iter().map(|c| c[0]).collect();
let d: Vec<f64> = den.iter().map(|c| c[0]).collect();
return Ok([hypergeometric_real(&n, &d, z[0], span)?, 0.0]);
}
let mut sum = cx::ONE;
let mut term = cx::ONE;
for k in 0..HYPERGEOMETRIC_TERMS {
let kk = [k as f64, 0.0];
let mut ratio = z;
for a in num {
ratio = cx::mul(ratio, cx::add(*a, kk));
}
for b in den {
ratio = cx::div(ratio, cx::add(*b, kk));
}
term = cx::div(cx::mul(term, ratio), [k as f64 + 1.0, 0.0]);
if !term[0].is_finite() || !term[1].is_finite() {
return Ok(term);
}
let before = sum;
sum = cx::add(sum, term);
if sum == before {
return Ok(sum);
}
}
Err(Error::domain(
format!("the hypergeometric series did not converge within {HYPERGEOMETRIC_TERMS} terms"),
span,
))
}
fn hypergeometric_real(num: &[f64], den: &[f64], z: f64, span: Span) -> Result<f64> {
let mut sum = 1.0f64;
let mut term = 1.0f64;
for k in 0..HYPERGEOMETRIC_TERMS {
let kk = k as f64;
let mut ratio = z;
for a in num {
ratio *= a + kk;
}
for b in den {
ratio /= b + kk;
}
term = term * ratio / (kk + 1.0);
if !term.is_finite() {
return Ok(term);
}
let before = sum;
sum += term;
if sum == before {
return Ok(sum);
}
}
Err(Error::domain(
format!("the hypergeometric series did not converge within {HYPERGEOMETRIC_TERMS} terms"),
span,
))
}
fn complex_or_real(values: Vec<Cx>) -> Array {
if values.iter().all(|z| z[1] == 0.0) {
return Array::from_f64(values.iter().map(|z| z[0]).collect());
}
Array::new(vec![values.len()], Data::Complex(values.into()))
}
fn poly_eval(x: &Array, y: &Array, span: Span) -> Result<Array> {
let at = poly_coeffs(y, span)?;
let at = at.first().copied().unwrap_or(cx::ZERO);
let value = match x.as_boxes() {
Some(parts) => {
if parts.len() != 2 {
return Err(Error::domain(
"the root form of a polynomial is `multiplier ; roots`",
span,
));
}
let multiplier = poly_coeffs(&parts[0], span)?;
let mut v = multiplier.first().copied().unwrap_or(cx::ONE);
for r in poly_coeffs(&parts[1], span)? {
v = cx::mul(v, cx::sub(at, r));
}
v
}
None => {
let c = poly_coeffs(x, span)?;
let mut v = cx::ZERO;
for &k in c.iter().rev() {
v = cx::add(cx::mul(v, at), k);
}
v
}
};
Ok(scalar_complex_or_real(value))
}
fn scalar_complex_or_real(z: Cx) -> Array {
if z[1] == 0.0 {
return Array::scalar_f64(z[0]);
}
Array::new(vec![], Data::Complex(vec![z].into()))
}
fn poly_roots(y: &Array, span: Span) -> Result<Array> {
if let Some(parts) = y.as_boxes() {
if parts.len() != 2 {
return Err(Error::domain(
"the root form of a polynomial is `multiplier ; roots`",
span,
));
}
let multiplier = poly_coeffs(&parts[0], span)?;
let multiplier = multiplier.first().copied().unwrap_or(cx::ONE);
let mut coeffs = vec![multiplier];
for r in poly_coeffs(&parts[1], span)? {
let mut next = vec![cx::ZERO; coeffs.len() + 1];
for (k, &c) in coeffs.iter().enumerate() {
next[k + 1] = cx::add(next[k + 1], c);
next[k] = cx::sub(next[k], cx::mul(c, r));
}
coeffs = next;
}
return Ok(complex_or_real(coeffs));
}
let mut c = poly_coeffs(y, span)?;
while c.len() > 1 && c[c.len() - 1] == cx::ZERO {
c.pop();
}
if c.iter().all(|&k| k == cx::ZERO) {
let pair = vec![Array::scalar_i64(0), Array::new(vec![0], Data::empty(DType::I64))];
return Ok(Array::new(vec![2], Data::Box(pair.into())));
}
if c.len() < 2 {
return Err(Error::domain("a polynomial's roots need a coefficient of x", span));
}
let lead = c[c.len() - 1];
let monic: Vec<Cx> = c.iter().map(|&k| cx::div(k, lead)).collect();
let roots = durand_kerner(&monic);
let pair = vec![scalar_complex_or_real(lead), complex_or_real(roots)];
Ok(Array::new(vec![2], Data::Box(pair.into())))
}
fn durand_kerner(monic: &[Cx]) -> Vec<Cx> {
let d = monic.len() - 1;
let seed = [0.4, 0.9];
let mut z: Vec<Cx> = Vec::with_capacity(d);
let mut p = cx::ONE;
for _ in 0..d {
z.push(p);
p = cx::mul(p, seed);
}
let value = |monic: &[Cx], at: Cx| {
let mut v = cx::ZERO;
for &k in monic.iter().rev() {
v = cx::add(cx::mul(v, at), k);
}
v
};
for _ in 0..500 {
let mut moved: f64 = 0.0;
for i in 0..d {
let mut denom = cx::ONE;
for j in 0..d {
if i != j {
denom = cx::mul(denom, cx::sub(z[i], z[j]));
}
}
if denom == cx::ZERO {
continue;
}
let step = cx::div(value(monic, z[i]), denom);
z[i] = cx::sub(z[i], step);
moved = moved.max(step[0].hypot(step[1]));
}
if moved < 1e-15 {
break;
}
}
for r in &mut z {
if r[1].abs() < 1e-9 {
r[1] = 0.0;
}
if r[0].abs() < 1e-12 {
r[0] = 0.0;
}
}
z.sort_by(|a, b| {
let close = (a[0] - b[0]).abs() <= 1e-9 * (a[0].abs().max(b[0].abs()) + 1.0);
let by_re = if close {
std::cmp::Ordering::Equal
} else {
b[0].partial_cmp(&a[0]).unwrap_or(std::cmp::Ordering::Equal)
};
by_re.then(b[1].partial_cmp(&a[1]).unwrap_or(std::cmp::Ordering::Equal))
});
z
}
fn poly_deriv(y: &Array, span: Span) -> Result<Array> {
let c = poly_coeffs(y, span)?;
if c.len() < 2 {
return Ok(Array::from_i64(vec![0]));
}
let out: Vec<Cx> =
c.iter().enumerate().skip(1).map(|(k, &v)| cx::mul(v, cx::from_real(k as f64))).collect();
Ok(narrow_numbers(complex_or_real(out)))
}
fn poly_integral(x: &Array, y: &Array, span: Span) -> Result<Array> {
let c = poly_coeffs(y, span)?;
let k = poly_coeffs(x, span)?;
let mut out = vec![k.first().copied().unwrap_or(cx::ZERO)];
for (i, &v) in c.iter().enumerate() {
out.push(cx::div(v, cx::from_real((i + 1) as f64)));
}
Ok(narrow_numbers(complex_or_real(out)))
}
fn narrow_numbers(a: Array) -> Array {
let Data::F64(v) = &a.data else { return a };
if v.iter().any(|x| !x.is_finite() || x.fract() != 0.0 || x.abs() > 9e15) {
return a;
}
let values: Vec<i64> = v.iter().map(|&x| x as i64).collect();
Array::new(a.shape, Data::I64(values.into()))
}
fn characteristics(u: &Verb, y: &Array, span: Span) -> Result<Array> {
let which = y.to_i64_vec().and_then(|v| v.first().copied());
let chars = |s: String| Ok(Array::from_chars(s.chars().collect()));
match which {
Some(0) => {
let ranks = u.ranks();
Ok(Array::from_f64(
ranks
.iter()
.map(|&r| if r == RANK_INF { f64::INFINITY } else { r as f64 })
.collect(),
))
}
Some(-1) => match obverse(u) {
Some(v) => chars(v.name()),
None => Err(Error::not_yet(
format!("the obverse of {} (no inverse is known)", u.name()),
span,
)),
},
Some(1) => match reduce_identity(u, 1).as_ref().map(identity_spelling) {
Some(s) => chars(s),
None => Err(Error::not_yet(
format!("the identity function of {} (u b. 1)", u.name()),
span,
)),
},
_ => Err(Error::not_yet("a verb characteristic other than 0, 1 and _1", span)),
}
}
fn identity_spelling(d: &Data) -> String {
let one = Array::new(Vec::new(), d.slice(0, 1));
let text = crate::fmt::format_array(&one, &crate::fmt::FmtOpts::J);
format!("{} $~ }}.@$", text.trim())
}
fn with_operands<R>(
alpha: &Verb,
omega: Option<&Verb>,
ctx: &mut Ctx<'_>,
f: impl FnOnce(&mut Ctx<'_>) -> Result<R>,
) -> Result<R> {
let saved = (ctx.env.verb("⍺⍺").cloned(), ctx.env.verb("⍵⍵").cloned());
ctx.env.define("⍺⍺".to_string(), alpha.clone());
if let Some(g) = omega {
ctx.env.define("⍵⍵".to_string(), g.clone());
}
let out = f(ctx);
match saved.0 {
Some(v) => ctx.env.define("⍺⍺".to_string(), v),
None => ctx.env.undefine("⍺⍺"),
}
match saved.1 {
Some(v) => ctx.env.define("⍵⍵".to_string(), v),
None => ctx.env.undefine("⍵⍵"),
}
out
}
fn is_mixed_simple(a: &Array) -> bool {
let Some(items) = a.as_boxes() else { return false };
if items.is_empty() || items.iter().any(|b| b.rank() != 0 || b.dtype() == DType::Box) {
return false;
}
let mut common = Some(items[0].dtype());
for b in &items[1..] {
common = common.and_then(|t| DType::promote(t, b.dtype()));
}
common.is_none()
}
fn nest(y: &Array) -> Array {
if y.dtype() == DType::Box || y.rank() == 0 {
return y.clone();
}
Array::boxed(y.clone())
}
fn key_pairs(
u: &Verb,
keys: &Array,
values: Option<&Array>,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let base = if keys.rank() == 0 { Array::new(vec![1], keys.data.clone()) } else { keys.clone() };
let n = base.items();
if let Some(v) = values && v.items() != n {
return Err(Error::new(
ErrorKind::Length,
format!("{n} key(s) for {} item(s)", v.items()),
Some(span),
));
}
let groups = group_positions(&base, ctx.cfg.tol);
let origin = ctx.cfg.rules.origin;
let mut cells = Vec::with_capacity(groups.len());
for (first, at) in &groups {
let key = item_or_self(&base, *first);
let group = match values {
Some(v) => select_items(v, at),
None => Array::from_i64(at.iter().map(|&i| origin + i as i64).collect()),
};
let monadic = matches!(u, Verb::Explicit(d) if d.left.is_none());
cells.push(if monadic {
u.monad(&group, ctx, span)?
} else {
u.dyad(&key, &group, ctx, span)?
});
}
let count = cells.len();
assemble(&[count], cells, span)
}
fn group_positions(y: &Array, tol: Tol) -> Vec<(usize, Vec<usize>)> {
let n = y.items();
let m = y.item_size();
let hashable = match y.dtype() {
DType::Box | DType::Ext | DType::Rat => false,
DType::F64 | DType::Complex => tol.ct == 0.0,
_ => true,
};
if hashable {
return if m == 1 {
group_by_key(n, |i| elem_key(&y.data, i))
} else {
group_by_key(n, |i| (0..m).map(|k| elem_key(&y.data, i * m + k)).collect::<Vec<u64>>())
};
}
let mut keys: Vec<Array> = Vec::new();
let mut groups: Vec<(usize, Vec<usize>)> = Vec::new();
for i in 0..n {
let item = y.item(i);
match keys.iter().position(|k| arrays_match(k, &item, tol)) {
Some(at) => groups[at].1.push(i),
None => {
keys.push(item);
groups.push((i, vec![i]));
}
}
}
groups
}
fn group_by_key<K, F>(n: usize, key: F) -> Vec<(usize, Vec<usize>)>
where
K: Eq + std::hash::Hash,
F: Fn(usize) -> K,
{
use std::collections::hash_map::Entry;
let mut groups: Vec<(usize, Vec<usize>)> = Vec::new();
let mut at: HashMap<K, usize, KeyHash> =
HashMap::with_capacity_and_hasher(n.min(1 << 16), KeyHash);
for i in 0..n {
match at.entry(key(i)) {
Entry::Occupied(e) => groups[*e.get()].1.push(i),
Entry::Vacant(e) => {
e.insert(groups.len());
groups.push((i, vec![i]));
}
}
}
groups
}
#[derive(Clone, Copy, Default)]
struct KeyHash;
impl std::hash::BuildHasher for KeyHash {
type Hasher = KeyHasher;
fn build_hasher(&self) -> KeyHasher {
KeyHasher(0)
}
}
struct KeyHasher(u64);
impl std::hash::Hasher for KeyHasher {
fn finish(&self) -> u64 {
let mut x = self.0;
x ^= x >> 33;
x = x.wrapping_mul(0xff51_afd7_ed55_8ccd);
x ^ (x >> 29)
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.write_u64(b as u64);
}
}
fn write_u64(&mut self, n: u64) {
self.0 = (self.0.rotate_left(5) ^ n).wrapping_mul(0x9e37_79b9_7f4a_7c15);
}
fn write_usize(&mut self, n: usize) {
self.write_u64(n as u64);
}
}
fn format_spec(x: &Array, y: &Array, fmt: &FmtOpts, span: Span) -> Result<Array> {
let spec = x
.to_i64_vec()
.ok_or_else(|| Error::domain("a format specification is whole numbers", span))?;
if y.dtype() == DType::Box {
return Err(Error::not_yet("format by specification of a nested array", span));
}
let cols = if y.rank() == 0 { 1 } else { y.shape[y.rank() - 1] };
let rows = y.count() / cols.max(1);
let pairs: Vec<(Option<i64>, i64)> = match spec.len() {
1 => vec![(None, spec[0]); cols],
2 => vec![(Some(spec[0]), spec[1]); cols],
n if n == 2 * cols => spec.chunks(2).map(|c| (Some(c[0]), c[1])).collect(),
n => {
return Err(Error::new(
ErrorKind::Length,
format!("{n} specification value(s) for {cols} column(s)"),
Some(span),
));
}
};
if pairs.iter().any(|&(w, p)| w.is_some_and(|w| w < 0) || p < 0) {
return Err(Error::domain("a format width and precision are nonnegative", span));
}
let numbers = y.to_f64_vec();
let text = |i: usize, p: i64| -> String {
match (&y.data, &numbers) {
(Data::Char(v), _) => v[i].to_string(),
(_, Some(v)) => {
let s = format!("{:.*}", p as usize, v[i]);
if v[i] < 0.0 { format!("{}{}", fmt.neg, &s[1..]) } else { s }
}
_ => String::new(),
}
};
if y.dtype() != DType::Char && numbers.is_none() {
return Err(Error::domain("format by specification takes numbers or characters", span));
}
let widths: Vec<usize> = pairs
.iter()
.enumerate()
.map(|(c, &(w, p))| match w {
Some(w) => w as usize,
None => {
(0..rows).map(|r| text(r * cols + c, p).chars().count()).max().unwrap_or(0) + 1
}
})
.collect();
let line: usize = widths.iter().sum();
let mut out: Vec<char> = Vec::with_capacity(rows * line);
for r in 0..rows {
for c in 0..cols {
let s = text(r * cols + c, pairs[c].1);
let len = s.chars().count();
if len > widths[c] {
return Err(Error::domain(
format!("{s} does not fit a field {} wide", widths[c]),
span,
));
}
out.extend(std::iter::repeat_n(' ', widths[c] - len));
out.extend(s.chars());
}
}
let mut shape = if y.rank() == 0 { Vec::new() } else { y.shape[..y.rank() - 1].to_vec() };
shape.push(line);
Ok(Array::new(shape, Data::Char(out.into())))
}
fn sequential_machine(x: &Array, y: &Array, span: Span) -> Result<Array> {
let Some(parts) = x.as_boxes() else {
return Err(Error::domain("a sequential machine is a boxed description", span));
};
if x.rank() > 1 || !(2..=4).contains(&parts.len()) {
return Err(Error::domain(
"a sequential machine is 2 to 4 boxes: f ; s ; m ; ijrd",
span,
));
}
let whole = |a: &Array, what: &str| -> Result<Vec<i64>> {
a.to_i64_vec().ok_or_else(|| Error::domain(format!("{what} is whole numbers"), span))
};
let form = *whole(&parts[0], "a sequential machine's result form")?
.first()
.ok_or_else(|| Error::domain("a sequential machine needs a result form", span))?;
if !(0..=5).contains(&form) {
return Err(Error::domain(format!("{form} is not a result form of 0 to 5"), span));
}
let table = &parts[1];
if table.rank() != 3 || table.shape[2] != 2 {
return Err(Error::new(
ErrorKind::Rank,
"a sequential machine's transition table is shaped p q 2",
Some(span),
));
}
let (states, classes) = (table.shape[0], table.shape[1]);
let entries = whole(table, "a transition table")?;
let map = parts.get(2).filter(|a| a.count() > 0);
let start = match parts.get(3) {
Some(a) => whole(a, "a sequential machine's starting values")?,
None => Vec::new(),
};
let start = if start.is_empty() { vec![0, -1, 0, -1] } else { start };
if start.len() != 4 {
return Err(Error::new(
ErrorKind::Length,
"a sequential machine starts from four values: i j r d",
Some(span),
));
}
let (mut i, mut word, mut state, ending) = (start[0], start[1], start[2], start[3]);
let n = y.count() as i64;
let codes: Option<Vec<i64>> = match map {
Some(m) => Some(whole(m, "a sequential machine's map")?),
None => None,
};
let values: Vec<i64> = match (&y.data, &codes) {
(Data::Char(v), Some(_)) => v.as_slice().iter().map(|&c| c as i64).collect(),
(_, None) => y
.to_i64_vec()
.ok_or_else(|| Error::domain("a sequential machine over characters needs a map", span))?,
_ => {
return Err(Error::not_yet(
"a sequential machine's map over a numeric argument (x's third box)",
span,
));
}
};
let class_at = |at: i64| -> Result<i64> {
let raw = values[at as usize];
let Some(m) = &codes else { return Ok(raw) };
if raw < 0 || raw as usize >= m.len() {
return Err(Error::new(
ErrorKind::Domain,
format!("{raw} is outside a map of {} entries", m.len()),
Some(span),
));
}
Ok(m[raw as usize])
};
let mut trace: Vec<i64> = Vec::new();
let mut words: Vec<(i64, i64, i64)> = Vec::new();
let mut emit = |word: i64, at: i64, place: i64| -> Result<()> {
if word < 0 {
return Err(Error::new(
ErrorKind::Domain,
"a sequential machine ended a word before one had begun",
Some(span),
));
}
words.push((word, at - word, place));
Ok(())
};
loop {
let class = if i < n {
class_at(i)?
} else if ending >= 0 {
ending
} else {
if word >= 0 {
emit(word, i, classes as i64 * state)?;
}
break;
};
if state < 0 || state as usize >= states || class < 0 || class as usize >= classes {
return Err(Error::new(
ErrorKind::Domain,
format!(
"state {state} and class {class} are outside a {states} by {classes} table"
),
Some(span),
));
}
let at = (state as usize * classes + class as usize) * 2;
let (next, code) = (entries[at], entries[at + 1]);
trace.extend_from_slice(&[i, word, state, class, next, code]);
let place = class + classes as i64 * state;
state = next;
match code {
0 => {}
1 => word = i,
2 => {
emit(word, i, place)?;
word = i;
}
3 => {
emit(word, i, place)?;
word = -1;
}
4 | 5 => {
return Err(Error::not_yet(
"a sequential machine's vector output (codes 4 and 5)",
span,
));
}
6 => break,
other => {
return Err(Error::domain(
format!("{other} is not a sequential machine output code"),
span,
));
}
}
if i >= n {
break;
}
i += 1;
}
Ok(sequential_result(form, &words, &trace, y))
}
fn sequential_result(form: i64, words: &[(i64, i64, i64)], trace: &[i64], y: &Array) -> Array {
let piece = |&(at, len, _): &(i64, i64, i64)| {
Array::new(vec![len as usize], y.data.slice(at as usize, (at + len) as usize))
};
match form {
0 => Array::new(
vec![words.len()],
Data::Box(words.iter().map(piece).collect::<Vec<_>>().into()),
),
1 => {
let mut data = Data::empty(y.dtype());
for w in words {
data.extend_from(&piece(w).data);
}
let n = data.len();
Array::new(vec![n], data)
}
2 => Array::new(
vec![words.len(), 2],
Data::I64(words.iter().flat_map(|&(at, len, _)| [at, len]).collect::<Vec<_>>().into()),
),
3 => Array::from_i64(words.iter().map(|&(_, _, place)| place).collect()),
4 => Array::new(
vec![words.len(), 3],
Data::I64(
words
.iter()
.flat_map(|&(at, len, place)| [at, len, place])
.collect::<Vec<_>>()
.into(),
),
),
_ => Array::new(vec![trace.len() / 6, 6], Data::I64(trace.to_vec().into())),
}
}
fn parse_numbers(x: &Array, y: &Array, span: Span) -> Result<Array> {
let Data::Char(text) = &y.data else {
return Err(Error::domain("reading numbers from text needs characters", span));
};
if x.count() != 1 {
return Err(Error::new(
ErrorKind::Rank,
"the stand-in for an unreadable word is one value",
Some(span),
));
}
let line: String = text.as_slice().iter().collect();
crate::frontend::j::numbers_from_text(&line, x)
.ok_or_else(|| Error::domain("the stand-in for an unreadable word is a number", span))
}
fn format_field(value: f64, precision: usize, exponential: bool, neg: char) -> String {
let sign = |s: String| match s.strip_prefix('-') {
Some(rest) if rest.bytes().all(|b| !b.is_ascii_digit() || b == b'0') => rest.to_string(),
Some(rest) => format!("{neg}{rest}"),
None => s,
};
if !exponential {
return sign(format!("{value:.precision$}"));
}
let text = format!("{value:.precision$e}");
let (mantissa, exponent) = text.split_once('e').unwrap_or((text.as_str(), "0"));
let exponent = match exponent.strip_prefix('-') {
Some(rest) => format!("{neg}{rest}"),
None => exponent.to_string(),
};
format!("{}e{exponent}", sign(mantissa.to_string()))
}
fn format_spec_j(x: &Array, y: &Array, fmt: &FmtOpts, span: Span) -> Result<Array> {
let Some(spec) = x.to_complex_vec() else {
return Err(Error::domain("a format specification is numbers", span));
};
if y.dtype() == DType::Box {
return Err(Error::domain("format by specification takes numbers", span));
}
let Some(values) = y.to_f64_vec() else {
return Err(Error::domain("format by specification takes numbers", span));
};
let cols = if y.rank() == 0 { 1 } else { y.shape[y.rank() - 1] };
let rows = if cols == 0 { 0 } else { y.count() / cols };
let fields: Vec<[f64; 2]> = match spec.len() {
1 => vec![spec[0]; cols],
n if n == cols => spec,
n => {
return Err(Error::new(
ErrorKind::Length,
format!("{n} specification value(s) for {cols} column(s)"),
Some(span),
));
}
};
let text = |r: usize, c: usize| {
let [w, d] = fields[c];
format_field(values[r * cols + c], d.max(0.0) as usize, w < 0.0, fmt.neg)
};
let widths: Vec<usize> = (0..cols)
.map(|c| {
let w = fields[c][0];
if w != 0.0 {
return w.abs() as usize;
}
let wide = (0..rows).map(|r| text(r, c).chars().count()).max().unwrap_or(0);
wide + usize::from(c > 0)
})
.collect();
let line: usize = widths.iter().sum();
let mut out: Vec<char> = Vec::with_capacity(rows * line);
for r in 0..rows {
for c in 0..cols {
let s = text(r, c);
let (lead, body) = match (fields[c][0] < 0.0, s.strip_prefix(fmt.neg)) {
(false, _) => (String::new(), s.as_str()),
(true, Some(rest)) => (fmt.neg.to_string(), rest),
(true, None) => (" ".to_string(), s.as_str()),
};
let len = lead.chars().count() + body.chars().count();
if len > widths[c] {
out.extend(std::iter::repeat_n('*', widths[c]));
continue;
}
if fields[c][0] < 0.0 {
out.extend(lead.chars());
out.extend(body.chars());
out.extend(std::iter::repeat_n(' ', widths[c] - len));
} else {
out.extend(std::iter::repeat_n(' ', widths[c] - len));
out.extend(body.chars());
}
}
}
let mut shape = if y.rank() == 0 { Vec::new() } else { y.shape[..y.rank() - 1].to_vec() };
shape.push(line);
Ok(Array::new(shape, Data::Char(out.into())))
}
fn iota_apl(y: &Array, origin: i64, span: Span) -> Result<Array> {
if y.rank() > 1 {
return Err(Error::new(
ErrorKind::Rank,
"the index generator takes a shape, which is a scalar or a vector",
Some(span),
));
}
let dims = y
.to_i64_vec()
.ok_or_else(|| Error::domain("index generator needs an integer argument", span))?;
if dims.iter().any(|&n| n < 0) {
return Err(Error::domain("index generator needs nonnegative lengths", span));
}
if dims.len() <= 1 {
let n = dims.first().copied().unwrap_or(0);
crate::limits::count(n as u128, span)?;
return Ok(Array::from_i64((0..n).map(|i| origin + i).collect()));
}
let shape: Vec<usize> = dims.iter().map(|&n| n as usize).collect();
let total = crate::limits::elements(&shape, span)?;
let mut cells = Vec::with_capacity(total);
let mut coord = vec![0usize; shape.len()];
for _ in 0..total {
cells.push(Array::from_i64(coord.iter().map(|&c| origin + c as i64).collect()));
odometer(&mut coord, &shape);
}
Ok(Array::new(shape, Data::Box(cells.into())))
}
fn carry_exact(result: Array, y: &Array) -> Array {
if !matches!(y.dtype(), DType::Ext | DType::Rat) {
return result;
}
match result.data.cast(DType::Ext) {
Some(data) => Array::new(result.shape, data),
None => result,
}
}
fn carry_exact2(result: Array, x: &Array, y: &Array) -> Array {
let widened = carry_exact(result, x);
carry_exact(widened, y)
}
fn truth_table(m: u8, x: &Array, y: &Array, span: Span) -> Result<Array> {
let table = m & 15;
let bit = |a: i64, b: i64| ((table >> (3 - (2 * a + b))) & 1) as i64;
let xs = x
.to_i64_vec()
.ok_or_else(|| Error::domain("a boolean function takes integers", span))?;
let ys = y
.to_i64_vec()
.ok_or_else(|| Error::domain("a boolean function takes integers", span))?;
let (a, b) = (xs.first().copied().unwrap_or(0), ys.first().copied().unwrap_or(0));
if m < 16 {
if !(0..=1).contains(&a) || !(0..=1).contains(&b) {
return Err(Error::domain(
format!("{m} b. takes 0 and 1; {m} b. + 16 is the same function on every bit"),
span,
));
}
return Ok(Array::scalar_bool(bit(a, b) != 0));
}
let mut out = 0i64;
for k in 0..64 {
if bit((a >> k) & 1, (b >> k) & 1) != 0 {
out |= 1i64 << k;
}
}
Ok(Array::scalar_i64(out))
}
pub fn amend_at(
base: &Array,
slots: &[Option<Array>],
value: &Array,
origin: i64,
span: Span,
) -> Result<Array> {
if slots.len() != base.rank() {
return Err(Error::new(
ErrorKind::Rank,
format!(
"indexed assignment needs one index per axis: {} slot(s) for a rank-{} value",
slots.len(),
base.rank()
),
Some(span),
));
}
if !base.is_row_major() || !value.is_row_major() {
let (b, v) = (base.to_row_major(), value.to_row_major());
return amend_at(&b, slots, &v, origin, span);
}
let mut axes: Vec<Vec<usize>> = Vec::with_capacity(slots.len());
let mut selected: Vec<usize> = Vec::new();
for (k, slot) in slots.iter().enumerate() {
let len = base.shape[k];
let Some(idx) = slot else {
axes.push((0..len).collect());
selected.push(len);
continue;
};
let Some(values) = idx.to_i64_vec() else {
return Err(Error::new(
ErrorKind::Type,
"an index must be numeric",
Some(span),
));
};
let mut positions = Vec::with_capacity(values.len());
for v in values {
let p = v - origin;
if p < 0 || p as usize >= len {
return Err(Error::new(
ErrorKind::Domain,
format!("index {v} is outside axis {k}, which has {len} element(s)"),
Some(span),
));
}
positions.push(p as usize);
}
if idx.rank() > 0 {
selected.push(positions.len());
}
axes.push(positions);
}
let count: usize = axes.iter().map(Vec::len).product();
if value.rank() != 0 && (value.shape != selected || value.count() != count) {
return Err(Error::new(
ErrorKind::Shape,
format!(
"indexed assignment needs a scalar or a {} value, not a {} one",
show_shape(&selected),
show_shape(&value.shape)
),
Some(span),
));
}
let dtype = DType::promote(base.dtype(), value.dtype()).ok_or_else(|| {
Error::new(
ErrorKind::Type,
format!(
"cannot put a {} value into a {} array",
value.dtype().name(),
base.dtype().name()
),
Some(span),
)
})?;
let mut out = base.cast(dtype).ok_or_else(|| Error::internal("promotion failed"))?;
let src = value.cast(dtype).ok_or_else(|| Error::internal("promotion failed"))?;
let strides = row_major_strides(&base.shape);
let mut coords = vec![0usize; axes.len()];
for n in 0..count {
let mut rest = n;
for k in (0..axes.len()).rev() {
let len = axes[k].len();
coords[k] = axes[k][rest % len];
rest /= len;
}
let at: usize = coords.iter().zip(&strides).map(|(c, s)| c * s).sum();
let from = if src.rank() == 0 { 0 } else { n };
put_element(&mut out.data, at, &src.data, from);
}
Ok(out)
}
fn row_major_strides(shape: &[usize]) -> Vec<usize> {
let mut strides = vec![1usize; shape.len()];
for k in (0..shape.len().saturating_sub(1)).rev() {
strides[k] = strides[k + 1] * shape[k + 1];
}
strides
}
fn put_element(dst: &mut Data, at: usize, src: &Data, from: usize) {
match (dst, src) {
(Data::Bool(d), Data::Bool(s)) => d.to_mut()[at] = s.as_slice()[from],
(Data::I64(d), Data::I64(s)) => d.to_mut()[at] = s.as_slice()[from],
(Data::Ext(d), Data::Ext(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
(Data::Rat(d), Data::Rat(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
(Data::F64(d), Data::F64(s)) => d.to_mut()[at] = s.as_slice()[from],
(Data::Char(d), Data::Char(s)) => d.to_mut()[at] = s.as_slice()[from],
(Data::Box(d), Data::Box(s)) => d.to_mut()[at] = s.as_slice()[from].clone(),
_ => debug_assert!(false, "amend across types"),
}
}
fn agenda_pick(
vs: &[Verb],
w: &Verb,
x: Option<&Array>,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Verb> {
let chosen = match x {
None => w.monad(y, ctx, span)?,
Some(x) => w.dyad(x, y, ctx, span)?,
};
let at = chosen
.to_i64_vec()
.and_then(|v| v.first().copied())
.ok_or_else(|| Error::domain("an agenda index must be an integer", span))?;
pick_gerund(vs, at, span)
}
pub(crate) fn pick_gerund(vs: &[Verb], at: i64, span: Span) -> Result<Verb> {
usize::try_from(at)
.ok()
.and_then(|k| vs.get(k))
.cloned()
.ok_or_else(|| {
Error::domain(
format!("agenda {at} is out of range: the gerund has {} verbs", vs.len()),
span,
)
})
}
fn evoke(
vs: &[Verb],
form: i64,
x: Option<&Array>,
y: &Array,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
if vs.is_empty() {
return Err(Error::domain("an evoked gerund is empty", span));
}
if form == 0 {
let mut cells = Vec::with_capacity(vs.len());
for v in vs {
cells.push(match x {
None => v.monad(y, ctx, span)?,
Some(x) => v.dyad(x, y, ctx, span)?,
});
}
return assemble(&[vs.len()], cells, span);
}
if x.is_some() {
return Err(Error::domain("m`:3 has no dyadic meaning", span));
}
let items = if y.rank() == 0 { vec![y.clone()] } else { y.cells(1) };
let Some((last, rest)) = items.split_last() else {
return Err(Error::domain("m`:3 needs an argument with items", span));
};
let mut acc = last.clone();
for (i, item) in rest.iter().enumerate().rev() {
acc = vs[i % vs.len()].dyad(item, &acc, ctx, span)?;
}
Ok(acc)
}
fn stencil(u: &Verb, w: &[i64], y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
if w.len() > y.rank() {
return Err(Error::new(
ErrorKind::Rank,
format!("a stencil of {} axis/axes into a rank-{} value", w.len(), y.rank()),
Some(span),
));
}
if w.iter().any(|&n| n <= 0) {
return Err(Error::domain("a stencil window is a positive size", span));
}
let y = y.to_row_major();
let k = w.len();
let st = strides(&y.shape);
let frame: Vec<usize> = y.shape[..k].to_vec();
let mut wshape: Vec<usize> = w.iter().map(|&n| n as usize).collect();
wshape.extend_from_slice(&y.shape[k..]);
let inner: usize = y.shape[k..].iter().product();
let total: usize = frame.iter().product();
let mut cells = Vec::with_capacity(total);
let mut at = vec![0usize; frame.len()];
let mut coord = vec![0usize; k];
for _ in 0..total {
let mut data = Data::empty(y.dtype());
coord.iter_mut().for_each(|c| *c = 0);
let count: usize = w.iter().map(|&n| n as usize).product();
for _ in 0..count {
let mut base = 0usize;
let mut inside = true;
for a in 0..k {
let off = at[a] as i64 + coord[a] as i64 - (w[a] - 1) / 2;
if off < 0 || off >= y.shape[a] as i64 {
inside = false;
break;
}
base += off as usize * st[a];
}
for j in 0..inner {
if inside {
push_elem(&mut data, &y.data, base + j);
} else {
data.push_fill();
}
}
odometer(&mut coord, &wshape[..k]);
}
cells.push(u.monad(&Array::new(wshape.clone(), data), ctx, span)?);
odometer(&mut at, &frame);
}
assemble(&frame, cells, span)
}
fn outfix(u: &Verb, x: &Array, y: &Array, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let k = one_int(x, "an outfix width", span)?;
let n = y.items() as i64;
let list = as_list(y);
let starts: Vec<i64> = if k < 0 {
let step = -k;
(0..(n + step - 1) / step).map(|i| i * step).collect()
} else {
(0..=(n - k)).collect()
};
let width = k.unsigned_abs() as usize;
let mut cells = Vec::with_capacity(starts.len());
for start in starts {
let start = start as usize;
let keep: Vec<usize> =
(0..n as usize).filter(|&i| i < start || i >= start + width).collect();
cells.push(u.monad(&select_items(&list, &keep), ctx, span)?);
}
assemble(&[cells.len()], cells, span)
}
pub(crate) fn obverse(v: &Verb) -> Option<Verb> {
let swap = |name: &'static str| -> Option<Verb> {
crate::frontend::j::verb_named(name)
};
Some(match v {
Verb::Prim(p) => {
use ScalarMonad as SM;
if matches!(
p.monad,
MonadOp::Scalar(SM::Conj | SM::Neg | SM::Recip | SM::OneMinus)
| MonadOp::Reverse
| MonadOp::TransposeAxes
) {
return Some(v.clone());
}
if matches!(p.monad, MonadOp::Scalar(SM::Imaginary)) {
return Some(Verb::Atop(Box::new(swap("-")?), Box::new(swap("j.")?)));
}
let by_monad: Option<&'static str> = match p.monad {
MonadOp::Scalar(SM::Exp) => Some("^."),
MonadOp::Scalar(SM::Ln) => Some("^"),
MonadOp::Scalar(SM::Sqrt) => Some("*:"),
MonadOp::Scalar(SM::Square) => Some("%:"),
MonadOp::Scalar(SM::Double) => Some("-:"),
MonadOp::Scalar(SM::Halve) => Some("+:"),
MonadOp::Scalar(SM::Inc) => Some("<:"),
MonadOp::Scalar(SM::Dec) => Some(">:"),
MonadOp::Enclose(_) => Some(">"),
MonadOp::Open => Some("<"),
MonadOp::DecodeBits => Some("#:"),
MonadOp::EncodeBits => Some("#."),
_ => None,
};
swap(by_monad?)?
}
Verb::WithObverse(_, w) => (**w).clone(),
Verb::Atop(f, g) => {
Verb::Atop(Box::new(obverse(g)?), Box::new(obverse(f)?))
}
Verb::Compose(f, g) | Verb::Beside(f, g) => {
Verb::Atop(Box::new(obverse(g)?), Box::new(obverse(f)?))
}
Verb::Rank(f, r) => Verb::Rank(Box::new(obverse(f)?), *r),
Verb::Fit(f, n) => Verb::Fit(Box::new(obverse(f)?), *n),
Verb::PowerN(f, Power::Times(n)) => {
Verb::PowerN(Box::new(obverse(f)?), Power::Times(*n))
}
Verb::BondLeft(m, f) => bond_obverse(m, f, true)?,
Verb::BondRight(f, n) => bond_obverse(n, f, false)?,
_ => return None,
})
}
fn bond_obverse(n: &Array, f: &Verb, left: bool) -> Option<Verb> {
let Verb::Prim(p) = f else { return None };
let named = |name: &'static str| crate::frontend::j::verb_named(name);
let bond = |name: &'static str, arg: &Array| -> Option<Verb> {
let g = named(name)?;
Some(if left {
Verb::BondLeft(arg.clone(), Box::new(g))
} else {
Verb::BondRight(Box::new(g), arg.clone())
})
};
use ScalarDyad as SD;
let DyadOp::Scalar(op) = p.dyad else { return None };
match (op, left) {
(SD::Sub | SD::DivJ | SD::DivApl, true) => bond(p.name, n),
(SD::Add, _) => Some(Verb::BondRight(Box::new(named("-")?), n.clone())),
(SD::Mul, _) => Some(Verb::BondRight(Box::new(named("%")?), n.clone())),
(SD::Sub, false) => bond("+", n),
(SD::DivJ | SD::DivApl, false) => bond("*", n),
(SD::Pow, false) => Some(Verb::BondLeft(n.clone(), Box::new(named("%:")?))),
(SD::Pow, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("^.")?))),
(SD::Log, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("^")?))),
(SD::Root, true) => Some(Verb::BondLeft(n.clone(), Box::new(named("^")?))),
_ => None,
}
}
fn self_classify(y: &Array, tol: Tol) -> Array {
let items = if y.rank() == 0 { 1 } else { y.items() };
let keys = nub(&as_list(y), tol);
let rows = keys.items();
let mut out = Vec::with_capacity(rows * items);
for i in 0..rows {
let key = item_or_self(&keys, i);
for j in 0..items {
out.push(arrays_match(&key, &item_or_self(y, j), tol) as u8);
}
}
Array::new(vec![rows, items], Data::Bool(out.into()))
}
fn nub_sieve(y: &Array, tol: Tol) -> Array {
let items = if y.rank() == 0 { 1 } else { y.items() };
let mut seen: Vec<Array> = Vec::new();
let mut out = Vec::with_capacity(items);
for i in 0..items {
let cell = item_or_self(y, i);
let fresh = !seen.iter().any(|s| arrays_match(s, &cell, tol));
if fresh {
seen.push(cell);
}
out.push(fresh as u8);
}
Array::new(vec![items], Data::Bool(out.into()))
}
fn as_list(y: &Array) -> Array {
if y.rank() == 0 { Array::new(vec![1], y.data.clone()) } else { y.clone() }
}
fn conforming_cells(y: &Array, item_rank: usize) -> Vec<Array> {
let frame_rank = y.rank().saturating_sub(item_rank);
let nf: usize = y.shape[..frame_rank].iter().product();
(0..nf).map(|i| y.cell_at(frame_rank, i)).collect()
}
fn item_marks(y: &Array, x: &Array, tol: Tol) -> Vec<bool> {
let n = if y.rank() == 0 { 1 } else { y.items() };
let item_rank = y.rank().saturating_sub(1);
let against = conforming_cells(x, item_rank);
(0..n)
.map(|i| {
let cell = item_or_self(y, i);
against.iter().any(|c| arrays_match(&cell, c, tol))
})
.collect()
}
fn set_less(x: &Array, y: &Array, tol: Tol) -> Array {
let xs = as_list(x);
let marks = item_marks(&xs, y, tol);
let keep: Vec<usize> = (0..marks.len()).filter(|&i| !marks[i]).collect();
select_items(&xs, &keep)
}
fn set_rank(cfg: EvalCfg, what: &str, x: &Array, y: &Array, span: Span) -> Result<()> {
if cfg.rules.lang == crate::Lang::Apl && (x.rank() > 1 || y.rank() > 1) {
return Err(Error::new(
ErrorKind::Rank,
format!("{what} takes vectors, not rank {} and rank {}", x.rank(), y.rank()),
Some(span),
));
}
Ok(())
}
fn intersect_items(x: &Array, y: &Array, tol: Tol) -> Array {
let xs = as_list(x);
let marks = item_marks(&xs, y, tol);
let keep: Vec<usize> = (0..marks.len()).filter(|&i| marks[i]).collect();
select_items(&xs, &keep)
}
fn union_items(x: &Array, y: &Array, tol: Tol, span: Span) -> Result<Array> {
let xs = as_list(x);
let ys = as_list(y);
let marks = item_marks(&ys, &xs, tol);
let mut extra: Vec<usize> = Vec::new();
for (i, &seen) in marks.iter().enumerate() {
if seen {
continue;
}
let cell = item_or_self(&ys, i);
if !extra.iter().any(|&j| arrays_match(&item_or_self(&ys, j), &cell, tol)) {
extra.push(i);
}
}
catenate(&xs, &select_items(&ys, &extra), true, false, span)
}
fn find_seq(x: &Array, y: &Array, tol: Tol, apl: bool, span: Span) -> Result<Array> {
let (xr, yr) = (x.rank(), y.rank());
if apl && xr > yr {
return Ok(Array::new(y.shape.clone(), Data::Bool(vec![0u8; y.count()].into())));
}
if !apl && xr.max(1) != yr {
return Err(Error::new(
ErrorKind::Rank,
format!("a rank-{xr} pattern in a rank-{yr} argument"),
Some(span),
));
}
let mut pattern = vec![1usize; yr];
pattern[yr - xr..].copy_from_slice(&x.shape);
let n = y.count();
let mut out = vec![0u8; n];
let (xrm, yrm) = (x.to_row_major(), y.to_row_major());
let yst = strides(&y.shape);
let cells: usize = pattern.iter().product();
let mut at = vec![0usize; yr];
for slot in out.iter_mut() {
if (0..yr).all(|a| at[a] + pattern[a] <= y.shape[a]) {
let mut off = vec![0usize; yr];
let mut hit = true;
for k in 0..cells {
let i: usize = (0..yr).map(|a| (at[a] + off[a]) * yst[a]).sum();
if !arrays_match(&atom(&xrm, k), &atom(&yrm, i), tol) {
hit = false;
break;
}
odometer(&mut off, &pattern);
}
*slot = hit as u8;
}
odometer(&mut at, &y.shape);
}
Ok(Array::new(y.shape.clone(), Data::Bool(out.into())))
}
fn bool_dyad(op: BoolDyad, x: &Array, y: &Array, cfg: EvalCfg, span: Span) -> Result<Array> {
let bit = |a: &Array| -> Result<u8> {
match a.to_i64_vec().as_deref() {
Some([0]) => Ok(0),
Some([1]) => Ok(1),
_ => Err(Error::domain("this verb reads values of 0 or 1", span)),
}
};
let _ = cfg;
let (a, b) = (bit(x)?, bit(y)?);
let v = match op {
BoolDyad::Nor => u8::from(a == 0 && b == 0),
BoolDyad::Nand => u8::from(a == 0 || b == 0),
};
Ok(Array::new(vec![], Data::Bool(vec![v].into())))
}
fn item_ranks(y: &Array, rules: Rules, span: Span) -> Result<Vec<usize>> {
check_gradable(y, rules, span)?;
if !y.dtype().is_numeric() {
return Err(Error::domain("an anagram index needs numbers", span));
}
let order = grade_order(&as_list(y), false, Tao::of(rules));
let mut ranks = vec![0usize; order.len()];
for (place, &i) in order.iter().enumerate() {
ranks[i] = place;
}
Ok(ranks)
}
fn anagram_index(y: &Array, rules: Rules, span: Span) -> Result<Array> {
let ranks = item_ranks(y, rules, span)?;
let n = ranks.len();
let mut index: i128 = 0;
for i in 0..n {
let smaller = ranks[i + 1..].iter().filter(|&&r| r < ranks[i]).count() as i128;
index = index
.checked_mul((n - i) as i128)
.and_then(|v| v.checked_add(smaller))
.ok_or_else(|| Error::not_yet("an anagram index too large for an integer", span))?;
}
i64::try_from(index)
.map(Array::scalar_i64)
.map_err(|_| Error::not_yet("an anagram index too large for an integer", span))
}
fn anagram_from(x: &Array, y: &Array, span: Span) -> Result<Array> {
let idx = x
.to_i64_vec()
.ok_or_else(|| Error::domain("an anagram index must be an integer", span))?;
let Some(&want) = idx.first() else {
return Err(Error::internal("anagram with no index"));
};
let ys = as_list(y);
let n = ys.items();
let mut total: i128 = 1;
for k in 1..=n as i128 {
total = total
.checked_mul(k)
.ok_or_else(|| Error::not_yet("permuting more items than an integer counts", span))?;
}
let mut at = want as i128;
if at < 0 {
at += total;
}
if at < 0 || at >= total {
return Err(Error::domain(
format!("permutation {want} is out of range: {n} items have {total} of them"),
span,
));
}
let mut pool: Vec<usize> = (0..n).collect();
let mut order = Vec::with_capacity(n);
let mut fact = total;
for i in 0..n {
fact /= (n - i) as i128;
let d = (at / fact) as usize;
at %= fact;
order.push(pool.remove(d));
}
Ok(select_items(&ys, &order))
}
fn cycle_form(y: &Array, span: Span) -> Result<Array> {
if y.dtype() == DType::Box {
let perm = cycles_to_direct(y, span)?;
return Ok(Array::from_i64(perm.iter().map(|&i| i as i64).collect()));
}
let perm = direct_permutation(y, span)?;
let mut boxes: Vec<Array> = Vec::new();
let mut done = vec![false; perm.len()];
for start in 0..perm.len() {
if done[start] {
continue;
}
let mut cycle = Vec::new();
let mut at = start;
while !done[at] {
done[at] = true;
cycle.push(at);
at = perm[at];
}
let top = cycle.iter().position(|&v| v == *cycle.iter().max().unwrap()).unwrap();
cycle.rotate_left(top);
boxes.push(Array::boxed(Array::from_i64(
cycle.iter().map(|&i| i as i64).collect(),
)));
}
boxes.sort_by_key(|b| b.as_boxes().map(|s| s[0].to_i64_vec().unwrap()[0]).unwrap_or(0));
let n = boxes.len();
let inner: Vec<Array> =
boxes.into_iter().map(|b| b.as_boxes().unwrap()[0].clone()).collect();
Ok(Array::new(vec![n], Data::Box(inner.into())))
}
fn direct_permutation(y: &Array, span: Span) -> Result<Vec<usize>> {
let v = y
.to_i64_vec()
.ok_or_else(|| Error::domain("a permutation is a list of integers", span))?;
let n = v.len();
let mut seen = vec![false; n];
let mut out = Vec::with_capacity(n);
for &i in &v {
let k = usize::try_from(i).ok().filter(|&k| k < n && !seen[k]).ok_or_else(|| {
Error::domain(format!("{i} does not belong to a permutation of {n} items"), span)
})?;
seen[k] = true;
out.push(k);
}
Ok(out)
}
fn cycles_to_direct(y: &Array, span: Span) -> Result<Vec<usize>> {
let boxes = y.as_boxes().ok_or_else(|| Error::internal("cycles from a simple array"))?;
let mut cycles: Vec<Vec<usize>> = Vec::new();
let mut top = 0usize;
for b in boxes {
let v = b
.to_i64_vec()
.ok_or_else(|| Error::domain("a cycle is a list of integers", span))?;
let mut cycle = Vec::with_capacity(v.len());
for &i in &v {
let k = usize::try_from(i)
.map_err(|_| Error::domain(format!("{i} is not an index"), span))?;
top = top.max(k + 1);
cycle.push(k);
}
cycles.push(cycle);
}
let mut perm: Vec<usize> = (0..top).collect();
for cycle in &cycles {
for w in 0..cycle.len() {
perm[cycle[w]] = cycle[(w + 1) % cycle.len()];
}
}
Ok(perm)
}
fn permute(x: &Array, y: &Array, span: Span) -> Result<Array> {
let ys = as_list(y);
let n = ys.items();
let cyclic = x.dtype() == DType::Box;
if !cyclic && x.rank() == 0 {
return Err(Error::not_yet("permuting by a single atom (x C. y)", span));
}
let mut perm =
if cyclic { cycles_to_direct(x, span)? } else { direct_permutation(&as_list(x), span)? };
if perm.len() > n {
return Err(Error::new(
ErrorKind::Length,
format!("a permutation of {} items applied to {n}", perm.len()),
Some(span),
));
}
if perm.len() < n {
if cyclic {
perm.extend(perm.len()..n);
} else {
let head: Vec<usize> = (perm.len()..n).collect();
perm.splice(0..0, head);
}
}
Ok(select_items(&ys, &perm))
}
fn unicode(y: &Array, pass_chars: bool, span: Span) -> Result<Array> {
if y.dtype() == DType::Char {
if pass_chars {
return Ok(y.clone());
}
return Ok(chars_to_codes(y));
}
codes_to_chars(y, span)
}
fn chars_to_codes(y: &Array) -> Array {
let Data::Char(v) = &y.data else { return y.clone() };
Array::new(y.shape.clone(), Data::I64(v.iter().map(|&c| c as i64).collect()))
}
fn codes_to_chars(y: &Array, span: Span) -> Result<Array> {
let v = y
.to_i64_vec()
.ok_or_else(|| Error::domain("a codepoint must be an integer", span))?;
let mut out = Vec::with_capacity(v.len());
for &c in &v {
let ch = u32::try_from(c).ok().and_then(char::from_u32).ok_or_else(|| {
Error::domain(format!("{c} is not a Unicode codepoint"), span)
})?;
out.push(ch);
}
Ok(Array::new(y.shape.clone(), Data::Char(out.into())))
}
fn unicode_form(x: &Array, y: &Array, span: Span) -> Result<Array> {
let form = x
.to_i64_vec()
.ok_or_else(|| Error::domain("a conversion form is an integer", span))?
.first()
.copied()
.unwrap_or(0);
match form {
3 if y.dtype() == DType::Char => Ok(chars_to_codes(y)),
3 => Err(Error::domain("form 3 converts characters to codepoints", span)),
10 => codes_to_chars(y, span),
n => Err(Error::not_yet(format!("the byte-oriented unicode form ({n} u:)"), span)),
}
}
fn to_symbols(y: &Array, span: Span) -> Result<Array> {
if let Some(boxes) = y.as_boxes() {
let mut ids = Vec::with_capacity(boxes.len());
for b in boxes {
if b.rank() > 1 {
return Err(Error::new(
ErrorKind::Rank,
"a boxed symbol name is a character list",
Some(span),
));
}
let row_major = b.to_row_major();
let Data::Char(v) = &row_major.data else {
if b.count() == 0 {
ids.push(crate::symbol::EMPTY);
continue;
}
return Err(Error::domain("a symbol is made from characters", span));
};
ids.push(crate::symbol::intern(&v.as_slice().iter().collect::<String>()));
}
return Ok(Array::new(y.shape.clone(), Data::Symbol(ids.into())));
}
let row_major = y.to_row_major();
let Data::Char(v) = &row_major.data else {
return Err(Error::domain(
format!("s: makes symbols from characters, not {} data", y.dtype().name()),
span,
));
};
let chars = v.as_slice();
if y.rank() >= 2 {
let width = y.shape[y.rank() - 1];
let mut ids = Vec::with_capacity(chars.len() / width.max(1));
for row in chars.chunks(width) {
let name: String = row.iter().collect();
ids.push(crate::symbol::intern(name.trim_end_matches(' ')));
}
return Ok(Array::new(y.shape[..y.rank() - 1].to_vec(), Data::Symbol(ids.into())));
}
let Some((&delim, rest)) = chars.split_first() else {
return Ok(Array::new(vec![0], Data::empty(DType::Symbol)));
};
let mut ids = Vec::new();
let mut name = String::new();
for &c in rest {
if c == delim {
ids.push(crate::symbol::intern(&name));
name.clear();
} else {
name.push(c);
}
}
ids.push(crate::symbol::intern(&name));
Ok(Array::new(vec![ids.len()], Data::Symbol(ids.into())))
}
fn symbol_form(x: &Array, y: &Array, span: Span) -> Result<Array> {
let form = x
.to_i64_vec()
.ok_or_else(|| Error::domain("a symbol form is an integer", span))?
.first()
.copied()
.unwrap_or(0);
if !matches!(form, 4 | 5) {
return Err(Error::not_yet(format!("the symbol-table form ({form} s:)"), span));
}
let row_major = y.to_row_major();
let Data::Symbol(ids) = &row_major.data else {
return Err(Error::domain(
format!("{form} s: reads symbols, not {} data", y.dtype().name()),
span,
));
};
let names = crate::symbol::names(ids.as_slice());
if form == 5 {
let boxes: Vec<Array> =
names.iter().map(|n| Array::from_chars(n.chars().collect())).collect();
return Ok(Array::new(y.shape.clone(), Data::Box(boxes.into())));
}
let width = names.iter().map(|n| n.chars().count()).max().unwrap_or(0);
let mut out: Vec<char> = Vec::with_capacity(names.len() * width);
for n in &names {
out.extend(n.chars());
out.resize(out.len() + width - n.chars().count(), ' ');
}
let mut shape = y.shape.clone();
shape.push(width);
Ok(Array::new(shape, Data::Char(out.into())))
}
fn boxing_level(y: &Array) -> i64 {
match y.as_boxes() {
None => 0,
Some(bs) => 1 + bs.iter().map(boxing_level).max().unwrap_or(0),
}
}
fn split_items(y: &Array) -> Array {
if y.rank() == 0 {
return Array::boxed(y.clone());
}
let last = y.shape[y.rank() - 1];
let outer: Vec<usize> = y.shape[..y.rank() - 1].to_vec();
let n: usize = outer.iter().product();
let mut boxes = Vec::with_capacity(n);
for i in 0..n {
let mut data = Data::empty(y.dtype());
for k in 0..last {
push_elem(&mut data, &y.data, i * last + k);
}
boxes.push(Array::new(vec![last], data));
}
Array::new(outer, Data::Box(boxes.into()))
}
fn pick(x: &Array, y: &Array, origin: i64, span: Span) -> Result<Array> {
let xs = as_list(x);
let mut cur = y.clone();
for i in 0..xs.items() {
let step = open_cell(&item_or_self(&xs, i));
let idx = step
.to_i64_vec()
.ok_or_else(|| Error::domain("a pick path holds integers", span))?;
let base =
if cur.rank() == 0 { Array::new(vec![1], cur.data.clone()) } else { cur.clone() };
if idx.len() > base.rank() {
return Err(Error::new(
ErrorKind::Length,
format!(
"a path step of {} index(es) into a value of rank {}",
idx.len(),
cur.rank()
),
Some(span),
));
}
let zeroed: Vec<i64> = idx.iter().map(|&v| v - origin).collect();
let at = cell_index(&base, &zeroed, span)?;
cur = open_cell(&base.cell_at(idx.len(), at));
}
Ok(cur)
}
fn prime_meta(x: &Array, y: &Array, span: Span) -> Result<Array> {
let form = one_int(x, "a prime query", span)?;
let n = one_int(y, "a prime query", span)?;
match form {
-1 => Ok(Array::scalar_i64(primes_below(n, span)?)),
0 => Ok(Array::scalar_bool(!is_prime(n))),
1 => Ok(Array::scalar_bool(is_prime(n))),
2 | 3 => {
let (ps, es) = factor_table(n, span)?;
let k = ps.len();
if form == 3 {
return Ok(Array::from_i64(ps));
}
let mut all = ps;
all.extend(es);
Ok(Array::new(vec![2, k], Data::I64(all.into())))
}
4 => Ok(Array::scalar_i64(next_prime(n, span)?)),
-4 => Ok(Array::scalar_i64(previous_prime(n, span)?)),
other => Err(Error::domain(format!("{other} is not a prime query"), span)),
}
}
fn prime_exponents(x: &Array, y: &Array, span: Span) -> Result<Array> {
let n = one_int(y, "prime exponents", span)?;
let count = x.to_f64_vec().and_then(|v| v.first().copied()).unwrap_or(0.0);
let (ps, es) = factor_table(n, span)?;
if count == f64::NEG_INFINITY {
let k = ps.len();
let mut all = ps;
all.extend(es);
return Ok(Array::new(vec![2, k], Data::I64(all.into())));
}
let want = one_int(x, "prime exponents", span)?;
if want < 0 {
return Err(Error::not_yet(format!("the prime exponent form ({want} q:)"), span));
}
let mut out = Vec::with_capacity(want as usize);
for i in 0..want {
let p = nth_prime(i, span)?;
out.push(ps.iter().position(|&q| q == p).map_or(0, |at| es[at]));
}
Ok(Array::from_i64(out))
}
fn factor_table(n: i64, span: Span) -> Result<(Vec<i64>, Vec<i64>)> {
let factors = prime_factors(n, span)?;
let mut ps: Vec<i64> = Vec::new();
let mut es: Vec<i64> = Vec::new();
for f in factors {
if ps.last() == Some(&f) {
*es.last_mut().unwrap() += 1;
} else {
ps.push(f);
es.push(1);
}
}
Ok((ps, es))
}
fn is_prime(n: i64) -> bool {
if n < 2 {
return false;
}
let mut d = 2i64;
while d.saturating_mul(d) <= n {
if n % d == 0 {
return false;
}
d += 1;
}
true
}
fn primes_below(n: i64, span: Span) -> Result<i64> {
if n < 0 {
return Err(Error::domain("counting the primes below a negative number", span));
}
Ok((2..n).filter(|&k| is_prime(k)).count() as i64)
}
fn next_prime(n: i64, span: Span) -> Result<i64> {
let mut k = n.checked_add(1).ok_or_else(|| Error::domain("no next prime", span))?;
while !is_prime(k) {
k = k.checked_add(1).ok_or_else(|| Error::domain("no next prime", span))?;
}
Ok(k)
}
fn previous_prime(n: i64, span: Span) -> Result<i64> {
let mut k = n - 1;
while k >= 2 {
if is_prime(k) {
return Ok(k);
}
k -= 1;
}
Err(Error::domain(format!("there is no prime below {n}"), span))
}
fn one_int(a: &Array, what: &str, span: Span) -> Result<i64> {
a.to_i64_vec()
.and_then(|v| v.first().copied())
.ok_or_else(|| Error::domain(format!("{what} needs an integer"), span))
}
fn expand(x: &Array, y: &Array, span: Span) -> Result<Array> {
let mask = x
.to_i64_vec()
.ok_or_else(|| Error::domain("an expansion mask holds 0s and 1s", span))?;
if mask.iter().any(|&b| b != 0 && b != 1) {
return Err(Error::domain("an expansion mask holds 0s and 1s", span));
}
let ys = as_list(y);
let taken = mask.iter().filter(|&&b| b == 1).count();
let n = ys.items();
let spread = n == 1 && taken != 1;
if !spread && taken != n {
return Err(Error::new(
ErrorKind::Length,
format!("an expansion mask taking {taken} item(s) over {n}"),
Some(span),
));
}
let m = ys.item_size();
let mut data = Data::empty(ys.dtype());
let mut at = 0usize;
for &b in &mask {
if b == 1 {
let from = if spread { 0 } else { at };
for k in 0..m {
push_elem(&mut data, &ys.data, from * m + k);
}
at += 1;
} else {
for _ in 0..m {
data.push_fill();
}
}
}
let mut shape = ys.shape.clone();
if shape.is_empty() {
shape.push(mask.len());
} else {
shape[0] = mask.len();
}
Ok(Array::new(shape, data))
}
fn execute(y: &Array, apl: bool, ctx: &mut Ctx<'_>, span: Span) -> Result<Array> {
let Data::Char(v) = &y.data else {
return Err(Error::domain("execute reads a character list", span));
};
let src: String = v.iter().collect();
execute_source(&src, apl, ctx, span)
}
pub(crate) fn execute_source(
src: &str,
apl: bool,
ctx: &mut Ctx<'_>,
span: Span,
) -> Result<Array> {
let lang = if apl { crate::Lang::Apl } else { crate::Lang::J };
let dialect = ctx.cfg.rules.dialect();
let nested = crate::compile(lang, src, &dialect).map_err(|e| nested_error(e, src, span))?;
if !nested.params.is_empty() {
return Err(Error::domain(
"an executed string cannot take host data: `{name}` has nothing to bind to",
span,
));
}
let mut rec = None;
let (value, _) = crate::ir::run_block(&nested.stmts, None, ctx, &mut rec)
.map_err(|e| nested_error(e, src, span))?;
value.ok_or_else(|| Error::domain("the executed string yielded no value", span))
}
fn stream_number(y: &Array, open: i64, what: &str, span: Span) -> Result<()> {
let closed = || {
Err(Error::sandbox(
format!("{what} the standard stream {open} only; a file is outside the program"),
span,
))
};
if matches!(y.data, Data::Box(_)) {
return closed();
}
match y.to_i64_vec().as_deref() {
Some([n]) if *n == open => Ok(()),
Some([_]) => closed(),
_ => Err(Error::domain(format!("{what} one stream number"), span)),
}
}
fn type_code(y: &Array) -> i64 {
match y.dtype() {
DType::Bool => 1,
DType::Char => 2,
DType::I64 => 4,
DType::F64 => 8,
DType::Complex => 16,
DType::Box => 32,
DType::Ext => 64,
DType::Rat => 128,
DType::Symbol => 65536,
}
}
fn nested_error(e: Error, src: &str, span: Span) -> Error {
let inner = e.render(src);
let mut out = Error::new(e.kind, format!("in the executed string: {}", e.msg), Some(span));
out.notes.push(inner.trim_end().to_string());
out
}
fn words(y: &Array, span: Span) -> Result<Array> {
let Data::Char(v) = &y.data else {
return Err(Error::domain("words reads a character list", span));
};
let src: Vec<char> = v.as_slice().to_vec();
let n = src.len();
let mut out: Vec<Array> = Vec::new();
let mut i = 0usize;
let numeric_start = |k: usize| -> bool {
k < n && (src[k].is_ascii_digit() || src[k] == '_')
};
while i < n {
let c = src[i];
if c == ' ' || c == '\t' {
i += 1;
continue;
}
let start = i;
if c == '\'' {
i += 1;
loop {
if i >= n {
return Err(Error::parse("a word list ends inside a string", span));
}
if src[i] == '\'' {
i += 1;
if i < n && src[i] == '\'' {
i += 1;
continue;
}
break;
}
i += 1;
}
} else if c.is_ascii_alphabetic() {
while i < n && (src[i].is_ascii_alphanumeric() || src[i] == '_') {
i += 1;
}
if i < n && (src[i] == '.' || src[i] == ':') {
i += 1;
}
if src[start..i].iter().collect::<String>() == "NB." {
while i < n && src[i] != '\n' {
i += 1;
}
}
} else if numeric_start(i) {
loop {
while i < n && (src[i].is_ascii_alphanumeric() || src[i] == '.' || src[i] == '_')
{
i += 1;
}
let mut j = i;
while j < n && src[j] == ' ' {
j += 1;
}
if j > i && numeric_start(j) {
i = j;
continue;
}
break;
}
} else {
i += 1;
while i < n && (src[i] == '.' || src[i] == ':') {
i += 1;
}
}
out.push(Array::from_chars(src[start..i].to_vec()));
}
let k = out.len();
Ok(Array::new(vec![k], Data::Box(out.into())))
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! ctx {
($name:ident, $agreement:expr) => {
let mut sink = |_: &str| {};
let mut env = Env::new(Vec::new());
#[allow(unused_mut)]
let mut $name = Ctx {
cfg: EvalCfg {
agreement: $agreement,
fmt: FmtOpts::J,
tol: Tol::J,
rules: crate::frontend::Dialect::default()
.rules(if $agreement == Agreement::ExactOrScalar {
crate::Lang::Apl
} else {
crate::Lang::J
})
.expect("the shipped dialect is implemented"),
},
out: &mut sink,
inp: None,
env: &mut env,
device: None,
};
};
($name:ident) => {
ctx!($name, Agreement::LeadingPrefix);
};
}
fn scalar_prim(name: &'static str, monad: MonadOp, dyad: DyadOp) -> Verb {
Verb::Prim(Prim { name, monad, dyad, ranks: [0, 0, 0] })
}
fn inf_prim(name: &'static str, monad: MonadOp, dyad: DyadOp) -> Verb {
Verb::Prim(Prim { name, monad, dyad, ranks: [RANK_INF, RANK_INF, RANK_INF] })
}
fn plus() -> Verb {
scalar_prim("+", MonadOp::Scalar(ScalarMonad::Conj), DyadOp::Scalar(ScalarDyad::Add))
}
fn minus() -> Verb {
scalar_prim("-", MonadOp::Scalar(ScalarMonad::Neg), DyadOp::Scalar(ScalarDyad::Sub))
}
fn times() -> Verb {
scalar_prim("*", MonadOp::Scalar(ScalarMonad::Signum), DyadOp::Scalar(ScalarDyad::Mul))
}
fn pct() -> Verb {
scalar_prim("%", MonadOp::Scalar(ScalarMonad::Recip), DyadOp::Scalar(ScalarDyad::DivJ))
}
fn div_apl() -> Verb {
scalar_prim("÷", MonadOp::Scalar(ScalarMonad::Recip), DyadOp::Scalar(ScalarDyad::DivApl))
}
fn floor_v() -> Verb {
scalar_prim("<.", MonadOp::Scalar(ScalarMonad::Floor), DyadOp::Scalar(ScalarDyad::Min))
}
fn ceil_v() -> Verb {
scalar_prim(">.", MonadOp::Scalar(ScalarMonad::Ceil), DyadOp::Scalar(ScalarDyad::Max))
}
fn pow_v() -> Verb {
scalar_prim("^", MonadOp::Scalar(ScalarMonad::Exp), DyadOp::Scalar(ScalarDyad::Pow))
}
fn residue_v() -> Verb {
scalar_prim("|", MonadOp::Scalar(ScalarMonad::Abs), DyadOp::Scalar(ScalarDyad::Residue))
}
fn eq_v() -> Verb {
scalar_prim("=", MonadOp::None, DyadOp::Scalar(ScalarDyad::Eq))
}
fn lt_v() -> Verb {
scalar_prim("<", MonadOp::None, DyadOp::Scalar(ScalarDyad::Lt))
}
fn not_v() -> Verb {
scalar_prim("-.", MonadOp::Scalar(ScalarMonad::Not), DyadOp::None)
}
fn sqrt_v() -> Verb {
scalar_prim("%:", MonadOp::Scalar(ScalarMonad::Sqrt), DyadOp::NotYet("dyadic root"))
}
fn dollar() -> Verb {
inf_prim("$", MonadOp::ShapeOf, DyadOp::Reshape)
}
fn pound() -> Verb {
inf_prim("#", MonadOp::Tally, DyadOp::NotYet("copy"))
}
fn comma() -> Verb {
inf_prim(",", MonadOp::Ravel, DyadOp::NotYet("append"))
}
fn transpose_v() -> Verb {
inf_prim("|:", MonadOp::TransposeAxes, DyadOp::NotYet("dyadic transpose"))
}
fn head_v() -> Verb {
inf_prim("{.", MonadOp::Head, DyadOp::Take)
}
fn behead_v() -> Verb {
inf_prim("}.", MonadOp::Behead, DyadOp::Drop)
}
fn iota() -> Verb {
inf_prim("i.", MonadOp::IotaJ, DyadOp::NotYet("index of"))
}
fn iota_apl(origin: i64) -> Verb {
inf_prim("⍳", MonadOp::IotaApl { origin }, DyadOp::NotYet("index of"))
}
fn right_v() -> Verb {
inf_prim("]", MonadOp::Same, DyadOp::Right)
}
fn echo_v() -> Verb {
inf_prim("echo", MonadOp::Echo, DyadOp::None)
}
fn b(v: Verb) -> Box<Verb> {
Box::new(v)
}
fn mat(rows: usize, cols: usize, v: Vec<i64>) -> Array {
Array::new(vec![rows, cols], Data::I64(v.into()))
}
fn ints(a: &Array) -> Vec<i64> {
a.to_row_major().as_i64_slice().expect("integer result").to_vec()
}
fn floats(a: &Array) -> Vec<f64> {
a.to_row_major().as_f64_slice().expect("float result").to_vec()
}
fn bools(a: &Array) -> Vec<u8> {
match &a.to_row_major().data {
Data::Bool(v) => v.to_vec(),
other => panic!("expected boolean result, got {other:?}"),
}
}
fn sp() -> Span {
Span::new(0, 1)
}
fn close(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9 || (a.is_infinite() && b.is_infinite() && a.signum() == b.signum())
}
#[test]
fn names_of_primitives_and_derived_verbs() {
assert_eq!(plus().name(), "+");
assert_eq!(Verb::Rank(b(plus()), [1, 1, 1]).name(), "+\"1");
assert_eq!(Verb::Rank(b(plus()), [0, 1, RANK_INF]).name(), "+\"0 1 _");
assert_eq!(Verb::Rank(b(plus()), [RANK_INF; 3]).name(), "+\"_");
assert_eq!(Verb::Reduce(b(plus())).name(), "+/");
assert_eq!(Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]).name(), "+/\"1");
assert_eq!(Verb::Fork(b(plus()), b(minus()), b(times())).name(), "(+ - *)");
assert_eq!(
Verb::NounFork(Array::scalar_i64(1), b(plus()), b(minus())).name(),
"(n + -)"
);
assert_eq!(Verb::Hook(b(plus()), b(minus())).name(), "(+ -)");
assert_eq!(Verb::Atop(b(plus()), b(minus())).name(), "(+@:-)");
assert_eq!(Verb::Compose(b(plus()), b(minus())).name(), "(+&:-)");
assert_eq!(Verb::BondLeft(Array::scalar_i64(1), b(plus())).name(), "(n&+)");
assert_eq!(Verb::BondRight(b(plus()), Array::scalar_i64(1)).name(), "(+&n)");
}
#[test]
fn composition_applies_the_right_verb_to_both_arguments() {
ctx!(c);
let v = Verb::Compose(b(plus()), b(times()));
let r = v.monad(&Array::from_i64(vec![-2, 0, 3]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![-1, 0, 1]);
let r = v
.dyad(&Array::scalar_i64(-5), &Array::scalar_i64(7), &mut c, sp())
.unwrap();
assert_eq!(ints(&r), vec![0]);
let bond = Verb::BondLeft(Array::scalar_i64(10), b(minus()));
let r = bond.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![9, 8]);
let e = bond
.dyad(&Array::scalar_i64(1), &Array::scalar_i64(2), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
let bond = Verb::BondRight(b(minus()), Array::scalar_i64(10));
let r = bond.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![-9, -8]);
}
#[test]
fn scalar_monad_covers_the_whole_buffer() {
ctx!(c);
let r = minus().monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 3]);
assert_eq!(ints(&r), vec![-1, -2, -3, -4, -5, -6]);
}
#[test]
fn leading_prefix_agreement_broadcasts_per_row() {
ctx!(c);
let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
let y = Array::from_i64(vec![10, 20]);
let r = plus().dyad(&x, &y, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 3]);
assert_eq!(ints(&r), vec![11, 12, 13, 24, 25, 26]);
let r = plus().dyad(&y, &x, &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![11, 12, 13, 24, 25, 26]);
}
#[test]
fn exact_or_scalar_rejects_a_prefix_frame() {
ctx!(c, Agreement::ExactOrScalar);
let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
let y = Array::from_i64(vec![10, 20]);
let e = plus().dyad(&x, &y, &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Shape);
assert!(e.msg.contains("2 3"), "{}", e.msg);
assert!(e.msg.contains("right shape 2"), "{}", e.msg);
}
#[test]
fn exact_or_scalar_accepts_equal_frames_and_scalars() {
ctx!(c, Agreement::ExactOrScalar);
let x = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
let r = plus().dyad(&x, &x, &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![2, 4, 6, 8, 10, 12]);
let r = plus().dyad(&Array::scalar_i64(10), &x, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 3]);
assert_eq!(ints(&r), vec![11, 12, 13, 14, 15, 16]);
let r = plus().dyad(&x, &Array::scalar_i64(10), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![11, 12, 13, 14, 15, 16]);
}
#[test]
fn vector_length_mismatch_is_a_length_error() {
ctx!(c);
let e = plus()
.dyad(&Array::from_i64(vec![1, 2, 3]), &Array::from_i64(vec![1, 2, 3, 4, 5]), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::Length);
assert!(e.msg.contains("left shape 3"), "{}", e.msg);
assert!(e.msg.contains("right shape 5"), "{}", e.msg);
assert!(e.notes[0].contains("axis 0"), "{:?}", e.notes);
}
#[test]
fn diverging_matrix_frames_name_the_axis() {
ctx!(c);
let e = plus()
.dyad(&mat(2, 3, vec![0; 6]), &mat(2, 4, vec![0; 8]), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::Shape);
assert!(e.notes[0].contains("axis 1"), "{:?}", e.notes);
}
#[test]
fn dyadic_rank_pairs_rows_with_the_whole_right_argument() {
ctx!(c);
let v = Verb::Rank(b(plus()), [0, 1, 1]);
let r = v
.dyad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &Array::from_i64(vec![10, 20, 30]), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![2, 3]);
assert_eq!(ints(&r), vec![11, 22, 33, 14, 25, 36]);
}
#[test]
fn surplus_frame_axes_repeat_the_shorter_frames_cells() {
ctx!(c);
let v = Verb::Rank(b(head_v()), [0, 0, 1]);
let x = mat(2, 2, vec![1, 1, 2, 2]);
let y = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
let r = v.dyad(&x, &y, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 2, 2]);
assert_eq!(ints(&r), vec![1, 0, 1, 0, 4, 5, 4, 5]);
}
#[test]
fn an_empty_frame_pairs_its_single_cell_with_every_other_cell() {
ctx!(c, Agreement::ExactOrScalar);
let v = Verb::Rank(b(head_v()), [0, 0, 1]);
let x = Array::from_i64(vec![1, 2]);
let y = Array::from_i64(vec![7, 8, 9]);
let r = v.dyad(&x, &y, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 2]);
assert_eq!(ints(&r), vec![7, 0, 7, 8]);
}
#[test]
fn negative_rank_leaves_frame_axes() {
ctx!(c);
let v = Verb::Rank(b(dollar()), [-1, -1, -1]);
let r = v.monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 1]);
assert_eq!(ints(&r), vec![3, 3]);
}
#[test]
fn effective_rank_clamps_and_counts_back() {
assert_eq!(effective_rank(0, 3), 0);
assert_eq!(effective_rank(2, 1), 1);
assert_eq!(effective_rank(RANK_INF, 4), 4);
assert_eq!(effective_rank(-1, 3), 2);
assert_eq!(effective_rank(-5, 3), 0);
}
#[test]
fn reduction_folds_right_to_left() {
ctx!(c);
let r = Verb::Reduce(b(minus()))
.monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp())
.unwrap();
assert!(r.shape.is_empty());
assert_eq!(ints(&r), vec![2]);
}
#[test]
fn reduction_of_one_item_and_of_a_scalar() {
ctx!(c);
let r = Verb::Reduce(b(plus()))
.monad(&Array::from_i64(vec![7]), &mut c, sp())
.unwrap();
assert!(r.shape.is_empty());
assert_eq!(ints(&r), vec![7]);
let r = Verb::Reduce(b(plus())).monad(&Array::scalar_i64(7), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![7]);
}
#[test]
fn reduction_runs_along_the_leading_axis() {
ctx!(c);
let r = Verb::Reduce(b(plus()))
.monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![3]);
assert_eq!(ints(&r), vec![5, 7, 9]);
}
#[test]
fn rank_wrapped_reduction_sums_the_last_axis() {
ctx!(c);
let v = Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]);
let r = v.monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2]);
assert_eq!(ints(&r), vec![6, 15]);
}
#[test]
fn empty_reduction_uses_the_identity_cell() {
ctx!(c);
let empty = Array::new(vec![0, 2], Data::I64(vec![].into()));
let r = Verb::Reduce(b(plus())).monad(&empty, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2]);
assert_eq!(ints(&r), vec![0, 0]);
let r = Verb::Reduce(b(times())).monad(&empty, &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![1, 1]);
let r = Verb::Reduce(b(floor_v())).monad(&empty, &mut c, sp()).unwrap();
assert!(floats(&r).iter().all(|&x| x == f64::INFINITY));
let r = Verb::Reduce(b(ceil_v())).monad(&empty, &mut c, sp()).unwrap();
assert!(floats(&r).iter().all(|&x| x == f64::NEG_INFINITY));
let r = Verb::Reduce(b(minus())).monad(&empty, &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![0, 0]);
let r = Verb::Reduce(b(pct())).monad(&empty, &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![1, 1]);
let r = Verb::Reduce(b(eq_v())).monad(&empty, &mut c, sp()).unwrap();
assert_eq!(bools(&r), vec![1, 1]);
let r = Verb::Reduce(b(plus()))
.monad(&Array::empty(DType::I64), &mut c, sp())
.unwrap();
assert!(r.shape.is_empty());
assert_eq!(ints(&r), vec![0]);
}
#[test]
fn empty_reduction_without_an_identity_is_a_domain_error() {
ctx!(c);
let v = Verb::Hook(b(plus()), b(minus()));
let e = Verb::Reduce(b(v)).monad(&Array::empty(DType::I64), &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
assert!(e.msg.contains("identity"), "{}", e.msg);
}
#[test]
fn reduction_with_a_non_primitive_verb_uses_the_general_fold() {
ctx!(c);
let v = Verb::Reduce(b(Verb::Hook(b(plus()), b(minus()))));
let r = v.monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![2]);
}
#[test]
fn dyadic_reduction_is_the_table() {
ctx!(c);
let v = Verb::Reduce(b(plus()));
let r = v
.dyad(&Array::scalar_i64(2), &Array::from_i64(vec![1, 2, 3]), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![3]);
assert_eq!(ints(&r), vec![3, 4, 5]);
let r = v
.dyad(&Array::from_i64(vec![1, 2, 3]), &Array::from_i64(vec![10, 20]), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![3, 2]);
assert_eq!(ints(&r), vec![11, 21, 12, 22, 13, 23]);
let cat = Verb::Reduce(b(inf_prim(",", MonadOp::Ravel, DyadOp::AppendLeading)));
let r = cat
.dyad(&Array::from_i64(vec![1, 2]), &Array::from_i64(vec![3, 4]), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![4]);
assert_eq!(ints(&r), vec![1, 2, 3, 4]);
}
#[test]
fn integer_overflow_promotes_the_whole_result_to_float() {
ctx!(c);
let r = plus()
.dyad(&Array::from_i64(vec![1, i64::MAX]), &Array::scalar_i64(1), &mut c, sp())
.unwrap();
assert_eq!(r.dtype(), DType::F64);
let v = floats(&r);
assert!(close(v[0], 2.0));
assert!(close(v[1], i64::MAX as f64 + 1.0));
let r = plus()
.dyad(&Array::from_i64(vec![1, 2]), &Array::scalar_i64(1), &mut c, sp())
.unwrap();
assert_eq!(r.dtype(), DType::I64);
}
#[test]
fn reduction_overflow_promotes_too() {
ctx!(c);
let r = Verb::Reduce(b(plus()))
.monad(&Array::from_i64(vec![i64::MAX, i64::MAX]), &mut c, sp())
.unwrap();
assert_eq!(r.dtype(), DType::F64);
assert!(close(floats(&r)[0], 2.0 * i64::MAX as f64));
}
#[test]
fn booleans_widen_to_integers_in_arithmetic() {
ctx!(c);
let bits = Array::new(vec![3], Data::Bool(vec![1, 0, 1].into()));
let r = plus().dyad(&bits, &bits, &mut c, sp()).unwrap();
assert_eq!(r.dtype(), DType::I64);
assert_eq!(ints(&r), vec![2, 0, 2]);
}
#[test]
fn j_division_is_float_and_survives_zero() {
ctx!(c);
let r = pct()
.dyad(&Array::from_i64(vec![1, -1, 0, 6]), &Array::from_i64(vec![0, 0, 0, 4]), &mut c, sp())
.unwrap();
let v = floats(&r);
assert_eq!(v[0], f64::INFINITY);
assert_eq!(v[1], f64::NEG_INFINITY);
assert_eq!(v[2], 0.0);
assert!(close(v[3], 1.5));
}
#[test]
fn apl_division_by_zero_is_a_domain_error_except_zero_by_zero() {
ctx!(c, Agreement::ExactOrScalar);
let r = div_apl()
.dyad(&Array::scalar_i64(0), &Array::scalar_i64(0), &mut c, sp())
.unwrap();
assert!(close(floats(&r)[0], 1.0));
let e = div_apl()
.dyad(&Array::scalar_i64(1), &Array::scalar_i64(0), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
assert!(e.msg.contains("division by zero"), "{}", e.msg);
let r = div_apl()
.dyad(&Array::scalar_i64(6), &Array::scalar_i64(4), &mut c, sp())
.unwrap();
assert!(close(floats(&r)[0], 1.5));
}
#[test]
fn reciprocal_of_zero_is_infinite() {
ctx!(c);
let r = pct().monad(&Array::from_i64(vec![0, 2]), &mut c, sp()).unwrap();
let v = floats(&r);
assert_eq!(v[0], f64::INFINITY);
assert!(close(v[1], 0.5));
}
#[test]
fn residue_takes_the_sign_of_the_left_argument() {
ctx!(c);
let x = Array::from_i64(vec![3, 3, -3, -3, 0]);
let y = Array::from_i64(vec![5, -5, 5, -5, 5]);
let r = residue_v().dyad(&x, &y, &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![2, 1, -1, -2, 5]);
let r = residue_v()
.dyad(&Array::from_f64(vec![2.5]), &Array::from_f64(vec![7.0]), &mut c, sp())
.unwrap();
assert!(close(floats(&r)[0], 2.0));
}
#[test]
fn power_stays_integral_when_it_can() {
ctx!(c);
let r = pow_v()
.dyad(&Array::from_i64(vec![2, 0, 5]), &Array::from_i64(vec![10, 0, 1]), &mut c, sp())
.unwrap();
assert_eq!(r.dtype(), DType::I64);
assert_eq!(ints(&r), vec![1024, 1, 5]);
let r = pow_v()
.dyad(&Array::from_i64(vec![2, 4]), &Array::from_i64(vec![-1, 2]), &mut c, sp())
.unwrap();
assert_eq!(r.dtype(), DType::F64);
assert!(close(floats(&r)[0], 0.5));
assert!(close(floats(&r)[1], 16.0));
let r = pow_v()
.dyad(&Array::scalar_i64(10), &Array::scalar_i64(30), &mut c, sp())
.unwrap();
assert_eq!(r.dtype(), DType::F64);
}
#[test]
fn comparisons_yield_booleans() {
ctx!(c);
let r = lt_v()
.dyad(&Array::from_i64(vec![1, 2, 3]), &Array::scalar_i64(2), &mut c, sp())
.unwrap();
assert_eq!(bools(&r), vec![1, 0, 0]);
let r = eq_v()
.dyad(&Array::from_f64(vec![1.0, 2.0]), &Array::from_i64(vec![1, 3]), &mut c, sp())
.unwrap();
assert_eq!(bools(&r), vec![1, 0]);
}
#[test]
fn characters_compare_but_do_not_add() {
ctx!(c);
let a = Array::from_chars(vec!['a', 'b']);
let bb = Array::from_chars(vec!['a', 'c']);
assert_eq!(bools(&eq_v().dyad(&a, &bb, &mut c, sp()).unwrap()), vec![1, 0]);
let e = plus().dyad(&a, &bb, &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Type);
assert!(e.msg.contains("characters"), "{}", e.msg);
let e = lt_v().dyad(&a, &bb, &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Type);
let e = plus().dyad(&a, &Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Type);
assert!(e.msg.contains("character"), "{}", e.msg);
let e = plus().monad(&a, &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Type);
}
#[test]
fn floor_and_ceiling_return_integers_when_they_fit() {
ctx!(c);
let r = floor_v().monad(&Array::from_f64(vec![1.5, -1.5]), &mut c, sp()).unwrap();
assert_eq!(r.dtype(), DType::I64);
assert_eq!(ints(&r), vec![1, -2]);
let r = ceil_v().monad(&Array::from_f64(vec![1.5, -1.5]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![2, -1]);
let r = floor_v().monad(&Array::from_f64(vec![1e30]), &mut c, sp()).unwrap();
assert_eq!(r.dtype(), DType::F64);
let r = floor_v().monad(&Array::from_i64(vec![3]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![3]);
}
#[test]
fn logical_negation_needs_zero_or_one() {
ctx!(c);
let r = not_v().monad(&Array::from_i64(vec![0, 1]), &mut c, sp()).unwrap();
assert_eq!(bools(&r), vec![1, 0]);
let e = not_v().monad(&Array::from_i64(vec![2]), &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
}
#[test]
fn signum_abs_and_negation_pick_their_types() {
ctx!(c);
let r = times().monad(&Array::from_i64(vec![-3, 0, 9]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![-1, 0, 1]);
let r = times().monad(&Array::from_f64(vec![-3.0, 0.0, 9.0]), &mut c, sp()).unwrap();
assert_eq!(floats(&r), vec![-1.0, 0.0, 1.0]);
let r = residue_v().monad(&Array::from_i64(vec![-3, 3]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![3, 3]);
let bits = Array::new(vec![2], Data::Bool(vec![0, 1].into()));
let r = minus().monad(&bits, &mut c, sp()).unwrap();
assert_eq!(r.dtype(), DType::I64);
assert_eq!(ints(&r), vec![0, -1]);
}
#[test]
fn square_root_of_a_negative_number_is_complex() {
ctx!(c);
let r = sqrt_v().monad(&Array::from_i64(vec![9]), &mut c, sp()).unwrap();
assert!(close(floats(&r)[0], 3.0));
let r = sqrt_v().monad(&Array::from_i64(vec![-4]), &mut c, sp()).unwrap();
assert_eq!(r.dtype(), DType::Complex);
assert_eq!(r.as_complex_slice().expect("complex data"), &[[0.0, 2.0]]);
}
#[test]
fn shape_tally_and_ravel() {
ctx!(c);
let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
let r = dollar().monad(&m, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2]);
assert_eq!(ints(&r), vec![2, 3]);
let r = pound().monad(&m, &mut c, sp()).unwrap();
assert!(r.shape.is_empty());
assert_eq!(ints(&r), vec![2]);
let r = pound().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![1]);
let r = comma().monad(&m, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![6]);
assert_eq!(ints(&r), vec![1, 2, 3, 4, 5, 6]);
}
#[test]
fn transpose_reverses_the_axes() {
ctx!(c);
let r = transpose_v().monad(&mat(2, 3, vec![1, 2, 3, 4, 5, 6]), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![3, 2]);
assert_eq!(ints(&r), vec![1, 4, 2, 5, 3, 6]);
let a = Array::new(vec![2, 1, 3], Data::I64(vec![1, 2, 3, 4, 5, 6].into()));
let r = transpose_v().monad(&a, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![3, 1, 2]);
assert_eq!(ints(&r), vec![1, 4, 2, 5, 3, 6]);
let v = Array::from_i64(vec![1, 2]);
assert_eq!(transpose_v().monad(&v, &mut c, sp()).unwrap(), v);
}
#[test]
fn head_and_behead() {
ctx!(c);
let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
let r = head_v().monad(&m, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![3]);
assert_eq!(ints(&r), vec![1, 2, 3]);
let r = behead_v().monad(&m, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![1, 3]);
assert_eq!(ints(&r), vec![4, 5, 6]);
let e = Array::new(vec![0, 2], Data::I64(vec![].into()));
let r = head_v().monad(&e, &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2]);
assert_eq!(ints(&r), vec![0, 0]);
assert_eq!(behead_v().monad(&e, &mut c, sp()).unwrap(), e);
assert_eq!(head_v().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap().shape, Vec::<usize>::new());
let err = behead_v().monad(&Array::scalar_i64(5), &mut c, sp()).unwrap_err();
assert_eq!(err.kind, ErrorKind::Domain);
}
#[test]
fn iota_fills_a_shape_and_reverses_negative_axes() {
ctx!(c);
let r = iota().monad(&Array::from_i64(vec![2, 3]), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 3]);
assert_eq!(ints(&r), vec![0, 1, 2, 3, 4, 5]);
let r = iota().monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![3]);
assert_eq!(ints(&r), vec![0, 1, 2]);
let r = iota().monad(&Array::scalar_i64(-3), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![2, 1, 0]);
let r = iota().monad(&Array::from_i64(vec![2, -3]), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 3]);
assert_eq!(ints(&r), vec![2, 1, 0, 5, 4, 3]);
let r = iota().monad(&Array::from_i64(vec![-2, 3]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![3, 4, 5, 0, 1, 2]);
let r = iota().monad(&Array::scalar_i64(0), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![0]);
assert!(ints(&r).is_empty());
let e = iota().monad(&Array::from_f64(vec![1.5]), &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
let e = iota().monad(&mat(1, 1, vec![1]), &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Rank);
}
#[test]
fn apl_iota_starts_at_the_index_origin() {
ctx!(c, Agreement::ExactOrScalar);
let r = iota_apl(1).monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![1, 2, 3]);
let r = iota_apl(0).monad(&Array::scalar_i64(3), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![0, 1, 2]);
let e = iota_apl(1).monad(&Array::scalar_i64(-1), &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
let r = iota_apl(1).monad(&Array::from_i64(vec![2, 3]), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![2, 3]);
assert_eq!(ints(&r.as_boxes().expect("boxed")[4]), vec![2, 2]);
}
#[test]
fn reshape_cycles_the_ravel() {
ctx!(c);
let r = dollar()
.dyad(&Array::from_i64(vec![2, 3]), &Array::from_i64(vec![1, 2]), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![2, 3]);
assert_eq!(ints(&r), vec![1, 2, 1, 2, 1, 2]);
let r = dollar()
.dyad(&Array::scalar_i64(3), &Array::from_i64(vec![7]), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![3]);
assert_eq!(ints(&r), vec![7, 7, 7]);
let r = dollar()
.dyad(&Array::scalar_i64(2), &Array::from_chars(vec!['a', 'b', 'c']), &mut c, sp())
.unwrap();
assert_eq!(r.dtype(), DType::Char);
let e = dollar()
.dyad(&Array::scalar_i64(2), &Array::empty(DType::I64), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::Length);
assert!(e.msg.contains("empty"), "{}", e.msg);
let r = dollar()
.dyad(&Array::scalar_i64(0), &Array::empty(DType::I64), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![0]);
let e = dollar()
.dyad(&Array::scalar_i64(-1), &Array::from_i64(vec![1]), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
}
#[test]
fn take_from_both_ends_and_beyond() {
ctx!(c);
let v = Array::from_i64(vec![1, 2, 3, 4]);
let take = |x: Array, y: &Array, c: &mut Ctx<'_>| head_v().dyad(&x, y, c, sp()).unwrap();
assert_eq!(ints(&take(Array::scalar_i64(2), &v, &mut c)), vec![1, 2]);
assert_eq!(ints(&take(Array::scalar_i64(-2), &v, &mut c)), vec![3, 4]);
let short = Array::from_i64(vec![1, 2, 3]);
assert_eq!(ints(&take(Array::scalar_i64(6), &short, &mut c)), vec![1, 2, 3, 0, 0, 0]);
assert_eq!(ints(&take(Array::scalar_i64(-6), &short, &mut c)), vec![0, 0, 0, 1, 2, 3]);
let r = take(Array::scalar_i64(2), &Array::scalar_i64(5), &mut c);
assert_eq!(r.shape, vec![2]);
assert_eq!(ints(&r), vec![5, 0]);
let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
let r = take(Array::scalar_i64(1), &m, &mut c);
assert_eq!(r.shape, vec![1, 3]);
assert_eq!(ints(&r), vec![1, 2, 3]);
let r = take(Array::scalar_i64(-1), &m, &mut c);
assert_eq!(ints(&r), vec![4, 5, 6]);
let r = take(Array::from_i64(vec![2, 2]), &m, &mut c);
assert_eq!(r.shape, vec![2, 2]);
assert_eq!(ints(&r), vec![1, 2, 4, 5]);
let r = take(Array::from_i64(vec![3, -2]), &m, &mut c);
assert_eq!(r.shape, vec![3, 2]);
assert_eq!(ints(&r), vec![2, 3, 5, 6, 0, 0]);
let r = head_v()
.dyad(&Array::scalar_i64(3), &Array::from_chars(vec!['a']), &mut c, sp())
.unwrap();
assert_eq!(r.data, Data::Char(vec!['a', ' ', ' '].into()));
let e = head_v()
.dyad(&Array::from_i64(vec![1, 1]), &Array::from_i64(vec![1, 2]), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::Length);
let r = head_v()
.dyad(&Array::from_i64(vec![1, 2]), &Array::scalar_i64(5), &mut c, sp())
.unwrap();
assert_eq!(r.shape, vec![1, 2]);
assert_eq!(ints(&r), vec![5, 0]);
}
#[test]
fn drop_from_both_ends_and_beyond() {
ctx!(c);
let v = Array::from_i64(vec![1, 2, 3]);
let drop = |x: Array, y: &Array, c: &mut Ctx<'_>| behead_v().dyad(&x, y, c, sp()).unwrap();
assert_eq!(ints(&drop(Array::scalar_i64(1), &v, &mut c)), vec![2, 3]);
assert_eq!(ints(&drop(Array::scalar_i64(-1), &v, &mut c)), vec![1, 2]);
let r = drop(Array::scalar_i64(5), &v, &mut c);
assert_eq!(r.shape, vec![0]);
assert!(ints(&r).is_empty());
let m = mat(2, 3, vec![1, 2, 3, 4, 5, 6]);
let r = drop(Array::scalar_i64(1), &m, &mut c);
assert_eq!(r.shape, vec![1, 3]);
assert_eq!(ints(&r), vec![4, 5, 6]);
let r = drop(Array::from_i64(vec![0, -1]), &m, &mut c);
assert_eq!(r.shape, vec![2, 2]);
assert_eq!(ints(&r), vec![1, 2, 4, 5]);
}
#[test]
fn cells_of_unequal_shapes_are_padded_with_fills() {
ctx!(c);
let v = Verb::Rank(b(iota()), [0, 0, 0]);
let r = v.monad(&Array::from_i64(vec![1, 2, 3]), &mut c, sp()).unwrap();
assert_eq!(r.shape, vec![3, 3]);
assert_eq!(ints(&r), vec![0, 0, 0, 0, 1, 0, 0, 1, 2]);
}
#[test]
fn framing_aligns_lower_rank_cells_at_the_trailing_axes() {
let cells = vec![Array::from_i64(vec![1, 2]), mat(2, 2, vec![1, 2, 3, 4])];
let r = assemble(&[2], cells, sp()).unwrap();
assert_eq!(r.shape, vec![2, 2, 2]);
assert_eq!(ints(&r), vec![1, 2, 0, 0, 1, 2, 3, 4]);
}
#[test]
fn framing_promotes_cell_types() {
let cells = vec![Array::from_i64(vec![1]), Array::from_f64(vec![2.5])];
let r = assemble(&[2], cells, sp()).unwrap();
assert_eq!(r.dtype(), DType::F64);
assert_eq!(floats(&r), vec![1.0, 2.5]);
let cells = vec![Array::from_i64(vec![1]), Array::from_chars(vec!['a'])];
let e = assemble(&[2], cells, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Type);
}
#[test]
fn framing_over_an_empty_frame_yields_an_empty_result() {
let r = assemble(&[0], Vec::new(), sp()).unwrap();
assert_eq!(r.shape, vec![0]);
assert_eq!(r.count(), 0);
}
#[test]
fn fork_applies_both_tines() {
ctx!(c);
let v = Verb::Fork(b(Verb::Reduce(b(plus()))), b(pct()), b(pound()));
let r = v.monad(&Array::from_i64(vec![1, 2, 3, 4]), &mut c, sp()).unwrap();
assert!(close(floats(&r)[0], 2.5));
let v = Verb::Fork(b(minus()), b(plus()), b(plus()));
let r = v
.dyad(&Array::from_i64(vec![5]), &Array::from_i64(vec![3]), &mut c, sp())
.unwrap();
assert_eq!(ints(&r), vec![10]);
}
#[test]
fn noun_fork_supplies_a_constant_left_argument() {
ctx!(c);
let v = Verb::NounFork(Array::scalar_i64(10), b(minus()), b(right_v()));
let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![9, 8]);
let r = v
.dyad(&Array::scalar_i64(0), &Array::from_i64(vec![1, 2]), &mut c, sp())
.unwrap();
assert_eq!(ints(&r), vec![9, 8]);
}
#[test]
fn hook_reuses_its_right_argument() {
ctx!(c);
let v = Verb::Hook(b(plus()), b(minus()));
let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![0, 0]);
let r = v
.dyad(&Array::from_i64(vec![10]), &Array::from_i64(vec![3]), &mut c, sp())
.unwrap();
assert_eq!(ints(&r), vec![7]);
}
#[test]
fn atop_composes() {
ctx!(c);
let v = Verb::Atop(b(minus()), b(plus()));
let r = v.monad(&Array::from_i64(vec![1, 2]), &mut c, sp()).unwrap();
assert_eq!(ints(&r), vec![-1, -2]);
let r = v
.dyad(&Array::from_i64(vec![1]), &Array::from_i64(vec![2]), &mut c, sp())
.unwrap();
assert_eq!(ints(&r), vec![-3]);
}
#[test]
fn trains_apply_to_the_whole_argument() {
assert_eq!(Verb::Hook(b(plus()), b(minus())).ranks(), [RANK_INF; 3]);
assert_eq!(Verb::Reduce(b(plus())).ranks(), [RANK_INF; 3]);
}
#[test]
fn absent_and_unwritten_meanings_are_reported_differently() {
ctx!(c);
let e = eq_v().monad(&Array::scalar_i64(1), &mut c, sp()).unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
assert!(e.msg.contains("no monadic meaning"), "{}", e.msg);
let e = not_v()
.dyad(&Array::scalar_i64(1), &Array::scalar_i64(1), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::Domain);
assert!(e.msg.contains("no dyadic meaning"), "{}", e.msg);
let e = pound()
.dyad(&Array::scalar_i64(1), &Array::scalar_i64(1), &mut c, sp())
.unwrap_err();
assert_eq!(e.kind, ErrorKind::NotYet);
assert!(e.msg.contains("copy"), "{}", e.msg);
let _ = echo_v();
}
fn seq_par<T: Send>(f: impl Fn() -> T + Sync + Send) -> (T, T) {
(par::with_threads(1, &f), par::with_threads(4, &f))
}
fn noise(n: usize) -> Vec<f64> {
let mut x = 0x2545_f491_4f6c_dd1du64;
(0..n)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
(x >> 11) as f64 / (1u64 << 53) as f64 - 0.5
})
.collect()
}
fn f64_mat(rows: usize, cols: usize) -> Array {
Array::new(vec![rows, cols], Data::F64(noise(rows * cols).into()))
}
const BIG: usize = 200_000;
#[test]
fn an_elementwise_dyad_splits_into_the_same_result() {
let x = Array::from_f64(noise(BIG));
let y = Array::from_f64(noise(BIG).iter().map(|v| v + 0.25).collect());
let (one, many) = seq_par(|| {
ctx!(c);
times().dyad(&x, &y, &mut c, sp()).unwrap()
});
assert_eq!(floats(&one), floats(&many));
let (one, many) = seq_par(|| {
ctx!(c);
plus().dyad(&Array::scalar_f64(0.5), &y, &mut c, sp()).unwrap()
});
assert_eq!(floats(&one), floats(&many));
}
#[test]
fn an_elementwise_dyad_that_overflows_widens_the_same_way() {
let mut v = vec![1i64; BIG];
v[BIG - 3] = i64::MAX;
let x = Array::from_i64(v);
let (one, many) = seq_par(|| {
ctx!(c);
plus().dyad(&x, &x, &mut c, sp()).unwrap()
});
assert_eq!(one.dtype(), DType::F64);
assert_eq!(floats(&one), floats(&many));
}
#[test]
fn an_elementwise_monad_splits_into_the_same_result() {
let y = Array::from_f64(noise(BIG));
for v in [minus(), sqrt_v(), floor_v(), pct()] {
let (one, many) = seq_par(|| {
ctx!(c);
v.monad(&Array::from_f64(y.as_f64_slice().unwrap().iter().map(|x| x.abs()).collect()), &mut c, sp())
.unwrap()
});
assert_eq!(one.data, many.data, "{}", v.name());
}
}
#[test]
fn monadic_cells_run_in_parallel_and_frame_in_order() {
let y = f64_mat(400, 512);
let v = Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]);
let (one, many) = seq_par(|| {
ctx!(c);
v.monad(&y, &mut c, sp()).unwrap()
});
assert_eq!(one.shape, vec![400]);
assert_eq!(floats(&one), floats(&many));
}
#[test]
fn dyadic_cells_run_in_parallel_and_frame_in_order() {
let x = f64_mat(400, 512);
let y = f64_mat(400, 512);
let v = Verb::Rank(b(plus()), [1, 1, 1]);
let (one, many) = seq_par(|| {
ctx!(c);
v.dyad(&x, &y, &mut c, sp()).unwrap()
});
assert_eq!(one.shape, vec![400, 512]);
assert_eq!(floats(&one), floats(&many));
}
#[test]
fn a_verb_that_writes_output_is_not_pure() {
assert!(plus().is_pure());
assert!(Verb::Rank(b(Verb::Reduce(b(plus()))), [1, 1, 1]).is_pure());
assert!(!echo_v().is_pure());
assert!(!Verb::Rank(b(Verb::Atop(b(echo_v()), b(plus()))), [1, 1, 1]).is_pure());
}
#[test]
fn an_impure_verb_keeps_its_cells_in_order() {
let y = Array::new(vec![16, 8192], Data::I64((0..16 * 8192).collect::<Vec<i64>>().into()));
let v = Verb::Rank(b(Verb::Atop(b(echo_v()), b(head_v()))), [1, 1, 1]);
let mut seen: Vec<i64> = Vec::new();
let mut sink = |s: &str| {
if let Some(first) = s.split_whitespace().next() && let Ok(n) = first.parse::<i64>() {
seen.push(n);
}
};
let mut env = Env::new(Vec::new());
let mut c = Ctx {
cfg: EvalCfg {
agreement: Agreement::LeadingPrefix,
fmt: FmtOpts::J,
tol: Tol::J,
rules: Rules::default(),
},
out: &mut sink,
inp: None,
env: &mut env,
device: None,
};
v.monad(&y, &mut c, sp()).unwrap();
assert_eq!(seen, (0..16).map(|i| i * 8192).collect::<Vec<i64>>());
}
#[test]
fn a_wide_item_reduce_folds_every_column_in_order() {
let y = f64_mat(300, 512);
for v in [plus(), minus(), floor_v()] {
let (one, many) = seq_par(|| {
ctx!(c);
Verb::Reduce(b(v.clone())).monad(&y, &mut c, sp()).unwrap()
});
assert_eq!(one.shape, vec![512]);
assert_eq!(floats(&one), floats(&many), "{}", v.name());
}
}
#[test]
fn a_wide_item_integer_reduce_is_exact() {
let n = 300;
let m = 512;
let y = Array::new(
vec![n, m],
Data::I64((0..(n * m) as i64).map(|i| i % 977 - 400).collect::<Vec<i64>>().into()),
);
let (one, many) = seq_par(|| {
ctx!(c);
Verb::Reduce(b(minus())).monad(&y, &mut c, sp()).unwrap()
});
assert_eq!(ints(&one), ints(&many));
}
#[test]
fn a_narrow_item_reduce_chunks_the_items() {
let y = f64_mat(300_000, 8);
let (one, many) = seq_par(|| {
ctx!(c);
Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
});
assert_eq!(one.shape, vec![8]);
for (p, q) in floats(&one).iter().zip(floats(&many)) {
assert!((p - q).abs() <= 1e-12 * p.abs().max(1.0), "{p} vs {q}");
}
let ints_y = Array::new(
vec![300_000, 8],
Data::I64((0..300_000 * 8).map(|i| (i % 101) as i64 - 50).collect::<Vec<i64>>().into()),
);
let (one, many) = seq_par(|| {
ctx!(c);
Verb::Reduce(b(plus())).monad(&ints_y, &mut c, sp()).unwrap()
});
assert_eq!(ints(&one), ints(&many));
}
#[test]
fn a_vector_reduce_folds_the_flat_buffer() {
let y = Array::from_f64(noise(BIG * 4));
let (one, many) = seq_par(|| {
ctx!(c);
Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
});
let (p, q) = (floats(&one)[0], floats(&many)[0]);
assert!((p - q).abs() <= 1e-12 * p.abs().max(1.0), "{p} vs {q}");
let ints_y = Array::from_i64((0..BIG as i64 * 4).map(|i| i % 1009 - 500).collect());
for v in [plus(), minus(), ceil_v()] {
let (one, many) = seq_par(|| {
ctx!(c);
Verb::Reduce(b(v.clone())).monad(&ints_y, &mut c, sp()).unwrap()
});
assert_eq!(ints(&one), ints(&many), "{}", v.name());
}
}
#[test]
fn a_reduce_that_overflows_falls_back_to_the_sequential_widening() {
let mut v: Vec<i64> = vec![1; BIG];
v[7] = i64::MAX;
let y = Array::from_i64(v);
let (one, many) = seq_par(|| {
ctx!(c);
Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
});
assert_eq!(one.dtype(), DType::F64);
assert_eq!(floats(&one), floats(&many));
}
#[test]
fn a_boolean_reduce_matches_the_sequential_promotion() {
let n = BIG;
let y = Array::new(
vec![n],
Data::Bool((0..n).map(|i| (i % 3 == 0) as u8).collect::<Vec<u8>>().into()),
);
let (one, many) = seq_par(|| {
ctx!(c);
Verb::Reduce(b(plus())).monad(&y, &mut c, sp()).unwrap()
});
assert_eq!(one.dtype(), DType::I64);
assert_eq!(ints(&one), ints(&many));
assert_eq!(ints(&one)[0], n.div_ceil(3) as i64);
}
}