jarvisplot 1.2.3__tar.gz → 1.2.5__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.
Files changed (51) hide show
  1. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/PKG-INFO +1 -1
  2. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/data_loader.py +197 -75
  3. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/inner_func.py +14 -19
  4. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot.egg-info/PKG-INFO +1 -1
  5. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/pyproject.toml +1 -1
  6. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/README.md +0 -0
  7. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/__init__.py +0 -0
  8. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/adapters.py +0 -0
  9. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  10. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/data_pipelines.py +0 -0
  11. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/figure.py +0 -0
  12. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/helper.py +0 -0
  13. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/load_data.py +0 -0
  14. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/method_registry.py +0 -0
  15. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/Figure/preprocessor.py +0 -0
  16. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/__init__.py +0 -0
  17. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cache_store.py +0 -0
  18. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  19. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
  20. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  21. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  22. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  23. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  24. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  25. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  26. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  27. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  28. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/args.json +0 -0
  29. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/colors/colormaps.json +0 -0
  30. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/default/output.json +0 -0
  31. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  32. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  33. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  34. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  35. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  36. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/icons/gambit.png +0 -0
  37. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/icons/gambit_small.png +0 -0
  38. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cards/style_preference.json +0 -0
  39. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/cli.py +0 -0
  40. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/client.py +0 -0
  41. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/config.py +0 -0
  42. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/core.py +0 -0
  43. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/utils/__init__.py +0 -0
  44. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/utils/cmaps.py +0 -0
  45. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot/utils/interpolator.py +0 -0
  46. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot.egg-info/SOURCES.txt +0 -0
  47. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot.egg-info/dependency_links.txt +0 -0
  48. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot.egg-info/entry_points.txt +0 -0
  49. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot.egg-info/requires.txt +0 -0
  50. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/jarvisplot.egg-info/top_level.txt +0 -0
  51. {jarvisplot-1.2.3 → jarvisplot-1.2.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvisplot
3
- Version: 1.2.3
3
+ Version: 1.2.5
4
4
  Summary: JarvisPLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -120,6 +120,15 @@ class DataSet():
120
120
  return f" HDF5 loaded!\n\t name -> {self.name}\n\t group -> {self.group}\n\t path -> {self.path}"
121
121
  return f" CSV loaded!\n\t name -> {self.name}\n\t path -> {self.path}"
122
122
 
123
+ def _debug_enabled(self) -> bool:
124
+ """Best-effort check for loguru debug level without assuming logger internals."""
125
+ try:
126
+ core = getattr(self.logger, "_core", None)
127
+ min_level = getattr(core, "min_level", 100)
128
+ return int(min_level) <= 10
129
+ except Exception:
130
+ return False
131
+
123
132
  def _emit_summary_text(self, summary_msg: str) -> None:
124
133
  if self.logger:
125
134
  self.logger.warning("\n" + str(summary_msg))
@@ -429,37 +438,74 @@ class DataSet():
429
438
  _, path, ds = best
430
439
  return path, ds[()]
431
440
 
432
- def _to_dataframe(arr, name=""):
433
- if isinstance(arr, np.ndarray) and getattr(arr.dtype, "names", None):
434
- df = pd.DataFrame.from_records(arr)
435
- # prefix columns to keep dataset origin
436
- if name:
437
- df.columns = [f"{name}:{c}" for c in df.columns]
438
- return df
439
- elif hasattr(arr, "ndim") and arr.ndim == 2:
440
- cols = [f"col{i}" for i in range(arr.shape[1])]
441
- if name:
442
- cols = [f"{name}:{c}" for c in cols]
443
- return pd.DataFrame(arr, columns=cols)
444
- else:
445
- col = name if name else "value"
446
- return pd.DataFrame({col: np.ravel(arr)})
447
-
448
- def _collect_group_datasets(g: h5py.Group, prefix: str="", whitelist=None):
449
- """Recursively collect (path, ndarray) for all datasets under a group."""
450
- items = []
441
+ def _collect_group_dataset_paths(g: h5py.Group, prefix: str="", whitelist=None):
442
+ """Recursively collect dataset paths under a group."""
443
+ paths = []
451
444
  for k, v in g.items():
452
445
  path = f"{prefix}/{k}" if prefix else k
453
446
  if isinstance(v, h5py.Dataset):
454
447
  if whitelist is None or path in whitelist:
455
- items.append((path, v[()]))
448
+ paths.append(path)
456
449
  elif isinstance(v, h5py.Group):
457
- items.extend(_collect_group_datasets(v, path, whitelist=whitelist))
458
- return items
450
+ paths.extend(_collect_group_dataset_paths(v, path, whitelist=whitelist))
451
+ return paths
452
+
453
+ def _dataset_to_columns(path: str, ds: h5py.Dataset, row_mask=None, rename_map=None):
454
+ """
455
+ Convert one HDF5 dataset to DataFrame-ready columns.
456
+ Returns: (columns_dict, nrows, shape_tuple)
457
+ """
458
+ arr = ds[()]
459
+
460
+ def _apply_mask(vec):
461
+ if row_mask is None:
462
+ return vec
463
+ if len(vec) != len(row_mask):
464
+ raise ValueError(
465
+ "HDF5 row mismatch while applying is_valid mask: "
466
+ f"dataset='{path}', rows={len(vec)}, mask_rows={len(row_mask)}"
467
+ )
468
+ return vec[row_mask]
469
+
470
+ def _map_name(name: str) -> str:
471
+ if isinstance(rename_map, dict):
472
+ return rename_map.get(name, name)
473
+ return name
474
+
475
+ if isinstance(arr, np.ndarray) and getattr(arr.dtype, "names", None):
476
+ # Structured array: one output column per field
477
+ out = {}
478
+ for fname in arr.dtype.names:
479
+ src = f"{path}:{fname}"
480
+ dst = _map_name(src)
481
+ vec = np.asarray(arr[fname]).reshape(-1)
482
+ out[dst] = _apply_mask(vec)
483
+ nrows = len(next(iter(out.values()))) if out else 0
484
+ return out, nrows, (nrows, len(out))
485
+
486
+ if hasattr(arr, "ndim") and getattr(arr, "ndim", 0) == 2:
487
+ # 2D array: one output column per axis-1 entry
488
+ out = {}
489
+ nrows = int(arr.shape[0])
490
+ ncols = int(arr.shape[1])
491
+ for i in range(ncols):
492
+ src = f"{path}:col{i}"
493
+ dst = _map_name(src)
494
+ out[dst] = _apply_mask(arr[:, i])
495
+ out_rows = len(next(iter(out.values()))) if out else 0
496
+ return out, out_rows, (nrows, ncols)
497
+
498
+ # Scalar / 1D / anything else: flatten to one column
499
+ flat = np.ravel(arr)
500
+ src = path if path else "value"
501
+ dst = _map_name(src)
502
+ out = _apply_mask(flat)
503
+ return {dst: out}, len(out), (len(flat), 1)
459
504
 
460
505
  with h5py.File(self.path, "r") as f1:
461
- # Log top-level keys to help the user
462
- print_hdf5_tree_ascii(f1[self.group], root_name=self.group, logger=self.logger)
506
+ # Tree traversal is expensive on large files; keep it behind debug mode.
507
+ if self._debug_enabled() and self.group in f1:
508
+ print_hdf5_tree_ascii(f1[self.group], root_name=self.group, logger=self.logger)
463
509
 
464
510
  if self.group in f1 and isinstance(f1[self.group], h5py.Group):
465
511
  group = f1[self.group]
@@ -473,9 +519,9 @@ class DataSet():
473
519
  "HDF5 load_whitelist enabled -> {} paths".format(len(whitelist))
474
520
  )
475
521
 
476
- # Collect all datasets under the group (recursively)
477
- items = _collect_group_datasets(group, prefix=self.group, whitelist=whitelist)
478
- if not items:
522
+ # Collect dataset paths only (defer heavy reads to reduce peak memory)
523
+ dataset_paths = _collect_group_dataset_paths(group, prefix=self.group, whitelist=whitelist)
524
+ if not dataset_paths:
479
525
  if whitelist is not None:
480
526
  raise RuntimeError(
481
527
  "HDF5 group '{}' has no datasets after applying columns.load_whitelist.".format(
@@ -484,43 +530,110 @@ class DataSet():
484
530
  )
485
531
  raise RuntimeError(f"HDF5 group '{self.group}' contains no datasets.")
486
532
 
487
- # If there is only one dataset, behave like before
488
- kkeys = []
489
- if len(items) == 1:
490
- path, arr = items[0]
491
- dfs = [(path, _to_dataframe(arr, name=path))]
492
- kkeys.append(path)
493
- else:
494
- # Build a dataframe per dataset
495
- dfs = [(p, _to_dataframe(arr, name=p)) for p, arr in items]
496
- kkeys = [p for p, arr in items]
497
-
498
- # Try to concatenate along columns; all datasets must have identical row counts
499
- lengths = {len(df) for _, df in dfs}
500
- if len(lengths) == 1:
533
+ dataset_set = set(dataset_paths)
534
+ base_paths = [p for p in dataset_paths if not p.endswith("_isvalid")]
535
+ isvalid_paths = [p for p in dataset_paths if p.endswith("_isvalid")]
536
+
537
+ # Step 1: parse columns config and finalize rename mapping first.
538
+ rename_entries = self._columns_dict().get("rename", [])
539
+ rename_map = {}
540
+ if isinstance(rename_entries, list) and rename_entries:
541
+ self.logger.warning("{}: Loading Columns Rename Map".format(self.name))
542
+ for item in rename_entries:
543
+ if not isinstance(item, dict):
544
+ continue
545
+ source = str(item.get("source", "")).strip()
546
+ target = str(item.get("target", "")).strip()
547
+ if not source or not target:
548
+ continue
549
+ source_canon = self._canonical_dataset_path(source)
550
+ rename_map[source_canon] = target
551
+ rename_map[f"{source_canon}_isvalid"] = f"{target}_isvalid"
552
+
553
+ # Step 2: build row mask by isvalid policy, before main table assembly.
554
+ row_mask = None
555
+ filter_rows_before = None
556
+ filter_rows_after = None
557
+ required_base = self._whitelist_base_paths if isinstance(self._whitelist_base_paths, set) else None
558
+
559
+ if self.isvalid_policy == "clean":
560
+ filter_isvalid_paths = []
561
+ if required_base is not None:
562
+ missing_base = sorted([c for c in required_base if c not in dataset_set])
563
+ missing_isvalid = sorted([f"{c}_isvalid" for c in required_base if f"{c}_isvalid" not in dataset_set])
564
+ if missing_base or missing_isvalid:
565
+ self.logger.warning(
566
+ "Skip is_valid policy: not all whitelist columns have companion _isvalid columns. "
567
+ "missing_base={}, missing_isvalid={}".format(missing_base, missing_isvalid)
568
+ )
569
+ else:
570
+ filter_isvalid_paths = [f"{c}_isvalid" for c in sorted(required_base)]
571
+ else:
572
+ # No whitelist: use all available *_isvalid columns with base companions.
573
+ filter_isvalid_paths = [p for p in isvalid_paths if p[:-8] in dataset_set]
574
+
575
+ if filter_isvalid_paths:
576
+ mask = None
577
+ nrows_ref = None
578
+ for vp in filter_isvalid_paths:
579
+ vec = np.ravel(f1[vp][()]).astype(bool, copy=False)
580
+ if nrows_ref is None:
581
+ nrows_ref = len(vec)
582
+ mask = vec.copy()
583
+ else:
584
+ if len(vec) != nrows_ref:
585
+ raise ValueError(
586
+ "HDF5 is_valid datasets have inconsistent row counts: '{}' has {}, expected {}.".format(
587
+ vp, len(vec), nrows_ref
588
+ )
589
+ )
590
+ mask &= vec
591
+ row_mask = mask
592
+ filter_rows_before = int(nrows_ref or 0)
593
+ filter_rows_after = int(mask.sum()) if mask is not None else filter_rows_before
594
+
595
+ # Step 3: decide final output columns, then assemble DataFrame once.
596
+ # clean mode drops *_isvalid by default; if whitelist is incomplete, keep them (existing behavior).
597
+ read_paths = list(base_paths)
598
+ if self.isvalid_policy == "raw":
599
+ read_paths.extend(isvalid_paths)
600
+ elif required_base is not None and row_mask is None:
601
+ read_paths.extend(isvalid_paths)
602
+
603
+ merged_cols = {}
604
+ shapes = {}
605
+ expected_rows = None
606
+ row_mismatch = False
607
+ for path in read_paths:
608
+ ds = f1[path]
609
+ cols_dict, nrows, shape_token = _dataset_to_columns(path, ds, row_mask=row_mask, rename_map=rename_map)
610
+ shapes[path] = shape_token
611
+
612
+ if expected_rows is None:
613
+ expected_rows = nrows
614
+ elif nrows != expected_rows:
615
+ row_mismatch = True
616
+
617
+ for cname, cval in cols_dict.items():
618
+ merged_cols[cname] = cval
619
+
620
+ if not row_mismatch:
501
621
  # safe to concat by columns → single merged DataFrame only
502
- self.data = pd.concat([df for _, df in dfs], axis=1)
622
+ self.data = pd.DataFrame(merged_cols, copy=False)
503
623
 
504
624
  self.keys = list(self.data.columns)
505
-
506
- self.apply_is_valid_policy(kkeys)
507
-
508
- rename_entries = self._columns_dict().get("rename", [])
509
- if isinstance(rename_entries, list) and rename_entries:
510
- self.logger.warning("{}: Loading Columns Rename Map".format(self.name))
511
- cmap = {}
512
- for item in rename_entries:
513
- if not isinstance(item, dict):
514
- continue
515
- source = str(item.get("source", "")).strip()
516
- target = str(item.get("target", "")).strip()
517
- if not source or not target:
518
- continue
519
- source_canon = self._canonical_dataset_path(source)
520
- cmap[source_canon] = target
521
- cmap[f"{source_canon}_isvalid"] = f"{target}_isvalid"
522
- if cmap:
523
- self.rename_columns(cmap)
625
+
626
+ if row_mask is not None and self.isvalid_policy == "clean":
627
+ self.logger.warning("Filtering Invalid Data from HDF5 Output")
628
+ self.logger.warning(
629
+ "DataSet Shape: \n\t Before filtering -> ({}, {})\n\t After filtering -> ({}, {})".format(
630
+ filter_rows_before if filter_rows_before is not None else self.data.shape[0],
631
+ self.data.shape[1],
632
+ filter_rows_after if filter_rows_after is not None else self.data.shape[0],
633
+ self.data.shape[1],
634
+ )
635
+ )
636
+
524
637
  self._apply_dataset_transform(stage="hdf5")
525
638
 
526
639
  # Emit a pretty summary BEFORE returning
@@ -542,7 +655,6 @@ class DataSet():
542
655
  print_hdf5_tree_ascii(group, root_name=self.group, logger=self.logger)
543
656
  except Exception:
544
657
  pass
545
- shapes = {p: df.shape for p, df in dfs}
546
658
  raise ValueError(
547
659
  "HDF5 group '{grp}' is invalid for merging: datasets have different row counts. "
548
660
  "Please fix the input or choose a different dataset/group. Details: {details}".format(
@@ -557,16 +669,14 @@ class DataSet():
557
669
  if self.data is None:
558
670
  self.load(force=False)
559
671
 
560
- isvalids = []
561
- all_isvalid_cols = []
562
- if self.keys is not None:
563
- for col in self.keys:
564
- if isinstance(col, str) and col.endswith("_isvalid"):
565
- all_isvalid_cols.append(col)
672
+ source_cols = list(self.keys or [])
673
+ source_col_set = set(source_cols)
674
+ all_isvalid_cols = [col for col in source_cols if isinstance(col, str) and col.endswith("_isvalid")]
566
675
 
567
- for kk in kkeys:
568
- if isinstance(kk, str) and kk.endswith("_isvalid") and kk[:-8] in self.keys:
569
- isvalids.append(kk)
676
+ isvalids = [
677
+ kk for kk in kkeys
678
+ if isinstance(kk, str) and kk.endswith("_isvalid") and kk[:-8] in source_col_set
679
+ ]
570
680
 
571
681
  # Fallback: if mapping by kkeys fails, still use loaded *_isvalid columns.
572
682
  if not isvalids:
@@ -574,9 +684,8 @@ class DataSet():
574
684
 
575
685
  required_base = self._whitelist_base_paths if isinstance(self._whitelist_base_paths, set) else None
576
686
  if required_base is not None:
577
- loaded_cols = set(self.keys or [])
578
- missing_base = sorted([c for c in required_base if c not in loaded_cols])
579
- missing_isvalid = sorted([f"{c}_isvalid" for c in required_base if f"{c}_isvalid" not in loaded_cols])
687
+ missing_base = sorted([c for c in required_base if c not in source_col_set])
688
+ missing_isvalid = sorted([f"{c}_isvalid" for c in required_base if f"{c}_isvalid" not in source_col_set])
580
689
  if missing_base or missing_isvalid:
581
690
  self.logger.warning(
582
691
  "Skip is_valid policy: not all whitelist columns have companion _isvalid columns. "
@@ -642,9 +751,21 @@ def dataframe_summary(df: pd.DataFrame, name: str = "") -> str:
642
751
  tail = max(0, width - head - 3)
643
752
  return s[:head] + "..." + s[-tail:]
644
753
 
754
+ def human_bytes(num_bytes: int) -> str:
755
+ size = float(max(0, num_bytes))
756
+ for unit in ("B", "KB", "MB", "GB", "TB"):
757
+ if size < 1024.0 or unit == "TB":
758
+ return f"{size:.2f} {unit}"
759
+ size /= 1024.0
760
+
645
761
  nrows, ncols = df.shape
646
762
  cols = list(df.columns)
647
763
  show_cols = cols[:]
764
+ try:
765
+ # Fast estimate (deep=False) to avoid expensive scans on large object columns.
766
+ mem_bytes = int(df.memory_usage(index=True, deep=False).sum())
767
+ except Exception:
768
+ mem_bytes = 0
648
769
 
649
770
  # Compute per-column stats for the shown columns
650
771
  dtypes = df[show_cols].dtypes.astype(str)
@@ -699,7 +820,8 @@ def dataframe_summary(df: pd.DataFrame, name: str = "") -> str:
699
820
  head_lines = []
700
821
  if name:
701
822
  head_lines.append(f"Selected dataset:{name}")
702
- head_lines.append(f"DataFrame shape:\n\t {nrows}\t rows × {ncols} \tcols\n")
823
+ head_lines.append(f"\t DataFrame shape\t-> {nrows}\t rows × {ncols} \tcols")
824
+ head_lines.append(f"\t Approx memory usage\t-> {human_bytes(mem_bytes)}")
703
825
  head_lines.append("=== DataFrame Summary Table ===")
704
826
 
705
827
  # Column table header
@@ -136,23 +136,14 @@ def LogGauss(xx, mean, err):
136
136
  return prob
137
137
 
138
138
 
139
- def _extract_operas_full_name_map(func_locals, numeric_funcs):
140
- """Build `namespace.function -> callable` map from Jarvis-Operas dicts."""
141
- full_name_map = {}
142
- for namespace, ns_obj in (func_locals or {}).items():
143
- if not isinstance(namespace, str):
144
- continue
145
- attrs = getattr(ns_obj, "__dict__", None)
146
- if not isinstance(attrs, dict):
147
- continue
148
- for short_name, symbol_fn in attrs.items():
149
- if not isinstance(short_name, str):
150
- continue
151
- symbolic_name = str(symbol_fn)
152
- fn = numeric_funcs.get(symbolic_name)
153
- if callable(fn):
154
- full_name_map[f"{namespace}.{short_name}"] = fn
155
- return full_name_map
139
+ def _extract_operas_full_name_map():
140
+ """Build `namespace.function -> callable` map from Jarvis-Operas registry."""
141
+ try:
142
+ from jarvis_operas import build_register_dicts
143
+
144
+ return build_register_dicts()
145
+ except Exception:
146
+ return {}
156
147
 
157
148
 
158
149
  def _build_operas_eval_namespaces(full_name_map):
@@ -184,11 +175,15 @@ def update_funcs(funcs):
184
175
  # Keep both symbolic/numeric views for compatibility, and provide
185
176
  # namespace-style objects so plain `eval` can call `namespace.func(x)`.
186
177
  try:
187
- from jarvis_operas import func_locals, numeric_funcs
178
+ from jarvis_operas import build_sympy_dicts
188
179
 
180
+ operas_full_name_map = _extract_operas_full_name_map()
181
+ func_locals, numeric_funcs = build_sympy_dicts(
182
+ operas_full_name_map,
183
+ include_all=True,
184
+ )
189
185
  funcs.update(numeric_funcs)
190
186
  funcs.update(func_locals)
191
- operas_full_name_map = _extract_operas_full_name_map(func_locals, numeric_funcs)
192
187
  funcs.update(operas_full_name_map)
193
188
  funcs.update(_build_operas_eval_namespaces(operas_full_name_map))
194
189
  except Exception:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvisplot
3
- Version: 1.2.3
3
+ Version: 1.2.5
4
4
  Summary: JarvisPLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jarvisplot"
7
- version = "1.2.3"
7
+ version = "1.2.5"
8
8
  description = "JarvisPLOT: YAML-driven plotting engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes
File without changes