Skip to content

Commit 3eb7823

Browse files
committed
[ENH] Adding numpy version of activation function
1 parent be71e84 commit 3eb7823

File tree

2 files changed

+60
-32
lines changed

2 files changed

+60
-32
lines changed

gempy_engine/modules/activator/activator_interface.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,28 @@ def activate_formation_block(exported_fields: ExportedFields, ids: np.ndarray,
1515
sigmoid_slope_negative = isinstance(sigmoid_slope, float) and sigmoid_slope < 0 # * sigmoid_slope can be array for finite faultskA
1616

1717
if LEGACY := False and not sigmoid_slope_negative: # * Here we branch to the experimental activation function with hard sigmoid
18-
sigm = activate_formation_block_from_args(Z_x, ids, scalar_value_at_sp, sigmoid_slope)
19-
else:
20-
sigm = soft_segment_unbounded(
21-
Z=Z_x,
22-
edges=scalar_value_at_sp,
23-
ids=ids,
18+
sigm = activate_formation_block_from_args(
19+
Z_x=Z_x,
20+
ids=ids,
21+
scalar_value_at_sp=scalar_value_at_sp,
2422
sigmoid_slope=sigmoid_slope
2523
)
24+
else:
25+
match BackendTensor.engine_backend:
26+
case AvailableBackends.PYTORCH:
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+
case AvailableBackends.numpy:
34+
sigm = soft_segment_unbounded_np(
35+
Z=Z_x,
36+
edges=scalar_value_at_sp,
37+
ids=ids,
38+
sigmoid_slope=sigmoid_slope
39+
)
2640

2741
return sigm
2842

@@ -110,3 +124,43 @@ def soft_segment_unbounded(Z, edges, ids, sigmoid_slope):
110124
# weighted sum by the ids
111125
ids__sum = (membership * ids).sum(dim=-1)
112126
return np.atleast_2d(ids__sum.numpy())
127+
128+
129+
import numpy as np
130+
131+
def soft_segment_unbounded_np(Z, edges, ids, sigmoid_slope):
132+
"""
133+
Z: array of shape (...,) of scalar values
134+
edges: array of shape (K-1,) of finite split points [e1, e2, ..., e_{K-1}]
135+
ids: array of shape (K,) of the id for each of the K bins
136+
sigmoid_slope: scalar target peak slope m > 0
137+
returns: array of shape (...,) of the soft-assigned id
138+
"""
139+
Z = np.asarray(Z)
140+
edges = np.asarray(edges)
141+
ids = np.asarray(ids)
142+
143+
# 1) per-edge temperatures τ_k = |Δ_k|/(4·m)
144+
jumps = np.abs(ids[1:] - ids[:-1]) # shape (K-1,)
145+
tau_k = jumps / (4 * sigmoid_slope) # shape (K-1,)
146+
147+
# 2) first bin (-∞, e1) via σ((e1 - Z)/τ₁)
148+
first = 1.0 / (1.0 + np.exp((Z - edges[0]) / tau_k[0])) # shape (...,)
149+
150+
# 3) last bin [e_{K-1}, ∞) via σ((Z - e_{K-1})/τ_{K-1})
151+
last = 1.0 / (1.0 + np.exp(-(Z - edges[-1]) / tau_k[-1])) # shape (...,)
152+
153+
# 4) middle bins [e_i, e_{i+1}): σ((Z - e_i)/τ_i) - σ((Z - e_{i+1})/τ_{i+1})
154+
Z_exp = Z[..., None] # shape (...,1)
155+
left = 1.0 / (1.0 + np.exp(-(Z_exp - edges[:-1]) / tau_k[:-1])) # (...,K-2)
156+
right = 1.0 / (1.0 + np.exp(-(Z_exp - edges[1: ]) / tau_k[1: ])) # (...,K-2)
157+
middle = left - right # (...,K-2)
158+
159+
# 5) assemble memberships and weight by ids
160+
membership = np.concatenate(
161+
[ first[..., None], middle, last[..., None] ],
162+
axis=-1
163+
) # shape (...,K)
164+
165+
ids__sum = np.sum(membership * ids, axis=-1)
166+
return np.atleast_2d(ids__sum)

tests/test_common/test_modules/test_activator_fns.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,18 @@ def test_activator_3_layers_segmentation_function(simple_model_3_layers, simple_
4141
print(Z_x, Z_x.shape[0])
4242
print(sasp)
4343

44-
BackendTensor.change_backend_gempy(AvailableBackends.PYTORCH)
4544
ids_block = activate_formation_block(
4645
exported_fields=exported_fields,
4746
ids=ids,
48-
# sigmoid_slope=5_000_000
4947
sigmoid_slope = 500*4
5048
)[0, :-7]
5149

52-
BackendTensor.change_backend_gempy(AvailableBackends.numpy)
53-
5450
if BackendTensor.engine_backend == AvailableBackends.PYTORCH:
5551
ids_block = ids_block.detach().numpy()
5652
Z_x = Z_x.detach().numpy()
5753
interpolation_input.surface_points.sp_coords = interpolation_input.surface_points.sp_coords.detach().numpy()
5854

5955
if plot:
60-
if False:
61-
_plot_4_categories(grid, ids_block, interpolation_input)
62-
6356
_plot_continious(grid, ids_block, interpolation_input)
6457

6558

@@ -82,22 +75,3 @@ def _plot_continious(grid, ids_block, interpolation_input):
8275
plt.show()
8376

8477

85-
def _plot_4_categories(grid, ids_block, interpolation_input):
86-
block__ = ids_block[grid.dense_grid_slice]
87-
reshaped_block = block__.reshape(50, 5, 50)[:, 2, :].T
88-
bounds = [0, 1, 2, 3, 4]
89-
# Create a normalized colormap with 5 distinct colors
90-
import matplotlib.colors as colors
91-
cmap = plt.cm.autumn
92-
norm = colors.BoundaryNorm(bounds, cmap.N)
93-
# Plot with 5 distinct categories
94-
im = plt.contourf(reshaped_block, levels=bounds, cmap=cmap, norm=norm,
95-
extent=(.25, .75, .25, .75))
96-
xyz = interpolation_input.surface_points.sp_coords
97-
plt.plot(xyz[:, 0], xyz[:, 2], "o")
98-
# Add a colorbar with category labels
99-
cbar = plt.colorbar(im, ticks=bounds)
100-
# cbar.ax.set_yticklabels(['', '<1', '1-2', '2-3', '3-4', '>4'])
101-
cbar.ax.set_yticklabels(['', '<1', '1-2', '2-3', '3-4'])
102-
plt.title('Formation Categories')
103-
plt.show()

0 commit comments

Comments
 (0)