@@ -15,14 +15,28 @@ def activate_formation_block(exported_fields: ExportedFields, ids: np.ndarray,
15
15
sigmoid_slope_negative = isinstance (sigmoid_slope , float ) and sigmoid_slope < 0 # * sigmoid_slope can be array for finite faultskA
16
16
17
17
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 ,
24
22
sigmoid_slope = sigmoid_slope
25
23
)
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
+ )
26
40
27
41
return sigm
28
42
@@ -110,3 +124,43 @@ def soft_segment_unbounded(Z, edges, ids, sigmoid_slope):
110
124
# weighted sum by the ids
111
125
ids__sum = (membership * ids ).sum (dim = - 1 )
112
126
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 )
0 commit comments