libjay 0.2.1__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.1 → libjay-0.3.0}/Cargo.lock +6 -5
- {libjay-0.2.1 → libjay-0.3.0}/Cargo.toml +1 -1
- {libjay-0.2.1 → libjay-0.3.0}/PKG-INFO +1 -1
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/array.rs +167 -8
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/complex.rs +19 -2
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/device/codegen.rs +28 -1
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/device/mod.rs +11 -3
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/error.rs +13 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/explain.rs +35 -3
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/fmt.rs +192 -9
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/frontend/apl.rs +469 -141
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/frontend/j.rs +60 -25
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/frontend/mod.rs +174 -21
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/fuse.rs +107 -33
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/ir.rs +72 -3
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/lib.rs +2 -1
- libjay-0.3.0/crates/libjay/src/sparse.rs +388 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/verb.rs +3092 -583
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay-python/src/data.rs +9 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay-python/src/lib.rs +96 -1
- {libjay-0.2.1 → 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.1 → libjay-0.3.0}/crates/libjay-testkit/src/eval.rs +28 -3
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay-testkit/src/lib.rs +1 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay-testkit/src/replay.rs +23 -7
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay-testkit/src/snapshot.rs +2 -2
- {libjay-0.2.1 → libjay-0.3.0}/python/jay/__init__.py +7 -0
- {libjay-0.2.1 → libjay-0.3.0}/python/jay/_cli.py +14 -1
- libjay-0.3.0/python/jay/lang.py +145 -0
- libjay-0.2.1/python/jay/lang.py +0 -85
- {libjay-0.2.1 → libjay-0.3.0}/LICENSE +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/Cargo.toml +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/LICENSE +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/README.md +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/device/gpu.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/dtype.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/exact.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/gerund.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/limits.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/par.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/rng.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/simd.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay/src/symbol.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay-python/Cargo.toml +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay-testkit/Cargo.toml +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/crates/libjay-testkit/src/compare.rs +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/pyproject.toml +0 -0
- {libjay-0.2.1 → libjay-0.3.0}/python/README.md +0 -0
- {libjay-0.2.1 → 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
|
]
|
|
@@ -793,20 +793,32 @@ pub enum Layout {
|
|
|
793
793
|
/// honour [`Array::layout`] or take [`Array::to_row_major`] first; the
|
|
794
794
|
/// runtime's rule is that a value reaching a verb has already been made
|
|
795
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.
|
|
796
802
|
#[derive(Clone, Debug)]
|
|
797
803
|
pub struct Array {
|
|
798
804
|
pub shape: Vec<usize>,
|
|
799
805
|
pub data: Data,
|
|
800
806
|
layout: Layout,
|
|
807
|
+
sparse: Option<crate::sparse::Handle>,
|
|
808
|
+
proto: Option<std::sync::Arc<Array>>,
|
|
801
809
|
}
|
|
802
810
|
|
|
803
811
|
/// Two arrays are equal when they hold the same elements at the same
|
|
804
|
-
/// indices, whatever buffer order each of them keeps.
|
|
812
|
+
/// indices, whatever buffer order — or storage kind — each of them keeps.
|
|
805
813
|
impl PartialEq for Array {
|
|
806
814
|
fn eq(&self, other: &Array) -> bool {
|
|
807
815
|
if self.shape != other.shape {
|
|
808
816
|
return false;
|
|
809
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
|
+
}
|
|
810
822
|
if self.layout == other.layout {
|
|
811
823
|
return self.data == other.data;
|
|
812
824
|
}
|
|
@@ -817,7 +829,52 @@ impl PartialEq for Array {
|
|
|
817
829
|
impl Array {
|
|
818
830
|
pub fn new(shape: Vec<usize>, data: Data) -> Array {
|
|
819
831
|
debug_assert_eq!(shape.iter().product::<usize>(), data.len());
|
|
820
|
-
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
|
+
}
|
|
821
878
|
}
|
|
822
879
|
|
|
823
880
|
/// An array whose buffer holds its first axis fastest — the columns of
|
|
@@ -825,7 +882,7 @@ impl Array {
|
|
|
825
882
|
pub fn col_major(shape: Vec<usize>, data: Data) -> Array {
|
|
826
883
|
debug_assert_eq!(shape.iter().product::<usize>(), data.len());
|
|
827
884
|
let layout = if shape.len() < 2 { Layout::RowMajor } else { Layout::ColMajor };
|
|
828
|
-
Array { shape, data, layout }
|
|
885
|
+
Array { shape, data, layout, sparse: None, proto: None }
|
|
829
886
|
}
|
|
830
887
|
|
|
831
888
|
/// The same buffer read the other way round. The caller is asserting
|
|
@@ -964,7 +1021,16 @@ impl Array {
|
|
|
964
1021
|
/// Widen the elements. A cast reads and writes the buffer as it lies,
|
|
965
1022
|
/// so the layout comes through untouched.
|
|
966
1023
|
pub fn cast(&self, to: DType) -> Option<Array> {
|
|
967
|
-
|
|
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
|
+
})
|
|
968
1034
|
}
|
|
969
1035
|
|
|
970
1036
|
/// Split into cells: the trailing `cell_rank` axes form the cell shape,
|
|
@@ -1062,9 +1128,20 @@ impl Array {
|
|
|
1062
1128
|
Data::Ext(v) => Some(v.iter().map(crate::exact::ext_to_f64).collect()),
|
|
1063
1129
|
Data::Rat(v) => Some(v.iter().map(Rat::to_f64).collect()),
|
|
1064
1130
|
Data::F64(v) => Some(v.to_vec()),
|
|
1065
|
-
// A complex value is
|
|
1066
|
-
//
|
|
1067
|
-
|
|
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,
|
|
1068
1145
|
}
|
|
1069
1146
|
}
|
|
1070
1147
|
|
|
@@ -1089,8 +1166,90 @@ impl Array {
|
|
|
1089
1166
|
}
|
|
1090
1167
|
Some(out)
|
|
1091
1168
|
}
|
|
1092
|
-
|
|
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);
|
|
1093
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)
|
|
1094
1253
|
}
|
|
1095
1254
|
}
|
|
1096
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
|
}
|
|
@@ -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);
|
|
@@ -433,10 +459,12 @@ fn verb_lines(v: &Verb, depth: usize, p: &Program, tr: &Trace, out: &mut String)
|
|
|
433
459
|
}
|
|
434
460
|
Verb::UserDerived { def, alpha, omega } => {
|
|
435
461
|
head(out, "a user-written operator with its operands");
|
|
436
|
-
|
|
437
|
-
|
|
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);
|
|
438
466
|
if let Some(g) = omega {
|
|
439
|
-
|
|
467
|
+
operand_lines(g, depth + 1, p, tr, out);
|
|
440
468
|
}
|
|
441
469
|
}
|
|
442
470
|
Verb::KeyPairs(u) => {
|
|
@@ -571,6 +599,10 @@ fn verb_lines(v: &Verb, depth: usize, p: &Program, tr: &Trace, out: &mut String)
|
|
|
571
599
|
}
|
|
572
600
|
Verb::SelfRef => head(out, "self-reference (the definition it stands in)"),
|
|
573
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
|
+
}
|
|
574
606
|
Verb::Each(u, kind) => {
|
|
575
607
|
head(
|
|
576
608
|
out,
|