12from numpy
import ndarray, sqrt, cbrt, pi, sum
17 A neighbour interaction loop used to determine the smoothing lengths.
23 distances from the particle to the neighbours
26 current guess for the particle's smoothing length
29 which SPH kernel to use.
32 number of dimensions to work for.
39 sum of particle's kernel weight at neighbour positions
42 sum of particle's kernel derivative weight at neighbour positions
46 H = sph_kernel_get_H(h, kernel, ndim)
49 w_all = sph_kernel_W(r, h, kernel, ndim)
50 dwdr_all = sph_kernel_dWdr(r, h, kernel, ndim)
52 dwdh_term = sum(ndim * w_all / h + q * dwdr_all)
53 wcount_dh = -dwdh_term
56 w = sph_kernel_W(0.0, h, kernel, ndim)
57 dwdr = sph_kernel_dWdr(0.0, h, kernel, ndim)
59 wcount_dh -= ndim * w / h
61 return wcount, wcount_dh
66 A neighbour interaction loop used to determine the smoothing lengths.
72 distances from the particle to the neighbours
75 current guess for the particle's smoothing length
78 which SPH kernel to use.
81 number of dimensions to work for.
88 sum of particle's kernel weight at neighbour positions
91 sum of particle's kernel derivative weight at neighbour positions
95 H = sph_kernel_get_H(h, kernel, ndim)
98 for n
in range(r.shape[0]):
100 w = sph_kernel_W(r[n], h, kernel, ndim)
101 dwdr = sph_kernel_dWdr(r[n], h, kernel, ndim)
103 dwdh_term = ndim * w / h + q * dwdr
104 wcount_dh -= dwdh_term
107 w = sph_kernel_W(0.0, h, kernel, ndim)
108 dwdr = sph_kernel_dWdr(0.0, h, kernel, ndim)
110 wcount_dh -= ndim * w / h
112 return wcount, wcount_dh
115def find_smoothing_length(
122 verbose: bool =
False,
125 Determine the smoothing length of a particle with coordinates xp
126 and neighbours with positions xn. This function assumes that the
127 an appropriate amount of neighbours is passed on via the `xn` array
128 of their positions, meaning that max{|xn - xp|} <= gamma * h.
134 Coordiate of particle to determine smoothing length for
137 Array of coordinates of neighbour particles
140 Which kernel to use. Choices are given in sph_kernels.py:sph_kernel_list
143 resolution eta to use, "smoothing scale". This determines the number of neighbours.
146 tolerance to adapt to consider smoothing length converged
149 number of dimensions to work with
152 whether to be verbose about what's going on
164 r2 = sum(dx[:, :ndim] ** 2, axis=1)
167 kernel_root = sph_kernel_root(kernel, ndim)
171 H_over_h = sph_kernel_get_H(1.0, kernel, ndim)
172 h_max = r.max() / H_over_h
176 dx_average = r.max() / xn.shape[0]
177 nneigh_expect = number_of_neighbours_from_eta(eta, kernel, ndim)
178 h = nneigh_expect * dx_average
183 if int(nneigh_expect) > xn.shape[0]:
184 print(
"WARNING: trying to find more neighbours than candidates provided")
195 print(
"Startup: h_init", h)
196 print(
"Startup: h_min", h_min)
197 print(
"Startup: h_max", h_max)
198 print(
"Startup: Neighbours: ", xn.shape[0])
203 while iteration < 10000:
207 h_dim_minus_one = h ** (ndim - 1.0)
210 if abs(wcount - kernel_root) < 1e-5 * kernel_root:
212 print(f
"Caught no neighbours case, h={h}")
216 n_sum = wcount * h_dim
218 f_prime = wcount_dh * h_dim + ndim * wcount * h_dim_minus_one
224 elif n_sum > n_target:
226 right = min(right, h)
230 print(
"Error in bisection bounds. Resetting them.")
234 if h >= h_max
and f < 0:
237Particle with coordinates {xp} reached h_max without converging.
238Increase the number of neighbours you pass to this script."""
242 if h <= h_min
and f > 0:
244 f
"WARNING: Particle with coordinates {xp} reached h_min. Returning that."
248 h_new = h - f / f_prime
261 h_new = min(2.0 * h, h_new)
262 h_new = max(0.5 * h, h_new)
263 h_new = max(h_new, left)
264 h_new = min(h_new, right)
270 print(
"h_new=", h_new)
271 print(
"wcount", wcount)
272 print(
"wcount_dh", wcount_dh)
274 print(
"f_prime", f_prime)
275 print(
"h - h_new", diff)
280 if abs(diff) < h_tolerance * h:
282 print(
"done h=", h_new)
287 if (h_new == left
and h == right)
or (h == left
and h_new == right):
290 print(
"h_new == left")
294 print(
"h_new == right")
297 h_new = (0.5 * (left**ndim + right**ndim)) ** (1.0 / ndim)
305 print(
"------------------------------------")
308 f
"Particle with coordinates {xp} didn't converge smoothing length after {iteration} iterations"
315def eta_from_number_of_neighbours(N: float, kernel: str, ndim: int):
317 Compute the resolution eta from a given (approximate) number of neighbours.
318 Note that the neighbour number does not need be an integer.
319 See Price 2012, https://ui.adsabs.harvard.edu/abs/2012JCoPh.231..759P, eq. 12
328 which kernel we're computing for.
331 how many dimensions we are working with.
338 the corresponding resolution eta
341 H_over_h = sph_kernel_get_H(1.0, kernel, ndim)
344 return N / (2.0 * H_over_h)
346 return sqrt(N / pi) / H_over_h
348 return cbrt(N * 3.0 / 4.0 / pi) / H_over_h
350 raise ValueError(
"Invalid number of dimensions", ndim)
353def number_of_neighbours_from_eta(eta: float, kernel: str, ndim: int):
355 Compute the (approximate) number of neighbours from a given resolution eta.
356 Note that the neighbour number does not need be an integer.
357 See Price 2012, https://ui.adsabs.harvard.edu/abs/2012JCoPh.231..759P, eq. 12
366 which kernel we're computing for.
369 how many dimensions we are working with.
376 the (approximate) number of neighbours
379 H_over_h = sph_kernel_get_H(1.0, kernel, ndim)
382 return 2.0 * H_over_h * eta
384 return pi * (H_over_h * eta) ** 2
386 return 4.0 / 3.0 * pi * (H_over_h * eta) ** 3
388 raise ValueError(
"Invalid number of dimensions", ndim)