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.
Files changed (61) hide show
  1. klongpy/__init__.py +19 -1
  2. klongpy/adverbs.py +5 -5
  3. klongpy/autograd.py +308 -0
  4. klongpy/backend.py +167 -99
  5. klongpy/backends/__init__.py +94 -0
  6. klongpy/backends/base.py +320 -0
  7. klongpy/backends/numpy_backend.py +122 -0
  8. klongpy/backends/torch_backend.py +995 -0
  9. klongpy-0.6.8.data/scripts/kgpy → klongpy/cli.py +65 -88
  10. klongpy/core.py +228 -106
  11. klongpy/db/sys_fn_db.py +4 -3
  12. klongpy/dyads.py +173 -32
  13. klongpy/interpreter.py +31 -3
  14. klongpy/lib/help.kg +2 -2
  15. klongpy/monads.py +49 -12
  16. klongpy/repl.py +91 -0
  17. klongpy/sys_fn.py +129 -18
  18. klongpy/sys_fn_autograd.py +290 -0
  19. klongpy/sys_fn_ipc.py +18 -7
  20. klongpy/sys_fn_timer.py +13 -3
  21. klongpy/web/sys_fn_web.py +28 -6
  22. klongpy-0.7.0.dist-info/METADATA +493 -0
  23. klongpy-0.7.0.dist-info/RECORD +48 -0
  24. {klongpy-0.6.8.dist-info → klongpy-0.7.0.dist-info}/WHEEL +1 -1
  25. klongpy-0.7.0.dist-info/entry_points.txt +2 -0
  26. {klongpy-0.6.8.dist-info → klongpy-0.7.0.dist-info}/top_level.txt +0 -1
  27. klongpy-0.6.8.dist-info/METADATA +0 -412
  28. klongpy-0.6.8.dist-info/RECORD +0 -72
  29. tests/__init__.py +0 -6
  30. tests/gen_join_over.py +0 -119
  31. tests/gen_py_suite.py +0 -77
  32. tests/gen_test_fn.py +0 -259
  33. tests/perf_async.py +0 -25
  34. tests/perf_avg.py +0 -18
  35. tests/perf_duckdb.py +0 -32
  36. tests/perf_gen.py +0 -38
  37. tests/perf_ipc_overhead.py +0 -34
  38. tests/perf_join.py +0 -53
  39. tests/perf_load.py +0 -17
  40. tests/perf_prog.py +0 -18
  41. tests/perf_serdes.py +0 -52
  42. tests/perf_sys_fn_db.py +0 -263
  43. tests/perf_vector.py +0 -40
  44. tests/test_accel.py +0 -227
  45. tests/test_df_cache.py +0 -85
  46. tests/test_examples.py +0 -64
  47. tests/test_extra_suite.py +0 -382
  48. tests/test_file_cache.py +0 -185
  49. tests/test_interop.py +0 -181
  50. tests/test_kgtests.py +0 -65
  51. tests/test_known_bugs.py +0 -206
  52. tests/test_prog.py +0 -107
  53. tests/test_suite.py +0 -1479
  54. tests/test_suite_file.py +0 -153
  55. tests/test_sys_fn.py +0 -420
  56. tests/test_sys_fn_db.py +0 -88
  57. tests/test_sys_fn_ipc.py +0 -587
  58. tests/test_sys_fn_timer.py +0 -133
  59. tests/test_util.py +0 -233
  60. tests/utils.py +0 -126
  61. {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
- def __init__(self, klong, fn):
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
- return issubclass(type(x), (int, np.integer))
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
- return issubclass(type(x), (float, np.floating, int))
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
- return is_float(a) or is_integer(a)
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
- Converts input data into a NumPy array, ensuring all sub-arrays are also NumPy arrays, to meet the requirements of KlongPy.
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
- na, nb = isinstance(a,np.ndarray), isinstance(b,np.ndarray)
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
- if na and nb and a.dtype == b.dtype and a.dtype != 'O':
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
- na, nb = na or isinstance(a,list), nb or isinstance(b,list)
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
- return len(a) == len(b) and all(kg_equal(x, y) for x, y in zip(a, b))
317
-
318
- return np.isclose(a,b) if is_number(a) and is_number(b) else a == b
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
- return kg_asarray([rec_fn(x, f) for x in a]) if is_list(a) else f(a)
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
- return kg_asarray([((vec_fn(x, f)) if is_list(x) else f(x)) for x in a]) if np.isarray(a) and a.dtype == 'O' else f(a)
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 kg_asarray([vec_fn2(x, y, f) for x,y in zip(a,b)])
503
+ return _kg_asarray([vec_fn2(x, y, f) for x,y in zip(a,b)])
431
504
  else:
432
- return kg_asarray([vec_fn2(x, b, f) for x in a])
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 kg_asarray([vec_fn2(x, y, f) for x,y in zip(a,b)])
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 kg_asarray([vec_fn2(a, x, f) for x in b])
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
- return isinstance(x, KGChar)
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
- # TODO: can we just transform chars to ints so that CuPy works?
459
- # we'll need to reassemble the strinsg, so pros/cons.
460
- def str_to_chr_arr(s):
461
- return np.asarray([KGChar(x) for x in s],dtype=object)
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
- aa = kg_asarray(arr)
583
- if aa.dtype.kind not in ['O','i','f']:
584
- aa = np.asarray(arr, dtype=object)
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 np.isarray(x):
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 np.isarray(y):
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 np.isarray(y):
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)