From 7bad77384c90d3f0e3d7200a5505732beba2632e Mon Sep 17 00:00:00 2001 From: "M. Kanamaru" Date: Sat, 27 Jan 2024 22:49:16 +0900 Subject: [PATCH] Add `face_forces` field to `SingleTPMResult` struct to store thermal force on every facet (#117) * Update TPM.jl Add `face_forces` into struct `SingleTPMResult` to store the thermal force on every facet of the shape model. * Update TPM.jl * Update TPM.jl Just a cleanup. * Add a progress meter option in `find_visiblefacets!` * Update src/TPM.jl Co-authored-by: Yuto Horikawa * Update TPM.jl --------- Co-authored-by: Yuto Horikawa --- src/TPM.jl | 63 ++++++++++++++++++++++++++++++++++++++-------------- src/facet.jl | 29 +++++++++++++++++++----- src/shape.jl | 17 ++++++++++++-- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/TPM.jl b/src/TPM.jl index dbc18d12..bc79bde9 100644 --- a/src/TPM.jl +++ b/src/TPM.jl @@ -175,7 +175,7 @@ function SingleTPM(shape, thermo_params; SELF_SHADOWING, SELF_HEATING, SOLVER, B flux = zeros(Ns, 3) temperature = zeros(Nz, Ns) - face_forces = [zero(SVector{3, Float64}) for _ in shape.faces] + face_forces = zeros(SVector{3, Float64}, Ns) force = zero(MVector{3, Float64}) torque = zero(MVector{3, Float64}) @@ -236,14 +236,17 @@ Output data format for `SingleTPM` - `torque` : Thermal torque on the asteroid [N ⋅ m] ## Saved only at the time steps desired by the user -- `times_to_save` : Timesteps to save temperature [s] -- `depth_nodes` : Depths of the calculation nodes for 1-D heat conduction [m], a vector of size `Nz` -- `surface_temperature` : Surface temperature [K], a matrix in size of `(Ns, Nt)`. +- `times_to_save` : Timesteps to save temperature and thermal force on every face [s] +- `depth_nodes` : Depths of the calculation nodes for 1-D heat conduction [m], a vector of size `Nz` +- `surface_temperature` : Surface temperature [K], a matrix in size of `(Ns, Nt)`. - `Ns` : Number of faces - `Nt` : Number of time steps to save surface temperature -- `subsurface_temperature` : Temperature [K] as a function of depth [m] and time [s], `Dict` with face ID as key and a matrix `(Nz, Nt)` as an entry. +- `subsurface_temperature` : Temperature [K] as a function of depth [m] and time [s], `Dict` with face ID as key and a matrix `(Nz, Nt)` as an entry. - `Nz` : The number of the depth nodes - `Nt` : The number of time steps to save temperature +- `face_forces` : Thermal force on every face of the shape model [N], a matrix in size of `(Ns, Nt)`. + - `Ns` : Number of faces + - `Nt` : Number of time steps to save surface temperature """ struct SingleTPMResult times ::Vector{Float64} @@ -253,10 +256,11 @@ struct SingleTPMResult force ::Vector{SVector{3, Float64}} torque ::Vector{SVector{3, Float64}} - times_to_save ::Vector{Float64} - depth_nodes ::Vector{Float64} - surface_temperature ::Matrix{Float64} - subsurface_temperature ::Dict{Int, Matrix{Float64}} + times_to_save ::Vector{Float64} + depth_nodes ::Vector{Float64} + surface_temperature ::Matrix{Float64} + subsurface_temperature ::Dict{Int, Matrix{Float64}} + face_forces ::Matrix{SVector{3, Float64}} end @@ -270,8 +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) - nsteps_to_save = length(times_to_save) + 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 E_in = zeros(nsteps) E_out = zeros(nsteps) @@ -280,10 +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.Nz-1) - surface_temperature = zeros(length(stpm.shape.faces), nsteps_to_save) + surface_temperature = zeros(nfaces, nsteps_to_save) subsurface_temperature = Dict{Int,Matrix{Float64}}( nₛ => zeros(stpm.thermo_params.Nz, nsteps_to_save) for nₛ in face_ID ) + face_forces = zeros(SVector{3, Float64}, nfaces, nsteps_to_save) return SingleTPMResult( ephem.time, @@ -296,6 +302,7 @@ function SingleTPMResult(stpm::SingleTPM, ephem, times_to_save::Vector{Float64}, depth_nodes, surface_temperature, subsurface_temperature, + face_forces, ) end @@ -370,6 +377,8 @@ function update_TPM_result!(result::SingleTPMResult, stpm::SingleTPM, nₜ::Inte for (nₛ, temperature) in result.subsurface_temperature temperature[:, nₜ_save] .= stpm.temperature[:, nₛ] end + + result.face_forces[:, nₜ_save] .= stpm.face_forces end end @@ -400,7 +409,8 @@ The output files are saved in the following directory structure: dirpath ├── physical_quantities.csv ├── subsurface_temperature.csv - └── surface_temperature.csv + ├── surface_temperature.csv + └── thermal_force.csv # Arguments - `dirpath` : Path to the directory to save CSV files. @@ -454,6 +464,23 @@ function export_TPM_results(dirpath, result::SingleTPMResult) df = df[:, ["time", "depth", keys_sorted...]] CSV.write(filepath, df) + + ##= Thermal force on every face of the shape model =## + filepath = joinpath(dirpath, "thermal_force.csv") + + n_faces = 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_faces * n_steps + + df = DataFrame( + time = reshape([t for _ in 1:n_faces, t in result.times_to_save], nrows), + face = reshape([i for i in 1:n_faces, _ in result.times_to_save], nrows), + ) + df.x = reshape([f[1] for f in result.face_forces], nrows) # x-component of the thermal force + df.y = reshape([f[2] for f in result.face_forces], nrows) # y-component of the thermal force + df.z = reshape([f[3] for f in result.face_forces], nrows) # z-component of the thermal force + + CSV.write(filepath, df) end @@ -465,13 +492,15 @@ The output files are saved in the following directory structure: dirpath ├── pri - │   ├── physical_quantities.csv - │   ├── subsurface_temperature.csv - │   └── surface_temperature.csv + │ ├── physical_quantities.csv + │ ├── subsurface_temperature.csv + │ ├── surface_temperature.csv + │ └── thermal_force.csv └── sec ├── physical_quantities.csv ├── subsurface_temperature.csv - └── surface_temperature.csv + ├── surface_temperature.csv + └── thermal_force.csv # Arguments - `dirpath` : Path to the directory to save CSV files. diff --git a/src/facet.jl b/src/facet.jl index 406cb18f..a9679b77 100644 --- a/src/facet.jl +++ b/src/facet.jl @@ -81,15 +81,17 @@ raycast(A::StaticVector{3}, B::StaticVector{3}, C::StaticVector{3}, R::StaticVec """ - find_visiblefacets!(obs::Facet, facets) + find_visiblefacets!(shape::ShapeModel; show_progress=true) Find facets that is visible from the facet where the observer is located. -# Parameters -- `obs` : Facet where the observer stands -- `facets` : Array of `Facet` +# Arguments +- `shape` : Shape model of an asteroid + +# Keyword arguments +- `show_progress` : Switch to show a progress meter """ -function find_visiblefacets!(shape::ShapeModel) +function find_visiblefacets!(shape::ShapeModel; show_progress=true) nodes = shape.nodes faces = shape.faces face_centers = shape.face_centers @@ -97,7 +99,13 @@ function find_visiblefacets!(shape::ShapeModel) face_areas = shape.face_areas visiblefacets = shape.visiblefacets - @showprogress 1 "Searching for visible faces..." for i in eachindex(faces) + ## `ProgressMeter` setting + if show_progress + p = Progress(length(faces); dt=1, desc="Searching for visible faces...", showspeed=true) + ProgressMeter.ijulia_behavior(:clear) + end + + for i in eachindex(faces) cᵢ = face_centers[i] n̂ᵢ = face_normals[i] aᵢ = face_areas[i] @@ -141,6 +149,15 @@ function find_visiblefacets!(shape::ShapeModel) push!(visiblefacets[i], VisibleFacet(j, view_factor(cᵢ, cⱼ, n̂ᵢ, n̂ⱼ, aⱼ)...)) # i -> j push!(visiblefacets[j], VisibleFacet(i, view_factor(cⱼ, cᵢ, n̂ⱼ, n̂ᵢ, aᵢ)...)) # j -> i end + + ## Update the progress meter + if show_progress + showvalues = [ + ("Face ID ", i), + ("Visible faces ", length(visiblefacets[i])), + ] + ProgressMeter.next!(p; showvalues) + end end end diff --git a/src/shape.jl b/src/shape.jl index 26cf1256..0be2b2bb 100644 --- a/src/shape.jl +++ b/src/shape.jl @@ -55,7 +55,20 @@ function Base.show(io::IO, shape::ShapeModel) print(io, msg) end -function load_shape_obj(shapepath; scale = 1.0, find_visible_facets = false) +""" + load_shape_obj(shapepath; scale=1.0, find_visible_facets=false; show_progress=true) + +Load a shape model from a Wavefront OBJ file. + +# Arguments +- `shapepath` : Path to a Wavefront OBJ file + +# Keyword arguments +- `scale` : Scale factor of the shape model +- `find_visible_facets` : Switch to find visible facets +- `show_progress` : Switch to show a progress meter +""" +function load_shape_obj(shapepath; scale=1.0, find_visible_facets=false, show_progress=true) # TODO: use MeshIO.jl nodes, faces = loadobj(shapepath; scale = scale, message = false) @@ -66,7 +79,7 @@ function load_shape_obj(shapepath; scale = 1.0, find_visible_facets = false) visiblefacets = [VisibleFacet[] for _ in faces] shape = ShapeModel(nodes, faces, face_centers, face_normals, face_areas, visiblefacets) - find_visible_facets && find_visiblefacets!(shape) + find_visible_facets && find_visiblefacets!(shape; show_progress) return shape end