libjay 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {libjay-0.2.0 → libjay-0.3.0}/Cargo.lock +6 -5
- {libjay-0.2.0 → libjay-0.3.0}/Cargo.toml +1 -1
- {libjay-0.2.0 → libjay-0.3.0}/PKG-INFO +1 -1
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/array.rs +183 -9
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/complex.rs +19 -2
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/device/codegen.rs +28 -1
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/device/mod.rs +11 -3
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/dtype.rs +9 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/error.rs +13 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/explain.rs +40 -3
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/fmt.rs +238 -20
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/frontend/apl.rs +619 -143
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/frontend/j.rs +108 -37
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/frontend/mod.rs +174 -21
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/fuse.rs +321 -104
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/ir.rs +75 -3
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/lib.rs +3 -1
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/par.rs +32 -2
- libjay-0.3.0/crates/libjay/src/sparse.rs +388 -0
- libjay-0.3.0/crates/libjay/src/symbol.rs +131 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/verb.rs +5105 -978
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-python/src/data.rs +19 -2
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-python/src/lib.rs +102 -1
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-testkit/src/corpus.rs +53 -2
- libjay-0.3.0/crates/libjay-testkit/src/dialect.rs +243 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-testkit/src/eval.rs +28 -3
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-testkit/src/lib.rs +1 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-testkit/src/replay.rs +23 -7
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-testkit/src/snapshot.rs +2 -2
- {libjay-0.2.0 → libjay-0.3.0}/python/jay/__init__.py +7 -0
- {libjay-0.2.0 → libjay-0.3.0}/python/jay/_cli.py +14 -1
- libjay-0.3.0/python/jay/lang.py +145 -0
- libjay-0.2.0/python/jay/lang.py +0 -85
- {libjay-0.2.0 → libjay-0.3.0}/LICENSE +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/Cargo.toml +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/LICENSE +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/README.md +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/device/gpu.rs +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/exact.rs +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/gerund.rs +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/limits.rs +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/rng.rs +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay/src/simd.rs +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-python/Cargo.toml +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-testkit/Cargo.toml +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/crates/libjay-testkit/src/compare.rs +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/pyproject.toml +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/python/README.md +0 -0
- {libjay-0.2.0 → libjay-0.3.0}/python/jay/_tstring.py +0 -0
|
@@ -494,7 +494,7 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
|
494
494
|
|
|
495
495
|
[[package]]
|
|
496
496
|
name = "libjay"
|
|
497
|
-
version = "0.
|
|
497
|
+
version = "0.3.0"
|
|
498
498
|
dependencies = [
|
|
499
499
|
"libjay-testkit",
|
|
500
500
|
"multiversion",
|
|
@@ -510,22 +510,23 @@ dependencies = [
|
|
|
510
510
|
|
|
511
511
|
[[package]]
|
|
512
512
|
name = "libjay-capi"
|
|
513
|
-
version = "0.
|
|
513
|
+
version = "0.3.0"
|
|
514
514
|
dependencies = [
|
|
515
515
|
"libjay",
|
|
516
516
|
]
|
|
517
517
|
|
|
518
518
|
[[package]]
|
|
519
519
|
name = "libjay-devtools"
|
|
520
|
-
version = "0.
|
|
520
|
+
version = "0.3.0"
|
|
521
521
|
dependencies = [
|
|
522
|
+
"libjay",
|
|
522
523
|
"libjay-testkit",
|
|
523
524
|
"rayon",
|
|
524
525
|
]
|
|
525
526
|
|
|
526
527
|
[[package]]
|
|
527
528
|
name = "libjay-python"
|
|
528
|
-
version = "0.
|
|
529
|
+
version = "0.3.0"
|
|
529
530
|
dependencies = [
|
|
530
531
|
"arrow-array",
|
|
531
532
|
"arrow-buffer",
|
|
@@ -537,7 +538,7 @@ dependencies = [
|
|
|
537
538
|
|
|
538
539
|
[[package]]
|
|
539
540
|
name = "libjay-testkit"
|
|
540
|
-
version = "0.
|
|
541
|
+
version = "0.3.0"
|
|
541
542
|
dependencies = [
|
|
542
543
|
"libjay",
|
|
543
544
|
]
|
|
@@ -436,6 +436,10 @@ pub enum Data {
|
|
|
436
436
|
/// pair of Arrow float columns all share.
|
|
437
437
|
Complex(Buf<Cx>),
|
|
438
438
|
Char(Buf<char>),
|
|
439
|
+
/// Symbols: every element is an index into the process-wide symbol
|
|
440
|
+
/// table (see [`crate::symbol`]), so the buffer is as flat and as
|
|
441
|
+
/// cheap to copy as one of integers and the names live once each.
|
|
442
|
+
Symbol(Buf<crate::symbol::Id>),
|
|
439
443
|
/// Boxes: every element is a whole array. Foreign memory never holds
|
|
440
444
|
/// these, so a boxed buffer is always owned and cloning it is a
|
|
441
445
|
/// refcount bump like any other.
|
|
@@ -452,6 +456,7 @@ impl Data {
|
|
|
452
456
|
Data::F64(_) => DType::F64,
|
|
453
457
|
Data::Complex(_) => DType::Complex,
|
|
454
458
|
Data::Char(_) => DType::Char,
|
|
459
|
+
Data::Symbol(_) => DType::Symbol,
|
|
455
460
|
Data::Box(_) => DType::Box,
|
|
456
461
|
}
|
|
457
462
|
}
|
|
@@ -465,6 +470,7 @@ impl Data {
|
|
|
465
470
|
Data::F64(v) => v.len(),
|
|
466
471
|
Data::Complex(v) => v.len(),
|
|
467
472
|
Data::Char(v) => v.len(),
|
|
473
|
+
Data::Symbol(v) => v.len(),
|
|
468
474
|
Data::Box(v) => v.len(),
|
|
469
475
|
}
|
|
470
476
|
}
|
|
@@ -483,6 +489,7 @@ impl Data {
|
|
|
483
489
|
Data::F64(v) => v.is_foreign(),
|
|
484
490
|
Data::Complex(v) => v.is_foreign(),
|
|
485
491
|
Data::Char(v) => v.is_foreign(),
|
|
492
|
+
Data::Symbol(v) => v.is_foreign(),
|
|
486
493
|
Data::Box(v) => v.is_foreign(),
|
|
487
494
|
}
|
|
488
495
|
}
|
|
@@ -498,6 +505,7 @@ impl Data {
|
|
|
498
505
|
Data::F64(v) => v.owner(),
|
|
499
506
|
Data::Complex(v) => v.owner(),
|
|
500
507
|
Data::Char(v) => v.owner(),
|
|
508
|
+
Data::Symbol(v) => v.owner(),
|
|
501
509
|
Data::Box(v) => v.owner(),
|
|
502
510
|
}
|
|
503
511
|
}
|
|
@@ -511,6 +519,7 @@ impl Data {
|
|
|
511
519
|
Data::F64(v) => Data::F64(v.slice(start, end)),
|
|
512
520
|
Data::Complex(v) => Data::Complex(v.slice(start, end)),
|
|
513
521
|
Data::Char(v) => Data::Char(v.slice(start, end)),
|
|
522
|
+
Data::Symbol(v) => Data::Symbol(v.slice(start, end)),
|
|
514
523
|
Data::Box(v) => Data::Box(v.slice(start, end)),
|
|
515
524
|
}
|
|
516
525
|
}
|
|
@@ -524,6 +533,7 @@ impl Data {
|
|
|
524
533
|
DType::F64 => Data::F64(Buf::new()),
|
|
525
534
|
DType::Complex => Data::Complex(Buf::new()),
|
|
526
535
|
DType::Char => Data::Char(Buf::new()),
|
|
536
|
+
DType::Symbol => Data::Symbol(Buf::new()),
|
|
527
537
|
DType::Box => Data::Box(Buf::new()),
|
|
528
538
|
}
|
|
529
539
|
}
|
|
@@ -539,6 +549,7 @@ impl Data {
|
|
|
539
549
|
Data::F64(v) => v.push(0.0),
|
|
540
550
|
Data::Complex(v) => v.push(crate::complex::ZERO),
|
|
541
551
|
Data::Char(v) => v.push(' '),
|
|
552
|
+
Data::Symbol(v) => v.push(crate::symbol::EMPTY),
|
|
542
553
|
Data::Box(v) => v.push(Array::box_fill()),
|
|
543
554
|
}
|
|
544
555
|
}
|
|
@@ -554,6 +565,7 @@ impl Data {
|
|
|
554
565
|
(Data::F64(a), Data::F64(b)) => a.push(b[i]),
|
|
555
566
|
(Data::Complex(a), Data::Complex(b)) => a.push(b[i]),
|
|
556
567
|
(Data::Char(a), Data::Char(b)) => a.push(b[i]),
|
|
568
|
+
(Data::Symbol(a), Data::Symbol(b)) => a.push(b[i]),
|
|
557
569
|
(Data::Box(a), Data::Box(b)) => a.push(b[i].clone()),
|
|
558
570
|
_ => {}
|
|
559
571
|
}
|
|
@@ -568,6 +580,7 @@ impl Data {
|
|
|
568
580
|
(Data::F64(a), Data::F64(b)) => a.extend_from_slice(b),
|
|
569
581
|
(Data::Complex(a), Data::Complex(b)) => a.extend_from_slice(b),
|
|
570
582
|
(Data::Char(a), Data::Char(b)) => a.extend_from_slice(b),
|
|
583
|
+
(Data::Symbol(a), Data::Symbol(b)) => a.extend_from_slice(b),
|
|
571
584
|
(Data::Box(a), Data::Box(b)) => a.extend_from_slice(b),
|
|
572
585
|
_ => return false,
|
|
573
586
|
}
|
|
@@ -605,6 +618,7 @@ impl Data {
|
|
|
605
618
|
DType::F64 => by!(F64, Buf::join_fast),
|
|
606
619
|
DType::Complex => by!(Complex, Buf::join_fast),
|
|
607
620
|
DType::Char => by!(Char, Buf::join),
|
|
621
|
+
DType::Symbol => by!(Symbol, Buf::join_fast),
|
|
608
622
|
DType::Ext => by!(Ext, Buf::join),
|
|
609
623
|
DType::Rat => by!(Rat, Buf::join),
|
|
610
624
|
DType::Box => by!(Box, Buf::join),
|
|
@@ -703,6 +717,7 @@ impl Data {
|
|
|
703
717
|
DType::F64 => by!(F64, weave),
|
|
704
718
|
DType::Complex => by!(Complex, weave),
|
|
705
719
|
DType::Char => by!(Char, weave),
|
|
720
|
+
DType::Symbol => by!(Symbol, weave),
|
|
706
721
|
DType::Ext => by!(Ext, weave_cloned),
|
|
707
722
|
DType::Rat => by!(Rat, weave_cloned),
|
|
708
723
|
DType::Box => by!(Box, weave_cloned),
|
|
@@ -778,20 +793,32 @@ pub enum Layout {
|
|
|
778
793
|
/// honour [`Array::layout`] or take [`Array::to_row_major`] first; the
|
|
779
794
|
/// runtime's rule is that a value reaching a verb has already been made
|
|
780
795
|
/// row-major unless that verb asked for the other one.
|
|
796
|
+
///
|
|
797
|
+
/// A SPARSE array is the one exception to "the buffer holds every element":
|
|
798
|
+
/// `shape` is still the logical shape, but `data` holds only the stored
|
|
799
|
+
/// cells and [`crate::sparse::Sparse`] says where they sit. Only `$.`, the
|
|
800
|
+
/// display and `":` read that form; every other reader takes
|
|
801
|
+
/// [`Array::densified`] first.
|
|
781
802
|
#[derive(Clone, Debug)]
|
|
782
803
|
pub struct Array {
|
|
783
804
|
pub shape: Vec<usize>,
|
|
784
805
|
pub data: Data,
|
|
785
806
|
layout: Layout,
|
|
807
|
+
sparse: Option<crate::sparse::Handle>,
|
|
808
|
+
proto: Option<std::sync::Arc<Array>>,
|
|
786
809
|
}
|
|
787
810
|
|
|
788
811
|
/// Two arrays are equal when they hold the same elements at the same
|
|
789
|
-
/// indices, whatever buffer order each of them keeps.
|
|
812
|
+
/// indices, whatever buffer order — or storage kind — each of them keeps.
|
|
790
813
|
impl PartialEq for Array {
|
|
791
814
|
fn eq(&self, other: &Array) -> bool {
|
|
792
815
|
if self.shape != other.shape {
|
|
793
816
|
return false;
|
|
794
817
|
}
|
|
818
|
+
if self.sparse.is_some() || other.sparse.is_some() {
|
|
819
|
+
let (a, b) = (self.densified(), other.densified());
|
|
820
|
+
return a.to_row_major().data == b.to_row_major().data;
|
|
821
|
+
}
|
|
795
822
|
if self.layout == other.layout {
|
|
796
823
|
return self.data == other.data;
|
|
797
824
|
}
|
|
@@ -802,7 +829,52 @@ impl PartialEq for Array {
|
|
|
802
829
|
impl Array {
|
|
803
830
|
pub fn new(shape: Vec<usize>, data: Data) -> Array {
|
|
804
831
|
debug_assert_eq!(shape.iter().product::<usize>(), data.len());
|
|
805
|
-
Array { shape, data, layout: Layout::RowMajor }
|
|
832
|
+
Array { shape, data, layout: Layout::RowMajor, sparse: None, proto: None }
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/// A sparse array: the logical `shape`, the stored cells, and the
|
|
836
|
+
/// description of where they sit. `data` holds `entries` cells and not
|
|
837
|
+
/// one element per position, so this is the only constructor that does
|
|
838
|
+
/// not tie the buffer's length to the shape.
|
|
839
|
+
pub fn sparse(shape: Vec<usize>, data: Data, sparse: crate::sparse::Sparse) -> Array {
|
|
840
|
+
Array { shape, data, layout: Layout::RowMajor, sparse: Some(std::sync::Arc::new(sparse)), proto: None }
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/// True while the array holds only its stored cells.
|
|
844
|
+
pub fn is_sparse(&self) -> bool {
|
|
845
|
+
self.sparse.is_some()
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/// The item an array with no items would have held — APL's prototype.
|
|
849
|
+
///
|
|
850
|
+
/// A simple array's type says what its fills look like, so nothing has
|
|
851
|
+
/// to be remembered; a nested one does, since an empty buffer of boxes
|
|
852
|
+
/// no longer says whether its items were pairs of numbers or of
|
|
853
|
+
/// characters. `0⍴⊂2 3⍴9` is such an array, and `↑` of it answers the
|
|
854
|
+
/// 2 by 3 table of zeros this holds. Only the operations that make an
|
|
855
|
+
/// empty out of a nested array set it, and only APL reads it.
|
|
856
|
+
pub fn proto(&self) -> Option<&Array> {
|
|
857
|
+
self.proto.as_deref()
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/// The same array, remembering what its items looked like.
|
|
861
|
+
pub fn with_proto(mut self, proto: Array) -> Array {
|
|
862
|
+
self.proto = Some(std::sync::Arc::new(proto));
|
|
863
|
+
self
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/// How this array is stored sparsely, or None for a dense one.
|
|
867
|
+
pub fn sparse_parts(&self) -> Option<&crate::sparse::Sparse> {
|
|
868
|
+
self.sparse.as_deref()
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/// This array with every position materialised. A dense array is a
|
|
872
|
+
/// refcount bump; a sparse one is expanded here and nowhere else.
|
|
873
|
+
pub fn densified(&self) -> Array {
|
|
874
|
+
match &self.sparse {
|
|
875
|
+
None => self.clone(),
|
|
876
|
+
Some(s) => crate::sparse::densify(self, s),
|
|
877
|
+
}
|
|
806
878
|
}
|
|
807
879
|
|
|
808
880
|
/// An array whose buffer holds its first axis fastest — the columns of
|
|
@@ -810,7 +882,7 @@ impl Array {
|
|
|
810
882
|
pub fn col_major(shape: Vec<usize>, data: Data) -> Array {
|
|
811
883
|
debug_assert_eq!(shape.iter().product::<usize>(), data.len());
|
|
812
884
|
let layout = if shape.len() < 2 { Layout::RowMajor } else { Layout::ColMajor };
|
|
813
|
-
Array { shape, data, layout }
|
|
885
|
+
Array { shape, data, layout, sparse: None, proto: None }
|
|
814
886
|
}
|
|
815
887
|
|
|
816
888
|
/// The same buffer read the other way round. The caller is asserting
|
|
@@ -949,7 +1021,16 @@ impl Array {
|
|
|
949
1021
|
/// Widen the elements. A cast reads and writes the buffer as it lies,
|
|
950
1022
|
/// so the layout comes through untouched.
|
|
951
1023
|
pub fn cast(&self, to: DType) -> Option<Array> {
|
|
952
|
-
|
|
1024
|
+
if self.is_sparse() {
|
|
1025
|
+
return self.densified().cast(to);
|
|
1026
|
+
}
|
|
1027
|
+
Some(Array {
|
|
1028
|
+
shape: self.shape.clone(),
|
|
1029
|
+
data: self.data.cast(to)?,
|
|
1030
|
+
layout: self.layout,
|
|
1031
|
+
sparse: None,
|
|
1032
|
+
proto: self.proto.clone(),
|
|
1033
|
+
})
|
|
953
1034
|
}
|
|
954
1035
|
|
|
955
1036
|
/// Split into cells: the trailing `cell_rank` axes form the cell shape,
|
|
@@ -1035,7 +1116,7 @@ impl Array {
|
|
|
1035
1116
|
Data::Rat(v) => Some(v.iter().map(|x| [x.to_f64(), 0.0]).collect()),
|
|
1036
1117
|
Data::F64(v) => Some(v.iter().map(|&x| [x, 0.0]).collect()),
|
|
1037
1118
|
Data::Complex(v) => Some(v.to_vec()),
|
|
1038
|
-
Data::Char(_) | Data::Box(_) => None,
|
|
1119
|
+
Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
|
|
1039
1120
|
}
|
|
1040
1121
|
}
|
|
1041
1122
|
|
|
@@ -1047,9 +1128,20 @@ impl Array {
|
|
|
1047
1128
|
Data::Ext(v) => Some(v.iter().map(crate::exact::ext_to_f64).collect()),
|
|
1048
1129
|
Data::Rat(v) => Some(v.iter().map(Rat::to_f64).collect()),
|
|
1049
1130
|
Data::F64(v) => Some(v.to_vec()),
|
|
1050
|
-
// A complex value is
|
|
1051
|
-
//
|
|
1052
|
-
|
|
1131
|
+
// A complex value whose imaginary part is zero IS a real one
|
|
1132
|
+
// wherever a real is wanted: J answers `1 <. j. 0` with 0 and
|
|
1133
|
+
// `i. 3j0` with `0 1 2`, while `3!:0 j. 0` still reports the
|
|
1134
|
+
// complex type, so the demotion is at the use and not at the
|
|
1135
|
+
// making.
|
|
1136
|
+
Data::Complex(v) => {
|
|
1137
|
+
v.iter().map(|z| (z[1] == 0.0).then_some(z[0])).collect()
|
|
1138
|
+
}
|
|
1139
|
+
// An EMPTY array carries no value of the wrong type, so it is
|
|
1140
|
+
// acceptable numeric data whatever type it was written at:
|
|
1141
|
+
// `#. ''` is 0 in J and `¯3⊥''` is 0 in GNU APL. An empty BOX
|
|
1142
|
+
// is not: J refuses `2 #. 0$<1` where it answers `2 #. ''`.
|
|
1143
|
+
Data::Char(_) | Data::Symbol(_) if self.count() == 0 => Some(Vec::new()),
|
|
1144
|
+
Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
|
|
1053
1145
|
}
|
|
1054
1146
|
}
|
|
1055
1147
|
|
|
@@ -1074,8 +1166,90 @@ impl Array {
|
|
|
1074
1166
|
}
|
|
1075
1167
|
Some(out)
|
|
1076
1168
|
}
|
|
1077
|
-
|
|
1169
|
+
// The same two readings [`Array::to_f64_vec`] gives: a complex
|
|
1170
|
+
// with no imaginary part is the real it displays as, and an
|
|
1171
|
+
// empty of a non-numeric type holds no value to refuse.
|
|
1172
|
+
Data::Complex(v) => v
|
|
1173
|
+
.iter()
|
|
1174
|
+
.map(|z| {
|
|
1175
|
+
(z[1] == 0.0 && z[0].fract() == 0.0 && z[0].abs() < i64::MAX as f64)
|
|
1176
|
+
.then_some(z[0] as i64)
|
|
1177
|
+
})
|
|
1178
|
+
.collect(),
|
|
1179
|
+
Data::Char(_) | Data::Symbol(_) if self.count() == 0 => Some(Vec::new()),
|
|
1180
|
+
Data::Char(_) | Data::Symbol(_) | Data::Box(_) => None,
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/// Numeric contents as i64 where a COUNT, a LENGTH or an INDEX is
|
|
1185
|
+
/// wanted, admitting a float that is merely near a whole number.
|
|
1186
|
+
///
|
|
1187
|
+
/// Both references round such a float to the whole number beside it
|
|
1188
|
+
/// rather than refusing it — `⍳2-1E¯14` is `1 2` and `(2-1e_14) {. 1 2 3`
|
|
1189
|
+
/// is `1 2` — and neither admission is the comparison tolerance:
|
|
1190
|
+
/// `⎕CT←0` and `9!:19 (0)` leave both exactly where they are. The two
|
|
1191
|
+
/// admissions differ in shape. [`NearInt::J`] is relative, a value
|
|
1192
|
+
/// within `2^-44` of a whole number's magnitude; [`NearInt::Apl`] is
|
|
1193
|
+
/// absolute, `1e-10`, whatever the magnitude; [`NearInt::Tolerant`] is
|
|
1194
|
+
/// relative and follows the comparison tolerance in force. Everything a
|
|
1195
|
+
/// full integer apart is still a refusal in all three.
|
|
1196
|
+
pub fn to_i64_vec_near(&self, near: NearInt) -> Option<Vec<i64>> {
|
|
1197
|
+
let Data::F64(v) = &self.data else {
|
|
1198
|
+
// Every other type is exact or is refused outright; only a
|
|
1199
|
+
// float can be near a whole number without being one.
|
|
1200
|
+
return self.to_i64_vec();
|
|
1201
|
+
};
|
|
1202
|
+
v.iter().map(|&x| near.round(x)).collect()
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/// The near-integer admission a count, a length or an index position uses.
|
|
1207
|
+
///
|
|
1208
|
+
/// In J and in GNU APL it is a language constant: neither lets a program
|
|
1209
|
+
/// move it, and neither is the comparison tolerance the same program can
|
|
1210
|
+
/// set. Dyalog's is the exception — relative and scaled by `⎕CT` — so the
|
|
1211
|
+
/// rule is a dialect setting there, and [`NearInt::Tolerant`] carries the
|
|
1212
|
+
/// tolerance in force with it.
|
|
1213
|
+
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
1214
|
+
pub enum NearInt {
|
|
1215
|
+
/// J: `|x - n| ≤ 2^-44 × max(|x|, |n|)`, so the window grows with the
|
|
1216
|
+
/// magnitude and closes completely at zero.
|
|
1217
|
+
J,
|
|
1218
|
+
/// APL: `|x - n| < 1e-10` at every magnitude.
|
|
1219
|
+
Apl,
|
|
1220
|
+
/// Dyalog: the dialect's own tolerant equality against the whole
|
|
1221
|
+
/// number, so the window grows with the magnitude and `⎕CT` moves it.
|
|
1222
|
+
Tolerant(crate::verb::Tol),
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
impl NearInt {
|
|
1226
|
+
/// J's relative admission, which is also the value J's comparison
|
|
1227
|
+
/// tolerance starts at — the two are separate settings that happen to
|
|
1228
|
+
/// share a number.
|
|
1229
|
+
pub const J_RELATIVE: f64 = 1.0 / 17_592_186_044_416.0;
|
|
1230
|
+
/// APL's absolute admission.
|
|
1231
|
+
pub const APL_ABSOLUTE: f64 = 1e-10;
|
|
1232
|
+
|
|
1233
|
+
/// The rule for a language, at that language's shipped dialect.
|
|
1234
|
+
pub fn of(lang: crate::Lang) -> NearInt {
|
|
1235
|
+
match lang {
|
|
1236
|
+
crate::Lang::J => NearInt::J,
|
|
1237
|
+
crate::Lang::Apl => NearInt::Apl,
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/// The whole number `x` stands for, or None when it stands for none.
|
|
1242
|
+
pub fn round(self, x: f64) -> Option<i64> {
|
|
1243
|
+
if x.fract() == 0.0 {
|
|
1244
|
+
return (x.abs() < i64::MAX as f64).then_some(x as i64);
|
|
1078
1245
|
}
|
|
1246
|
+
let n = x.round();
|
|
1247
|
+
let within = match self {
|
|
1248
|
+
NearInt::J => (x - n).abs() <= Self::J_RELATIVE * x.abs().max(n.abs()),
|
|
1249
|
+
NearInt::Apl => (x - n).abs() < Self::APL_ABSOLUTE,
|
|
1250
|
+
NearInt::Tolerant(tol) => tol.eq(x, n),
|
|
1251
|
+
};
|
|
1252
|
+
(within && n.abs() < i64::MAX as f64).then_some(n as i64)
|
|
1079
1253
|
}
|
|
1080
1254
|
}
|
|
1081
1255
|
|
|
@@ -39,9 +39,23 @@ pub fn conj(a: Cx) -> Cx {
|
|
|
39
39
|
[a[0], -a[1]]
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/// A complex product is four real ones, and each of them follows J's rule
|
|
43
|
+
/// that a zero factor wins: `_ * 0j1` is `0j_` and `0j_ * 0j_` is `__`
|
|
44
|
+
/// only when `_ * 0` is 0 rather than a NaN. It is also what gives `j. _`
|
|
45
|
+
/// its value, because `j.` multiplies by the imaginary unit. GNU APL never
|
|
46
|
+
/// reaches the case — it refuses an infinite operand to `×` outright — so
|
|
47
|
+
/// the rule costs nothing there.
|
|
48
|
+
#[inline]
|
|
49
|
+
fn prod(x: f64, y: f64) -> f64 {
|
|
50
|
+
if (x == 0.0 || y == 0.0) && !(x.is_finite() && y.is_finite()) {
|
|
51
|
+
return 0.0;
|
|
52
|
+
}
|
|
53
|
+
x * y
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
#[inline]
|
|
43
57
|
pub fn mul(a: Cx, b: Cx) -> Cx {
|
|
44
|
-
[a[0]
|
|
58
|
+
[prod(a[0], b[0]) - prod(a[1], b[1]), prod(a[0], b[1]) + prod(a[1], b[0])]
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
/// Division, with J's rule for a zero divisor carried onto both parts:
|
|
@@ -156,7 +170,10 @@ pub fn pow(a: Cx, b: Cx) -> Cx {
|
|
|
156
170
|
if a[1] == 0.0 && a[0] < 0.0 && b[1] == 0.0 {
|
|
157
171
|
let m = (-a[0]).powf(b[0]);
|
|
158
172
|
let (c, s) = cos_sin_pi(b[0]);
|
|
159
|
-
|
|
173
|
+
// `prod`, not `*`: at a half turn the cosine is an exact zero, and
|
|
174
|
+
// an infinite magnitude beside it is the zero-factor case again.
|
|
175
|
+
// `__ ^ 0.5` is `0j_` and `__ ^ 1.5` is `0j__`.
|
|
176
|
+
return [prod(m, c), prod(m, s)];
|
|
160
177
|
}
|
|
161
178
|
exp(mul(b, ln(a)))
|
|
162
179
|
}
|
|
@@ -408,9 +408,36 @@ fn helpers(needs: &Needs, tol: Tol, p: Precision) -> String {
|
|
|
408
408
|
s.push_str(" return x / y;\n}\n");
|
|
409
409
|
}
|
|
410
410
|
if needs.residue {
|
|
411
|
+
// The quotient is rounded with the dialect's tolerance, as it is on
|
|
412
|
+
// the host: J takes the tolerant floor and answers an exact zero
|
|
413
|
+
// when the product is tolerantly the dividend, GNU APL shifts the
|
|
414
|
+
// quotient by `⎕CT` and reads the remainder against the modulus.
|
|
415
|
+
let ct = lit(tol.ct, p);
|
|
411
416
|
s.push_str(&format!("fn residue(x: {t}, y: {t}) -> {t} {{\n"));
|
|
412
417
|
s.push_str(&format!(" if (x == {zero}) {{ return y; }}\n"));
|
|
413
|
-
|
|
418
|
+
if tol.by_smaller {
|
|
419
|
+
s.push_str(" let q = y / x;\n");
|
|
420
|
+
s.push_str(" let c = ceil(q);\n");
|
|
421
|
+
s.push_str(&format!(
|
|
422
|
+
" let k = select(floor(q), c, abs(q - c) < {ct} * min(abs(q), abs(c)));\n"
|
|
423
|
+
));
|
|
424
|
+
s.push_str(" let d = x * k;\n");
|
|
425
|
+
s.push_str(&format!(
|
|
426
|
+
" if (abs(y - d) < {ct} * min(abs(y), abs(d))) {{ return {zero}; }}\n"
|
|
427
|
+
));
|
|
428
|
+
s.push_str(" return y - d;\n}\n");
|
|
429
|
+
} else {
|
|
430
|
+
s.push_str(" let q = y / x;\n let c = ceil(q);\n let gap = c - q;\n");
|
|
431
|
+
s.push_str(&format!(
|
|
432
|
+
" let k = select(floor(q), c, gap <= {ct} || gap < {ct} * max(abs(q), abs(c)));\n"
|
|
433
|
+
));
|
|
434
|
+
s.push_str(" let r = y - x * k;\n");
|
|
435
|
+
s.push_str(&format!(" if (abs(r) < {ct} * abs(x)) {{ return {zero}; }}\n"));
|
|
436
|
+
s.push_str(&format!(
|
|
437
|
+
" if (r != {zero} && (r < {zero}) != (x < {zero})) {{ return r + x; }}\n"
|
|
438
|
+
));
|
|
439
|
+
s.push_str(" return r;\n}\n");
|
|
440
|
+
}
|
|
414
441
|
}
|
|
415
442
|
if !s.is_empty() {
|
|
416
443
|
s.push('\n');
|
|
@@ -145,12 +145,14 @@ impl Device {
|
|
|
145
145
|
pub fn upload(&self, y: &Array) -> Result<Array, DeviceError> {
|
|
146
146
|
let Some(backend) = self.backend() else { return Ok(y.clone()) };
|
|
147
147
|
// What goes to the device is the elements in row-major order; a
|
|
148
|
-
// column-major argument is laid out once before it leaves
|
|
148
|
+
// column-major argument is laid out once before it leaves, and a
|
|
149
|
+
// sparse one is expanded — a device buffer is one element per
|
|
150
|
+
// position and nothing else.
|
|
149
151
|
let laid_out;
|
|
150
|
-
let y = if y.is_row_major() {
|
|
152
|
+
let y = if y.is_row_major() && !y.is_sparse() {
|
|
151
153
|
y
|
|
152
154
|
} else {
|
|
153
|
-
laid_out = y.to_row_major();
|
|
155
|
+
laid_out = y.densified().to_row_major();
|
|
154
156
|
&laid_out
|
|
155
157
|
};
|
|
156
158
|
// A float array is uploaded from its own buffer; anything else is
|
|
@@ -307,6 +309,9 @@ pub enum Refusal {
|
|
|
307
309
|
Unsupported(&'static str),
|
|
308
310
|
/// The kernel itself would decline these inputs, device or no device.
|
|
309
311
|
Declined,
|
|
312
|
+
/// The shader's answer holds an infinity or a NaN, where the dialect
|
|
313
|
+
/// has a rule of its own and the shader has only IEEE arithmetic.
|
|
314
|
+
NonFinite,
|
|
310
315
|
/// Too little data to pay for a dispatch.
|
|
311
316
|
TooSmall,
|
|
312
317
|
/// The device refused: an allocation, a shader, a queue submission.
|
|
@@ -323,6 +328,9 @@ impl Refusal {
|
|
|
323
328
|
}
|
|
324
329
|
Refusal::Unsupported(op) => format!("`{op}` has no shader form here"),
|
|
325
330
|
Refusal::Declined => "the fused kernel declined these inputs".into(),
|
|
331
|
+
Refusal::NonFinite => {
|
|
332
|
+
"the answer holds an infinity or a NaN, which the dialect's rules read".into()
|
|
333
|
+
}
|
|
326
334
|
Refusal::TooSmall => "there is too little data to pay for a dispatch".into(),
|
|
327
335
|
Refusal::Failed(e) => format!("the device refused: {e}"),
|
|
328
336
|
}
|
|
@@ -13,6 +13,9 @@ pub enum DType {
|
|
|
13
13
|
/// A complex number, held as an interleaved `[re, im]` pair.
|
|
14
14
|
Complex,
|
|
15
15
|
Char,
|
|
16
|
+
/// An interned name (J `s:`). The element is an index into the
|
|
17
|
+
/// process-wide symbol table, not the text itself.
|
|
18
|
+
Symbol,
|
|
16
19
|
/// A box: every element is itself an array (J `<`, APL `⊂`).
|
|
17
20
|
Box,
|
|
18
21
|
}
|
|
@@ -27,6 +30,7 @@ impl DType {
|
|
|
27
30
|
DType::F64 => "float",
|
|
28
31
|
DType::Complex => "complex",
|
|
29
32
|
DType::Char => "character",
|
|
33
|
+
DType::Symbol => "symbol",
|
|
30
34
|
DType::Box => "boxed",
|
|
31
35
|
}
|
|
32
36
|
}
|
|
@@ -55,6 +59,8 @@ impl DType {
|
|
|
55
59
|
(Box, _) | (_, Box) => None,
|
|
56
60
|
(Char, Char) => Some(Char),
|
|
57
61
|
(Char, _) | (_, Char) => None,
|
|
62
|
+
(Symbol, Symbol) => Some(Symbol),
|
|
63
|
+
(Symbol, _) | (_, Symbol) => None,
|
|
58
64
|
(Complex, _) | (_, Complex) => Some(Complex),
|
|
59
65
|
(F64, _) | (_, F64) => Some(F64),
|
|
60
66
|
(Rat, _) | (_, Rat) => Some(Rat),
|
|
@@ -85,5 +91,8 @@ mod tests {
|
|
|
85
91
|
fn characters_and_boxes_mix_with_nothing() {
|
|
86
92
|
assert_eq!(super::DType::promote(Char, Ext), None);
|
|
87
93
|
assert_eq!(super::DType::promote(Box, Rat), None);
|
|
94
|
+
assert_eq!(super::DType::promote(Symbol, Char), None);
|
|
95
|
+
assert_eq!(super::DType::promote(Symbol, I64), None);
|
|
96
|
+
assert_eq!(super::DType::promote(Symbol, Symbol), Some(Symbol));
|
|
88
97
|
}
|
|
89
98
|
}
|
|
@@ -30,6 +30,11 @@ pub enum ErrorKind {
|
|
|
30
30
|
Domain,
|
|
31
31
|
Type,
|
|
32
32
|
Value,
|
|
33
|
+
/// Arithmetic with no value at all: J refuses a NaN its own arithmetic
|
|
34
|
+
/// made (`_ - _`, `2 | _`), and names the failure this way. A NaN the
|
|
35
|
+
/// program itself wrote (`_.`) travels through unrefused, so this is
|
|
36
|
+
/// about the operation, not the operand.
|
|
37
|
+
Nan,
|
|
33
38
|
/// Present in the language, not implemented yet.
|
|
34
39
|
NotYet,
|
|
35
40
|
/// Absent from the language itself; will never exist.
|
|
@@ -52,6 +57,7 @@ impl ErrorKind {
|
|
|
52
57
|
ErrorKind::Domain => "domain error",
|
|
53
58
|
ErrorKind::Type => "type error",
|
|
54
59
|
ErrorKind::Value => "value error",
|
|
60
|
+
ErrorKind::Nan => "NaN error",
|
|
55
61
|
ErrorKind::NotYet => "not supported yet",
|
|
56
62
|
ErrorKind::Language => "not in the language",
|
|
57
63
|
ErrorKind::Sandbox => "closed by the sandbox",
|
|
@@ -99,6 +105,13 @@ impl Error {
|
|
|
99
105
|
Self::new(ErrorKind::Domain, msg, Some(span))
|
|
100
106
|
}
|
|
101
107
|
|
|
108
|
+
/// Arithmetic whose answer is a NaN nobody asked for. The message names
|
|
109
|
+
/// the operation and the pair that produced it, in the source language's
|
|
110
|
+
/// own spelling of the infinities.
|
|
111
|
+
pub fn nan(msg: impl Into<String>, span: Span) -> Self {
|
|
112
|
+
Self::new(ErrorKind::Nan, msg, Some(span))
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
pub fn internal(msg: impl Into<String>) -> Self {
|
|
103
116
|
Self::new(ErrorKind::Internal, msg, None)
|
|
104
117
|
}
|
|
@@ -226,6 +226,11 @@ fn control_lines(c: &Control, depth: usize, p: &Program, tr: &Trace, out: &mut S
|
|
|
226
226
|
let _ = writeln!(out, "{pad}branch →");
|
|
227
227
|
expr_lines(target, depth + 1, p, tr, out);
|
|
228
228
|
}
|
|
229
|
+
Control::Guard { test, body } => {
|
|
230
|
+
let _ = writeln!(out, "{pad}guard — the dfn's answer when it holds");
|
|
231
|
+
block("test", test, out);
|
|
232
|
+
block("body", body, out);
|
|
233
|
+
}
|
|
229
234
|
Control::If { arms, otherwise } => {
|
|
230
235
|
let _ = writeln!(out, "{pad}if — {} arm(s)", arms.len());
|
|
231
236
|
for (i, arm) in arms.iter().enumerate() {
|
|
@@ -377,6 +382,23 @@ fn source_of(p: &Program, e: &Expr) -> String {
|
|
|
377
382
|
|
|
378
383
|
// ------------------------------------------------------------------- verbs
|
|
379
384
|
|
|
385
|
+
/// One operand of a user-written operator: the function's own tree, or the
|
|
386
|
+
/// one line an array operand needs.
|
|
387
|
+
fn operand_lines(
|
|
388
|
+
o: &crate::verb::Operand,
|
|
389
|
+
depth: usize,
|
|
390
|
+
p: &Program,
|
|
391
|
+
tr: &Trace,
|
|
392
|
+
out: &mut String,
|
|
393
|
+
) {
|
|
394
|
+
match o {
|
|
395
|
+
crate::verb::Operand::Func(v) => verb_lines(v, depth, p, tr, out),
|
|
396
|
+
crate::verb::Operand::Value(_) => {
|
|
397
|
+
let _ = writeln!(out, "{}an array operand", " ".repeat(depth * STEP));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
380
402
|
fn verb_lines(v: &Verb, depth: usize, p: &Program, tr: &Trace, out: &mut String) {
|
|
381
403
|
let pad = " ".repeat(depth * STEP);
|
|
382
404
|
let head = |out: &mut String, what: &str| {
|
|
@@ -395,6 +417,10 @@ fn verb_lines(v: &Verb, depth: usize, p: &Program, tr: &Trace, out: &mut String)
|
|
|
395
417
|
head(out, "reduce (insert between items)");
|
|
396
418
|
verb_lines(u, depth + 1, p, tr, out);
|
|
397
419
|
}
|
|
420
|
+
Verb::NWise(u) => {
|
|
421
|
+
head(out, "reduce (insert between items); n-wise with a left argument");
|
|
422
|
+
verb_lines(u, depth + 1, p, tr, out);
|
|
423
|
+
}
|
|
398
424
|
Verb::Fit(u, n) => {
|
|
399
425
|
head(out, &format!("fit !.{n} (comparison tolerance)"));
|
|
400
426
|
verb_lines(u, depth + 1, p, tr, out);
|
|
@@ -426,12 +452,19 @@ fn verb_lines(v: &Verb, depth: usize, p: &Program, tr: &Trace, out: &mut String)
|
|
|
426
452
|
verb_lines(f, depth + 1, p, tr, out);
|
|
427
453
|
verb_lines(g, depth + 1, p, tr, out);
|
|
428
454
|
}
|
|
455
|
+
Verb::InnerProduct { u, v, .. } => {
|
|
456
|
+
head(out, "inner product (u . v; the matrix product for +/ . *)");
|
|
457
|
+
verb_lines(u, depth + 1, p, tr, out);
|
|
458
|
+
verb_lines(v, depth + 1, p, tr, out);
|
|
459
|
+
}
|
|
429
460
|
Verb::UserDerived { def, alpha, omega } => {
|
|
430
461
|
head(out, "a user-written operator with its operands");
|
|
431
|
-
|
|
432
|
-
|
|
462
|
+
if let Ok(body) = def.pick(alpha, omega.as_ref()) {
|
|
463
|
+
verb_lines(body, depth + 1, p, tr, out);
|
|
464
|
+
}
|
|
465
|
+
operand_lines(alpha, depth + 1, p, tr, out);
|
|
433
466
|
if let Some(g) = omega {
|
|
434
|
-
|
|
467
|
+
operand_lines(g, depth + 1, p, tr, out);
|
|
435
468
|
}
|
|
436
469
|
}
|
|
437
470
|
Verb::KeyPairs(u) => {
|
|
@@ -566,6 +599,10 @@ fn verb_lines(v: &Verb, depth: usize, p: &Program, tr: &Trace, out: &mut String)
|
|
|
566
599
|
}
|
|
567
600
|
Verb::SelfRef => head(out, "self-reference (the definition it stands in)"),
|
|
568
601
|
Verb::Named(n) => head(out, &format!("verb named {n}, resolved when it is applied")),
|
|
602
|
+
Verb::UnderRavel(u) => {
|
|
603
|
+
head(out, "under ravel (flatten, apply, put the shape back)");
|
|
604
|
+
verb_lines(u, depth + 1, p, tr, out);
|
|
605
|
+
}
|
|
569
606
|
Verb::Each(u, kind) => {
|
|
570
607
|
head(
|
|
571
608
|
out,
|