Skip to content

Commit bf408ab

Browse files
committed
[ENH] Implementing soft_segment_unbounded for numpy and torch
1 parent c6b728b commit bf408ab

File tree

2 files changed

+111
-11
lines changed

2 files changed

+111
-11
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import numbers
2+
3+
import numpy as np
4+
5+
from ...core.backend_tensor import BackendTensor as bt, BackendTensor
6+
7+
try:
8+
import torch
9+
except ModuleNotFoundError:
10+
pass
11+
12+
13+
def soft_segment_unbounded(Z, edges, ids, sigmoid_slope):
14+
"""
15+
Z: array of shape (...,) of scalar values
16+
edges: array of shape (K-1,) of finite split points [e1, e2, ..., e_{K-1}]
17+
ids: array of shape (K,) of the id for each of the K bins
18+
sigmoid_slope: scalar target peak slope m > 0
19+
returns: array of shape (...,) of the soft-assigned id
20+
"""
21+
ids = bt.t.array(ids[::-1].copy())
22+
23+
# Check if sigmoid function is num or array
24+
match sigmoid_slope:
25+
case numbers.Number():
26+
membership = _lith_segmentation(Z, edges, ids, sigmoid_slope)
27+
case _ if isinstance(sigmoid_slope, (np.ndarray, torch.Tensor)):
28+
membership = _final_faults_segmentation(Z, edges, sigmoid_slope)
29+
case _:
30+
raise ValueError("sigmoid_slope must be a float or an array")
31+
32+
ids__sum = bt.t.sum(membership * ids, axis=-1)
33+
return ids__sum[None, :]
34+
35+
36+
def _final_faults_segmentation(Z, edges, sigmoid_slope):
37+
first = _sigmoid(
38+
scalar_field=Z,
39+
edges=edges[0],
40+
tau_k=1 / sigmoid_slope
41+
) # shape (...,)
42+
last = _sigmoid(
43+
scalar_field=Z,
44+
edges=edges[-1],
45+
tau_k=1 / sigmoid_slope
46+
)
47+
membership = bt.t.concatenate(
48+
[first[..., None], last[..., None]],
49+
axis=-1
50+
) # shape (...,K)
51+
return membership
52+
53+
54+
def _lith_segmentation(Z, edges, ids, sigmoid_slope):
55+
# 1) per-edge temperatures τ_k = |Δ_k|/(4·m)
56+
jumps = bt.t.abs(ids[1:] - ids[:-1]) # shape (K-1,)
57+
tau_k = jumps / float(sigmoid_slope) # shape (K-1,)
58+
# 2) first bin (-∞, e1) via σ((e1 - Z)/τ₁)
59+
first = _sigmoid(
60+
scalar_field=-Z,
61+
edges=-edges[0],
62+
tau_k=tau_k[0]
63+
) # shape (...,)
64+
# 3) last bin [e_{K-1}, ∞) via σ((Z - e_{K-1})/τ_{K-1})
65+
# last = 1.0 / (1.0 + np.exp(-(Z - edges[-1]) / tau_k[-1])) # shape (...,)
66+
last = _sigmoid(
67+
scalar_field=Z,
68+
edges=edges[-1],
69+
tau_k=tau_k[-1]
70+
)
71+
# 4) middle bins [e_i, e_{i+1}): σ((Z - e_i)/τ_i) - σ((Z - e_{i+1})/τ_{i+1})
72+
# shape (...,1)
73+
left = _sigmoid(
74+
scalar_field=(Z[..., None]),
75+
edges=edges[:-1],
76+
tau_k=tau_k[:-1]
77+
)
78+
right = _sigmoid(
79+
scalar_field=(Z[..., None]),
80+
edges=edges[1:],
81+
tau_k=tau_k[1:]
82+
)
83+
middle = left - right # (...,K-2)
84+
# 5) assemble memberships and weight by ids
85+
membership = bt.t.concatenate(
86+
[first[..., None], middle, last[..., None]],
87+
axis=-1
88+
) # shape (...,K)
89+
return membership
90+
91+
92+
def _sigmoid(scalar_field, edges, tau_k):
93+
return 1.0 / (1.0 + bt.t.exp(-(scalar_field - edges) / tau_k))

gempy_engine/modules/activator/activator_interface.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import warnings
22

3-
from gempy_engine.config import DEBUG_MODE, AvailableBackends
4-
from gempy_engine.core.backend_tensor import BackendTensor as bt, BackendTensor
3+
from ...config import DEBUG_MODE, AvailableBackends
4+
from ...core.backend_tensor import BackendTensor as bt, BackendTensor
5+
from ...core.data.exported_fields import ExportedFields
6+
from ._soft_segment import soft_segment_unbounded
7+
58
import numpy as np
69
import numbers
710

8-
from gempy_engine.core.data.exported_fields import ExportedFields
9-
1011

1112
def activate_formation_block(exported_fields: ExportedFields, ids: np.ndarray,
1213
sigmoid_slope: float) -> np.ndarray:
@@ -23,9 +24,17 @@ def activate_formation_block(exported_fields: ExportedFields, ids: np.ndarray,
2324
sigmoid_slope=sigmoid_slope
2425
)
2526
else:
27+
sigm = soft_segment_unbounded(
28+
Z=Z_x,
29+
edges=scalar_value_at_sp,
30+
ids=ids,
31+
sigmoid_slope=sigmoid_slope
32+
)
33+
return sigm
34+
2635
match BackendTensor.engine_backend:
2736
case AvailableBackends.PYTORCH:
28-
sigm = soft_segment_unbounded(
37+
sigm = soft_segment_unbounded_torch(
2938
Z=Z_x,
3039
edges=scalar_value_at_sp,
3140
ids=ids,
@@ -85,7 +94,7 @@ def _compute_sigmoid(Z_x, scale_0, scale_1, drift_0, drift_1, drift_id, sigmoid_
8594
import torch
8695

8796

88-
def soft_segment_unbounded(Z, edges, ids, sigmoid_slope):
97+
def soft_segment_unbounded_torch(Z, edges, ids, sigmoid_slope):
8998
"""
9099
Z: (...,) tensor of scalar values
91100
edges: (K-1,) tensor of finite split points [e1, e2, ..., e_{K-1}]
@@ -124,7 +133,7 @@ def soft_segment_unbounded(Z, edges, ids, sigmoid_slope):
124133

125134
# weighted sum by the ids
126135
ids__sum = (membership * ids).sum(dim=-1)
127-
136+
128137
# make it at least 2d
129138
ids__sum = ids__sum[None, :]
130139

@@ -151,10 +160,9 @@ def soft_segment_unbounded_np(Z, edges, ids, sigmoid_slope):
151160
case np.ndarray():
152161
membership = _final_faults_segmentation(Z, edges, sigmoid_slope)
153162
case numbers.Number():
154-
membership = _lith_segmentation(Z, edges, ids, sigmoid_slope)
163+
membership = _lith_segmentation(Z, edges, ids, sigmoid_slope)
155164
case _:
156-
raise ValueError("sigmoid_slope must be a float or an array")
157-
165+
raise ValueError("sigmoid_slope must be a float or an array")
158166

159167
ids__sum = np.sum(membership * ids, axis=-1)
160168
return np.atleast_2d(ids__sum)
@@ -179,7 +187,6 @@ def _final_faults_segmentation(Z, edges, sigmoid_slope):
179187

180188

181189
def _lith_segmentation(Z, edges, ids, sigmoid_slope):
182-
183190
# 1) per-edge temperatures τ_k = |Δ_k|/(4·m)
184191
jumps = np.abs(ids[1:] - ids[:-1]) # shape (K-1,)
185192
tau_k = jumps / float(sigmoid_slope) # shape (K-1,)

0 commit comments

Comments
 (0)