Skip to content
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

Type hierarchy #4

Merged
merged 11 commits into from
Nov 20, 2024
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