@@ -39,56 +39,185 @@ def random_pick_seed(choices, probs=None):
3939 return choices [idx ]
4040
4141
42- def make_weight (Particles , ind ):
43- """Update weighting array with weights at this index"""
44- # get stage values for neighboring cells
45- stage_ind = Particles .stage [ind [0 ]- 1 :ind [0 ]+ 2 , ind [1 ]- 1 :ind [1 ]+ 2 ]
42+ def big_sliding_window (raster ):
43+ """Creates 3D array organizing local neighbors at every index
44+
45+ Returns a raster of shape (L,W,9) which organizes (along the third
46+ dimension) all of the neighbors in the original raster at a given
47+ index, in the order [NW, N, NE, W, 0, E, SW, S, SE]. Outputs are
48+ ordered to match np.ravel(), so it functions similarly to a loop
49+ applying ravel to the elements around each index.
50+ For example, the neighboring values in raster indexed at (i,j) are
51+ raster(i-1:i+2, j-1:j+2).ravel(). These 9 values have been mapped to
52+ big_ravel(i,j,:) for ease of computations. Helper function for make_weight.
4653
47- # calculate surface slope weights
48- weight_sfc = maximum (0 ,
49- (Particles .stage [ind ] - stage_ind ) /
50- Particles .distances )
54+ **Inputs** :
55+
56+ raster : `ndarray`
57+ 2D array of values (e.g. stage, qx)
58+
59+ **Outputs** :
60+
61+ big_ravel : `ndarray`
62+ 3D array which sorts the D8 neighbors at index (i,j) in
63+ raster into the 3rd dimension at (i,j,:)
64+
65+ """
66+ big_ravel = np .zeros ((raster .shape [0 ],raster .shape [1 ],9 ))
67+ big_ravel [1 :- 1 ,1 :- 1 ,0 ] = raster [0 :- 2 ,0 :- 2 ]
68+ big_ravel [1 :- 1 ,1 :- 1 ,1 ] = raster [0 :- 2 ,1 :- 1 ]
69+ big_ravel [1 :- 1 ,1 :- 1 ,2 ] = raster [0 :- 2 ,2 :]
70+ big_ravel [1 :- 1 ,1 :- 1 ,3 ] = raster [1 :- 1 ,0 :- 2 ]
71+ big_ravel [1 :- 1 ,1 :- 1 ,4 ] = raster [1 :- 1 ,1 :- 1 ]
72+ big_ravel [1 :- 1 ,1 :- 1 ,5 ] = raster [1 :- 1 ,2 :]
73+ big_ravel [1 :- 1 ,1 :- 1 ,6 ] = raster [2 :,0 :- 2 ]
74+ big_ravel [1 :- 1 ,1 :- 1 ,7 ] = raster [2 :,1 :- 1 ]
75+ big_ravel [1 :- 1 ,1 :- 1 ,8 ] = raster [2 :,2 :]
76+
77+ return big_ravel
78+
79+
80+ def tile_local_array (local_array , L , W ):
81+ """Take a local array [[NW, N, NE], [W, 0, E], [SW, S, SE]]
82+ and repeat it into an array of shape (L,W,9), where L, W are
83+ the shape of the domain, and the original elements are ordered
84+ along the third axis. Helper function for make_weight.
85+
86+ **Inputs** :
87+
88+ local_array : `ndarray`
89+ 2D array of represnting the D8 neighbors around
90+ some index (e.g. ivec, jvec)
91+
92+ L : `int`
93+ Length of the domain
94+
95+ W : `int`
96+ Width of the domain
97+
98+ **Outputs** :
99+
100+ tiled_array : `ndarray`
101+ 3D array repeating local_array.ravel() LxW times
102+
103+ """
104+ return np .tile (local_array .ravel (), (L , W , 1 ))
105+
106+
107+ def tile_domain_array (raster ):
108+ """Repeat a large 2D array 9 times along the third axis.
109+ Helper function for make_weight.
110+
111+ **Inputs** :
112+
113+ raster : `ndarray`
114+ 2D array of values (e.g. stage, qx)
115+
116+ **Outputs** :
117+
118+ tiled_array : `ndarray`
119+ 3D array repeating raster 9 times
120+
121+ """
122+ return np .repeat (raster [:, :, np .newaxis ], 9 , axis = 2 )
51123
52- # calculate inertial component weights
53- weight_int = maximum (0 , ((Particles .qx [ind ] * Particles .jvec +
54- Particles .qy [ind ] * Particles .ivec ) /
55- Particles .distances ))
56124
125+ def clear_borders (tiled_array ):
126+ """Set to zero all the edge elements of a vertical stack
127+ of 2D arrays. Helper function for make_weight.
128+
129+ **Inputs** :
130+
131+ tiled_array : `ndarray`
132+ 3D array repeating raster (e.g. stage, qx) 9 times
133+ along the third axis
134+
135+ **Outputs** :
136+
137+ tiled_array : `ndarray`
138+ The same 3D array as the input, but with the borders
139+ in the first and second dimension set to 0.
140+
141+ """
142+ tiled_array [0 ,:,:] = 0.
143+ tiled_array [:,0 ,:] = 0.
144+ tiled_array [- 1 ,:,:] = 0.
145+ tiled_array [:,- 1 ,:] = 0.
146+ return
147+
148+
149+ def make_weight (Particles ):
150+ """Create the weighting array for particle routing
151+
152+ Function to compute the routing weights at each index, which gets
153+ stored inside the :obj:`dorado.particle_track.Particles` object
154+ for use when routing. Called when the object gets instantiated.
155+
156+ **Inputs** :
157+
158+ Particles : :obj:`dorado.particle_track.Particles`
159+ A :obj:`dorado.particle_track.Particles` object
160+
161+ **Outputs** :
162+
163+ Updates the weights array inside the
164+ :obj:`dorado.particle_track.Particles` object
165+
166+ """
167+ L , W = Particles .stage .shape
168+
169+ # calculate surface slope weights
170+ weight_sfc = (tile_domain_array (Particles .stage ) \
171+ - big_sliding_window (Particles .stage ))
172+ weight_sfc /= tile_local_array (Particles .distances , L , W )
173+ weight_sfc [weight_sfc <= 0 ] = 0
174+ clear_borders (weight_sfc )
175+
176+ # calculate inertial component weights
177+ weight_int = (tile_domain_array (Particles .qx )* tile_local_array (Particles .jvec , L , W )) \
178+ + (tile_domain_array (Particles .qy )* tile_local_array (Particles .ivec , L , W ))
179+ weight_int /= tile_local_array (Particles .distances , L , W )
180+ weight_int [weight_int <= 0 ] = 0
181+ clear_borders (weight_int )
182+
57183 # get depth and cell types for neighboring cells
58- depth_ind = Particles .depth [ ind [ 0 ] - 1 : ind [ 0 ] + 2 , ind [ 1 ] - 1 : ind [ 1 ] + 2 ]
59- ct_ind = Particles .cell_type [ ind [ 0 ] - 1 : ind [ 0 ] + 2 , ind [ 1 ] - 1 : ind [ 1 ] + 2 ]
184+ depth_ind = big_sliding_window ( Particles .depth )
185+ ct_ind = big_sliding_window ( Particles .cell_type )
60186
61187 # set weights for cells that are too shallow, or invalid 0
62188 weight_sfc [(depth_ind <= Particles .dry_depth ) | (ct_ind == 2 )] = 0
63189 weight_int [(depth_ind <= Particles .dry_depth ) | (ct_ind == 2 )] = 0
64-
190+
65191 # if sum of weights is above 0 normalize by sum of weights
66- if nansum (weight_sfc ) > 0 :
67- weight_sfc = weight_sfc / nansum (weight_sfc )
68-
69- # if sum of weight is above 0 normalize by sum of weights
70- if nansum (weight_int ) > 0 :
71- weight_int = weight_int / nansum (weight_int )
192+ norm_sfc = np .nansum (weight_sfc , axis = 2 )
193+ idx_sfc = tile_domain_array ((norm_sfc > 0 ))
194+ weight_sfc [idx_sfc ] /= tile_domain_array (norm_sfc )[idx_sfc ]
195+
196+ norm_int = np .nansum (weight_int , axis = 2 )
197+ idx_int = tile_domain_array ((norm_int > 0 ))
198+ weight_int [idx_int ] /= tile_domain_array (norm_int )[idx_int ]
72199
73200 # define actual weight by using gamma, and weight components
74201 weight = Particles .gamma * weight_sfc + \
75- (1 - Particles .gamma ) * weight_int
76-
202+ (1 - Particles .gamma ) * weight_int
203+
77204 # modify the weight by the depth and theta weighting parameter
78205 weight = depth_ind ** Particles .theta * weight
79-
80- # if the depth is below the minimum depth then location is not
81- # considered therefore set the associated weight to nan
82- weight [(depth_ind <= Particles .dry_depth ) | (ct_ind == 2 )] \
83- = np .nan
206+
207+ # if the depth is below the minimum depth then set weight to 0
208+ weight [(depth_ind <= Particles .dry_depth ) | (ct_ind == 2 )] = 0
84209
85210 # if it's a dead end with only nans and 0's, choose deepest cell
86- if nansum (weight ) <= 0 :
87- weight = np .zeros_like (weight )
88- weight [depth_ind == np .max (depth_ind )] = 1.0
211+ zero_weights = tile_domain_array ((np .nansum (weight , axis = 2 ) <= 0 ))
212+ deepest_cells = (depth_ind == tile_domain_array (np .max (depth_ind ,axis = 2 )))
213+ choose_deep_cells = (zero_weights & deepest_cells )
214+ weight [choose_deep_cells ] = 1.0
215+
216+ # Final checks, eliminate invalid choices
217+ clear_borders (weight )
89218
90219 # set weight in the true weight array
91- Particles .weight [ ind [ 0 ], ind [ 1 ], :] = weight . ravel ()
220+ Particles .weight = weight
92221
93222
94223def get_weight (Particles , ind ):
@@ -111,9 +240,6 @@ def get_weight(Particles, ind):
111240 New location given as a value between 1 and 8 (inclusive)
112241
113242 """
114- # Check if weights have been computed for this location:
115- if nansum (Particles .weight [ind [0 ], ind [1 ], :]) <= 0 :
116- make_weight (Particles , ind )
117243 # randomly pick the new cell for the particle to move to using the
118244 # random_pick function and the set of weights
119245 if Particles .steepest_descent is not True :
0 commit comments