Skip to content

Commit

Permalink
Merge branch 'main' into fix/Nan-in-E_cons
Browse files Browse the repository at this point in the history
  • Loading branch information
MasanoriKanamaru authored Jul 6, 2024
2 parents 524db45 + 62b829e commit 2d46a01
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CompatHelper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
run: which julia
continue-on-error: true
- name: Install Julia, but only if it is not already available in the PATH
uses: julia-actions/setup-julia@v1
uses: julia-actions/setup-julia@v2
with:
version: '1'
arch: ${{ runner.arch }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- x64
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
Expand Down
37 changes: 36 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
# AsteroidThermoPhysicalModels.jl

A package for dynamical simulation of an asteroid.
A Julia-based toolkit for thermophysical modeling (TPM) of asteroids. It allows you to simulate the temperature distribution of the asteroid and predict non-gravitational perturbations on its dynamics. Sample notebooks are available in [Astroshaper-examples](https://github.com/Astroshaper/Astroshaper-examples).

## TPM workflow
A thermophysical simulation is performed as the following workflow.

- Set ephemerides of a target asteroid(s)
- Load a shape model(s)
- Set thermophysical parameters

After running the simulation, you will get the following output files. You should note that the thermal force and torque is calculated in the asteroid-fixed frame.
- `physical_quantities.csv`
- `time` : Time steps
- `E_in` : Input energy per second on the whole surface [W]
- `E_out` : Output enegey per second from the whole surface [W]
- `E_cons` : Energy conservation ratio [-], ratio of total energy going out to total energy coming in during the last rotation cycle
- `force_x` : x-component of the thermal force
- `force_y` : y-component of the thermal force
- `force_z` : z-component of the thermal force
- `torque_x` : x-component of the thermal torque
- `torque_y` : y-component of the thermal torque
- `torque_z` : z-component of the thermal torque
- `subsurface_temperature.csv` : Temperature [K] as a function of depth [m] and time [s]
- `surface_temperature.csv` : Surface temperature of every face [K] as a function of time [s]
- `thermal_force.csv` : Thermal force on every face of the shape model [N] as a function of time

## Surface/sub-surface temperature analysis
Coming soon.

## Non-Gravitational Effects

### Yarkovsky effect
Coming soon.

### YORP effect
Coming soon.

34 changes: 17 additions & 17 deletions src/TPM.jl
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,17 @@ end


"""
struct BinaryTPM <: ThermoPhysicalModel
struct BinaryTPM{M1, M2} <: ThermoPhysicalModel
# Fields
- `pri` : TPM for the primary
- `sec` : TPM for the secondary
- `MUTUAL_SHADOWING` : Flag to consider mutual shadowing
- `MUTUAL_HEATING` : Flag to consider mutual heating
"""
struct BinaryTPM <: ThermoPhysicalModel
pri ::SingleTPM
sec ::SingleTPM
struct BinaryTPM{M1, M2} <: ThermoPhysicalModel
pri ::M1
sec ::M2

MUTUAL_SHADOWING ::Bool
MUTUAL_HEATING ::Bool
Expand Down Expand Up @@ -274,9 +274,9 @@ Outer constructor of `SingleTPMResult`
- `face_ID` : Face indices to save subsurface temperature
"""
function SingleTPMResult(stpm::SingleTPM, ephem, times_to_save::Vector{Float64}, face_ID::Vector{Int})
nsteps = length(ephem.time) # Number of time steps
nsteps_to_save = length(times_to_save) # Number of time steps to save temperature
nfaces = length(stpm.shape.faces) # Number of faces of the shape model
n_step = length(ephem.time) # Number of time steps
n_step_to_save = length(times_to_save) # Number of time steps to save temperature
n_face = length(stpm.shape.faces) # Number of faces of the shape model

E_in = zeros(nsteps)
E_out = zeros(nsteps)
Expand All @@ -285,11 +285,11 @@ function SingleTPMResult(stpm::SingleTPM, ephem, times_to_save::Vector{Float64},
torque = zeros(SVector{3, Float64}, nsteps)

depth_nodes = stpm.thermo_params.Δz * (0:stpm.thermo_params.n_depth-1)
surface_temperature = zeros(nfaces, nsteps_to_save)
surface_temperature = zeros(n_face, n_step_to_save)
subsurface_temperature = Dict{Int,Matrix{Float64}}(
i => zeros(stpm.thermo_params.n_depth, nsteps_to_save) for i in face_ID
i => zeros(stpm.thermo_params.n_depth, n_step_to_save) for i in face_ID
)
face_forces = zeros(SVector{3, Float64}, nfaces, nsteps_to_save)
face_forces = zeros(SVector{3, Float64}, n_face, n_step_to_save)

return SingleTPMResult(
ephem.time,
Expand Down Expand Up @@ -356,15 +356,15 @@ function update_TPM_result!(result::SingleTPMResult, stpm::SingleTPM, i_time::In
result.force[i_time] = stpm.force
result.torque[i_time] = stpm.torque

P = stpm.thermo_params.P # Rotation period
P = stpm.thermo_params.period # Rotation period
t = result.times[i_time] # Current time
t₀ = result.times[begin] # Time at the beginning of the simulation

if t > t₀ + P # Note that `E_cons` cannot be calculated during the first rotation
nsteps_in_period = count(@. t - P result.times < t) # Number of time steps within the last rotation
n_step_in_period = count(@. t - P result.times < t) # Number of time steps within the last rotation

ΣE_in = sum(result.E_in[n-1] * (result.times[n] - result.times[n-1]) for n in (i_time - nsteps_in_period + 1):i_time)
ΣE_out = sum(result.E_out[n-1] * (result.times[n] - result.times[n-1]) for n in (i_time - nsteps_in_period + 1):i_time)
ΣE_in = sum(result.E_in[n-1] * (result.times[n] - result.times[n-1]) for n in (i_time - n_step_in_period + 1):i_time)
ΣE_out = sum(result.E_out[n-1] * (result.times[n] - result.times[n-1]) for n in (i_time - n_step_in_period + 1):i_time)

result.E_cons[i_time] = ΣE_out / ΣE_in
end
Expand Down Expand Up @@ -469,8 +469,8 @@ function export_TPM_results(dirpath, result::SingleTPMResult)
filepath = joinpath(dirpath, "thermal_force.csv")

n_face = size(result.face_forces, 1) # Number of faces of the shape model
n_steps = size(result.face_forces, 2) # Number of time steps to save temperature
nrows = n_face * n_steps
n_step = size(result.face_forces, 2) # Number of time steps to save temperature
nrows = n_face * n_step

df = DataFrame(
time = reshape([t for _ in 1:n_face, t in result.times_to_save], nrows),
Expand Down Expand Up @@ -529,7 +529,7 @@ end
Subsolar temperature [K] on an asteroid at a heliocentric distance `r☉` [m],
assuming radiative equilibrium with zero conductivity.
"""
subsolar_temperature(r☉, params::AbstractThermoParams) = subsolar_temperature(r☉, params.A_B, params.ε)
subsolar_temperature(r☉, params::AbstractThermoParams) = subsolar_temperature(r☉, params.A_B, params.emissivity)

function subsolar_temperature(r☉, A_B, ε)
Φ = SOLAR_CONST / SPICE.convrt(norm(r☉), "m", "au")^2 # Energy flux at the solar distance [W/m²]
Expand Down
8 changes: 4 additions & 4 deletions src/energy_flux.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Output enegey per second from the whole surface [W]
function energy_out(stpm::SingleTPM)
E_out = 0.
for i in eachindex(stpm.shape.faces)
ε = (stpm.thermo_params.ε isa Real ? stpm.thermo_params.ε : stpm.thermo_params.ε[i])
ε = (stpm.thermo_params.emissivity isa Real ? stpm.thermo_params.emissivity : stpm.thermo_params.emissivity[i])
T = stpm.temperature[begin, i] # Surface temperature
a = stpm.shape.face_areas[i]

Expand Down Expand Up @@ -204,7 +204,7 @@ function update_flux_rad_single!(stpm::SingleTPM)
for visiblefacet in stpm.shape.visiblefacets[i]
j = visiblefacet.id
fᵢⱼ = visiblefacet.f
ε = (stpm.thermo_params.ε isa Real ? stpm.thermo_params.ε : stpm.thermo_params.ε[j])
ε = (stpm.thermo_params.emissivity isa Real ? stpm.thermo_params.emissivity : stpm.thermo_params.emissivity[j])
A_TH = (stpm.thermo_params.A_TH isa Real ? stpm.thermo_params.A_TH : stpm.thermo_params.A_TH[j])
Tⱼ = stpm.temperature[begin, j]

Expand Down Expand Up @@ -421,8 +421,8 @@ function mutual_heating!(btpm::BinaryTPM, rₛ, R₂₁)
T₁ = btpm.pri.temperature[begin, i]
T₂ = btpm.sec.temperature[begin, j]

ε₁ = (thermo_params1.ε isa Real ? thermo_params1.ε : thermo_params1.ε[i])
ε₂ = (thermo_params2.ε isa Real ? thermo_params2.ε : thermo_params2.ε[j])
ε₁ = (thermo_params1.emissivity isa Real ? thermo_params1.emissivity : thermo_params1.emissivity[i])
ε₂ = (thermo_params2.emissivity isa Real ? thermo_params2.emissivity : thermo_params2.emissivity[j])
A_B₁ = (thermo_params1.A_B isa Real ? thermo_params1.A_B : thermo_params1.A_B[i])
A_B₂ = (thermo_params2.A_B isa Real ? thermo_params2.A_B : thermo_params2.A_B[j])
A_TH₁ = (thermo_params1.A_TH isa Real ? thermo_params1.A_TH : thermo_params1.A_TH[i])
Expand Down
22 changes: 11 additions & 11 deletions src/heat_conduction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ function forward_euler!(stpm::SingleTPM, Δt)
n_face = size(T, 2)

## Zero-conductivity (thermal inertia) case
if iszero(stpm.thermo_params.Γ)
if iszero(stpm.thermo_params.inertia)
for i_face in 1:n_face
A_B = (stpm.thermo_params.A_B isa Real ? stpm.thermo_params.A_B : stpm.thermo_params.A_B[i_face] )
A_TH = (stpm.thermo_params.A_TH isa Real ? stpm.thermo_params.A_TH : stpm.thermo_params.A_TH[i_face])
ε = (stpm.thermo_params.ε isa Real ? stpm.thermo_params.ε : stpm.thermo_params.ε[i_face] )
ε = (stpm.thermo_params.emissivity isa Real ? stpm.thermo_params.emissivity : stpm.thermo_params.emissivity[i_face])
εσ = ε * σ_SB

F_sun, F_scat, F_rad = stpm.flux[i_face, :]
Expand All @@ -80,9 +80,9 @@ function forward_euler!(stpm::SingleTPM, Δt)
## Non-zero-conductivity (thermal inertia) case
else
for i_face in 1:n_face
P = stpm.thermo_params.P
P = stpm.thermo_params.period
Δz = stpm.thermo_params.Δz
l = (stpm.thermo_params.l isa Real ? stpm.thermo_params.l : stpm.thermo_params.l[i_face])
l = (stpm.thermo_params.skindepth isa Real ? stpm.thermo_params.skindepth : stpm.thermo_params.skindepth[i_face])

λ = (Δt/P) / (Δz/l)^2 / 4π
λ 0.5 && error("The forward Euler method is unstable because λ = . This should be less than 0.5.")
Expand Down Expand Up @@ -156,8 +156,8 @@ function crank_nicolson!(stpm::SingleTPM, Δt)
# n_depth = size(T, 1)
# n_face = size(T, 2)

# Δt̄ = stpm.thermo_params.Δt / stpm.thermo_params.P # Non-dimensional timestep, normalized by period
# Δz̄ = stpm.thermo_params.Δz / stpm.thermo_params.l # Non-dimensional step in depth, normalized by thermal skin depth
# Δt̄ = stpm.thermo_params.Δt / stpm.thermo_params.period # Non-dimensional timestep, normalized by period
# Δz̄ = stpm.thermo_params.Δz / stpm.thermo_params.skindepth # Non-dimensional step in depth, normalized by thermal skin depth
# r = (1/4π) * (Δt̄ / 2Δz̄^2)

# for i_face in 1:n_face
Expand Down Expand Up @@ -242,12 +242,12 @@ function update_upper_temperature!(stpm::SingleTPM, i::Integer)

#### Radiation boundary condition ####
if stpm.BC_UPPER isa RadiationBoundaryCondition
P = stpm.thermo_params.P
l = (stpm.thermo_params.l isa Real ? stpm.thermo_params.l : stpm.thermo_params.l[i] )
Γ = (stpm.thermo_params.Γ isa Real ? stpm.thermo_params.Γ : stpm.thermo_params.Γ[i] )
P = stpm.thermo_params.period
l = (stpm.thermo_params.skindepth isa Real ? stpm.thermo_params.skindepth : stpm.thermo_params.skindepth[i] )
Γ = (stpm.thermo_params.inertia isa Real ? stpm.thermo_params.inertia : stpm.thermo_params.inertia[i])
A_B = (stpm.thermo_params.A_B isa Real ? stpm.thermo_params.A_B : stpm.thermo_params.A_B[i] )
A_TH = (stpm.thermo_params.A_TH isa Real ? stpm.thermo_params.A_TH : stpm.thermo_params.A_TH[i])
ε = (stpm.thermo_params.ε isa Real ? stpm.thermo_params.ε : stpm.thermo_params.ε[i] )
ε = (stpm.thermo_params.emissivity isa Real ? stpm.thermo_params.emissivity : stpm.thermo_params.emissivity[i])
Δz = stpm.thermo_params.Δz

F_sun, F_scat, F_rad = stpm.flux[i, :]
Expand Down Expand Up @@ -276,7 +276,7 @@ Newton's method to update the surface temperature under radiation boundary condi
- `Γ` : Thermal inertia [tiu]
- `P` : Period of thermal cycle [sec]
- `Δz̄` : Non-dimensional step in depth, normalized by thermal skin depth `l`
- `ε` : Emissivity
- `ε` : Emissivity [-]
"""
function update_surface_temperature!(T::AbstractVector, F_total::Float64, P::Float64, l::Float64, Γ::Float64, ε::Float64, Δz::Float64)
Δz̄ = Δz / l # Dimensionless length of depth step
Expand Down
2 changes: 1 addition & 1 deletion src/non_grav.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function update_thermal_force!(stpm::SingleTPM)
## Note that both scattered light and thermal radiation are assumed to be isotropic.
A_B = (stpm.thermo_params.A_B isa Real ? stpm.thermo_params.A_B : stpm.thermo_params.A_B[i])
A_TH = (stpm.thermo_params.A_TH isa Real ? stpm.thermo_params.A_TH : stpm.thermo_params.A_TH[i])
ε = (stpm.thermo_params.ε isa Real ? stpm.thermo_params.ε : stpm.thermo_params.ε[i])
ε = (stpm.thermo_params.emissivity isa Real ? stpm.thermo_params.emissivity : stpm.thermo_params.emissivity[i])
Eᵢ = A_B * F_sun + A_B * F_scat + A_TH * F_rad + ε * σ_SB * Tᵢ^4

## Thermal force on each face
Expand Down
46 changes: 23 additions & 23 deletions src/thermo_params.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,24 @@ abstract type AbstractThermoParams end
struct NonUniformThermoParams
# Fields
- `P` : Cycle of thermal cycle (rotation period) [sec]
- `l` : Thermal skin depth [m]
- `Γ` : Thermal inertia [J ⋅ m⁻² ⋅ K⁻¹ ⋅ s⁻⁰⁵ (tiu)]
- `period` : Cycle of thermal cycle (rotation period) [sec]
- `skindepth` : Thermal skin depth [m]
- `inertia` : Thermal inertia [J ⋅ m⁻² ⋅ K⁻¹ ⋅ s⁻⁰⁵ (tiu)]
- `A_B` : Bond albedo
- `A_TH` : Albedo at thermal radiation wavelength
- `ε` : Emissivity
- `emissivity` : Emissivity [-]
- `z_max` : Depth of the bottom of a heat conduction equation [m]
- `Δz` : Depth step width [m]
- `n_depth` : Number of depth steps
"""
struct NonUniformThermoParams <: AbstractThermoParams
P ::Float64 # Common for all faces
l ::Vector{Float64}
Γ ::Vector{Float64}
period ::Float64 # Common for all faces
skindepth::Vector{Float64}
inertia ::Vector{Float64}
A_B ::Vector{Float64}
A_TH ::Vector{Float64}
ε ::Vector{Float64}
emissivity::Vector{Float64}

z_max ::Float64 # Common for all faces
Δz ::Float64 # Common for all faces
Expand All @@ -71,24 +71,24 @@ end
struct UniformThermoParams
# Fields
- `P` : Thermal cycle (rotation period) [sec]
- `l` : Thermal skin depth [m]
- `Γ` : Thermal inertia [J ⋅ m⁻² ⋅ K⁻¹ ⋅ s⁻⁰⁵ (tiu)]
- `period`: Thermal cycle (rotation period) [sec]
- `skindepth`: Thermal skin depth [m]
- `inertia` : Thermal inertia [J ⋅ m⁻² ⋅ K⁻¹ ⋅ s⁻⁰⁵ (tiu)]
- `A_B` : Bond albedo
- `A_TH` : Albedo at thermal radiation wavelength
- `ε` : Emissivity
- `emissivity` : Emissivity [-]
- `z_max` : Depth of the bottom of a heat conduction equation [m]
- `Δz` : Depth step width [m]
- `n_depth`: Number of depth steps
"""
struct UniformThermoParams <: AbstractThermoParams
P ::Float64
l ::Float64
Γ ::Float64
period ::Float64
skindepth::Float64
inertia ::Float64
A_B ::Float64
A_TH ::Float64
ε ::Float64
emissivity::Float64

z_max ::Float64
Δz ::Float64
Expand Down Expand Up @@ -124,20 +124,20 @@ function Base.show(io::IO, params::UniformThermoParams)
msg *= "| Thermophysical parameters |\n"
msg *= "⋅-----------------------------------⋅\n"

msg *= " P = $(params.P) [sec]\n"
msg *= " = $(SPICE.convrt(params.P, "seconds", "hours")) [h]\n"
msg *= " l = $(params.l) [m]\n"
msg *= " Γ = $(params.Γ) [tiu]\n"
msg *= " P = $(params.period) [sec]\n"
msg *= " = $(SPICE.convrt(params.period, "seconds", "hours")) [h]\n"
msg *= " l = $(params.skindepth) [m]\n"
msg *= " Γ = $(params.inertia) [tiu]\n"
msg *= " A_B = $(params.A_B)\n"
msg *= " A_TH = $(params.A_TH)\n"
msg *= " ε = $(params.ε)\n"
msg *= " ε = $(params.emissivity)\n"

msg *= "-----------------------------------\n"

msg *= " z_max = $(params.z_max) [m]\n"
msg *= " = $(params.z_max / params.l) [l]\n"
msg *= " = $(params.z_max / params.skindepth) [l]\n"
msg *= " Δz = $(params.Δz) [m]\n"
msg *= " = $(params.Δz / params.l) [l]\n"
msg *= " = $(params.Δz / params.skindepth) [l]\n"
msg *= " n_depth = $(params.n_depth)\n"

msg *= "-----------------------------------\n"
Expand Down
14 changes: 7 additions & 7 deletions test/TPM_Didymos/TPM_Didymos.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@

##= Download SPICE kernels =##
for path_kernel in paths_kernel
url_kernel = "https://s2e2.cosmos.esa.int/bitbucket/projects/SPICE_KERNELS/repos/hera/raw/kernels/$(path_kernel)"
url_kernel = "https://s2e2.cosmos.esa.int/bitbucket/projects/SPICE_KERNELS/repos/hera/raw/kernels/$(path_kernel)?at=refs%2Ftags%2Fv161_20230929_001"
filepath = joinpath("kernel", path_kernel)
mkpath(dirname(filepath))
isfile(filepath) || Downloads.download(url_kernel, filepath)
end

##= Download shape models =##
for path_shape in paths_shape
url_kernel = "https://s2e2.cosmos.esa.int/bitbucket/projects/SPICE_KERNELS/repos/hera/raw/kernels/dsk/$(path_shape)"
url_kernel = "https://s2e2.cosmos.esa.int/bitbucket/projects/SPICE_KERNELS/repos/hera/raw/kernels/dsk/$(path_shape)?at=refs%2Ftags%2Fv161_20230929_001"
filepath = joinpath("shape", path_shape)
mkpath(dirname(filepath))
isfile(filepath) || Downloads.download(url_kernel, filepath)
Expand All @@ -53,12 +53,12 @@
P₁ = SPICE.convrt(2.2593, "hours", "seconds") # Rotation period of Didymos
P₂ = SPICE.convrt(11.93 , "hours", "seconds") # Rotation period of Dimorphos

ncycles = 2 # Number of cycles to perform TPM
nsteps_in_cycle = 72 # Number of time steps in one rotation period
n_cycle = 2 # Number of cycles to perform TPM
n_step_in_cycle = 72 # Number of time steps in one rotation period

et_begin = SPICE.utc2et("2027-02-18T00:00:00") # Start time of TPM
et_end = et_begin + P₂ * ncycles # End time of TPM
et_range = range(et_begin, et_end; length=nsteps_in_cycle*ncycles+1)
et_end = et_begin + P₂ * n_cycle # End time of TPM
et_range = range(et_begin, et_end; length=n_step_in_cycle*n_cycle+1)

"""
- `time` : Ephemeris times
Expand Down Expand Up @@ -143,7 +143,7 @@
AsteroidThermoPhysicalModels.init_temperature!(btpm, 200.)

##= Run TPM =##
times_to_save = ephem.time[end-nsteps_in_cycle:end] # Save temperature during the final rotation
times_to_save = ephem.time[end-n_step_in_cycle:end] # Save temperature during the final rotation
face_ID_pri = [1, 2, 3, 4, 10] # Face indices to save subsurface temperature of the primary
face_ID_sec = [1, 2, 3, 4, 20] # Face indices to save subsurface temperature of the secondary

Expand Down
Loading

0 comments on commit 2d46a01

Please sign in to comment.