klongpy 0.6.8__py3-none-any.whl → 0.7.0__py3-none-any.whl
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.
- klongpy/__init__.py +19 -1
- klongpy/adverbs.py +5 -5
- klongpy/autograd.py +308 -0
- klongpy/backend.py +167 -99
- klongpy/backends/__init__.py +94 -0
- klongpy/backends/base.py +320 -0
- klongpy/backends/numpy_backend.py +122 -0
- klongpy/backends/torch_backend.py +995 -0
- klongpy-0.6.8.data/scripts/kgpy → klongpy/cli.py +65 -88
- klongpy/core.py +228 -106
- klongpy/db/sys_fn_db.py +4 -3
- klongpy/dyads.py +173 -32
- klongpy/interpreter.py +31 -3
- klongpy/lib/help.kg +2 -2
- klongpy/monads.py +49 -12
- klongpy/repl.py +91 -0
- klongpy/sys_fn.py +129 -18
- klongpy/sys_fn_autograd.py +290 -0
- klongpy/sys_fn_ipc.py +18 -7
- klongpy/sys_fn_timer.py +13 -3
- klongpy/web/sys_fn_web.py +28 -6
- klongpy-0.7.0.dist-info/METADATA +493 -0
- klongpy-0.7.0.dist-info/RECORD +48 -0
- {klongpy-0.6.8.dist-info → klongpy-0.7.0.dist-info}/WHEEL +1 -1
- klongpy-0.7.0.dist-info/entry_points.txt +2 -0
- {klongpy-0.6.8.dist-info → klongpy-0.7.0.dist-info}/top_level.txt +0 -1
- klongpy-0.6.8.dist-info/METADATA +0 -412
- klongpy-0.6.8.dist-info/RECORD +0 -72
- tests/__init__.py +0 -6
- tests/gen_join_over.py +0 -119
- tests/gen_py_suite.py +0 -77
- tests/gen_test_fn.py +0 -259
- tests/perf_async.py +0 -25
- tests/perf_avg.py +0 -18
- tests/perf_duckdb.py +0 -32
- tests/perf_gen.py +0 -38
- tests/perf_ipc_overhead.py +0 -34
- tests/perf_join.py +0 -53
- tests/perf_load.py +0 -17
- tests/perf_prog.py +0 -18
- tests/perf_serdes.py +0 -52
- tests/perf_sys_fn_db.py +0 -263
- tests/perf_vector.py +0 -40
- tests/test_accel.py +0 -227
- tests/test_df_cache.py +0 -85
- tests/test_examples.py +0 -64
- tests/test_extra_suite.py +0 -382
- tests/test_file_cache.py +0 -185
- tests/test_interop.py +0 -181
- tests/test_kgtests.py +0 -65
- tests/test_known_bugs.py +0 -206
- tests/test_prog.py +0 -107
- tests/test_suite.py +0 -1479
- tests/test_suite_file.py +0 -153
- tests/test_sys_fn.py +0 -420
- tests/test_sys_fn_db.py +0 -88
- tests/test_sys_fn_ipc.py +0 -587
- tests/test_sys_fn_timer.py +0 -133
- tests/test_util.py +0 -233
- tests/utils.py +0 -126
- {klongpy-0.6.8.dist-info → klongpy-0.7.0.dist-info/licenses}/LICENSE +0 -0
klongpy/core.py
CHANGED
|
@@ -3,14 +3,30 @@ import inspect
|
|
|
3
3
|
import weakref
|
|
4
4
|
from enum import Enum
|
|
5
5
|
import sys
|
|
6
|
+
import numpy
|
|
6
7
|
|
|
7
|
-
from .backend import np
|
|
8
|
+
from .backend import np, TorchUnsupportedDtypeError, get_default_backend, to_numpy
|
|
8
9
|
|
|
9
10
|
# python3.11 support
|
|
10
11
|
if not hasattr(inspect, 'getargspec'):
|
|
11
12
|
inspect.getargspec = inspect.getfullargspec
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
def get_dtype_kind(arr, backend):
|
|
16
|
+
"""
|
|
17
|
+
Get the dtype 'kind' character for an array (numpy or torch).
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
'O' for object dtype
|
|
21
|
+
'i' for integer types
|
|
22
|
+
'f' for float types
|
|
23
|
+
'u' for unsigned integer
|
|
24
|
+
'b' for boolean
|
|
25
|
+
'c' for complex
|
|
26
|
+
"""
|
|
27
|
+
return backend.get_dtype_kind(arr)
|
|
28
|
+
|
|
29
|
+
|
|
14
30
|
class KlongException(Exception):
|
|
15
31
|
pass
|
|
16
32
|
|
|
@@ -51,11 +67,55 @@ class KGFn:
|
|
|
51
67
|
|
|
52
68
|
|
|
53
69
|
class KGFnWrapper:
|
|
54
|
-
|
|
70
|
+
"""
|
|
71
|
+
Wrapper for KGFn that enables calling from Python with dynamic symbol resolution.
|
|
72
|
+
|
|
73
|
+
When a KGFn is stored and later invoked, this wrapper automatically re-resolves
|
|
74
|
+
the symbol to use the current function definition. This matches k4 behavior and
|
|
75
|
+
provides REPL-friendly semantics where function redefinitions take effect immediately.
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
fn = klong['callback'] # Returns KGFnWrapper
|
|
79
|
+
klong('callback::{new implementation}')
|
|
80
|
+
fn(args) # Uses the NEW implementation
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(self, klong, fn, sym=None):
|
|
55
84
|
self.klong = klong
|
|
56
85
|
self.fn = fn
|
|
86
|
+
# Use provided symbol name (cached) or search for it
|
|
87
|
+
self._sym = sym if sym is not None else self._find_symbol(fn)
|
|
88
|
+
|
|
89
|
+
def _find_symbol(self, fn):
|
|
90
|
+
"""Find which symbol this function is currently bound to"""
|
|
91
|
+
if not isinstance(fn, KGFn) or isinstance(fn, KGCall):
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
# Search the context for this function
|
|
95
|
+
# Skip reserved symbols (x, y, z, .f) which are function parameters, not stored callbacks
|
|
96
|
+
for sym, value in self.klong._context:
|
|
97
|
+
# Skip reserved symbols - use the module constants
|
|
98
|
+
if sym in reserved_fn_symbols or sym == reserved_dot_f_symbol:
|
|
99
|
+
continue
|
|
100
|
+
if value is fn:
|
|
101
|
+
return sym
|
|
102
|
+
return None
|
|
57
103
|
|
|
58
104
|
def __call__(self, *args, **kwargs):
|
|
105
|
+
# Try to resolve dynamically first if we have a symbol
|
|
106
|
+
if self._sym is not None:
|
|
107
|
+
try:
|
|
108
|
+
current = self.klong._context[self._sym]
|
|
109
|
+
if isinstance(current, KGFn) and not isinstance(current, KGCall):
|
|
110
|
+
# Use the current definition
|
|
111
|
+
if len(args) != current.arity:
|
|
112
|
+
raise RuntimeError(f"Klong function called with {len(args)} but expected {current.arity}")
|
|
113
|
+
fn_args = [np.asarray(x) if isinstance(x, list) else x for x in args]
|
|
114
|
+
return self.klong.call(KGCall(current.a, [*fn_args], current.arity))
|
|
115
|
+
except KeyError:
|
|
116
|
+
# Symbol was deleted, fall through to original function
|
|
117
|
+
pass
|
|
118
|
+
|
|
59
119
|
if len(args) != self.fn.arity:
|
|
60
120
|
raise RuntimeError(f"Klong function called with {len(args)} but expected {self.fn.arity}")
|
|
61
121
|
fn_args = [np.asarray(x) if isinstance(x, list) else x for x in args]
|
|
@@ -206,16 +266,36 @@ def to_list(a):
|
|
|
206
266
|
return a if isinstance(a, list) else a.tolist() if np.isarray(a) else [a]
|
|
207
267
|
|
|
208
268
|
|
|
209
|
-
def is_integer(x):
|
|
210
|
-
|
|
269
|
+
def is_integer(x, backend):
|
|
270
|
+
if issubclass(type(x), (int, numpy.integer)):
|
|
271
|
+
return True
|
|
272
|
+
# Handle 0-dim numpy arrays
|
|
273
|
+
if isinstance(x, numpy.ndarray) and x.ndim == 0:
|
|
274
|
+
return numpy.issubdtype(x.dtype, numpy.integer)
|
|
275
|
+
# Handle backend-specific scalar integers (e.g., torch tensors)
|
|
276
|
+
return backend.is_scalar_integer(x)
|
|
211
277
|
|
|
212
278
|
|
|
213
|
-
def is_float(x):
|
|
214
|
-
|
|
279
|
+
def is_float(x, backend):
|
|
280
|
+
if issubclass(type(x), (float, numpy.floating, int)):
|
|
281
|
+
return True
|
|
282
|
+
# Handle 0-dim numpy arrays
|
|
283
|
+
if isinstance(x, numpy.ndarray) and x.ndim == 0:
|
|
284
|
+
return numpy.issubdtype(x.dtype, numpy.floating)
|
|
285
|
+
# Handle backend-specific scalar floats (e.g., torch tensors)
|
|
286
|
+
return backend.is_scalar_float(x)
|
|
215
287
|
|
|
216
288
|
|
|
217
|
-
def is_number(a):
|
|
218
|
-
|
|
289
|
+
def is_number(a, backend):
|
|
290
|
+
if is_float(a, backend) or is_integer(a, backend):
|
|
291
|
+
return True
|
|
292
|
+
# Handle 0-dim numpy arrays
|
|
293
|
+
if isinstance(a, numpy.ndarray) and a.ndim == 0:
|
|
294
|
+
return numpy.issubdtype(a.dtype, numpy.number)
|
|
295
|
+
# Handle 0-dim backend tensors as numbers
|
|
296
|
+
if backend.is_backend_array(a) and hasattr(a, 'ndim') and a.ndim == 0:
|
|
297
|
+
return True
|
|
298
|
+
return False
|
|
219
299
|
|
|
220
300
|
|
|
221
301
|
def str_is_float(b):
|
|
@@ -232,90 +312,76 @@ def in_map(x, v):
|
|
|
232
312
|
return False
|
|
233
313
|
|
|
234
314
|
|
|
235
|
-
def kg_asarray(a):
|
|
236
|
-
"""
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
KlongPy treats NumPy arrays as data and Python lists as "programs". Therefore, it is crucial that all elements and
|
|
240
|
-
sub-arrays of the input data are converted into NumPy arrays. This function attempts to achieve this, while handling
|
|
241
|
-
unpredictable and complex data structures that may result from prior manipulations to the data.
|
|
242
|
-
|
|
243
|
-
The function first tries to convert the input data into a NumPy array. In the case of a jagged structure
|
|
244
|
-
(i.e., irregularly-shaped sub-arrays), it defaults to the object data type. If the initial conversion is unsuccessful,
|
|
245
|
-
it tries to convert the input data to an object dtype array. If all these attempts fail, it converts each element to
|
|
246
|
-
a list, if it is a NumPy array, or keeps the original element if it is not, and then attempts to convert the whole
|
|
247
|
-
structure into an object dtype array again.
|
|
248
|
-
|
|
249
|
-
Detecting the type in NumPy directly, as opposed to checking it in Python, significantly enhances
|
|
250
|
-
performance, hence the approach adopted in this function.
|
|
251
|
-
|
|
252
|
-
Parameters
|
|
253
|
-
----------
|
|
254
|
-
a : list or array-like
|
|
255
|
-
The input data to be converted into a NumPy array.
|
|
256
|
-
|
|
257
|
-
Returns
|
|
258
|
-
-------
|
|
259
|
-
arr : ndarray
|
|
260
|
-
The converted input data as a NumPy array, where all elements and sub-arrays are also NumPy arrays.
|
|
261
|
-
"""
|
|
262
|
-
try:
|
|
263
|
-
arr = np.asarray(a)
|
|
264
|
-
if arr.dtype.kind not in ['O','i','f']:
|
|
265
|
-
raise ValueError
|
|
266
|
-
except (np.VisibleDeprecationWarning, ValueError):
|
|
267
|
-
try:
|
|
268
|
-
arr = np.asarray(a,dtype=object)
|
|
269
|
-
except ValueError:
|
|
270
|
-
arr = [x.tolist() if np.isarray(x) else x for x in a]
|
|
271
|
-
arr = np.asarray(arr,dtype=object)
|
|
272
|
-
arr = np.asarray([kg_asarray(x) if isinstance(x,list) else x for x in arr],dtype=object)
|
|
273
|
-
return arr
|
|
315
|
+
def kg_asarray(a, backend):
|
|
316
|
+
"""Convert input to array using the backend's kg_asarray method."""
|
|
317
|
+
return backend.kg_asarray(a)
|
|
274
318
|
|
|
275
319
|
|
|
276
|
-
def kg_equal(a, b):
|
|
277
|
-
"""
|
|
278
|
-
Compares two values or arrays (including nested arrays) for equality.
|
|
279
|
-
|
|
280
|
-
This function recursively checks if two values or arrays are equal. It can handle
|
|
281
|
-
nested arrays and is more general-purpose than standard NumPy functions such as
|
|
282
|
-
np.array_equal.
|
|
283
|
-
|
|
284
|
-
If the inputs are lists, the function checks that their lengths are equal, and
|
|
285
|
-
then compares each element pair for equality. If the inputs are NumPy arrays with
|
|
286
|
-
the same dtype (excluding object dtype), it uses the np.array_equal function for
|
|
287
|
-
comparison.
|
|
288
|
-
|
|
289
|
-
For non-list inputs, the function compares the two values directly. If they are
|
|
290
|
-
both numbers, it uses np.isclose to allow for minor floating-point differences.
|
|
291
|
-
|
|
292
|
-
Parameters
|
|
293
|
-
----------
|
|
294
|
-
a, b : Any
|
|
295
|
-
The two inputs to compare. These can be any type of values or arrays.
|
|
296
|
-
|
|
297
|
-
Returns
|
|
298
|
-
-------
|
|
299
|
-
bool
|
|
300
|
-
True if the two inputs are equal, False otherwise.
|
|
301
|
-
"""
|
|
320
|
+
def kg_equal(a, b, backend):
|
|
321
|
+
"""Compare two values or arrays for equality, handling nested arrays and tensors."""
|
|
302
322
|
if a is b:
|
|
303
323
|
return True
|
|
304
324
|
|
|
305
|
-
|
|
325
|
+
# Check for arrays (numpy or backend-specific)
|
|
326
|
+
is_numpy_a = isinstance(a, numpy.ndarray)
|
|
327
|
+
is_numpy_b = isinstance(b, numpy.ndarray)
|
|
328
|
+
is_backend_a = backend.is_backend_array(a)
|
|
329
|
+
is_backend_b = backend.is_backend_array(b)
|
|
306
330
|
|
|
307
|
-
|
|
308
|
-
return np.array_equal(a,b)
|
|
331
|
+
na, nb = is_numpy_a or is_backend_a, is_numpy_b or is_backend_b
|
|
309
332
|
|
|
310
|
-
|
|
333
|
+
# Handle arrays with same dtype
|
|
334
|
+
if na and nb:
|
|
335
|
+
a_dtype = get_dtype_kind(a, backend)
|
|
336
|
+
b_dtype = get_dtype_kind(b, backend)
|
|
337
|
+
if a_dtype == b_dtype and a_dtype != 'O':
|
|
338
|
+
return bool(np.array_equal(a, b))
|
|
339
|
+
|
|
340
|
+
na, nb = na or isinstance(a, list), nb or isinstance(b, list)
|
|
311
341
|
|
|
312
342
|
if na != nb:
|
|
343
|
+
# One is array/list, the other is not - could be scalar tensor/array vs scalar
|
|
344
|
+
# Handle comparing 0-dim arrays/tensors with scalars
|
|
345
|
+
if is_numpy_a and a.ndim == 0 and not nb:
|
|
346
|
+
return kg_equal(a.item(), b, backend)
|
|
347
|
+
if is_numpy_b and b.ndim == 0 and not na:
|
|
348
|
+
return kg_equal(a, b.item(), backend)
|
|
349
|
+
if is_backend_a and hasattr(a, 'ndim') and a.ndim == 0 and not nb:
|
|
350
|
+
return kg_equal(backend.scalar_to_python(a), b, backend)
|
|
351
|
+
if is_backend_b and hasattr(b, 'ndim') and b.ndim == 0 and not na:
|
|
352
|
+
return kg_equal(a, backend.scalar_to_python(b), backend)
|
|
313
353
|
return False
|
|
314
354
|
|
|
315
355
|
if na:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
356
|
+
# Handle 0-dim arrays/tensors - compare as scalars
|
|
357
|
+
a_is_0d = hasattr(a, 'ndim') and a.ndim == 0
|
|
358
|
+
b_is_0d = hasattr(b, 'ndim') and b.ndim == 0
|
|
359
|
+
if a_is_0d or b_is_0d:
|
|
360
|
+
a_val = backend.scalar_to_python(a) if a_is_0d else a
|
|
361
|
+
b_val = backend.scalar_to_python(b) if b_is_0d else b
|
|
362
|
+
return kg_equal(a_val, b_val, backend)
|
|
363
|
+
return len(a) == len(b) and all(kg_equal(x, y, backend) for x, y in zip(a, b))
|
|
364
|
+
|
|
365
|
+
if is_number(a, backend) and is_number(b, backend):
|
|
366
|
+
# Convert tensors to Python scalars for comparison
|
|
367
|
+
if backend.is_backend_array(a):
|
|
368
|
+
a = backend.scalar_to_python(a)
|
|
369
|
+
if backend.is_backend_array(b):
|
|
370
|
+
b = backend.scalar_to_python(b)
|
|
371
|
+
result = np.isclose(a, b)
|
|
372
|
+
# np.isclose might return an array/tensor, ensure we return bool
|
|
373
|
+
if hasattr(result, 'item'):
|
|
374
|
+
return bool(result.item())
|
|
375
|
+
return bool(result)
|
|
376
|
+
|
|
377
|
+
result = a == b
|
|
378
|
+
# Handle tensor/array result from comparison
|
|
379
|
+
if hasattr(result, 'all'):
|
|
380
|
+
# For arrays, check if all elements are equal
|
|
381
|
+
return bool(result.all())
|
|
382
|
+
if hasattr(result, 'item'):
|
|
383
|
+
return bool(result.item())
|
|
384
|
+
return bool(result)
|
|
319
385
|
|
|
320
386
|
def has_none(a):
|
|
321
387
|
if isinstance(a,list):
|
|
@@ -376,14 +442,19 @@ def rec_flatten(a):
|
|
|
376
442
|
|
|
377
443
|
|
|
378
444
|
def rec_fn(a,f):
|
|
379
|
-
|
|
445
|
+
_backend = get_default_backend()
|
|
446
|
+
return _backend.kg_asarray([rec_fn(x, f) for x in a]) if is_list(a) else f(a)
|
|
380
447
|
|
|
381
448
|
|
|
382
|
-
def vec_fn(a, f):
|
|
449
|
+
def vec_fn(a, f, backend):
|
|
383
450
|
"""
|
|
384
451
|
Apply a function `f` to an array `a`, with support for both nested arrays and direct vectorized operation.
|
|
385
452
|
"""
|
|
386
|
-
|
|
453
|
+
if np.isarray(a) and a.dtype == 'O':
|
|
454
|
+
# For object arrays, process each element and preserve structure
|
|
455
|
+
result = [((vec_fn(x, f, backend)) if is_list(x) else f(x)) for x in a]
|
|
456
|
+
return numpy.asarray(result, dtype=object)
|
|
457
|
+
return f(a)
|
|
387
458
|
|
|
388
459
|
|
|
389
460
|
def vec_fn2(a, b, f):
|
|
@@ -423,18 +494,20 @@ def vec_fn2(a, b, f):
|
|
|
423
494
|
not satisfied.
|
|
424
495
|
|
|
425
496
|
"""
|
|
497
|
+
_backend = get_default_backend()
|
|
498
|
+
_kg_asarray = _backend.kg_asarray
|
|
426
499
|
if np.isarray(a):
|
|
427
500
|
if a.dtype == 'O':
|
|
428
501
|
if np.isarray(b):
|
|
429
502
|
assert len(a) == len(b)
|
|
430
|
-
return
|
|
503
|
+
return _kg_asarray([vec_fn2(x, y, f) for x,y in zip(a,b)])
|
|
431
504
|
else:
|
|
432
|
-
return
|
|
505
|
+
return _kg_asarray([vec_fn2(x, b, f) for x in a])
|
|
433
506
|
elif np.isarray(b) and b.dtype == 'O':
|
|
434
507
|
assert len(a) == len(b)
|
|
435
|
-
return
|
|
508
|
+
return _kg_asarray([vec_fn2(x, y, f) for x,y in zip(a,b)])
|
|
436
509
|
elif np.isarray(b) and b.dtype == 'O':
|
|
437
|
-
return
|
|
510
|
+
return _kg_asarray([vec_fn2(a, x, f) for x in b])
|
|
438
511
|
return f(a,b)
|
|
439
512
|
|
|
440
513
|
|
|
@@ -443,7 +516,11 @@ def is_symbolic(c):
|
|
|
443
516
|
|
|
444
517
|
|
|
445
518
|
def is_char(x):
|
|
446
|
-
|
|
519
|
+
# Check for both core and backend KGChar classes
|
|
520
|
+
if isinstance(x, KGChar):
|
|
521
|
+
return True
|
|
522
|
+
# Also check for backend KGChar (in case they're different classes)
|
|
523
|
+
return type(x).__name__ == 'KGChar' and isinstance(x, str)
|
|
447
524
|
|
|
448
525
|
|
|
449
526
|
def is_atom(x):
|
|
@@ -455,10 +532,28 @@ def kg_truth(x):
|
|
|
455
532
|
return x*1
|
|
456
533
|
|
|
457
534
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
535
|
+
def str_to_chr_arr(s, backend):
|
|
536
|
+
"""
|
|
537
|
+
Convert string to character array.
|
|
538
|
+
|
|
539
|
+
Parameters
|
|
540
|
+
----------
|
|
541
|
+
s : str
|
|
542
|
+
The string to convert.
|
|
543
|
+
backend : BackendProvider
|
|
544
|
+
The backend to use.
|
|
545
|
+
|
|
546
|
+
Returns
|
|
547
|
+
-------
|
|
548
|
+
array
|
|
549
|
+
Array of KGChar objects.
|
|
550
|
+
|
|
551
|
+
Raises
|
|
552
|
+
------
|
|
553
|
+
UnsupportedDtypeError
|
|
554
|
+
If the backend doesn't support string operations.
|
|
555
|
+
"""
|
|
556
|
+
return backend.str_to_char_array(s)
|
|
462
557
|
|
|
463
558
|
|
|
464
559
|
def read_num(t, i=0):
|
|
@@ -565,6 +660,7 @@ def read_list(t, delim, i=0, module=None, level=1):
|
|
|
565
660
|
L := '[' (C|L)* ']'
|
|
566
661
|
|
|
567
662
|
"""
|
|
663
|
+
backend = get_default_backend()
|
|
568
664
|
arr = []
|
|
569
665
|
i = skip(t,i,ignore_newline=True)
|
|
570
666
|
while not cmatch(t,i,delim) and i < len(t):
|
|
@@ -579,9 +675,24 @@ def read_list(t, delim, i=0, module=None, level=1):
|
|
|
579
675
|
if cmatch(t,i,delim):
|
|
580
676
|
i += 1
|
|
581
677
|
if level == 1:
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
678
|
+
try:
|
|
679
|
+
aa = kg_asarray(arr, backend)
|
|
680
|
+
if get_dtype_kind(aa, backend) not in ['O','i','f']:
|
|
681
|
+
aa = numpy.asarray(arr, dtype=object)
|
|
682
|
+
except TorchUnsupportedDtypeError:
|
|
683
|
+
# Backend can't handle this data - fall back to numpy object array
|
|
684
|
+
# Recursively convert inner lists to arrays, converting tensors to numpy
|
|
685
|
+
def convert_inner(x):
|
|
686
|
+
if isinstance(x, list):
|
|
687
|
+
try:
|
|
688
|
+
result = kg_asarray(x, backend)
|
|
689
|
+
# Convert tensor to numpy for object array compatibility
|
|
690
|
+
return to_numpy(result)
|
|
691
|
+
except TorchUnsupportedDtypeError:
|
|
692
|
+
return numpy.asarray([convert_inner(e) for e in x], dtype=object)
|
|
693
|
+
# Convert any tensors to numpy
|
|
694
|
+
return to_numpy(x)
|
|
695
|
+
aa = numpy.asarray([convert_inner(x) for x in arr], dtype=object)
|
|
585
696
|
else:
|
|
586
697
|
aa = arr
|
|
587
698
|
return i, aa
|
|
@@ -738,7 +849,7 @@ def kg_write_symbol(x, display=False):
|
|
|
738
849
|
|
|
739
850
|
|
|
740
851
|
def kg_write_integer(x, display=False):
|
|
741
|
-
return str(x)
|
|
852
|
+
return str(int(x))
|
|
742
853
|
|
|
743
854
|
|
|
744
855
|
def kg_write_float(x, display=False):
|
|
@@ -761,16 +872,16 @@ def kg_write_string(s, display=False):
|
|
|
761
872
|
return ''.join(arr)
|
|
762
873
|
|
|
763
874
|
|
|
764
|
-
def kg_write_dict(d, display=False):
|
|
875
|
+
def kg_write_dict(d, backend, display=False):
|
|
765
876
|
# determine if the object d has overwritten the default __str__ and call it
|
|
766
877
|
# if so, otherwise use the default dict str
|
|
767
878
|
if d.__class__.__name__ != 'dict':
|
|
768
879
|
return str(d)
|
|
769
|
-
return ''.join([':{', ' '.join([kg_write(list(e), display=display) for e in d.items()]), '}'])
|
|
880
|
+
return ''.join([':{', ' '.join([kg_write(list(e), backend, display=display) for e in d.items()]), '}'])
|
|
770
881
|
|
|
771
882
|
|
|
772
|
-
def kg_write_list(x, display=False):
|
|
773
|
-
return ''.join(['[', ' '.join([kg_write(q, display=display) for q in x]), ']'])
|
|
883
|
+
def kg_write_list(x, backend, display=False):
|
|
884
|
+
return ''.join(['[', ' '.join([kg_write(q, backend, display=display) for q in x]), ']'])
|
|
774
885
|
|
|
775
886
|
|
|
776
887
|
def kg_write_fn(x, display=False):
|
|
@@ -783,21 +894,24 @@ def kg_write_channel(x, display=False):
|
|
|
783
894
|
return f":outchan.{2 if x.raw == sys.stderr else 1}"
|
|
784
895
|
|
|
785
896
|
|
|
786
|
-
def kg_write(a, display=False):
|
|
897
|
+
def kg_write(a, backend, display=False):
|
|
898
|
+
_backend = backend
|
|
899
|
+
# Convert backend arrays (e.g., torch tensors) to display-friendly format
|
|
900
|
+
a = _backend.to_display(a)
|
|
787
901
|
if isinstance(a,KGSym):
|
|
788
902
|
return kg_write_symbol(a, display=display)
|
|
789
|
-
elif is_integer(a):
|
|
903
|
+
elif is_integer(a, _backend):
|
|
790
904
|
return kg_write_integer(a,display=display)
|
|
791
|
-
elif is_float(a):
|
|
905
|
+
elif is_float(a, _backend):
|
|
792
906
|
return kg_write_float(a,display=display)
|
|
793
907
|
elif isinstance(a,KGChar):
|
|
794
908
|
return kg_write_char(a,display=display)
|
|
795
909
|
elif isinstance(a, str):
|
|
796
910
|
return kg_write_string(a,display=display)
|
|
797
911
|
elif isinstance(a,dict):
|
|
798
|
-
return kg_write_dict(a,display=display)
|
|
912
|
+
return kg_write_dict(a, _backend, display=display)
|
|
799
913
|
elif is_list(a):
|
|
800
|
-
return kg_write_list(a,display=display)
|
|
914
|
+
return kg_write_list(a, _backend, display=display)
|
|
801
915
|
elif isinstance(a,KGFn):
|
|
802
916
|
return kg_write_fn(a,display=display)
|
|
803
917
|
elif isinstance(a,KGChannel):
|
|
@@ -808,7 +922,7 @@ def kg_write(a, display=False):
|
|
|
808
922
|
return ":undefined"
|
|
809
923
|
|
|
810
924
|
|
|
811
|
-
def kg_argsort(a, descending=False):
|
|
925
|
+
def kg_argsort(a, backend, descending=False):
|
|
812
926
|
"""
|
|
813
927
|
|
|
814
928
|
Return the indices of the sorted array (may be nested) or a string. Duplicate elements are disambiguated by their position in the array.
|
|
@@ -824,6 +938,14 @@ def kg_argsort(a, descending=False):
|
|
|
824
938
|
"""
|
|
825
939
|
if not is_iterable(a) or len(a) == 0:
|
|
826
940
|
return a
|
|
941
|
+
|
|
942
|
+
# Fast path: for simple 1D numeric arrays, use native argsort
|
|
943
|
+
if hasattr(a, 'ndim') and a.ndim == 1:
|
|
944
|
+
dtype_kind = get_dtype_kind(a, backend)
|
|
945
|
+
if dtype_kind in ('i', 'f', 'u'):
|
|
946
|
+
return backend.argsort(a, descending=descending)
|
|
947
|
+
|
|
948
|
+
# Slow path: nested arrays or strings need element-by-element comparison
|
|
827
949
|
def _e(x):
|
|
828
950
|
return (-np.inf,x) if is_empty(a[x]) else (np.max(a[x]),x) if is_list(a[x]) else (a[x],x)
|
|
829
951
|
return np.asarray(sorted(range(len(a)), key=_e, reverse=descending))
|
klongpy/db/sys_fn_db.py
CHANGED
|
@@ -6,6 +6,7 @@ import pandas as pd
|
|
|
6
6
|
|
|
7
7
|
from klongpy.core import (KGCall, KGLambda, KlongException, reserved_fn_args,
|
|
8
8
|
reserved_fn_symbol_map)
|
|
9
|
+
from klongpy.backend import np as backend_np
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class KlongDbException(KlongException):
|
|
@@ -179,7 +180,7 @@ def eval_sys_fn_create_table(x):
|
|
|
179
180
|
t,"c",,c
|
|
180
181
|
|
|
181
182
|
"""
|
|
182
|
-
if
|
|
183
|
+
if backend_np.isarray(x):
|
|
183
184
|
return Table({k:v for k,v in x}, columns=[k for k,_ in x])
|
|
184
185
|
elif isinstance(x, pd.DataFrame):
|
|
185
186
|
return Table(x)
|
|
@@ -202,7 +203,7 @@ def eval_sys_fn_index(x, y):
|
|
|
202
203
|
raise KlongDbException(x, "An index may only be created on a table.")
|
|
203
204
|
if x.has_index():
|
|
204
205
|
raise KlongDbException(x, "Table already has an index.")
|
|
205
|
-
if not
|
|
206
|
+
if not backend_np.isarray(y):
|
|
206
207
|
raise KlongDbException(x, "An index must be a list of column names")
|
|
207
208
|
for q in y:
|
|
208
209
|
if q not in x.columns:
|
|
@@ -266,7 +267,7 @@ def eval_sys_fn_insert_table(x, y):
|
|
|
266
267
|
"""
|
|
267
268
|
if not isinstance(x,Table):
|
|
268
269
|
raise KlongDbException(x, "Inserts must be applied to a table")
|
|
269
|
-
if not
|
|
270
|
+
if not backend_np.isarray(y):
|
|
270
271
|
raise KlongDbException(x, "Values to insert must be a list")
|
|
271
272
|
batch = len(y.shape) > 1
|
|
272
273
|
y_cols = len(y[0]) if batch else len(y)
|