Skip to content

Add support for simulation for dead layer-Step3: BulkSurfaceConstantLifetimeChargeTrappingModel #480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: transition
Choose a base branch
from

Conversation

zpdg
Copy link

@zpdg zpdg commented Apr 21, 2025

Changes

Add a new charge trapping model:

  • Based on the constant lifetime for electron/hole
  • Parameters for the bulk (sensitive region) and surface (dead layer, or the inactive layer) volumes respectively

Test

The pulses while the hole lifetimes are 10 ns, 100 ns,,,1 ms. (the electron lifetime = 1ms)
image

Code for the test

using Revise
using Plots
using Pkg
Pkg.develop(path="./SolidStateDetectors.jl")
using SolidStateDetectors
using Unitful

T = Float32
sim = Simulation{T}(SSD_examples[:TrueCoaxial])
det_r=T(10/1000) # m
det_z=T(10/1000) # m
simulate!(sim)

bel = shl = sel = 1e6
plots=plot()
bhl_list = [10, 100, 1000, 10000, 100000, 1000000]
legend_name_list = ["10 ns", "100 ns", "1 µs", "10 µs", "100 µs", "1 ms"]
for (bhl,legend_name) in zip(bhl_list, legend_name_list)
    legend_name = "Hole lifetime = $(legend_name)"
    if_in_bulk = pos -> true
    parameters = Dict("parameters" => Dict("bhl" => bhl*u"ns", "bel" => bel*u"ns", "shl" => shl*u"ns", "sel" => sel*u"ns", "if_in_bulk" => if_in_bulk))
    trapping_model=BulkSurfaceConstantLifetimeChargeTrappingModel{T}(parameters)
    sim.detector = SolidStateDetector(sim.detector, trapping_model)

    energy_depos =[T(2.95/1000)] * u"keV"
    starting_positions = [CylindricalPoint{T}(det_r/2, deg2rad(0), det_z/2)]
    evt1 = Event(starting_positions, energy_depos);
    simulate!(evt1, sim, Δt = 1u"ns",max_nsteps=100)
    collected_charge=ustrip(maximum(evt1.waveforms[1].signal))
    plot!(evt1.waveforms[1], lw = 1.5, xlabel = "Time", ylabel = "Charge", 
                        unitformat = :slash, legend = true, label=legend_name, tickfontsize = 12, guidefontsize = 14,xlim=[0,100])
end
plots

@zpdg zpdg changed the title Constant lifetime trapping model Add support for simulation for dead layer-Step3: BulkSurfaceConstantLifetimeChargeTrappingModel Apr 21, 2025
@zpdg
Copy link
Author

zpdg commented Apr 21, 2025

@fhagemann
Ah, sorry, seems the rebase doesn't work. I did the same thing as last time: git pull origin XXX --rebase. I thought it will work this time. Next time I will make sure it's in the right way.

@fhagemann fhagemann force-pushed the ConstantLifetimeTrappingModel branch from ff8fb10 to b8bfdee Compare April 22, 2025 12:55
Copy link
Collaborator

@fhagemann fhagemann left a comment

Choose a reason for hiding this comment

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

In general, the struct BulkSurfaceConstantLifeTimeChargeTrappingModel is very long and I would avoid having a function if there if possible.
What about this:

  • Just having a ConstantLifeTimeChargeTrappingModel with electron and hole lifetime.
  • Define a virtual volume that defines "the surface" where you just apply another ConstantLifeTimeChargeTrappingModel

This way you don't need the function if_in_bulk and can have a simpler struct that is easier to deal with.

if haskey(parameters, "bel") bel = _parse_value(T, parameters["bel"], internal_time_unit) end
if haskey(parameters, "shl") shl = _parse_value(T, parameters["shl"], internal_time_unit) end
if haskey(parameters, "sel") sel = _parse_value(T, parameters["sel"], internal_time_unit) end
if haskey(parameters, "if_in_bulk") if_in_bulk = parameters["if_in_bulk"] 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 you envision defining this in a config file?
I expected parameters["if_in_bulk"] to be a String and to fail this line.

@@ -85,6 +85,11 @@ function Semiconductor{T}(dict::AbstractDict, input_units::NamedTuple, outer_tra
haskey(dict["charge_trapping_model"], "model") &&
dict["charge_trapping_model"]["model"] == "Boggs"
BoggsChargeTrappingModel{T}(dict["charge_trapping_model"], temperature = temperature)
# TODO: implementation method of the BulkSurfaceConstantLifetimeChargeTrappingModel using the Configuration file
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah I see, this is still WIP. Do you plan to do this in this PR?

Copy link
Author

Choose a reason for hiding this comment

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

The only problem is we need the the is_in_bulk function, which is tricky to implement in the config. I am gonna try first, using the virtual volume.

@@ -80,6 +80,21 @@ end

end
end
@timed_testset "Charge Trapping: BulkSurfaceConstantLifetimeChargeTrappingModel" begin
bhl = bel = shl = sel = 0.1 # 0.1 ms -> signalsum: ~1.9778; 0.01 -> ~1.8029; 1 -> ~1.9963
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you know what the values are, you could test for exactly this: test lifetimes getting shorter and shorter and testing that the signal sum decreases

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I will add a few more tests.

bel::T
shl::T
sel::T
if_in_bulk::Function
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this be is_in_bulk?

Copy link
Author

Choose a reason for hiding this comment

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

Ah, yes, is_in_bulk is more in line with the other function names. I will change this.


@inbounds for i in eachindex(tmp_signal)
Δt::T = (i > 1) ? (pathtimestamps[i] - pathtimestamps[i-1]) : zero(T)
Δq::T = ctm.if_in_bulk(path[i]) ? q * Δt/bl : q * Δt/sl
Copy link
Collaborator

Choose a reason for hiding this comment

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

What if Δt is bigger than bl and sl ? Then, you will flip the sign of the charge.

Copy link
Author

@zpdg zpdg Apr 22, 2025

Choose a reason for hiding this comment

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

Yes, that is a potential breaking. But the bl and sl are much bigger than 100 ns in most of the cases, while we usually set the Δt to 1 ns.

I think we should add a reminder that the Δt should be much smaller than bl and sl.


parameters = haskey(config_dict, "parameters") ? config_dict["parameters"] : config_dict

allowed_keys = ("bhl", "bel", "shl", "sel", "if_in_bulk")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe the keywords should be a bit more descriptive than this?
sel to me sound rather like selection rather than the abbreviation of surface electron lifetime.
Maybe use τ (τh and τe)?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, use the symbol is better.

@zpdg
Copy link
Author

zpdg commented Apr 22, 2025

In general, the struct BulkSurfaceConstantLifeTimeChargeTrappingModel is very long and I would avoid having a function if there if possible. What about this:

  • Just having a ConstantLifeTimeChargeTrappingModel with electron and hole lifetime.
  • Define a virtual volume that defines "the surface" where you just apply another ConstantLifeTimeChargeTrappingModel

This way you don't need the function if_in_bulk and can have a simpler struct that is easier to deal with.

Sounds better. I will look into this virtual volume way.

@zpdg
Copy link
Author

zpdg commented Apr 24, 2025

@fhagemann
Now, we could define the inactive layer in the configuration fire under the charge_trapping_model field with the field name inactive_layer_geometry, and pass the geometry to the trapping model in the semiconductor.jl. And I've made the inactive layer feature optional.

I didn't choose to use the VirtualVolumes. Because we would need to pass the virtual volumes geometry to the trapping model, which means much more modification.

Comment on lines 305 to 307
# only model the sensitive volume
τh = τe = 1e6
parameters = Dict("parameters" => Dict("τh" => τh*u"ns", "τe" => τe*u"ns"))
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
# only model the sensitive volume
τh = τe = 1e6
parameters = Dict("parameters" => Dict("τh" => τh*u"ns", "τe" => τe*u"ns"))
# only model the sensitive volume
τh = τe = 1e6*u"ns"
parameters = Dict("parameters" => Dict("τh" => τh, "τe" => τe))

Or maybe even do τh = τe = 1u"ms" if that also works

Comment on lines 310 to 312
τh = τe = 1e6
τh_inactive = τe_inactive = 80
parameters = Dict("parameters" => Dict("τh" => τh*u"ns", "τe" => τe*u"ns", "τh_inactive" => τh_inactive*u"ns", "τe_inactive" => τe_inactive*u"ns"), "inactive_layer_geometry" => sim.detector.semiconductor.charge_trapping_model.inactive_layer_geometry)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Apply the units already to τh etc.:

Suggested change
τh = τe = 1e6
τh_inactive = τe_inactive = 80
parameters = Dict("parameters" => Dict("τh" => τh*u"ns", "τe" => τe*u"ns", "τh_inactive" => τh_inactive*u"ns", "τe_inactive" => τe_inactive*u"ns"), "inactive_layer_geometry" => sim.detector.semiconductor.charge_trapping_model.inactive_layer_geometry)
τh = τe = 1e6u"ns"
τh_inactive = τe_inactive = 80u"ns"
parameters = Dict("parameters" => Dict("τh" => τh, "τe" => τe, "τh_inactive" => τh_inactive, "τe_inactive" => τe_inactive), "inactive_layer_geometry" => sim.detector.semiconductor.charge_trapping_model.inactive_layer_geometry)

"""
struct ConstantLifetimeChargeTrappingModel{T <: SSDFloat} <: AbstractChargeTrappingModel{T}
This constant-lifetime-based charge trapping model is similar to the Boggs model, which is constant-mean-free-path based.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe define this without referencing Boggs here, something like

Suggested change
This constant-lifetime-based charge trapping model is similar to the Boggs model, which is constant-mean-free-path based.
This constant-lifetime-based charge trapping model assumes electrons and holes to have a constant free lifetime throughout the crystal.

This constant-lifetime-based charge trapping model is similar to the Boggs model, which is constant-mean-free-path based.
The model implemented has two sets of parameters for the sensitive (bulk) and inactive (dead layer/surface layer) volume respectively.
The inactive layer effect will only be considered if the corresponding `virtual_drift_volume` is defined in the configuration file.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it a virtual_drift_volume? You call it inactive_layer_geometry in the documentation.

running_sum::T = zero(T)
q::T = charge

inactive_layer_exist = ctm.inactive_layer_geometry !== missing
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
inactive_layer_exist = ctm.inactive_layer_geometry !== missing
inactive_layer_exist = !ismissing(ctm.inactive_layer_geometry)

Again, consider using nothing as default, you can then use !isnothing here.


@inbounds for i in eachindex(tmp_signal)
Δt::T = (i > 1) ? (pathtimestamps[i] - pathtimestamps[i-1]) : zero(T)
Δq::T = (inactive_layer_exist && in(path[i], ctm.inactive_layer_geometry)) ? q * Δt/τ_inactive : q * Δt/τ
Copy link
Collaborator

Choose a reason for hiding this comment

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

Split this into two lines so it's easier to see what is happening:

Suggested change
Δq::T = (inactive_layer_exist && in(path[i], ctm.inactive_layer_geometry)) ? q * Δt/τ_inactive : q * Δt/τ
τi::T = (inactive_layer_exist && in(path[i], ctm.inactive_layer_geometry)) ? τ_inactive : τ
Δq::T = q * Δt/τi

end

parameters = haskey(config_dict, "parameters") ? config_dict["parameters"] : config_dict
inactive_layer_geometry = haskey(config_dict, "inactive_layer_geometry") ? config_dict["inactive_layer_geometry"] : missing
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider using nothing here as default.

end

parameters = haskey(config_dict, "parameters") ? config_dict["parameters"] : config_dict
inactive_layer_geometry = haskey(config_dict, "inactive_layer_geometry") ? config_dict["inactive_layer_geometry"] : missing
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider using nothing here as default.

Suggested change
inactive_layer_geometry = haskey(config_dict, "inactive_layer_geometry") ? config_dict["inactive_layer_geometry"] : missing
inactive_layer_geometry = haskey(config_dict, "inactive_layer_geometry") ? config_dict["inactive_layer_geometry"] : nothing

@fhagemann
Copy link
Collaborator

Code for the test

using Revise
using Plots
using Pkg
Pkg.develop(path="./SolidStateDetectors.jl")
using SolidStateDetectors
using Unitful

T = Float32
sim = Simulation{T}(SSD_examples[:TrueCoaxial])
det_r=T(10/1000) # m
det_z=T(10/1000) # m
simulate!(sim)

bel = shl = sel = 1e6
plots=plot()
bhl_list = [10, 100, 1000, 10000, 100000, 1000000]
legend_name_list = ["10 ns", "100 ns", "1 µs", "10 µs", "100 µs", "1 ms"]
for (bhl,legend_name) in zip(bhl_list, legend_name_list)
    legend_name = "Hole lifetime = $(legend_name)"
    if_in_bulk = pos -> true
    parameters = Dict("parameters" => Dict("bhl" => bhl*u"ns", "bel" => bel*u"ns", "shl" => shl*u"ns", "sel" => sel*u"ns", "if_in_bulk" => if_in_bulk))
    trapping_model=BulkSurfaceConstantLifetimeChargeTrappingModel{T}(parameters)
    sim.detector = SolidStateDetector(sim.detector, trapping_model)

    energy_depos =[T(2.95/1000)] * u"keV"
    starting_positions = [CylindricalPoint{T}(det_r/2, deg2rad(0), det_z/2)]
    evt1 = Event(starting_positions, energy_depos);
    simulate!(evt1, sim, Δt = 1u"ns",max_nsteps=100)
    collected_charge=ustrip(maximum(evt1.waveforms[1].signal))
    plot!(evt1.waveforms[1], lw = 1.5, xlabel = "Time", ylabel = "Charge", 
                        unitformat = :slash, legend = true, label=legend_name, tickfontsize = 12, guidefontsize = 14,xlim=[0,100])
end
plots

Once this PR is finished, could you update the test code to work with the new implementation/naming? :)

@zpdg
Copy link
Author

zpdg commented Apr 28, 2025

Code for the test

using Revise
using Plots
using Pkg
Pkg.develop(path="./SolidStateDetectors.jl")
using SolidStateDetectors
using Unitful

T = Float32
sim = Simulation{T}(SSD_examples[:TrueCoaxial])
det_r=T(10/1000) # m
det_z=T(10/1000) # m
simulate!(sim)

bel = shl = sel = 1e6
plots=plot()
bhl_list = [10, 100, 1000, 10000, 100000, 1000000]
legend_name_list = ["10 ns", "100 ns", "1 µs", "10 µs", "100 µs", "1 ms"]
for (bhl,legend_name) in zip(bhl_list, legend_name_list)
    legend_name = "Hole lifetime = $(legend_name)"
    if_in_bulk = pos -> true
    parameters = Dict("parameters" => Dict("bhl" => bhl*u"ns", "bel" => bel*u"ns", "shl" => shl*u"ns", "sel" => sel*u"ns", "if_in_bulk" => if_in_bulk))
    trapping_model=BulkSurfaceConstantLifetimeChargeTrappingModel{T}(parameters)
    sim.detector = SolidStateDetector(sim.detector, trapping_model)

    energy_depos =[T(2.95/1000)] * u"keV"
    starting_positions = [CylindricalPoint{T}(det_r/2, deg2rad(0), det_z/2)]
    evt1 = Event(starting_positions, energy_depos);
    simulate!(evt1, sim, Δt = 1u"ns",max_nsteps=100)
    collected_charge=ustrip(maximum(evt1.waveforms[1].signal))
    plot!(evt1.waveforms[1], lw = 1.5, xlabel = "Time", ylabel = "Charge", 
                        unitformat = :slash, legend = true, label=legend_name, tickfontsize = 12, guidefontsize = 14,xlim=[0,100])
end
plots

Once this PR is finished, could you update the test code to work with the new implementation/naming? :)

Sure! I will update all the naming and the test code tomorrow.

@zpdg
Copy link
Author

zpdg commented Apr 29, 2025

Updated test code for this PR:

using Revise
using Plots
using Pkg
Pkg.develop(path="./SolidStateDetectors.jl")
using SolidStateDetectors
using Unitful
T = Float32
sim = Simulation{T}(SSD_examples[:TrueCoaxial])
simulate!(sim)

plots = plot()
τe = 1u"ms"
τh_list = (10u"ns", 100u"ns", 1u"µs", 10u"µs", 100u"µs", 1u"ms")
for τh in τh_list
    trapping_model = ConstantLifetimeChargeTrappingModel{T}(Dict("parameters" => Dict("τh" => τh, "τe" => τe)))
    sim.detector = SolidStateDetector(sim.detector, trapping_model)
    energy_depos = [sim.detector.semiconductor.material.E_ionisation]
    starting_positions = [CylindricalPoint{T}(0.005, 0, 0.005)]
    evt = Event(starting_positions, energy_depos)
    simulate!(evt, sim, Δt=1u"ns", max_nsteps=100)
    plot!(evt.waveforms[1], xlabel="Time", ylabel="Charge", label="Hole lifetime = $(τh)", xlim=[0, 100])
end
plots

Comment on lines +86 to +87
config_dict = SolidStateDetectors.parse_config_file(SSD_examples[:TrueCoaxial])
simA = @test_nowarn Simulation{T}(config_dict)
Copy link
Collaborator

Choose a reason for hiding this comment

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

You don't seem to use config_dict anywhere else, so you could just do

Suggested change
config_dict = SolidStateDetectors.parse_config_file(SSD_examples[:TrueCoaxial])
simA = @test_nowarn Simulation{T}(config_dict)
simA = @test_nowarn Simulation{T}(SSD_examples[:TrueCoaxial])

@test simA.detector.semiconductor.charge_trapping_model.inactive_layer_geometry.hZ == T(0.005)
end

simA = Simulation{T}(SSD_examples[:TrueCoaxial])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Then you can also skip this line and continue using simA from line 86

simA_inactive_layer_geometry=deepcopy(simA.detector.semiconductor.charge_trapping_model.inactive_layer_geometry)

# test2: only model the bulk volume, test the bulk signals while varying the lifetime: τ
pos = CartesianPoint{T}(0.005,0,0.005); Edep = 1u"eV"
signalsum_list = []
Copy link
Collaborator

Choose a reason for hiding this comment

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

In general, if you create an Array to push! to it afterwards, consider specifying a type

Suggested change
signalsum_list = []
signalsum_list = T[]

Comment on lines +127 to +133
pos_bulk = CartesianPoint{T}(0.0085,0,0.005); Edep = 1u"eV"
@test !in(pos_bulk, simA_inactive_layer_geometry)
signalsum_list_bulk = []
τ_list = (10 .^ (-2:0.2:0))*u"ms"
pos_inactive = CartesianPoint{T}(0.0095,0,0.005); Edep = 1u"eV"
@test in(pos_inactive, simA_inactive_layer_geometry)
signalsum_list_inactive = []
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:

Suggested change
pos_bulk = CartesianPoint{T}(0.0085,0,0.005); Edep = 1u"eV"
@test !in(pos_bulk, simA_inactive_layer_geometry)
signalsum_list_bulk = []
τ_list = (10 .^ (-2:0.2:0))*u"ms"
pos_inactive = CartesianPoint{T}(0.0095,0,0.005); Edep = 1u"eV"
@test in(pos_inactive, simA_inactive_layer_geometry)
signalsum_list_inactive = []
pos_bulk = CartesianPoint{T}(0.0085,0,0.005); Edep = 1u"eV"
@test !in(pos_bulk, simA_inactive_layer_geometry)
signalsum_list_bulk = T[]
τ_list = (10 .^ (-2:0.2:0))*u"ms"
pos_inactive = CartesianPoint{T}(0.0095,0,0.005); Edep = 1u"eV"
@test in(pos_inactive, simA_inactive_layer_geometry)
signalsum_list_inactive = T[]

Comment on lines +158 to +164
@info signalsum_list_bulk
@test all(signalsum_list_bulk .< T(2.0))
@test all(diff(signalsum_list_bulk) .> 0)
@info signalsum_list_inactive
@test all(signalsum_list_inactive .< T(2.0))
@test all(diff(signalsum_list_inactive) .> 0)
@test all(signalsum_list_bulk .> signalsum_list_inactive)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe remove the @info statements for the tests

Suggested change
@info signalsum_list_bulk
@test all(signalsum_list_bulk .< T(2.0))
@test all(diff(signalsum_list_bulk) .> 0)
@info signalsum_list_inactive
@test all(signalsum_list_inactive .< T(2.0))
@test all(diff(signalsum_list_inactive) .> 0)
@test all(signalsum_list_bulk .> signalsum_list_inactive)
@test all(signalsum_list_bulk .< T(2.0))
@test all(diff(signalsum_list_bulk) .> 0)
@test all(signalsum_list_inactive .< T(2.0))
@test all(diff(signalsum_list_inactive) .> 0)
@test all(signalsum_list_bulk .> signalsum_list_inactive)

pos_inactive = CartesianPoint{T}(0.01-depth,0,0.005); Edep = 1u"eV"
@test in(pos_inactive, simA_inactive_layer_geometry)
evt_inactive = Event(pos_inactive , Edep)
timed_simulate!(evt_inactive, simA, diffusion=false, Δt=1u"ns")
Copy link
Collaborator

Choose a reason for hiding this comment

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

No need to have explicit diffusion = false, this is the default

Suggested change
timed_simulate!(evt_inactive, simA, diffusion=false, Δt=1u"ns")
timed_simulate!(evt_inactive, simA, Δt=1u"ns")

"inactive_layer_geometry" => simA_inactive_layer_geometry)
trapping_model=ConstantLifetimeChargeTrappingModel{T}(parameters)
simA.detector = SolidStateDetector(simA.detector, trapping_model)
signalsum_list_inactive = []
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
signalsum_list_inactive = []
signalsum_list_inactive = T[]

z: 5.0
```

The inactive layer could only be modeled with the help of the configuration file.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is that still true? I saw in the tests that you can pass a geometry as inactive_layer to create a new SolidStateDetector

Copy link
Author

Choose a reason for hiding this comment

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

Yes, we can pass the geometry without the config, but the geometry has to be defined in the config.

Comment on lines +309 to +314
# model both the sensitive and inactive volumes based on the inactive volume geometry defined in the configuration file
τh = τe = 1u"ms"
τh_inactive = τe_inactive = 80u"ns"
parameters = Dict("parameters" => Dict("τh" => τh, "τe" => τe, "τh_inactive" => τh_inactive, "τe_inactive" => τe_inactive), "inactive_layer_geometry" => sim.detector.semiconductor.charge_trapping_model.inactive_layer_geometry)
sim.detector = SolidStateDetector(sim.detector, ConstantLifetimeChargeTrappingModel{T}(parameters))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also here, it looks like you can pass a geometry, even without using a configuration file.

* `τh_inactive::T`: Lifetime for holes in surface layer (dead layer/inactive layer) (default: `τh_inactive = 1ms`).
* `τe_inactive::T`: Lifetime for electrons in surface layer (default: `τe_inactive = 1ms`).
* `inactive_layer_geometry`: The geometry of the inactive layer. When this is defined, the model will consider the inactive effect.
> Note: All τ must be much bigger than `Δt`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you explicitly check for this in the drift code? If not, it might make sense to throw a warning or error if τ is smaller than Δt.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, it works like expected when τ is bigger than 10 ns, and it doesn't work when τ is 1 ns. I didn't test more values, but it already makes a lot of sense. When solving the difference equation, you should always make sure the step is small enough. But it is indeed better to throw a warning when τ and Δt are close.

Actually, the Boggs model also has this problem. The parameter (mean free path) should also be much bigger than the typical step length.

inactive_layer_exist = !isnothing(ctm.inactive_layer_geometry)
τ::T = ifelse(charge > 0, ctm.τh, ctm.τe)
τ_inactive::T = ifelse(charge > 0, ctm.τh_inactive, ctm.τe_inactive)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a check here to see that τ is bigger than Δt?

Copy link
Author

Choose a reason for hiding this comment

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

Sure

Comment on lines +184 to +206
function ConstantLifetimeChargeTrappingModel{T}(config_dict::AbstractDict = Dict()) where {T <: SSDFloat}
τh::T = ustrip(u"s", 1u"ms")
τe::T = ustrip(u"s", 1u"ms")
τh_inactive::T = ustrip(u"s", 80u"ns")
τe_inactive::T = ustrip(u"s", 80u"ns")

if haskey(config_dict, "model") && !haskey(config_dict, "parameters")
throw(ConfigFileError("`ConstantLifetimeChargeTrappingModel` does not have `parameters`"))
end

parameters = haskey(config_dict, "parameters") ? config_dict["parameters"] : config_dict
inactive_layer_geometry = haskey(config_dict, "inactive_layer_geometry") ? config_dict["inactive_layer_geometry"] : nothing

allowed_keys = ("τh", "τe", "τh_inactive", "τe_inactive")
k = filter(k -> !(k in allowed_keys), keys(parameters))
!isempty(k) && @warn "The following keys will be ignored: $(k).\nAllowed keys are: $(allowed_keys)"

if haskey(parameters, "τh") τh = _parse_value(T, parameters["τh"], internal_time_unit) end
if haskey(parameters, "τe") τe = _parse_value(T, parameters["τe"], internal_time_unit) end
if haskey(parameters, "τh_inactive") τh_inactive = _parse_value(T, parameters["τh_inactive"], internal_time_unit) end
if haskey(parameters, "τe_inactive") τe_inactive = _parse_value(T, parameters["τe_inactive"], internal_time_unit) end
ConstantLifetimeChargeTrappingModel{T, typeof(inactive_layer_geometry)}(τh, τe, τh_inactive, τe_inactive, inactive_layer_geometry)
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it already possible to define the model something like this?

charge_trapping_model = Dict(
    "model" => Dict(
        "τh" => 1u"ms",
        "τe" => 1u"ms",
        "τh_inactive" => 80u"ns",
        "τe_inactive" => 80u"ns"
    ),
    "inactive_layer_geometry" => Dict(
        "tube" => Dict(
            "r" => Dict("from" => 9.0u"mm", "to" => 10.0u"mm"),
            "h" => 10.0u"mm",
            "origin" => Dict("z" => 5.0u"mm")
        )
    )
)  

or can inactive_layer_geometry only be a Geometry object (or nothing)?

Copy link
Author

Choose a reason for hiding this comment

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

Ah, yes! This seems to be a way to define the full trapping model without the config. I will try to build the geometry with a dict in the trapping model struct.

Comment on lines +96 to +98
if haskey(constant_lifetime_ctm_dict, "inactive_layer_geometry")
constant_lifetime_ctm_dict["inactive_layer_geometry"] = Geometry(T, dict["charge_trapping_model"]["inactive_layer_geometry"], input_units, transformations)
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe something like this could be added to the constructor of ConstantLifetimeChargeTrappingModel?

Copy link
Author

Choose a reason for hiding this comment

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

Exactly!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants