Skip to content

Commit

Permalink
feat: more exhaustive type hierarchy (#4)
Browse files Browse the repository at this point in the history
* feat:  inner constructor for Particle

* feat: Add CustomParticle

* doc: Update particle type hierarchy to add CustomParticle

* refactor: more exhaustive type hierarchy

* refactor: Update special particles type hierarchy

- Make _special_particles.jl public visible, add Positron

* refactor: Rename ChargedParticleImpl to Particle and update related code

The `Particle` type now represents the concrete implementation for storing particle properties.

* refactor: rename `Particle` interface for creating particles to `particle`, and add functionality to process special particles

* doc: update `particle`

* chore: more aliases
  • Loading branch information
Beforerr authored Nov 20, 2024
1 parent dd3a62d commit d531e6c
Show file tree
Hide file tree
Showing 16 changed files with 277 additions and 127 deletions.
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ makedocs(
],
"API Reference" => "api.md"
],
checkdocs=:exports,
doctest=true
)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Order = [:type]
## Constructors

```@docs
Particle
particle
proton
electron
```
Expand Down
8 changes: 4 additions & 4 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ p = proton() # proton
α = Particle("alpha") # alpha particle
# Create ions and isotopes
fe3 = Particle("Fe3+") # Iron(III) ion
fe56 = Particle("Fe-56") # Iron-56
fe = Particle("Fe-56 3+") # Iron(III) ion
fe54 = Particle(:Fe, 3, 54)
d = Particle("D+") # Deuteron
# Access properties
println("Electron mass: ", mass(e))
println("Alpha particle charge: ", charge(α))
println("Iron charge: ", charge(fe3))
println("Iron-56 mass number: ", mass_number(fe56))
println("Iron(III) ion charge: ", charge(fe))
println("Iron-54 mass number: ", mass_number(fe54))
println("Deuteron mass: ", mass(d))
```

Expand Down
21 changes: 17 additions & 4 deletions docs/src/manual/particle-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,32 @@ ChargedParticles.jl provides a flexible type system for representing various typ
## Type Hierarchy

```@raw html
The package uses a exhaustive type hierarchy:
<pre>
AbstractParticle
└── ChargedParticleImpl
├── AbstractChargeParticle
│ ├── Particle
├── AbstractFermion
│ ├── AbstractLepton
│ │ ├── Electron
│ │ └── Muon
│ └── AbstractQuark
│ └── Neutron
│ └── ...
└── CustomParticle
</pre>
```

The package uses a simple two-level type hierarchy:
- `AbstractParticle`: Base abstract type for all particles
- `ChargedParticleImpl`: Concrete implementation storing particle properties
- `AbstractChargeParticle`: Particles that could carry an electric charge.
- `Particle`: Physically meaningful particle type (for ions where symbol encodes the actual type of the particle)
- `CustomParticle`: Custom particle type for user-defined particles (where symbol is just a label)

## Particle Properties

Each particle has three fundamental properties:
Each particle (`Particle`) has three fundamental properties:

1. **Symbol** (`symbol::Symbol`): Chemical symbol or particle identifier
- Regular elements: `:Fe`, `:He`, etc.
Expand Down
11 changes: 6 additions & 5 deletions src/ChargedParticles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ using Mendeleev: elements # for PeriodicTable compatibility
using Match
using Unitful: me, mp

const ELEMENTARY_PARTICLES = (:e, , :n)

include("./types.jl")
include("./particle.jl")
include("./properties.jl")
include("./aliases.jl")
include("./utils.jl")
include("./display.jl")
include("./_special_particles.jl")
include("./custom_particles.jl")
include("./special_particles.jl")

export AbstractParticle, Particle, ChargedParticleImpl
export AbstractParticle, Particle, CustomParticle
export Electron, Muon, Neutron
export mass, charge, charge_number, atomic_number, mass_number, element, mass_energy
export is_ion, is_chemical_element, is_default_isotope
export electron, proton
export particle, electron, proton
end
11 changes: 0 additions & 11 deletions src/_special_particles.jl

This file was deleted.

23 changes: 12 additions & 11 deletions src/aliases.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

const ELECTRON_ALIASES = ("electron", "e-", "e")
const PROTON_ALIASES = ("proton", "p+", "p", "H+")
const POSITRON_ALIASES = ("positron", "e+")
const NEUTRON_ALIASES = ("neutron", "n")
const MUON_ALIASES = ("muon", "μ", "μ-", "mu-", "mu")

"""
PARTICLE_ALIASES
Expand All @@ -10,23 +14,20 @@ Dictionary of common particle aliases and their corresponding (symbol, charge, m
Each entry maps a string alias to a tuple of (symbol, charge, mass_number)
"""
PARTICLE_ALIASES = Dict(
"e+" => ("e", 1, 0),
"positron" => ("e", 1, 0),
"neutron" => ("n", 0, 1),
"n" => ("n", 0, 1),
(PROTON_ALIASES .=> Ref(("H", 1, 1)))...,
(ELECTRON_ALIASES .=> :Electron)...,
(NEUTRON_ALIASES .=> :Neutron)...,
(POSITRON_ALIASES .=> :Positron)...,
(MUON_ALIASES .=> :Muon)...,
"alpha" => ("He", 2, 4),
"deuteron" => ("H", 1, 2),
"D+" => ("H", 1, 2),
"tritium" => ("H", 0, 3),
"T" => ("H", 0, 3),
"triton" => ("H", 1, 3),
"T+" => ("H", 1, 3),
"mu-" => ("μ", -1, 0),
"muon" => ("μ", -1, 0),
"mu-" => :Muon,
"muon" => :Muon,
"antimuon" => ("μ", 1, 0),
"mu+" => ("μ", 1, 0),
)

ELECTRON_ALIASES_DICT = Dict(str => ("e", -1, 0) for str in ELECTRON_ALIASES)
PROTON_ALIASES_DICT = Dict(str => ("H", 1, 1) for str in PROTON_ALIASES)
PARTICLE_ALIASES = merge(PARTICLE_ALIASES, ELECTRON_ALIASES_DICT, PROTON_ALIASES_DICT)
)
36 changes: 36 additions & 0 deletions src/custom_particles.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
CustomParticle <: AbstractParticle
A particle with user-defined mass and charge.
# Fields
- `mass`: The mass of the particle (can be any numeric type or Unitful quantity)
- `charge_number`: Integer representing the charge state
- `symbol`: Optional symbol identifier (defaults to nothing)
# Examples
```julia
CustomParticle(1.67e-27u"kg", 1) # A particle with proton-like mass and +1 charge
CustomParticle(2.0u"GeV", -1, :custom) # A custom particle with specified symbol
```
"""
@kwdef struct CustomParticle <: AbstractParticle
mass::Unitful.Mass
charge_number::Int
symbol::Symbol
function CustomParticle(mass, charge_number, symbol=:custom)
new(mass, charge_number, symbol)
end
end

function CustomParticle(mass_energy::Unitful.Energy, charge_number, symbol=:custom)
mass = uconvert(u"kg", mass_energy / Unitful.c^2)
CustomParticle(mass, charge_number, symbol)
end

# Override mass method for CustomParticle
mass(p::CustomParticle) = p.mass
# CustomParticle doesn't have a mass number
mass_number(::CustomParticle) = nothing
# CustomParticle doesn't have an element
element(::CustomParticle) = nothing
73 changes: 73 additions & 0 deletions src/particle.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
particle(str::AbstractString; mass_numb=nothing, z=nothing)
Create a particle from a string representation.
# Arguments
- `str::AbstractString`: String representation of the particle
# String Format Support
- Element symbols: `"Fe"`, `"He"`
- Isotopes: `"Fe-56"`, `"D"`
- Ions: `"Fe2+"`, `"H-"`
- Common aliases: `"electron"`, `"proton"`, `"alpha"`, `"mu-"`
# Examples
```jldoctest; output = false
# Elementary particles
electron = particle("e-")
muon = particle("mu-")
positron = particle("e+")
# Ions and isotopes
proton = particle("H+")
alpha = particle("He2+")
deuteron = particle("D+")
iron56 = particle("Fe-56")
# output
Fe
```
"""
function particle(str::AbstractString; mass_numb=nothing, z=nothing)
# Check aliases first
if haskey(PARTICLE_ALIASES, str)
result = PARTICLE_ALIASES[str]
if result isa Tuple
symbol, charge, mass_number = result
return Particle(Symbol(symbol), charge, mass_number)
else
return eval(result)()
end
end

# Try to parse as element with optional mass number and charge
result = parse_particle_string(str)
if !isnothing(result)
(symbol, parsed_charge, parsed_mass_numb) = result
element = elements[symbol]
charge = determine(parsed_charge, z; default=0)
mass_number = determine(parsed_mass_numb, mass_numb; default=element.mass_number)
return Particle(symbol, charge, mass_number)
end
throw(ArgumentError("Invalid particle string format: $str"))
end

"""
particle(sym::Symbol)
Create a particle from its symbol representation.
# Examples
```jldoctest; output = false
# Elementary particles
electron = particle(:e)
muon = particle(:muon)
proton = particle(:p)
# output
H⁺
```
"""
particle(sym::Symbol; kwargs...) = particle(string(sym); kwargs...)

Particle(str::AbstractString; mass_numb=nothing, z=nothing) = particle(str; mass_numb, z)
Particle(sym::Symbol; kwargs...) = particle(string(sym); kwargs...)
22 changes: 11 additions & 11 deletions src/properties.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const calculated_properties = (:charge, :atomic_number, :element, :mass_energy, :mass)
const calculated_properties = (:charge_number, :charge, :atomic_number, :element, :mass_energy, :mass, :symbol)
const properties_fn_map = Dict()
const synonym_properties = Dict(
:A => :mass_number,
Expand All @@ -22,10 +22,8 @@ end
# Basic properties
"""Return the mass of the particle"""
function mass(p::AbstractParticle)
get(mass_dicts, p.symbol) do
base_mass = mass(p.element, p.mass_number)
return base_mass - p.charge_number * Unitful.me
end
base_mass = mass(p.element, p.mass_number)
return base_mass - p.charge_number * Unitful.me
end

charge_number(p::AbstractParticle) = p.charge_number
Expand Down Expand Up @@ -68,21 +66,23 @@ println(mass_number(e)) # 0
mass_number(p) = p.mass_number
mass_number(::Nothing) = nothing

function element(p::AbstractParticle)
element(::AbstractParticle) = nothing

function element(p::Particle)
@match p.symbol begin
x, if x in ELEMENTARY_PARTICLES
end => return nothing
:p => return elements[:H]
_ => return elements[p.symbol]
end
end

mass_energy(p::AbstractParticle) = _format_energy(uconvert(u"eV", p.mass * Unitful.c^2))

function Base.getproperty(p::ChargedParticleImpl, s::Symbol)
function Base.getproperty(p::AbstractParticle, s::Symbol)
s in fieldnames(typeof(p)) && return getfield(p, s)
s in calculated_properties && return eval(get(properties_fn_map, s, s))(p)
s in keys(synonym_properties) && return getproperty(p, synonym_properties[s])
return getfield(p, s)
end

Base.propertynames(p::ChargedParticleImpl) = (sort collect union)(keys(synonym_properties), calculated_properties, fieldnames(ChargedParticleImpl))
function Base.propertynames(::T) where {T<:AbstractParticle}
(sort collect union)(keys(synonym_properties), calculated_properties, fieldnames(T))
end
38 changes: 38 additions & 0 deletions src/special_particles.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const leptons = ("e-", "mu-", "tau-", "nu_e", "nu_mu", "nu_tau")
const antileptons = ("e+", "mu+", "tau+", "anti_nu_e", "anti_nu_mu", "anti_nu_tau")
const baryons = ("p+", "n")
const antibaryons = ("p-", "antineutron")

struct Electron <: AbstractLepton end
struct Positron <: AbstractLepton end
struct Muon <: AbstractLepton end
struct Neutron <: AbstractFermion end

# Properties
atomic_number(::AbstractFermion) = 0
mass_number(::AbstractFermion) = 0

## Electron and Positron
charge_number(::Electron) = -1
mass(::Electron) = me
symbol(::Electron) = :e

charge_number(::Positron) = 1
mass(::Positron) = me
symbol(::Positron) = :e

## Muon
charge_number(::Muon) = -1
mass(::Muon) = 206.7682827me
symbol(::Muon) =

## Neutron
charge_number(::Neutron) = 0
mass(::Neutron) = Unitful.mn
symbol(::Neutron) = :n
mass_number(::Neutron) = 1


# Convenience constructors for common particles
"""Create an electron"""
electron() = Electron()
Loading

0 comments on commit d531e6c

Please sign in to comment.