diffbio 0.1.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.
- diffbio/__init__.py +39 -0
- diffbio/configs.py +75 -0
- diffbio/constants.py +204 -0
- diffbio/core/__init__.py +127 -0
- diffbio/core/base_operators.py +612 -0
- diffbio/core/data_types.py +260 -0
- diffbio/core/gnn_components.py +629 -0
- diffbio/core/graph_utils.py +149 -0
- diffbio/core/neural_components.py +270 -0
- diffbio/core/optimal_transport.py +133 -0
- diffbio/core/soft_ops/__init__.py +216 -0
- diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
- diffbio/core/soft_ops/_projections_simplex.py +240 -0
- diffbio/core/soft_ops/_projections_transport.py +508 -0
- diffbio/core/soft_ops/_sorting_network.py +204 -0
- diffbio/core/soft_ops/_types.py +15 -0
- diffbio/core/soft_ops/_utils.py +342 -0
- diffbio/core/soft_ops/autograd_safe.py +120 -0
- diffbio/core/soft_ops/comparison.py +235 -0
- diffbio/core/soft_ops/elementwise.py +309 -0
- diffbio/core/soft_ops/logical.py +146 -0
- diffbio/core/soft_ops/quantile.py +376 -0
- diffbio/core/soft_ops/selection.py +236 -0
- diffbio/core/soft_ops/sorting.py +926 -0
- diffbio/core/soft_ops/straight_through.py +261 -0
- diffbio/core/uncertainty.py +279 -0
- diffbio/evaluation/__init__.py +42 -0
- diffbio/evaluation/adapters.py +409 -0
- diffbio/evaluation/graders.py +223 -0
- diffbio/evaluation/problem.py +157 -0
- diffbio/evaluation/runner.py +277 -0
- diffbio/losses/__init__.py +59 -0
- diffbio/losses/alignment_losses.py +222 -0
- diffbio/losses/biological_regularization.py +288 -0
- diffbio/losses/metric_losses.py +139 -0
- diffbio/losses/singlecell_losses.py +387 -0
- diffbio/losses/statistical_losses.py +345 -0
- diffbio/operators/__init__.py +60 -0
- diffbio/operators/_count_vae.py +197 -0
- diffbio/operators/_loss_balancing.py +65 -0
- diffbio/operators/_masked_gene_transformer.py +118 -0
- diffbio/operators/_transformer_validation.py +50 -0
- diffbio/operators/alignment/__init__.py +51 -0
- diffbio/operators/alignment/profile_hmm.py +350 -0
- diffbio/operators/alignment/scoring.py +127 -0
- diffbio/operators/alignment/smith_waterman.py +261 -0
- diffbio/operators/alignment/soft_msa.py +419 -0
- diffbio/operators/assembly/__init__.py +27 -0
- diffbio/operators/assembly/gnn_assembly.py +252 -0
- diffbio/operators/assembly/metagenomic_binning.py +296 -0
- diffbio/operators/crispr/__init__.py +17 -0
- diffbio/operators/crispr/guide_scoring.py +269 -0
- diffbio/operators/drug_discovery/__init__.py +133 -0
- diffbio/operators/drug_discovery/_graph_utils.py +142 -0
- diffbio/operators/drug_discovery/admet_predictor.py +285 -0
- diffbio/operators/drug_discovery/attentive_fp.py +411 -0
- diffbio/operators/drug_discovery/dti.py +261 -0
- diffbio/operators/drug_discovery/fingerprint.py +490 -0
- diffbio/operators/drug_discovery/maccs_keys.py +267 -0
- diffbio/operators/drug_discovery/message_passing.py +200 -0
- diffbio/operators/drug_discovery/primitives.py +242 -0
- diffbio/operators/drug_discovery/property_predictor.py +163 -0
- diffbio/operators/drug_discovery/similarity.py +193 -0
- diffbio/operators/epigenomics/__init__.py +35 -0
- diffbio/operators/epigenomics/chromatin_state.py +491 -0
- diffbio/operators/epigenomics/contextual.py +288 -0
- diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
- diffbio/operators/epigenomics/peak_calling.py +555 -0
- diffbio/operators/foundation_models/__init__.py +119 -0
- diffbio/operators/foundation_models/adapters.py +114 -0
- diffbio/operators/foundation_models/contracts.py +245 -0
- diffbio/operators/foundation_models/embedding_probe.py +83 -0
- diffbio/operators/foundation_models/experimental.py +128 -0
- diffbio/operators/foundation_models/foundation_model.py +332 -0
- diffbio/operators/foundation_models/frozen.py +59 -0
- diffbio/operators/foundation_models/precomputed.py +270 -0
- diffbio/operators/foundation_models/transformer_encoder.py +564 -0
- diffbio/operators/mapping/__init__.py +17 -0
- diffbio/operators/mapping/neural_mapper.py +493 -0
- diffbio/operators/metabolomics/__init__.py +39 -0
- diffbio/operators/metabolomics/spectral_similarity.py +315 -0
- diffbio/operators/molecular_dynamics/__init__.py +51 -0
- diffbio/operators/molecular_dynamics/force_field.py +265 -0
- diffbio/operators/molecular_dynamics/integrator.py +304 -0
- diffbio/operators/molecular_dynamics/primitives.py +115 -0
- diffbio/operators/multiomics/__init__.py +38 -0
- diffbio/operators/multiomics/hic_contact.py +377 -0
- diffbio/operators/multiomics/multiomics_vae.py +325 -0
- diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
- diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
- diffbio/operators/normalization/__init__.py +42 -0
- diffbio/operators/normalization/embedding.py +222 -0
- diffbio/operators/normalization/phate.py +400 -0
- diffbio/operators/normalization/umap.py +261 -0
- diffbio/operators/normalization/vae_normalizer.py +258 -0
- diffbio/operators/population/__init__.py +17 -0
- diffbio/operators/population/ancestry_estimation.py +274 -0
- diffbio/operators/preprocessing/__init__.py +76 -0
- diffbio/operators/preprocessing/adapter_removal.py +311 -0
- diffbio/operators/preprocessing/duplicate_filter.py +317 -0
- diffbio/operators/preprocessing/error_correction.py +287 -0
- diffbio/operators/protein/__init__.py +31 -0
- diffbio/operators/protein/secondary_structure.py +509 -0
- diffbio/operators/quality_filter.py +128 -0
- diffbio/operators/rna_structure/__init__.py +35 -0
- diffbio/operators/rna_structure/rna_folding.py +509 -0
- diffbio/operators/rnaseq/__init__.py +23 -0
- diffbio/operators/rnaseq/motif_discovery.py +251 -0
- diffbio/operators/rnaseq/splicing_psi.py +216 -0
- diffbio/operators/singlecell/__init__.py +193 -0
- diffbio/operators/singlecell/ambient_removal.py +333 -0
- diffbio/operators/singlecell/archetypes.py +191 -0
- diffbio/operators/singlecell/batch_correction.py +288 -0
- diffbio/operators/singlecell/cell_annotation.py +519 -0
- diffbio/operators/singlecell/communication.py +704 -0
- diffbio/operators/singlecell/differential_distribution.py +243 -0
- diffbio/operators/singlecell/doublet_detection.py +657 -0
- diffbio/operators/singlecell/downsampling.py +166 -0
- diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
- diffbio/operators/singlecell/grn_inference.py +336 -0
- diffbio/operators/singlecell/imputation.py +429 -0
- diffbio/operators/singlecell/knockdown_filter.py +176 -0
- diffbio/operators/singlecell/ot_trajectory.py +277 -0
- diffbio/operators/singlecell/simulation.py +444 -0
- diffbio/operators/singlecell/sindy_grn.py +247 -0
- diffbio/operators/singlecell/soft_clustering.py +211 -0
- diffbio/operators/singlecell/spatial_domains.py +677 -0
- diffbio/operators/singlecell/switch_de.py +184 -0
- diffbio/operators/singlecell/trajectory.py +447 -0
- diffbio/operators/singlecell/velocity.py +361 -0
- diffbio/operators/statistical/__init__.py +35 -0
- diffbio/operators/statistical/em_quantification.py +260 -0
- diffbio/operators/statistical/hmm.py +234 -0
- diffbio/operators/statistical/nb_glm.py +272 -0
- diffbio/operators/variant/__init__.py +64 -0
- diffbio/operators/variant/classifier.py +333 -0
- diffbio/operators/variant/cnn_classifier.py +255 -0
- diffbio/operators/variant/cnv_segmentation.py +678 -0
- diffbio/operators/variant/deepvariant_pileup.py +426 -0
- diffbio/operators/variant/pileup.py +240 -0
- diffbio/operators/variant/quality_recalibration.py +274 -0
- diffbio/pipelines/__init__.py +65 -0
- diffbio/pipelines/differential_expression.py +279 -0
- diffbio/pipelines/enhanced_variant_calling.py +326 -0
- diffbio/pipelines/perturbation.py +407 -0
- diffbio/pipelines/preprocessing.py +267 -0
- diffbio/pipelines/single_cell.py +366 -0
- diffbio/pipelines/variant_calling.py +490 -0
- diffbio/samplers/__init__.py +9 -0
- diffbio/samplers/perturbation_sampler.py +142 -0
- diffbio/sequences/__init__.py +34 -0
- diffbio/sequences/dna.py +239 -0
- diffbio/sources/__init__.py +149 -0
- diffbio/sources/_anndata_shared.py +89 -0
- diffbio/sources/_batch_iteration.py +37 -0
- diffbio/sources/_benchmark_source.py +152 -0
- diffbio/sources/_indexed_batch_source.py +38 -0
- diffbio/sources/_utils.py +45 -0
- diffbio/sources/anndata_interop.py +387 -0
- diffbio/sources/anndata_source.py +361 -0
- diffbio/sources/archive_ii.py +174 -0
- diffbio/sources/balifam.py +207 -0
- diffbio/sources/bam.py +265 -0
- diffbio/sources/bengrn_ground_truth.py +306 -0
- diffbio/sources/contextual_epigenomics.py +242 -0
- diffbio/sources/dti.py +359 -0
- diffbio/sources/embeddings.py +203 -0
- diffbio/sources/encode_peaks.py +223 -0
- diffbio/sources/fasta.py +226 -0
- diffbio/sources/immune_human.py +172 -0
- diffbio/sources/indexed_embeddings.py +128 -0
- diffbio/sources/indexed_view.py +191 -0
- diffbio/sources/molnet.py +493 -0
- diffbio/sources/multiomics.py +279 -0
- diffbio/sources/pancreas.py +108 -0
- diffbio/sources/perturbation/__init__.py +69 -0
- diffbio/sources/perturbation/_types.py +51 -0
- diffbio/sources/perturbation/_utils.py +125 -0
- diffbio/sources/perturbation/concat_source.py +115 -0
- diffbio/sources/perturbation/control_mapping.py +215 -0
- diffbio/sources/perturbation/experiment_config.py +261 -0
- diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
- diffbio/sources/perturbation/output_space.py +52 -0
- diffbio/sources/perturbation/perturbation_source.py +513 -0
- diffbio/sources/seqfish.py +145 -0
- diffbio/sources/sequence_foundation.py +68 -0
- diffbio/sources/singlecell_foundation.py +68 -0
- diffbio/splitters/__init__.py +63 -0
- diffbio/splitters/base.py +251 -0
- diffbio/splitters/molecular.py +330 -0
- diffbio/splitters/perturbation.py +199 -0
- diffbio/splitters/random.py +217 -0
- diffbio/splitters/sequence.py +201 -0
- diffbio/utils/__init__.py +55 -0
- diffbio/utils/dependency_runtime.py +115 -0
- diffbio/utils/nn_utils.py +157 -0
- diffbio/utils/quality.py +45 -0
- diffbio/utils/training.py +585 -0
- diffbio-0.1.0.dist-info/METADATA +480 -0
- diffbio-0.1.0.dist-info/RECORD +202 -0
- diffbio-0.1.0.dist-info/WHEEL +4 -0
- diffbio-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Elementwise soft operations.
|
|
2
|
+
|
|
3
|
+
Provides differentiable relaxations of elementwise non-smooth functions
|
|
4
|
+
(abs, sign, relu, clip, round, heaviside) using sigmoidal smoothing
|
|
5
|
+
with configurable smoothness modes.
|
|
6
|
+
|
|
7
|
+
All functions accept a ``mode`` parameter controlling the smoothness:
|
|
8
|
+
|
|
9
|
+
- ``"hard"``: Exact (non-differentiable) version matching JAX.
|
|
10
|
+
- ``"smooth"``: C-infinity smooth via logistic sigmoid.
|
|
11
|
+
- ``"c0"``: Continuous (C0) via piecewise linear/quadratic.
|
|
12
|
+
- ``"c1"``: Once differentiable (C1) via cubic Hermite polynomial.
|
|
13
|
+
- ``"c2"``: Twice differentiable (C2) via quintic Hermite polynomial.
|
|
14
|
+
|
|
15
|
+
The ``softness`` parameter controls the width of the transition region.
|
|
16
|
+
Higher softness = smoother (wider transition). All functions are
|
|
17
|
+
JIT-compatible and support ``jax.grad``/``jax.vmap``.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Literal
|
|
21
|
+
|
|
22
|
+
import jax
|
|
23
|
+
import jax.numpy as jnp
|
|
24
|
+
from jax import Array
|
|
25
|
+
|
|
26
|
+
from diffbio.core.soft_ops._types import SoftBool
|
|
27
|
+
from diffbio.core.soft_ops._utils import ensure_float, validate_softness
|
|
28
|
+
|
|
29
|
+
Mode = Literal["hard", "smooth", "c0", "c1", "c2"]
|
|
30
|
+
SigmoidalMode = Literal["smooth", "c0", "c1", "c2"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sigmoidal(
|
|
34
|
+
x: Array,
|
|
35
|
+
softness: float | Array = 0.1,
|
|
36
|
+
mode: SigmoidalMode = "smooth",
|
|
37
|
+
) -> SoftBool:
|
|
38
|
+
"""Sigmoidal S-curve function mapping R -> (0, 1).
|
|
39
|
+
|
|
40
|
+
Foundation for all other elementwise operations. Maps input values
|
|
41
|
+
through an S-shaped curve centered at 0, approaching 0 for large
|
|
42
|
+
negative values and 1 for large positive values.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
x: Input array.
|
|
46
|
+
softness: Width of transition region (> 0). Higher = smoother.
|
|
47
|
+
mode: Smoothness family: ``"smooth"`` (logistic sigmoid),
|
|
48
|
+
``"c0"`` (piecewise linear), ``"c1"`` (cubic Hermite),
|
|
49
|
+
``"c2"`` (quintic Hermite).
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
SoftBool array with values in [0, 1].
|
|
53
|
+
"""
|
|
54
|
+
validate_softness(softness)
|
|
55
|
+
x = x / softness
|
|
56
|
+
if mode == "smooth":
|
|
57
|
+
return jax.nn.sigmoid(x)
|
|
58
|
+
|
|
59
|
+
# Piecewise modes: scale by 1/5 so transition region [-5s, 5s]
|
|
60
|
+
# matches smooth sigmoid's effective range.
|
|
61
|
+
x = x / 5.0
|
|
62
|
+
if mode == "c0":
|
|
63
|
+
y = jnp.polyval(jnp.array([0.5, 0.5], dtype=x.dtype), x)
|
|
64
|
+
return jnp.where(x < -1.0, 0.0, jnp.where(x < 1.0, y, 1.0))
|
|
65
|
+
if mode == "c1":
|
|
66
|
+
y = jnp.polyval(
|
|
67
|
+
jnp.array([-0.25, 0.0, 0.75, 0.5], dtype=x.dtype),
|
|
68
|
+
x,
|
|
69
|
+
)
|
|
70
|
+
return jnp.where(x < -1.0, 0.0, jnp.where(x < 1.0, y, 1.0))
|
|
71
|
+
if mode == "c2":
|
|
72
|
+
y = jnp.polyval(
|
|
73
|
+
jnp.array([0.1875, 0.0, -0.625, 0.0, 0.9375, 0.5], dtype=x.dtype),
|
|
74
|
+
x,
|
|
75
|
+
)
|
|
76
|
+
return jnp.where(x < -1.0, 0.0, jnp.where(x < 1.0, y, 1.0))
|
|
77
|
+
msg = f"Invalid mode: {mode!r}. Must be 'smooth', 'c0', 'c1', or 'c2'."
|
|
78
|
+
raise ValueError(msg)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def softrelu(
|
|
82
|
+
x: Array,
|
|
83
|
+
softness: float | Array = 0.1,
|
|
84
|
+
mode: SigmoidalMode = "smooth",
|
|
85
|
+
gated: bool = False,
|
|
86
|
+
) -> Array:
|
|
87
|
+
"""Family of soft relaxations to ReLU.
|
|
88
|
+
|
|
89
|
+
Two variants:
|
|
90
|
+
- **Non-gated** (default): Antiderivative of :func:`sigmoidal`.
|
|
91
|
+
Smooth analog of ``max(0, x)``.
|
|
92
|
+
- **Gated**: ``x * sigmoidal(x)``. SiLU-style gating.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
x: Input array.
|
|
96
|
+
softness: Width of transition region (> 0).
|
|
97
|
+
mode: Smoothness family (see :func:`sigmoidal`).
|
|
98
|
+
gated: If True, use gated version ``x * sigmoidal(x)``.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Soft ReLU output, same shape as ``x``.
|
|
102
|
+
"""
|
|
103
|
+
validate_softness(softness)
|
|
104
|
+
x = x / softness
|
|
105
|
+
if mode == "smooth":
|
|
106
|
+
if gated:
|
|
107
|
+
y = x * sigmoidal(x, softness=1.0, mode="smooth")
|
|
108
|
+
else:
|
|
109
|
+
y = jax.nn.softplus(x)
|
|
110
|
+
else:
|
|
111
|
+
u = x / 5.0
|
|
112
|
+
if mode == "c0":
|
|
113
|
+
if gated:
|
|
114
|
+
y = x * sigmoidal(x, softness=1.0, mode="c0")
|
|
115
|
+
else:
|
|
116
|
+
y = 5.0 * jnp.polyval(
|
|
117
|
+
jnp.array([0.25, 0.5, 0.25], dtype=u.dtype),
|
|
118
|
+
u,
|
|
119
|
+
)
|
|
120
|
+
y = jnp.where(u < -1.0, 0.0, jnp.where(u < 1.0, y, x))
|
|
121
|
+
elif mode == "c1":
|
|
122
|
+
if gated:
|
|
123
|
+
y = x * sigmoidal(x, softness=1.0, mode="c1")
|
|
124
|
+
else:
|
|
125
|
+
y = 5.0 * jnp.polyval(
|
|
126
|
+
jnp.array([-0.0625, 0.0, 0.375, 0.5, 0.1875], dtype=u.dtype),
|
|
127
|
+
u,
|
|
128
|
+
)
|
|
129
|
+
y = jnp.where(u < -1.0, 0.0, jnp.where(u < 1.0, y, x))
|
|
130
|
+
elif mode == "c2":
|
|
131
|
+
if gated:
|
|
132
|
+
y = x * sigmoidal(x, softness=1.0, mode="c2")
|
|
133
|
+
else:
|
|
134
|
+
y = 5.0 * jnp.polyval(
|
|
135
|
+
jnp.array(
|
|
136
|
+
[0.03125, 0.0, -0.15625, 0.0, 0.46875, 0.5, 0.15625],
|
|
137
|
+
dtype=u.dtype,
|
|
138
|
+
),
|
|
139
|
+
u,
|
|
140
|
+
)
|
|
141
|
+
y = jnp.where(u < -1.0, 0.0, jnp.where(u < 1.0, y, x))
|
|
142
|
+
else:
|
|
143
|
+
msg = f"Invalid mode: {mode!r}"
|
|
144
|
+
raise ValueError(msg)
|
|
145
|
+
return y * softness
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def heaviside(
|
|
149
|
+
x: Array,
|
|
150
|
+
softness: float | Array = 0.1,
|
|
151
|
+
mode: Mode = "smooth",
|
|
152
|
+
) -> SoftBool:
|
|
153
|
+
"""Soft Heaviside step function.
|
|
154
|
+
|
|
155
|
+
Returns 0 for x < 0, 1 for x > 0, and 0.5 at x = 0 (hard mode).
|
|
156
|
+
Soft modes use :func:`sigmoidal` for smooth transition.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
x: Input array.
|
|
160
|
+
softness: Width of transition (> 0).
|
|
161
|
+
mode: ``"hard"`` or sigmoidal mode.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
SoftBool in [0, 1].
|
|
165
|
+
"""
|
|
166
|
+
x = ensure_float(x)
|
|
167
|
+
if mode == "hard":
|
|
168
|
+
return jnp.where(x < 0.0, 0.0, jnp.where(x > 0.0, 1.0, 0.5)).astype(
|
|
169
|
+
x.dtype,
|
|
170
|
+
)
|
|
171
|
+
return sigmoidal(x, softness=softness, mode=mode)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def round(
|
|
175
|
+
x: Array,
|
|
176
|
+
softness: float | Array = 0.1,
|
|
177
|
+
mode: Mode = "smooth",
|
|
178
|
+
neighbor_radius: int = 5,
|
|
179
|
+
) -> Array:
|
|
180
|
+
"""Soft rounding.
|
|
181
|
+
|
|
182
|
+
Hard mode returns ``jnp.round(x)``. Soft modes use a weighted sum
|
|
183
|
+
of nearby integers, with weights from :func:`sigmoidal`.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
x: Input array.
|
|
187
|
+
softness: Width of transition (> 0).
|
|
188
|
+
mode: ``"hard"`` or sigmoidal mode.
|
|
189
|
+
neighbor_radius: Number of integer neighbors to consider.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Soft-rounded values.
|
|
193
|
+
"""
|
|
194
|
+
if mode == "hard":
|
|
195
|
+
return jnp.round(x)
|
|
196
|
+
x = ensure_float(x)
|
|
197
|
+
center = jax.lax.stop_gradient(jnp.floor(x))
|
|
198
|
+
offsets = jnp.arange(
|
|
199
|
+
-neighbor_radius,
|
|
200
|
+
neighbor_radius + 1,
|
|
201
|
+
dtype=x.dtype,
|
|
202
|
+
)
|
|
203
|
+
n = center[..., None] + offsets
|
|
204
|
+
w_left = sigmoidal(x[..., None] - (n - 0.5), softness=softness, mode=mode)
|
|
205
|
+
w_right = sigmoidal(x[..., None] - (n + 0.5), softness=softness, mode=mode)
|
|
206
|
+
return jnp.sum(n * (w_left - w_right), axis=-1)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def sign(
|
|
210
|
+
x: Array,
|
|
211
|
+
softness: float | Array = 0.1,
|
|
212
|
+
mode: Mode = "smooth",
|
|
213
|
+
) -> Array:
|
|
214
|
+
"""Soft sign function.
|
|
215
|
+
|
|
216
|
+
Maps to [-1, 1]. Hard mode returns ``jnp.sign(x)``.
|
|
217
|
+
Soft modes use ``2 * sigmoidal(x) - 1``.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
x: Input array.
|
|
221
|
+
softness: Width of transition (> 0).
|
|
222
|
+
mode: ``"hard"`` or sigmoidal mode.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Values in [-1, 1].
|
|
226
|
+
"""
|
|
227
|
+
x = ensure_float(x)
|
|
228
|
+
if mode == "hard":
|
|
229
|
+
return jnp.sign(x).astype(x.dtype)
|
|
230
|
+
return sigmoidal(x, mode=mode, softness=softness) * 2.0 - 1.0
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def abs(
|
|
234
|
+
x: Array,
|
|
235
|
+
softness: float | Array = 0.1,
|
|
236
|
+
mode: Mode = "smooth",
|
|
237
|
+
) -> Array:
|
|
238
|
+
"""Soft absolute value.
|
|
239
|
+
|
|
240
|
+
Hard mode returns ``jnp.abs(x)``. Soft modes use
|
|
241
|
+
``x * sign(x, softness, mode)``.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
x: Input array.
|
|
245
|
+
softness: Width of transition (> 0).
|
|
246
|
+
mode: ``"hard"`` or sigmoidal mode.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Non-negative values (approximately).
|
|
250
|
+
"""
|
|
251
|
+
if mode == "hard":
|
|
252
|
+
return jnp.abs(x)
|
|
253
|
+
return x * sign(x, mode=mode, softness=softness)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def relu(
|
|
257
|
+
x: Array,
|
|
258
|
+
softness: float | Array = 0.1,
|
|
259
|
+
mode: Mode = "smooth",
|
|
260
|
+
gated: bool = False,
|
|
261
|
+
) -> Array:
|
|
262
|
+
"""Soft ReLU.
|
|
263
|
+
|
|
264
|
+
Hard mode returns ``jax.nn.relu(x)``. Soft modes delegate to
|
|
265
|
+
:func:`softrelu`.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
x: Input array.
|
|
269
|
+
softness: Width of transition (> 0).
|
|
270
|
+
mode: ``"hard"`` or sigmoidal mode.
|
|
271
|
+
gated: If True, use gated variant.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Soft ReLU output.
|
|
275
|
+
"""
|
|
276
|
+
if mode == "hard":
|
|
277
|
+
return jax.nn.relu(x)
|
|
278
|
+
return softrelu(x, mode=mode, softness=softness, gated=gated)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def clip(
|
|
282
|
+
x: Array,
|
|
283
|
+
a: float | Array,
|
|
284
|
+
b: float | Array,
|
|
285
|
+
softness: float | Array = 0.1,
|
|
286
|
+
mode: Mode = "smooth",
|
|
287
|
+
gated: bool = False,
|
|
288
|
+
) -> Array:
|
|
289
|
+
"""Soft clipping to [a, b].
|
|
290
|
+
|
|
291
|
+
Hard mode returns ``jnp.clip(x, a, b)``. Soft modes use two
|
|
292
|
+
:func:`softrelu` calls: ``a + softrelu(x - a) - softrelu(x - b)``.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
x: Input array.
|
|
296
|
+
a: Lower bound.
|
|
297
|
+
b: Upper bound.
|
|
298
|
+
softness: Width of transition (> 0).
|
|
299
|
+
mode: ``"hard"`` or sigmoidal mode.
|
|
300
|
+
gated: If True, use gated softrelu variant.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Clipped values approximately in [a, b].
|
|
304
|
+
"""
|
|
305
|
+
if mode == "hard":
|
|
306
|
+
return jnp.clip(x, a, b)
|
|
307
|
+
tmp1 = softrelu(x - a, mode=mode, softness=softness, gated=gated)
|
|
308
|
+
tmp2 = softrelu(x - b, mode=mode, softness=softness, gated=gated)
|
|
309
|
+
return a + tmp1 - tmp2
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Soft logical operators (fuzzy logic).
|
|
2
|
+
|
|
3
|
+
Provides differentiable fuzzy logic operations on SoftBool values.
|
|
4
|
+
No ``softness`` parameter -- these operate purely on probability
|
|
5
|
+
values in [0, 1].
|
|
6
|
+
|
|
7
|
+
Fuzzy logic semantics:
|
|
8
|
+
- NOT: ``1 - x``
|
|
9
|
+
- AND (product): ``prod(x)`` or geometric mean
|
|
10
|
+
- OR: ``1 - AND(NOT(x))``
|
|
11
|
+
- XOR: ``AND(x, NOT(y)) OR AND(NOT(x), y)``
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import jax.numpy as jnp
|
|
15
|
+
|
|
16
|
+
from diffbio.core.soft_ops._types import SoftBool
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def logical_not(x: SoftBool) -> SoftBool:
|
|
20
|
+
"""Soft logical NOT: ``1 - x``.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
x: SoftBool input in [0, 1].
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Complement in [0, 1].
|
|
27
|
+
"""
|
|
28
|
+
return 1.0 - x
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def all(
|
|
32
|
+
x: SoftBool,
|
|
33
|
+
axis: int = -1,
|
|
34
|
+
epsilon: float = 1e-10,
|
|
35
|
+
use_geometric_mean: bool = False,
|
|
36
|
+
) -> SoftBool:
|
|
37
|
+
"""Soft logical AND reduction along axis.
|
|
38
|
+
|
|
39
|
+
Uses product (default) or geometric mean to combine probabilities.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
x: SoftBool input in [0, 1].
|
|
43
|
+
axis: Axis along which to reduce.
|
|
44
|
+
epsilon: Minimum value for numerical stability in log.
|
|
45
|
+
use_geometric_mean: If True, use geometric mean instead of product.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Reduced SoftBool.
|
|
49
|
+
"""
|
|
50
|
+
if use_geometric_mean:
|
|
51
|
+
return jnp.exp(
|
|
52
|
+
jnp.mean(jnp.log(jnp.clip(x, min=epsilon)), axis=axis),
|
|
53
|
+
)
|
|
54
|
+
return jnp.prod(x, axis=axis)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def any(
|
|
58
|
+
x: SoftBool,
|
|
59
|
+
axis: int = -1,
|
|
60
|
+
use_geometric_mean: bool = False,
|
|
61
|
+
) -> SoftBool:
|
|
62
|
+
"""Soft logical OR reduction along axis.
|
|
63
|
+
|
|
64
|
+
Implemented as ``1 - all(1 - x)``.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
x: SoftBool input in [0, 1].
|
|
68
|
+
axis: Axis along which to reduce.
|
|
69
|
+
use_geometric_mean: If True, use geometric mean in the inner AND.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Reduced SoftBool.
|
|
73
|
+
"""
|
|
74
|
+
return logical_not(
|
|
75
|
+
all(logical_not(x), axis=axis, use_geometric_mean=use_geometric_mean),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def logical_and(
|
|
80
|
+
x: SoftBool,
|
|
81
|
+
y: SoftBool,
|
|
82
|
+
use_geometric_mean: bool = False,
|
|
83
|
+
) -> SoftBool:
|
|
84
|
+
"""Soft logical AND between two SoftBools.
|
|
85
|
+
|
|
86
|
+
Stacks inputs and applies :func:`all` along the stack axis.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
x: First SoftBool.
|
|
90
|
+
y: Second SoftBool.
|
|
91
|
+
use_geometric_mean: If True, use geometric mean.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
SoftBool in [0, 1].
|
|
95
|
+
"""
|
|
96
|
+
return all(
|
|
97
|
+
jnp.stack([x, y], axis=-1),
|
|
98
|
+
axis=-1,
|
|
99
|
+
use_geometric_mean=use_geometric_mean,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def logical_or(
|
|
104
|
+
x: SoftBool,
|
|
105
|
+
y: SoftBool,
|
|
106
|
+
use_geometric_mean: bool = False,
|
|
107
|
+
) -> SoftBool:
|
|
108
|
+
"""Soft logical OR between two SoftBools.
|
|
109
|
+
|
|
110
|
+
Stacks inputs and applies :func:`any` along the stack axis.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
x: First SoftBool.
|
|
114
|
+
y: Second SoftBool.
|
|
115
|
+
use_geometric_mean: If True, use geometric mean in inner AND.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
SoftBool in [0, 1].
|
|
119
|
+
"""
|
|
120
|
+
return any(
|
|
121
|
+
jnp.stack([x, y], axis=-1),
|
|
122
|
+
axis=-1,
|
|
123
|
+
use_geometric_mean=use_geometric_mean,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def logical_xor(
|
|
128
|
+
x: SoftBool,
|
|
129
|
+
y: SoftBool,
|
|
130
|
+
use_geometric_mean: bool = False,
|
|
131
|
+
) -> SoftBool:
|
|
132
|
+
"""Soft logical XOR between two SoftBools.
|
|
133
|
+
|
|
134
|
+
Implemented as ``(x AND NOT y) OR (NOT x AND y)``.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
x: First SoftBool.
|
|
138
|
+
y: Second SoftBool.
|
|
139
|
+
use_geometric_mean: If True, use geometric mean in AND/OR.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
SoftBool in [0, 1].
|
|
143
|
+
"""
|
|
144
|
+
t1 = logical_and(x, logical_not(y), use_geometric_mean=use_geometric_mean)
|
|
145
|
+
t2 = logical_and(logical_not(x), y, use_geometric_mean=use_geometric_mean)
|
|
146
|
+
return logical_or(t1, t2, use_geometric_mean=use_geometric_mean)
|