Skip to content

Surface Grid Refinement#594

Open
claudiaalvgar wants to merge 12 commits intoJuliaPhysics:mainfrom
claudiaalvgar:RefineSurface_Grid
Open

Surface Grid Refinement#594
claudiaalvgar wants to merge 12 commits intoJuliaPhysics:mainfrom
claudiaalvgar:RefineSurface_Grid

Conversation

@claudiaalvgar
Copy link
Collaborator

@claudiaalvgar claudiaalvgar commented Feb 17, 2026

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:

  • 0.02mm grid spacing: wrong behaviour (I attach the code in case this issue has to be reproduced):
T = Float64 
sim = Simulation{T}(SSD_examples[:IVCIlayer])
calculate_electric_potential!(sim ,max_n_iterations = 10 ,grid= Grid(sim) ,verbose = true ,depletion_handling = true)
g = sim.electric_potential.grid
ax1, ax2, ax3 = g.axes
tick_dis=0.02*mm 
user_additional_ticks_ax1=sort(vcat(ax1.interval.left:tick_dis:ax1.interval.right)) 
user_additional_ticks_ax3=sort(vcat(ax3.interval.left:tick_dis:ax3.interval.right))[2:end] # only support even number of ticks in z-direction 
user_ax1 = typeof(ax1)(ax1.interval, user_additional_ticks_ax1) 
user_ax3 = typeof(ax3)(ax3.interval, user_additional_ticks_ax3) 
user_g = typeof(g)((user_ax1, ax2, user_ax3)) 
calculate_electric_potential!(sim, refinement_limits = [0.2, 0.1, 0.05, 0.02], use_nthreads=8, grid = user_g, depletion_handling = true)
InvertedCoax_PR3_tick0 02mm_refinements * 0.1mm grid spacing custom initial grid: correct behaviour: InvertedCoax_PR3_tick0 1mm_refinements

---> Without RCC layer but using custom grid: same issue was observed:

  • 0.02mm custom grid spacing: wrong behaviour
InvertedCoax_noinactivelayer_PR3_tick0 02mm_refinements * 0.1mm custom grid spacing: correct behaviour InvertedCoax_noinactivelayer_PR3_tick0 1mm_refinements The new implementation checks whether a given slice of point types contains any surface (inactive layer) points and builds a new refined grid across all three axes. Surface refinement is automatically triggered during the electric potential calculation when:
  • 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:

DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfafter_3rdref_grids DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfafter_3rdref_griddensity DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfafter_3rdref_epot The weighting potential is also well behaved even when passing the custom grid from the surface-refined electric potential: DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfafter_3rdref_wp_customgrid_from_epot

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:

DeadLayerGrid_norefinements_custominitialgrid0 1mm_griddensity Custom_grid_0 1mm_noref_nosurfref_epot The surface refinement is applied after three initial bulk refinements (the default refinement setup for the electric potential calculation) because only at that stage does the surface behaviour become well-resolved. Therefore, this model requires the default bulk refinement setup in order to correctly apply the surface refinement: Default: refinement_limits = [0.2, 0.1, 0.05]) + surface refinement. Applying the surface refinement after the first or second refinement step does not fully capture the surface layer, leading to incomplete refinement, as you can see :
  • Surface Refinement after 2nd refinement uses [0.2, 0.1, -surface ref- 0.05]) and min_spacing = (1e-4, 1e-4, 1e-4)m in surface layer
DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfafter_2ndref
  • Initial surface refinement (this uses -surface ref- + [0.2, 0.1, 0.05]) and min_spacing = (1e-4, 1e-4, 1e-4)m in surface layer
DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfbefore This PR implements the default behaviour (3 refinements) plus the surface refinement, ensuring accurate resolution near surfaces. Additionally, the model now supports refining surface intervals down to 1e-5m, preserving correct physical behaviour and avoiding the incorrect results that can occur with custom grids resulting in undepleted detectors.
  • New method 0.02mm spacing: Correct behaviour
DeadLayerGrid_norefinements_2e-5min_spacingdeadlayer_surfafter_3rdref_grids DeadLayerGrid_norefinements_2e-5min_spacingdeadlayer_surfafter_3rdref_griddensity DeadLayerGrid_norefinements_2e-5min_spacingdeadlayer_surfafter_3rdref_epot

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.

@fhagemann
Copy link
Collaborator

fhagemann commented Feb 17, 2026

I need to see when I can find enough time to look at this in detail.
Are there any tests you could add to test the new code for it's correct behavior (When it should be throwing warnings, edge cases, etc.) that you could add here?

@claudiaalvgar
Copy link
Collaborator Author

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

@fhagemann fhagemann added enhancement Improvement of existing features notation Notation and Interface convenience Improve user-friendliness labels Feb 18, 2026
@fhagemann fhagemann marked this pull request as draft February 18, 2026 16:04
@claudiaalvgar
Copy link
Collaborator Author

Update: when doing some tests with other detectors, I saw that refining only inside the surface was sometimes not enough since it didn't capture the edges very well. See this example for a BEGe detector where the surface goes beyond the detector limit:
BEGe_DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfafter_3rdref_epot

This was solved in my last commit by refining also in a margin around the surface layer (the variables extra_before and extra_after were added, that I set to be 5 intervals around the surface), with this, we get a much better definition of the surface:

BEGe_DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfafter_3rdref_epot_codecorrection BEGe_DeadLayerGrid_norefinements_1e-4min_spacingdeadlayer_surfafter_3rdref_griddensity_codecorrection

@claudiaalvgar
Copy link
Collaborator Author

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

@claudiaalvgar claudiaalvgar marked this pull request as ready for review February 27, 2026 09:00
electric_potential::Union{ElectricPotential{T}, Missing}
weighting_potentials::Vector{Any}
electric_field::Union{ElectricField{T}, Missing}
spacing_surface_refinement::Union{NTuple{3,T}, Nothing}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of adding this as a field to Simulation.
Could this be something that could be added to the World in sim.world?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented this

boundaries:
left: inf
right: inf
spacing_surface_refinement: [1e-4, 1e-4, 1e-4] #m
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's here semantically, I would also add this to the World that we create from it and save it to sim.world.

Not necessarily for this PR, but we could think about also allowing to put things like the refinement limits, max/min tick distance etc. in here, so that they do not have to be passed at runtime to the calculate_potential! methods.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +170 to +188
# Cylindrical: inner loop over z (3rd dimension)
i1_safe = clamp(i1, 1, size(ϵ_r, 3))
in1_safe = clamp(in1, 1, size(ϵ_r, 3))
i2_safe = clamp(i2, 1, size(ϵ_r, 2))
in2_safe = clamp(in2, 1, size(ϵ_r, 2))
i3_safe = clamp(i3, 1, size(ϵ_r, 1))
in3_safe = clamp(in3, 1, size(ϵ_r, 1))
# ϵ_r is not transformed into an red-black-4D-array.
# The inner loop (over i1) is along the z-Dimension (Cylindrical Case),
# which is the 3rd dimension for Cylindrical coordinates: (r, φ, z)
return @inbounds begin
ϵ_r[ in3, in2, in1 ],
ϵ_r[ i3, in2, in1 ],
ϵ_r[ in3, i2, in1 ],
ϵ_r[ i3, i2, in1 ],
ϵ_r[ in3, in2, i1 ],
ϵ_r[ i3, in2, i1 ],
ϵ_r[ in3, i2, i1 ],
ϵ_r[ i3, i2, i1 ]
ϵ_r[in3_safe, in2_safe, in1_safe],
ϵ_r[i3_safe, in2_safe, in1_safe],
ϵ_r[in3_safe, i2_safe, in1_safe],
ϵ_r[i3_safe, i2_safe, in1_safe],
ϵ_r[in3_safe, in2_safe, i1_safe],
ϵ_r[i3_safe, in2_safe, i1_safe],
ϵ_r[in3_safe, i2_safe, i1_safe],
ϵ_r[i3_safe, i2_safe, i1_safe]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed now? Was it needed before already (is this a bug fix)?
Or is this something required now with the things you have added in this PR?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit of the code was giving a bound error when adding more grid points around the surface layer. When refining the grid near the surface, some index would fall just outside the valid array range. Previously this did not show up because the grid configurations we used did not hit these boundary cases, but with the refinement introduced in this PR it can occur and leads to a BoundsError. So the clamping would act as a safeguard to keep the indices within the range. In that sense this is effectively a bug fix for boundary handling, but it only became visible with the refinement changes added in this PR

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to work with the extended grid then, to also keep boundary conditions fulfilled?

function get_extended_ticks( ax::DiscreteAxis{T, :reflecting, :reflecting} )::Vector{T} where {T}
ticks_ext::Vector{T} = Array{T}(undef, length(ax.ticks) + 2)
ticks_ext[2:end-1] = ax.ticks
set_periodic_boundary_ticks!(ticks_ext, ax.interval)
return ticks_ext
end
function get_extended_ticks( ax::DiscreteAxis{T, :fixed, :reflecting} )::Vector{T} where {T}
ticks_ext::Vector{T} = Array{T}(undef, length(ax.ticks) + 2)
ticks_ext[2:end-1] = ax.ticks
set_periodic_boundary_ticks!(ticks_ext, ax.interval)
return ticks_ext
end
function get_extended_ticks( ax::DiscreteAxis{T, :reflecting, :fixed} )::Vector{T} where {T}
ticks_ext::Vector{T} = Array{T}(undef, length(ax.ticks) + 2)
ticks_ext[2:end-1] = ax.ticks
set_periodic_boundary_ticks!(ticks_ext, ax.interval)
return ticks_ext
end
function get_extended_ticks( ax::DiscreteAxis{T, :periodic, :periodic} )::Vector{T} where {T}
ticks_ext::Vector{T} = Array{T}(undef, length(ax.ticks) + 2)
ticks_ext[2:end-1] = ax.ticks
set_periodic_boundary_ticks!(ticks_ext, ax.interval)
return ticks_ext
end
function get_extended_ticks( ax::DiscreteAxis{T, :infinite, :infinite} )::Vector{T} where {T}
ticks_ext::Vector{T} = Array{T}(undef, length(ax.ticks) + 2)
ticks_ext[2:end-1] = ax.ticks
Δ::T = 1 * (ticks_ext[end-1] - ticks_ext[2])
ticks_ext[1] = ticks_ext[2] - Δ
ticks_ext[end] = ticks_ext[end - 1] + Δ
return ticks_ext
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a better approach. I’ll check whether we can use the extended grid here instead

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_extended_ticks is already used in ϵ_r and all the approaches I tried to fix this issue didn’t work but clamp only affects cases where the index would go out of bounds, so for example the very refined grid of 1e-5m, for all usual cases that previously ran without errors, it changes nothing, so I think it’s a safe fix for very fine grids or edge conditions

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just by looking at the code, I don't fully understand this?
Can you clarify in the most simple terms why we need this clamping? When refining surface intervals close to the boundaries of the world? If so, why don't we care about the boundary conditions applied to the boundaries of the world?

Comment on lines +196 to +213
# 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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to implement this

Comment on lines +860 to +871
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"
if min_spacing[d] < T(1e-5)
@warn "Using min_spacing[$d] = 1e-5 m"
else
@warn "Using min_spacing[$d] = 1e-3 m"
end
end
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about avoiding throwing a warning for the phi-axis in cylindrical coordinates.

Comment on lines +910 to +911
only2d = size(closed_pot.data, 2) == 1
int = interpolate_closed_potential(closed_pot, Val(only2d))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this perform correctly in Cartesian coordinates?
Would only2d also require some is_cyl check?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the 2D interpolation path is only intended for the cylindrical case when the φ dimension has size 1, so maybe it makes more sense to have only2d = is_cyl && size(closed_pot.data, 2) == 1, I added it

Comment on lines +933 to +934
pcs = PotentialCalculationSetup(sim.detector, sim.electric_potential.grid, sim.medium, sim.electric_potential.data,
not_only_paint_contacts = not_only_paint_contacts, paint_contacts = paint_contacts)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could shorten this to

Suggested change
pcs = PotentialCalculationSetup(sim.detector, sim.electric_potential.grid, sim.medium, sim.electric_potential.data,
not_only_paint_contacts = not_only_paint_contacts, paint_contacts = paint_contacts)
pcs = PotentialCalculationSetup(sim.detector, sim.electric_potential.grid, sim.medium, sim.electric_potential.data;
not_only_paint_contacts, paint_contacts)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

has_surface_model = isdefined(sim.detector.semiconductor.impurity_density_model, :surface_imp_model)

if has_surface_model
if sim.spacing_surface_refinement === nothing
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually use isnothing to check equality,
not sure if there is a preferred way:

Suggested change
if sim.spacing_surface_refinement === nothing
if isnothing(sim.spacing_surface_refinement)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

paint_contacts = paint_contacts,
sor_consts = is_last_ref ? T(1) : sor_consts )

if has_surface_model && iref==3 && sim.spacing_surface_refinement !== nothing
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if has_surface_model && iref==3 && sim.spacing_surface_refinement !== nothing
if has_surface_model && iref == 3 && !isnothing(sim.spacing_surface_refinement)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@claudiaalvgar
Copy link
Collaborator Author

Since there are many changes suggested I commented the ones I've added in the new commits to keep track of them

@claudiaalvgar
Copy link
Collaborator Author

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)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my end, it still looks like this is not done (?)

Suggested change
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));

"""
struct World{T <: SSDFloat, N, S} <: AbstractWorld{T, N}
intervals::NTuple{N, SSDInterval{T}}
spacing_surface_refinement::Union{NTuple{N,T}, Nothing}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to have this type-stable.
Could we replace nothing by (NaN, NaN, NaN), such that it's always an NTuple{N,T}?

Comment on lines +67 to +71
spacing_surface_refinement = if haskey(dict, "spacing_surface_refinement")
ntuple(i -> _parse_value(T, dict["spacing_surface_refinement"][i], internal_length_unit), 3)
else
nothing
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we deal with the fact that the second value for a cylindrical world should not be a "length"? Also: replace nothing by Tuple of NaNs?

Suggested change
spacing_surface_refinement = if haskey(dict, "spacing_surface_refinement")
ntuple(i -> _parse_value(T, dict["spacing_surface_refinement"][i], internal_length_unit), 3)
else
nothing
end
spacing_surface_refinement = if haskey(dict, "spacing_surface_refinement")
ntuple(i -> _parse_value(T, dict["spacing_surface_refinement"][i], internal_length_unit), 3)
else
ntuple(i -> NaN, 3)
end



function CylindricalWorld(T, dict::AbstractDict, input_units::NamedTuple)::World
function CylindricalWorld(T, dict::AbstractDict, input_units::NamedTuple, spacing_surface_refinement)::World
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here (and elsewhere): can you define the type of spacing_surface_refinement?


old_grid = sim.electric_potential.grid

# Enforce minimum and maximum spacing for surface refinement (10 µm < min_spacing < 1 mm)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comment

Comment on lines +170 to +188
# Cylindrical: inner loop over z (3rd dimension)
i1_safe = clamp(i1, 1, size(ϵ_r, 3))
in1_safe = clamp(in1, 1, size(ϵ_r, 3))
i2_safe = clamp(i2, 1, size(ϵ_r, 2))
in2_safe = clamp(in2, 1, size(ϵ_r, 2))
i3_safe = clamp(i3, 1, size(ϵ_r, 1))
in3_safe = clamp(in3, 1, size(ϵ_r, 1))
# ϵ_r is not transformed into an red-black-4D-array.
# The inner loop (over i1) is along the z-Dimension (Cylindrical Case),
# which is the 3rd dimension for Cylindrical coordinates: (r, φ, z)
return @inbounds begin
ϵ_r[ in3, in2, in1 ],
ϵ_r[ i3, in2, in1 ],
ϵ_r[ in3, i2, in1 ],
ϵ_r[ i3, i2, in1 ],
ϵ_r[ in3, in2, i1 ],
ϵ_r[ i3, in2, i1 ],
ϵ_r[ in3, i2, i1 ],
ϵ_r[ i3, i2, i1 ]
ϵ_r[in3_safe, in2_safe, in1_safe],
ϵ_r[i3_safe, in2_safe, in1_safe],
ϵ_r[in3_safe, i2_safe, in1_safe],
ϵ_r[i3_safe, i2_safe, in1_safe],
ϵ_r[in3_safe, in2_safe, i1_safe],
ϵ_r[i3_safe, in2_safe, i1_safe],
ϵ_r[in3_safe, i2_safe, i1_safe],
ϵ_r[i3_safe, i2_safe, i1_safe]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just by looking at the code, I don't fully understand this?
Can you clarify in the most simple terms why we need this clamping? When refining surface intervals close to the boundaries of the world? If so, why don't we care about the boundary conditions applied to the boundaries of the world?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

convenience Improve user-friendliness enhancement Improvement of existing features notation Notation and Interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants