Surface Grid Refinement#594
Conversation
|
I need to see when I can find enough time to look at this in detail. |
|
Sure no problem. This code is yet a bit preliminary since it doesn't handle the 3D case i.e. it won't refine the phi axis (this I found a bit tricky) and I also didn't try the model on cartesian detectors but I think this should work |
|
I've now tested this model on cartesian defined detectors and also on partial-phi defined detectors. The surface refinement is disabled in phi angle. I also added tests for edge cases and I think if all tests pass we can move this to normal PR review |
| # Cartesian: inner loop over x (1st dimension) | ||
| i1_safe = clamp(i1, 1, size(ϵ_r, 1)) | ||
| in1_safe = clamp(in1, 1, size(ϵ_r, 1)) | ||
| i2_safe = clamp(i2, 1, size(ϵ_r, 2)) | ||
| in2_safe = clamp(in2, 1, size(ϵ_r, 2)) | ||
| i3_safe = clamp(i3, 1, size(ϵ_r, 3)) | ||
| in3_safe = clamp(in3, 1, size(ϵ_r, 3)) | ||
| # The inner loop (over i1) is along the x-Dimension (Cartesian Case), | ||
| # which is the 1rd dimension for Cartesian coordinates: (x, y, z) | ||
| return @inbounds begin | ||
| ϵ_r[ in1, in2, in3 ], | ||
| ϵ_r[ in1, in2, i3 ], | ||
| ϵ_r[ in1, i2, in3 ], | ||
| ϵ_r[ in1, i2, i3 ], | ||
| ϵ_r[ i1, in2, in3 ], | ||
| ϵ_r[ i1, in2, i3 ], | ||
| ϵ_r[ i1, i2, in3 ], | ||
| ϵ_r[ i1, i2, i3 ] | ||
| ϵ_r[in1_safe, in2_safe, in3_safe], | ||
| ϵ_r[in1_safe, in2_safe, i3_safe], | ||
| ϵ_r[in1_safe, i2_safe, in3_safe], | ||
| ϵ_r[in1_safe, i2_safe, i3_safe], | ||
| ϵ_r[ i1_safe, in2_safe, in3_safe], | ||
| ϵ_r[ i1_safe, in2_safe, i3_safe], | ||
| ϵ_r[ i1_safe, i2_safe, in3_safe], | ||
| ϵ_r[ i1_safe, i2_safe, i3_safe] |
There was a problem hiding this comment.
I still need to implement this
|
Since there are many changes suggested I commented the ones I've added in the new commits to keep track of them |
|
I think I've now implemented all the changes, let me know if there's still something missing/ or to improve |
|
|
||
| """ | ||
| refine_surface!(sim::Simulation{T}; | ||
| min_spacing::NTuple{3,T} = (T(1e-4), T(1e-4), T(1e-4)), |
There was a problem hiding this comment.
On my end, it still looks like this is not done (?)
| min_spacing::NTuple{3,T} = (T(1e-4), T(1e-4), T(1e-4)), | |
| min_spacing::NTuple{3,T} = (T(1e-4), T(1e-4), T(1e-4)); |
|
|
||
| old_grid = sim.electric_potential.grid | ||
|
|
||
| # Enforce minimum and maximum spacing for surface refinement (10 µm < min_spacing < 1 mm) |
There was a problem hiding this comment.
I think it would be nice to make this depend on the dimensions of the world.
I wouldn't be surprised if smaller/larger worlds would be better off with smaller/larger values.
| for d in 1:3 | ||
| if length(old_grid.axes[d].ticks) != 1 | ||
| if min_spacing[d] < T(1e-5) || min_spacing[d] > T(1e-3) | ||
| @warn "min_spacing[$d] = $(min_spacing[d]) m is out of bounds (1e-5 m < min_spacing < 1e-3 m). Higher values don't require surface refinement" |
|
The |
And this is because, instead of expecting maximum 1 tick going "outside" of the grid, you might have more than 1 now? |
|
I just want to make sure that we expect accessing |
| # Keep φ-axis type but close it for interpolation | ||
| _get_closed_axis(old_grid.axes[i]) | ||
| else | ||
| _refine_axis_surface( old_grid.axes[i], surface_intervals[i], min_spacing[i],) |
There was a problem hiding this comment.
Right now, there is no possibility to pass extra_before and extra_After to _refine_axis_surface when calling refine_surface!.
Is this intended?
|
Do you have an example (some config file + some simulation settings when running |
f43290c to
e37bdee
Compare
e37bdee to
d135b0d
Compare
cf7e851 to
9e6b5c0
Compare
|
The new changes look good to me and the code behaves as intended so I think this is good to go on my side |
|
Okay, I will deal with min/max distance and also look into performance, but will merge this afterwards! |
07450a7 to
d0b3843
Compare
| # Create flags for all intervals to refine | ||
| refine_flags = copy(surface_intervals) |
There was a problem hiding this comment.
surface_intervals are not used anymore after calling this functions, so we should be fine modifying this array instead of allocating more memory.
| # Merge consecutive intervals to refine | ||
| merged = Vector{UnitRange{Int}}() | ||
| i = 1 | ||
| while i <= n_int | ||
| if refine_flags[i] | ||
| j = i | ||
| while j < n_int && refine_flags[j+1] | ||
| j += 1 | ||
| end | ||
| push!(merged, i:j) | ||
| i = j + 1 | ||
| else | ||
| i += 1 | ||
| end | ||
| end | ||
|
|
||
| # Compute number of points to add per interval | ||
| ns = zeros(Int, n_int) | ||
| tmp = (zero(T), 1) | ||
| for r in merged | ||
| for i in r | ||
| for i in eachindex(refine_flags) | ||
| if refine_flags[i] |
There was a problem hiding this comment.
Instead of creating a merged array of unit ranges, we can merge those two for-loops into one and just perform interval refinement for the i that have refine_flags set.
| sub_widths = [(old_ticks[i+1] - old_ticks[i]) / (ns[i]+1) for i in 1:n_int] | ||
| ticks = Vector{T}(undef, length(old_ticks) + sum(ns)) | ||
| i_tick = 1 | ||
| for j in 1:n_int | ||
| ticks[i_tick] = old_ticks[j] | ||
| sub_width = (old_ticks[j+1] - old_ticks[j]) / (ns[j]+1) | ||
| for k in 1:ns[j] | ||
| i_tick += 1 | ||
| ticks[i_tick] = old_ticks[j] + k*sub_widths[j] | ||
| ticks[i_tick] = old_ticks[j] + k*sub_width |
There was a problem hiding this comment.
No need to allocate memory for sub_widths if the values can just be calculated "on-the-fly" in the for-loop
5e965f0 to
c9bf933
Compare
| max_spacing = collect(max_spacing) | ||
|
|
||
| for d in 1:3 | ||
| if length(old_grid.axes[d].ticks) != 1 | ||
| axis_width = rightendpoint(old_grid.axes[d].interval) - leftendpoint(old_grid.axes[d].interval) | ||
| min_allowed = axis_width * T(5e-5) | ||
| max_allowed = axis_width * T(1e-1) | ||
|
|
||
| val = T(max_spacing[d]) | ||
|
|
||
| if val < min_allowed || val > max_allowed | ||
| @warn "max_spacing[$d] = $val m is out of bounds (min_allowed = $min_allowed, max_allowed = $max_allowed)." | ||
| max_spacing[d] = clamp(val, min_allowed, max_allowed) | ||
| end | ||
|
|
||
| max_spacing = ntuple(d -> if length(old_grid.axes[d].ticks) != 1 | ||
| axis_width = width(old_grid.axes[d].interval) | ||
| min_allowed = axis_width * T(5e-5) | ||
| max_allowed = axis_width * T(1e-1) | ||
| val = T(max_spacing[d]) | ||
| if val < min_allowed || val > max_allowed | ||
| @warn "max_spacing[$d] = $val m is out of bounds (min_allowed = $min_allowed, max_allowed = $max_allowed)." | ||
| end | ||
| end | ||
|
|
||
| max_spacing = ntuple(d -> length(old_grid.axes[d].ticks) == 1 ? T(0.0) : max_spacing[d], 3) | ||
| clamp(val, min_allowed, max_allowed) | ||
| else | ||
| zero(T) | ||
| end, 3) |
There was a problem hiding this comment.
This avoids allocations by preventing creating a Vector for max_spacing, but letting it remain it a Tuple throughout the function.








This PR introduces surface-only grid refinement for electric potential simulations and fixes boundary-related indexing issues that could lead to segmentation faults at high refinement resolutions e.g for 1e-5m grid spacing. In addition, it also fixes an existent issue when calculating the electric potential with custom grids for small grid spacing. For custom small grid spacing (e.g. 2e-5m), the detector appeared to be undepleted while for bigger grid spacing (1e-4m) the behaviour was correct. This issue was not related to the new RCC layer model since this was also spotted for simulations not including the RCC layer model. See the issue below:
---> With RCC layer custom initial grid:
---> Without RCC layer but using custom grid: same issue was observed:
A surface impurity model is present
At least 3 refinement steps are used in the electric potential calculation
Final refinement limit ≤ 0.05
A YAML entry was added to configure spacing_surface_refinement, that controls the biggest grid spacing allowed in the surface for the 3 axis. The new method identifies the surface regions and it just refines in this area as can be seen, for example for a 0.1mm spacing:
Having this
spacing_surface_refinement: [1e-4, 1e-4, 1e-4]in the yaml file and running the default:calculate_electric_potential!(sim, use_nthreads=8, grid=g, verbose = true, depletion_handling = true)will now give the correct behaviour for the surface layer, or even:calculate_electric_potential!(sim, refinement_limits = [0.2, 0.1, 0.05, 0.02], use_nthreads=8, grid = user_g, depletion_handling = true)if we want to further refine the bulk:This provides a faster calculation O(3min) than for the custom grid case O(10min) since the initial grid was refined from the very beginning, see old case behaviour for custom grid:
NOTE: this model has not been tested on cartesian grid defined detectors but I wanted to ask for inputs before. I wasn't sure neither if this should have been a Draft PR, so feel free to move it.