brisc 0.2.3__tar.gz → 0.2.4__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 (61) hide show
  1. {brisc-0.2.3 → brisc-0.2.4}/PKG-INFO +1 -1
  2. {brisc-0.2.3 → brisc-0.2.4}/brisc/harmonize.pyx +34 -30
  3. {brisc-0.2.3 → brisc-0.2.4}/brisc/norm_factors.pyx +3 -6
  4. {brisc-0.2.3 → brisc-0.2.4}/brisc/pseudobulk.py +1 -3
  5. {brisc-0.2.3 → brisc-0.2.4}/brisc/single_cell.py +21 -73
  6. {brisc-0.2.3 → brisc-0.2.4}/brisc.egg-info/PKG-INFO +1 -1
  7. brisc-0.2.4/brisc.egg-info/scm_version.json +8 -0
  8. brisc-0.2.3/brisc.egg-info/scm_version.json +0 -8
  9. {brisc-0.2.3 → brisc-0.2.4}/.git_archival.txt +0 -0
  10. {brisc-0.2.3 → brisc-0.2.4}/.gitattributes +0 -0
  11. {brisc-0.2.3 → brisc-0.2.4}/.gitignore +0 -0
  12. {brisc-0.2.3 → brisc-0.2.4}/LICENSE +0 -0
  13. {brisc-0.2.3 → brisc-0.2.4}/MANIFEST.in +0 -0
  14. {brisc-0.2.3 → brisc-0.2.4}/README.md +0 -0
  15. {brisc-0.2.3 → brisc-0.2.4}/brisc/__init__.py +0 -0
  16. {brisc-0.2.3 → brisc-0.2.4}/brisc/colorspacious.py +0 -0
  17. {brisc-0.2.3 → brisc-0.2.4}/brisc/concatenate.py +0 -0
  18. {brisc-0.2.3 → brisc-0.2.4}/brisc/cysparse.pyx +0 -0
  19. {brisc-0.2.3 → brisc-0.2.4}/brisc/cyutils.pxd +0 -0
  20. {brisc-0.2.3 → brisc-0.2.4}/brisc/cyutils.pyx +0 -0
  21. {brisc-0.2.3 → brisc-0.2.4}/brisc/de.py +0 -0
  22. {brisc-0.2.3 → brisc-0.2.4}/brisc/doublets.pyx +0 -0
  23. {brisc-0.2.3 → brisc-0.2.4}/brisc/embed.pyx +0 -0
  24. {brisc-0.2.3 → brisc-0.2.4}/brisc/hdf5.pyx +0 -0
  25. {brisc-0.2.3 → brisc-0.2.4}/brisc/hvg.pyx +0 -0
  26. {brisc-0.2.3 → brisc-0.2.4}/brisc/kmeans.pyx +0 -0
  27. {brisc-0.2.3 → brisc-0.2.4}/brisc/knn.pyx +0 -0
  28. {brisc-0.2.3 → brisc-0.2.4}/brisc/labels.pyx +0 -0
  29. {brisc-0.2.3 → brisc-0.2.4}/brisc/leiden.pyx +0 -0
  30. {brisc-0.2.3 → brisc-0.2.4}/brisc/loess.pyx +0 -0
  31. {brisc-0.2.3 → brisc-0.2.4}/brisc/normalize.pyx +0 -0
  32. {brisc-0.2.3 → brisc-0.2.4}/brisc/pca.pyx +0 -0
  33. {brisc-0.2.3 → brisc-0.2.4}/brisc/pseudobulk_and_markers.pyx +0 -0
  34. {brisc-0.2.3 → brisc-0.2.4}/brisc/qc.pyx +0 -0
  35. {brisc-0.2.3 → brisc-0.2.4}/brisc/snn.pyx +0 -0
  36. {brisc-0.2.3 → brisc-0.2.4}/brisc/sparse.py +0 -0
  37. {brisc-0.2.3 → brisc-0.2.4}/brisc/threadpoolctl.py +0 -0
  38. {brisc-0.2.3 → brisc-0.2.4}/brisc/type_aliases.py +0 -0
  39. {brisc-0.2.3 → brisc-0.2.4}/brisc/utils.py +0 -0
  40. {brisc-0.2.3 → brisc-0.2.4}/brisc/validated_dict.py +0 -0
  41. {brisc-0.2.3 → brisc-0.2.4}/brisc.egg-info/SOURCES.txt +0 -0
  42. {brisc-0.2.3 → brisc-0.2.4}/brisc.egg-info/dependency_links.txt +0 -0
  43. {brisc-0.2.3 → brisc-0.2.4}/brisc.egg-info/requires.txt +0 -0
  44. {brisc-0.2.3 → brisc-0.2.4}/brisc.egg-info/scm_file_list.json +0 -0
  45. {brisc-0.2.3 → brisc-0.2.4}/brisc.egg-info/top_level.txt +0 -0
  46. {brisc-0.2.3 → brisc-0.2.4}/pyproject.toml +0 -0
  47. {brisc-0.2.3 → brisc-0.2.4}/setup.cfg +0 -0
  48. {brisc-0.2.3 → brisc-0.2.4}/setup.py +0 -0
  49. {brisc-0.2.3 → brisc-0.2.4}/tests/conftest.py +0 -0
  50. {brisc-0.2.3 → brisc-0.2.4}/tests/make_test_data.py +0 -0
  51. {brisc-0.2.3 → brisc-0.2.4}/tests/test_cluster.py +0 -0
  52. {brisc-0.2.3 → brisc-0.2.4}/tests/test_de.py +0 -0
  53. {brisc-0.2.3 → brisc-0.2.4}/tests/test_embedding.py +0 -0
  54. {brisc-0.2.3 → brisc-0.2.4}/tests/test_hvg.py +0 -0
  55. {brisc-0.2.3 → brisc-0.2.4}/tests/test_integration.py +0 -0
  56. {brisc-0.2.3 → brisc-0.2.4}/tests/test_markers.py +0 -0
  57. {brisc-0.2.3 → brisc-0.2.4}/tests/test_neighbors.py +0 -0
  58. {brisc-0.2.3 → brisc-0.2.4}/tests/test_normalize.py +0 -0
  59. {brisc-0.2.3 → brisc-0.2.4}/tests/test_pca.py +0 -0
  60. {brisc-0.2.3 → brisc-0.2.4}/tests/test_pseudobulk.py +0 -0
  61. {brisc-0.2.3 → brisc-0.2.4}/tests/test_qc.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brisc
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Fast single-cell toolkit
5
5
  Author-email: Michael Wainberg <m.wainberg@utoronto.ca>
6
6
  License-Expression: MIT
@@ -372,8 +372,7 @@ def harmony(const float[:, ::1] PCs,
372
372
  cdef float base, kmeans_error, entropy_term, norm, Rij, \
373
373
  diversity_penalty, prev_objective, total, Pr_bi, Eij, Oij, Rkj, \
374
374
  objective, last_two, old, new, ridge_lambda, factor, beta, \
375
- two_over_sigma = <float> 2 / sigma, half_sigma = sigma / <float> 2, \
376
- exp_neg_two_over_sigma = exp(-two_over_sigma)
375
+ two_over_sigma = <float> 2 / sigma, half_sigma = sigma / <float> 2
377
376
  cdef float past_clustering_objectives[3]
378
377
  cdef bint compute_objective, compute_Y, converged, cold_start
379
378
  cdef str metrics
@@ -471,8 +470,8 @@ def harmony(const float[:, ::1] PCs,
471
470
  if tau:
472
471
  for i in range(num_batches):
473
472
  Pr_b[i] = <float> N_b[i] / num_cells
474
- base = exp(-(<float> N_b[i]) / (num_clusters * tau))
475
- theta_batch[i] = theta * (<float> 1 - base * base)
473
+ base = (<float> N_b[i]) / (num_clusters * tau)
474
+ theta_batch[i] = theta * (<float> 1 - exp(-base * base))
476
475
  else:
477
476
  for i in range(num_batches):
478
477
  Pr_b[i] = <float> N_b[i] / num_cells
@@ -568,7 +567,9 @@ def harmony(const float[:, ::1] PCs,
568
567
  kmeans_error = kmeans_error + Rij * (
569
568
  <float> 1 - half_sigma *
570
569
  distances[thread_index, i, j])
571
- entropy_term = entropy_term + Rij * log(Rij)
570
+ # Skip entries of `R` that underflowed to 0
571
+ if Rij > <float> 0:
572
+ entropy_term = entropy_term + Rij * log(Rij)
572
573
  distances[thread_index, i, j] = Rij
573
574
  beta = piece > slab_piece_offsets[slab]
574
575
  matrix_multiply(
@@ -667,11 +668,12 @@ def harmony(const float[:, ::1] PCs,
667
668
  # then chunkwise within each block. Note that the full formula
668
669
  # for `R` is: `((E + 1) / (O + E + 1)) ** theta *
669
670
  # exp(-2 / sigma * (1 - Z @ Y.T))`
670
- # which we can calculate as `ratio * exp(distances)` where
671
- # `ratio = exp(-2 / sigma) *
672
- # ((E + 1) / (O + E + 1)) ** theta` and
673
- # `distances = 2 / sigma * Z @ Y.T`. Calculating `R` this way
674
- # saves a few multiplications.
671
+ # which we calculate as `ratio * exp(distances - 2 / sigma)`
672
+ # where `ratio = ((E + 1) / (O + E + 1)) ** theta` and
673
+ # `distances = 2 / sigma * Z @ Y.T`. Since the rows of `Z` and
674
+ # `Y` have unit norm, the exponent is never positive, so
675
+ # `exp()` can underflow but never overflow, even when `sigma`
676
+ # is small.
675
677
  #
676
678
  # If we are done clustering or `early_stopping=True`, also
677
679
  # compute the first two components of the objective function,
@@ -701,8 +703,7 @@ def harmony(const float[:, ::1] PCs,
701
703
 
702
704
  # Get the chunk of `Z`, the normalized PCs, to
703
705
  # process. Calculate the observed-to-expected ratio
704
- # `ratio = exp(-2 / sigma) *
705
- # ((E + 1) / (O + E + 1)) ** theta`
706
+ # `ratio = ((E + 1) / (O + E + 1)) ** theta`
706
707
  # for the chunk, subtracting the chunk's own
707
708
  # contributions to `E` and `O`: `Pr_b` times
708
709
  # `R_sums[chunk_index]` and `delta_O[chunk_index]`,
@@ -727,11 +728,10 @@ def harmony(const float[:, ::1] PCs,
727
728
  Eij = E[i, j] - \
728
729
  Pr_bi * R_sums[chunk_index, j]
729
730
  Oij = O[i, j] + delta_O[chunk_index, i, j]
730
- ratio[chunk_index, i, j] = \
731
- exp_neg_two_over_sigma * \
732
- pow((Eij + <float> 1) /
733
- (Oij + Eij + <float> 1),
734
- theta_batch[i])
731
+ ratio[chunk_index, i, j] = pow(
732
+ (Eij + <float> 1) /
733
+ (Oij + Eij + <float> 1),
734
+ theta_batch[i])
735
735
 
736
736
  # Compute `distances = 2 / sigma * Z @ Y.T`
737
737
  matrix_multiply(
@@ -758,7 +758,8 @@ def harmony(const float[:, ::1] PCs,
758
758
  norm = 0
759
759
  for j in range(num_clusters):
760
760
  R[k, j] = exp(distances[
761
- thread_index, i, j]) * \
761
+ thread_index, i, j] -
762
+ two_over_sigma) * \
762
763
  ratio[chunk_index, batch_label, j]
763
764
  norm += R[k, j]
764
765
  norm = <float> 1 / norm
@@ -779,8 +780,11 @@ def harmony(const float[:, ::1] PCs,
779
780
  kmeans_error = kmeans_error + Rkj * (
780
781
  <float> 1 - half_sigma *
781
782
  distances[thread_index, i, j])
782
- entropy_term = \
783
- entropy_term + Rkj * log(Rkj)
783
+ # Skip entries of `R` that
784
+ # underflowed to 0
785
+ if Rkj > <float> 0:
786
+ entropy_term = entropy_term + \
787
+ Rkj * log(Rkj)
784
788
  kmeans_errors[slot] = kmeans_error
785
789
  entropy_terms[slot] = entropy_term
786
790
  if compute_Y:
@@ -889,10 +893,12 @@ def harmony(const float[:, ::1] PCs,
889
893
  with gil:
890
894
  PyErr_CheckSignals()
891
895
 
892
- # If Harmony has converged, this is the last iteration, so the
893
- # embeddings are not normalized. Unless this is the last iteration,
894
- # recompute `R`, `O`, and `E` from the corrected embeddings before
895
- # the next round of clustering (a "cold start"), as Harmony2 does.
896
+ # Unless this is the last iteration (because we have converged or
897
+ # `max_iterations` has been reached), normalize the corrected
898
+ # embeddings and recompute `R`, `O`, and `E` from them before the
899
+ # next round of clustering (a "cold start"). On the last
900
+ # iteration, leave the embeddings unnormalized, so that the
901
+ # corrected PCs are returned either way.
896
902
  converged = prev_objective - objective < \
897
903
  tolerance * abs(prev_objective)
898
904
  cold_start = not converged and iteration < max_iterations
@@ -991,10 +997,9 @@ def harmony(const float[:, ::1] PCs,
991
997
  W[i, k, j] = factor * (
992
998
  W[i, k, j] - O[i, k] * intercepts[k, j])
993
999
 
994
- # Correct `Z` slab by slab. Unless Harmony has converged, also
995
- # normalize each row of `Z`. For a cold start, also recompute `R`,
996
- # each slab's contributions to `O`, and each slab's partial sum of
997
- # `R.T @ Z` for the next `Y`.
1000
+ # Correct `Z` slab by slab. For a cold start, also normalize each
1001
+ # row of `Z` and recompute `R`, each slab's contributions to `O`,
1002
+ # and each slab's partial sum of `R.T @ Z` for the next `Y`.
998
1003
  with parallel(num_threads=num_threads_or_chunks):
999
1004
  thread_index = threadid()
1000
1005
  for slab in prange(num_slabs):
@@ -1014,7 +1019,7 @@ def harmony(const float[:, ::1] PCs,
1014
1019
  W[batch_label], Z_chunk[thread_index],
1015
1020
  transpose_A=False, transpose_B=False, alpha=-1,
1016
1021
  beta=1)
1017
- if not converged:
1022
+ if cold_start:
1018
1023
  for i in range(end_row - start_row):
1019
1024
  norm = 0
1020
1025
  for j in range(num_PCs):
@@ -1025,7 +1030,6 @@ def harmony(const float[:, ::1] PCs,
1025
1030
  for j in range(num_PCs):
1026
1031
  Z_chunk[thread_index, i, j] = \
1027
1032
  Z_chunk[thread_index, i, j] * norm
1028
- if cold_start:
1029
1033
  matrix_multiply(
1030
1034
  Z_chunk[thread_index, :end_row - start_row],
1031
1035
  Y, distances[thread_index], transpose_A=False,
@@ -80,8 +80,7 @@ def calc_norm_factors(const numeric[:, ::1] X,
80
80
  const unsigned ref_sample,
81
81
  float[::1] norm_factors,
82
82
  sum_type[::1] library_size,
83
- unsigned num_threads,
84
- const bint is_win):
83
+ unsigned num_threads):
85
84
  cdef numeric ref_count, count
86
85
  cdef sum_type ref_library_size
87
86
  cdef unsigned i, j, n, loL, hiL, loS, hiS, thread_index, start_sample, \
@@ -126,11 +125,9 @@ def calc_norm_factors(const numeric[:, ::1] X,
126
125
  log_normalized_X_ref[j] = \
127
126
  log2(<float> count * inverse_ref_library_size)
128
127
 
129
- # Calculate the normalization factor for each sample. Force the parallel
130
- # path for Windows for floating-point consistency between 1 and multiple
131
- # threads.
128
+ # Calculate the normalization factor for each sample
132
129
  num_threads = min(num_threads, num_samples)
133
- if num_threads <= 1 and not is_win:
130
+ if num_threads <= 1:
134
131
  logR_buffer.resize(num_genes)
135
132
  absE_buffer.resize(num_genes)
136
133
  logR_rank_buffer.resize(num_genes)
@@ -4,7 +4,6 @@ import os
4
4
  import polars as pl
5
5
  import re
6
6
  import shutil
7
- import sys
8
7
  from collections.abc import Iterable
9
8
  from itertools import islice, pairwise
10
9
  from pathlib import Path
@@ -4252,11 +4251,10 @@ class Pseudobulk:
4252
4251
 
4253
4252
  # Calculate norm factors
4254
4253
  norm_factors = np.empty(num_samples, dtype=np.float32)
4255
- is_win = sys.platform == 'win32'
4256
4254
  calc_norm_factors(X=X, logratio_trim=logratio_trim, sum_trim=sum_trim,
4257
4255
  A_cutoff=A_cutoff, ref_sample=ref_sample,
4258
4256
  norm_factors=norm_factors, library_size=library_size,
4259
- num_threads=num_threads, is_win=is_win)
4257
+ num_threads=num_threads)
4260
4258
 
4261
4259
  return norm_factors # this is actually `library_size * norm_factors`
4262
4260
 
@@ -13333,7 +13333,7 @@ class SingleCell:
13333
13333
  oversampling_factor: int | np.integer | float |
13334
13334
  np.floating = 1,
13335
13335
  chunk_size_kmeans: int | np.integer | None = None,
13336
- max_clustering_iterations: int | np.integer = 5,
13336
+ max_clustering_iterations: int | np.integer = 4,
13337
13337
  block_proportion: int | float | np.integer |
13338
13338
  np.floating = 0.05,
13339
13339
  tolerance: int | float | np.integer | np.floating = 0.01,
@@ -13355,52 +13355,12 @@ class SingleCell:
13355
13355
  multiple batches of the same dataset, with
13356
13356
  [Harmony2](https://github.com/immunogenomics/harmony).
13357
13357
 
13358
- Our implementation differs from the original Harmony2 implementation in
13359
- three key ways:
13360
-
13361
- First, we parallelize Harmony via an innovative nested block strategy.
13362
- The original implementation partitions cells randomly into blocks, each
13363
- containing a fixed fraction (`block_proportion`, 5% by default) of the
13364
- total cells. It iterates over each block, subtracting the contribution
13365
- of the cells in the block to the observed and expected cluster-batch
13366
- co-occurence matrices `O` and `E`, re-calculating the soft-clustering
13367
- assignments `R` based on the residual `O` and `E`, then adding back the
13368
- contribution of the cells in the block to `O` and `E` based on the new
13369
- `R`. This approach resists straightforward parallelization because
13370
- updates to `R` for future blocks depend on updates to `O` and `E` from
13371
- previous blocks, leading to convergence failure if naively
13372
- parallelized. Instead, we parallelize within blocks by dividing them
13373
- into chunks of `chunk_size_Harmony` cells. We process each chunk in
13374
- parallel, subtracting only the `O` and `E` contributions of the chunk
13375
- itself, updating `R` for the chunk, and, after processing all chunks in
13376
- the block, add back the `O` and `E` contributions for all chunks based
13377
- on the updated `R`. Updating `O` and `E` at the end of each block
13378
- (rather than after processing every cell in the dataset) ensures
13379
- convergence, while the inner chunking enables parallelization without
13380
- disrupting convergence. To use the original implementation's strategy
13381
- of updating `R`, `O`, and `E` for each block as a whole, set
13382
- `chunk_size_Harmony` to at least the block size (`block_proportion`
13383
- times the total number of cells, rounded up to a multiple of 16), at
13384
- the cost of no longer parallelizing the update of `R`.
13385
-
13386
- Second, we reduce the default number of clustering iterations
13387
- (Harmony's inner loop) from 20 to 5, but always complete all 5
13388
- iterations without early stopping based on convergence. In practice,
13389
- the largest changes to the Harmony objective function result from
13390
- updating the PCs (outer loop), not updating the soft-clustering
13391
- assignments (inner loop). Our implementation makes updating the PCs
13392
- sufficiently fast that there are rapidly diminishing returns from
13393
- performing lots of clustering updates, versus just skipping directly to
13394
- updating the PCs after a few clustering iterations. To use the original
13395
- implementation's default of 20 iterations with early stopping, specify
13396
- `early_stopping=True, max_clustering_iterations=20`.
13397
-
13398
- Third, we use random k-means initialization, like version 1 of Harmony,
13399
- rather than Harmony2's kmeans++ initialization. We implement a parallel
13400
- version of kmeans++ called [k-means||](https://arxiv.org/abs/1203.6402),
13401
- but find it does not improve the accuracy of label transfer relative to
13402
- random initialization and increases runtime. kmeans|| initialization can
13403
- be enabled with `kmeans_barbar=True`.
13358
+ Unlike Harmony2, our implementation parallelizes the clustering step
13359
+ within each block of cells, by dividing each block into chunks of
13360
+ `chunk_size_Harmony` cells that are updated in parallel. It also uses
13361
+ random k-means initialization rather than k-means++ (initialization
13362
+ with k-means||, a parallel version of k-means++, can be enabled with
13363
+ `kmeans_barbar=True`).
13404
13364
 
13405
13365
  Args:
13406
13366
  others: the other SingleCell datasets to harmonize this one with.
@@ -13448,20 +13408,15 @@ class SingleCell:
13448
13408
  30 and 3000 cells.
13449
13409
  max_iterations: the maximum number of iterations to run Harmony
13450
13410
  for, if convergence is not achieved. Defaults to
13451
- 10, like the original Harmony R package,
13452
- harmony-pytorch, and harmonypy. Set to `None` to
13453
- use as many iterations as necessary to achieve
13454
- convergence.
13411
+ 10. Set to `None` to use as many iterations as
13412
+ necessary to achieve convergence.
13455
13413
  num_kmeans_iterations: the maximum number of iterations of
13456
- k-means clustering to perform before
13457
- starting the nearest-neighbor search,
13458
- stopping early if a relative convergence of
13414
+ k-means clustering to perform to
13415
+ initialize the cluster centroids, stopping
13416
+ early if a relative convergence of
13459
13417
  `kmeans_tolerance` is reached. Defaults to
13460
- 25, like the original Harmony R package,
13461
- harmony-pytorch, and harmonypy. However,
13462
- unlike these packages, only one
13463
- initialization is tried rather than 10 to
13464
- reduce runtime.
13418
+ 25, whereas Harmony2 always runs 10
13419
+ iterations without early stopping.
13465
13420
  kmeans_tolerance: the relative change in inertia (the sum of
13466
13421
  squared distances from each cell to its assigned
13467
13422
  centroid) used to determine whether to stop
@@ -13504,19 +13459,13 @@ class SingleCell:
13504
13459
  clustering step within each Harmony
13505
13460
  iteration for, or the maximum number of
13506
13461
  iterations if `early_stopping=True`.
13507
- Unlike the original Harmony algorithm,
13508
- convergence of the clustering is not
13462
+ Convergence of the clustering is not
13509
13463
  checked unless `early_stopping=True`.
13510
- Defaults to 5 iterations; this differs
13511
- from the 20 used by the original harmony
13512
- R package and harmonypy and the 200
13513
- iterations used by harmony-pytorch,
13514
- which do have convergence checks.
13515
- Must be 4 or more when
13516
- `early_stopping=True`, since Harmony's
13517
- clustering convergence check requires
13518
- knowing the errors from the past 3
13519
- iterations.
13464
+ Defaults to 4 iterations. Must be 4 or
13465
+ more when `early_stopping=True`, since
13466
+ the clustering convergence check
13467
+ requires knowing the errors from the
13468
+ past 3 iterations.
13520
13469
  block_proportion: the proportion of cells to use in each batch
13521
13470
  update in the clustering step; must be greater
13522
13471
  than zero and less than or equal to 1
@@ -13525,8 +13474,7 @@ class SingleCell:
13525
13474
  `max_iterations` iterations
13526
13475
  early_stopping: whether to stop clustering before
13527
13476
  `max_clustering_iterations` iterations if
13528
- convergence is reached, like in the original
13529
- Harmony algorithm
13477
+ convergence is reached.
13530
13478
  clustering_tolerance: the relative tolerance used to determine
13531
13479
  whether to stop clustering before
13532
13480
  `max_clustering_iterations` iterations. Only
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brisc
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Fast single-cell toolkit
5
5
  Author-email: Michael Wainberg <m.wainberg@utoronto.ca>
6
6
  License-Expression: MIT
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.2.4",
3
+ "distance": 0,
4
+ "node": "gba159a34ecfeeba309d63514d0ed75ab859226c3",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-31"
8
+ }
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.2.3",
3
- "distance": 0,
4
- "node": "g7458aedfcaa8dbcfe4436b468bd19a068abbe8ae",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-10-25"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes