Main API
This page covers the basic API used for running simple simulations. Each section will also link to the relevant advanced API that can be used to further customise the behaviour of that part of the simulation. See Advanced API Introduction for more information on how to use the advanced API.
Solver
HSSSimulations.Solver.problemSolver
— FunctionproblemSolver(problem::Problem) -> Tuple{Any, Any}
The main function that is called to solve a simulation and the struct that defines the problem to simulate.
Takes a fully defined problem and solves it, saving the solution to disk and returning the filename where it is saved. See Problem
for how to define the problem.
HSSSimulations.Types.Problem
— MethodProblem(
;
geometry,
matProp,
params,
loadSets,
init,
file,
initLay,
ink,
description,
otherResults,
options
)
Assemble a problem out of its components.
Arguments
geometry::
Geometry
: See Simulation GeometrymatProp::
AbstractMatProp
: See Materialsparams::
AbstractProblemParams
: See Boundary ParametersloadSets::Vector{
AbstractLoadSet
}
: The list of load sets to simulate, in order that the need simulating. See Boundary Loadsink::
Ink
: The locations for ink deposition. This should be the same size as the finial dimension of the simulation. See Ink Structinit::
AbstractResult
: The initial results struct. This should be the same size as the finial dimension of the simulation. See Time Step ResultsinitLay::Int
: The thickness of powder deposited before the simulation starts, given in number of layers thick. This must be greater than zerofile::String
: The file path and name for the output file of the simulationdescription::String=""
: A short description of what is being simulatedotherResults::
AbstractOtherResults
=
OtherResults
()
: The struct to save the final results to. See Other Resultsoptions::
Options
=Options()
: The options to use for the simulation. See Settings
Simulation Geometry
HSSSimulations.Types.Geometry
— MethodGeometry(
simSize, Δx, Δt;
Δy=Δx, Δz=Δx, name="NA", Δh=0,
offset=(0.0, 0.0), buildSize=nothing,
force=false,
)
Constructor for the Geometry
type that is is used to store all of the geometry information (and time step length for some reason) for a rectangular build volume of the machine being simulated (given as the buildSize
). It also saves the information for the subset of the build volume to actually be simulated (of size simSize
, offset form the machine origin by offset
), if the full build volume is not being simulated. If no buildSize
is given then it is assumed to be just big enougth to fit the simSize
with the given offset
.
Δh
is the layer height in meters. If it is given as 0
(or not given) then it is assumed that the simulation isn't representing a full build, but instead something like the preheat or cooldown phase. In this case no layer recoat logic can be run (make sure not to include a recoating Types.Load
).
Δt
is the time step (in seconds) and Δx, Δy and Δz
are the node spacing (in meters). If not given then Δy and Δz
default to the same as Δx
. The timestep is included in the geometry as it is tied to the node spacing when it comes to making a stable simulation for the explicit finite difference method used in this model.
If the force
argement is given then the divisible errors will be suppressed, this will result in the geometry not being properly represented.
Use the force
argument with great caution. It was only added to allow for the creation of geometries that were blocked due to floating point math errors. If it is used when things aren't actually divisible then it will result in the geometry not being properly represented, and a disconnect between what you think you are simulating and what is actually being simulated.
Ink Struct
HSSSimulations.Types.Ink
— Typestruct Ink{T}
Defines the volume of the ink placement within that (and therefore hopefully the part to be made). See Ink Pattern Recipes for some example patterns.
Fields
nodes::Array{T, 3} where T
: The emmisivity of the models nodes, set to eₚ for nodes without ink.name::String
: Just used for future reference of results
This is the emmisivity relative to the lamp. So the emmisivity of the ink over the range of the wavelengths that the lamp outputs, scaled by the relative output power of the lamp at those wavelengths.
Materials
HSSSimulations.Material.PA2200
— FunctionPA2200(
geometry::Geometry
) -> MatProp{Interpolations.Extrapolation{Float64, 2, Interpolations.ScaledInterpolation{Float64, 2, Interpolations.BSplineInterpolation{Float64, 2, Matrix{Float64}, Interpolations.BSpline{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, Interpolations.BSpline{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Tuple{UnitRange{Int64}, UnitRange{Int64}}}, Interpolations.BSpline{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Interpolations.Flat{Nothing}}, _A, _B, _C, _D, _E, _F, typeof(PA_Ċ), Array{Float64, 3}} where {_A, _B, _C, _D, _E, _F}
An example material based on EOS's PA2200, using a rate of consolidation based on melt state. With eyeball correction to consolidation rate. Calling this with the Geometry
struct to be used for the simulation as the only argument will return the relevant MatProp
struct.
The sources of the data used are summarized in PA2200. For more details, check the material model chapter of my thesis.
This is one example material. To see how to define new materials or new material models, see Material Model.
Time Step Results
HSSSimulations.Results.Result
— Typestruct Result{P<:AbstractArray} <: AbstractResult
The results from a single timestep, use directly to create the initial conditions. Also created for each time step during the simulation.
This saves the data from the default material model and the heat transfer solver. For information on how to create a new result struct see Time/Load Step Results.
Fields
T::AbstractArray
: Temperature for each nodeM::AbstractArray
: Melt state for each nodeC::AbstractArray
: Consolidation state for each nodet::Float64
: Time of timestep, since the start of the buildtₚ::Float64
: The progress through the load step (0=start, 0.5=half way, 1=end)
HSSSimulations.Results.Result
— MethodResult(geomSize, Tᵢ, Mᵢ, Cᵢ) -> Result
Create a result with uniform fields
HSSSimulations.Results.Result
— MethodResult(geomSize, Tᵢ, Mᵢ, Cᵢ, t) -> Result
Create a result with uniform fields and a given time.
HSSSimulations.Results.Result
— MethodResult(geomSize, t, tₚ) -> Result
Create an empty result. This is used during the simulation to create the results for each time step.
Other Results
These are results that are saved once at the end of the simulation, for when
The default final results struct shown below saves the maximum melt state from the default material model and nothing else. For information on how to create a new result struct see End of Simulation Results.
HSSSimulations.Types.OtherResults
— Typestruct OtherResults <: AbstractOtherResults
The default struct stores no additional data and only acts as a placeholder. When used, the simulation will store the maximum melt state to Results
folder of the output file and no other final results (time step results are still saved each load step).
Boundary Loads
Two functions are provided below to create loads and load sets. The basicLoad
function creates a single basic load that can be used with FixedLoadSet
and LayerLoadSet
to make load sets for the loadSets
argument of the Problem
. For information on how to make more loads, see Boundary Model.
The HSSLoads
constructor creates a full list of load sets needed to simulate a typical HSS build. For more information on these loads, see HSS Boundary.
HSSSimulations.Boundary.basicLoad
— FunctionbasicLoad(
tₗ,
skip
) -> Load{SymetryBoundary, SymetryBoundary, SymetryBoundary, SymetryBoundary}
A basic Types.Load
with a conduction boundary on the bottom surface and a convection boundary on the top. All other surfaces are symetrical boundaries.
Examples
julia> loadStep = basicLoad(5, 2)
x₁ : SymetryBoundary
x₂ : SymetryBoundary
y₁ : SymetryBoundary
y₂ : SymetryBoundary
z₁ : ConductionBoundary
z₂ : ConvectionBoundary
name : NA
tₗ : 5.0
skip : 2
This returns a single Load
, for a single load step. To make a load set you will need an array of Load
s.
HSSSimulations.Solver.FixedLoadSet
— Typestruct FixedLoadSet <: AbstractLoadSet
#Fields
name::String
: Name of the load setloads::Vector{Load}
: List of loads for load set
HSSSimulations.Solver.LayerLoadSet
— Typestruct LayerLoadSet <: AbstractLoadSet
#Fields
name::String
: Name of the load setfinishLayer::Int64
: The last layer to deposit as part of this load setloads::Vector{Load}
: List of loads for load set
HSSSimulations.HSSBound.HSSLoads
— FunctionHSSLoads(
skip,
geometry;
nrPreheat,
lenPreheat,
nrCool,
lenCool,
sinterSpeed,
lcAndBedWidth
) -> Vector{AbstractLoadSet}
Returns a list of load sets, one for the preheat, build, and cooldown phases of a default build for the HSS example. The same skip
is used for all load steps (See Why We Skip Some Results for more information on skip
).
The loads used in these load sets are explained further below, and all assume the use of the HSSParams
parameter set.
julia> HSSLoads(10, Geometry((1,1,1),1,1); nrPreheat=5, lenPreheat=60.0, nrCool=5, lenCool=60.0, sinterSpeed=0.160)
3-element Vector{AbstractLoadSet}:
name : Preheat
loads
----------------------
Name: Overheads Only
For 5 loads
name : Layer
finishLayer : 0
loads
----------------------
Name: Overheads Only
Name: Sintering
Name: Overheads Only
Name: Recoating
Name: Overheads Only
Name: No Inking
Name: Overheads Only
Name: Inking
name : Cooldown
loads
----------------------
Name: Overheads Off
For 5 loads
Extended help
Here we will cover the details on exactly what it is that the example boundary is replicating. We will cover this one boundary at a time. As the simulation is a cuboid, it has six external surfaces, each of which must have defined boundary conditions.
The simulation is to be compared to an array of identical, symmetrical parts being printed. Assuming that this array is infinite results in a symmetrical boundary condition. This means that the four conditions representing the side walls (x₁, x₂, y₁ and y₂ in the notation used in Types.Load
), can all use the default boundary condition provided by Boundary.SymetryBoundary
.
The bottom boundary (z₁) is where the build bed is in contact with the piston. As the piston is one of the few consistent things on our machine, this can be simulated as a constant temperature boundary with a contact conduction coefficient. This is done using HSSBound.PistonBoundary
.
The final boundary, the top surface of the powder (z₂) is by far the most complicated. It changes constantly throughout the build as new layers are added and sintered.
Overhead Heaters
In the default state with nothing happening the top boundary has heat loss due to convection to the forced air draft over the surface and loss from radiation to the surrounding surfaces. In addition, there is a stationary overhead heater. During preheating (during the FixedLoadSet
named Preheat) the overhead heater is set to a fixed power (Simulated using HSSBound.loadOverheads
).
Once the build starts (during the LayerLoadSet
named Layer), the overhead power is adjusted, starting at a set amount (usually around 60% (of a 300W heater)) and changing by a set amount (usually 1 percent of maximum power) every set number of layers (usually every 3 layers) with the goal of reaching the target temperature of the top surface (Simulated using HSSBound.loadOverheads
). Once the build is finished (during the FixedLoadSet
named Cooldown) the overhead is turned off (set to 0W power) and left to cool down (Simulated using HSSBound.loadCooldown
).
The overhead heater boundary is implemented as a radiation boundary condition, because of this the overhead temperature is needed (not the input power, which is all we defined above). For this, the HSSBound.overheadTempFunc
is used to calculate the overhead temperature based on its previous temperature and the change in temperature caused by its current input power.
Carriages
Most of this change comes from the movement of two carriages, the lamp carriage and the print carriage. The first contains both the powder hopper (for recoating) and the sinter lamp. The second contains the print heads used to deposit the absorptive ink.
During each layer the following happens (described as if looking from the front of the machine, with the x-axis going front to back, and slightly confusingly the y-axis going right to left (don't ask, I regret this choice)):
- The lamp carriage moves from left to right with the lamp set at sinter power (
loadSinterStroke
) - The lamp carriage moves from right to left with the lamp set to recoat power and the powder hopper deposits a layer of powder (
loadRecoatStroke
) - The print carriage moves from right to left whilst doing nothing special (
loadBlankStroke
) - The print carriage moves from left to right as the print heads deposit the ink (
loadInkStroke
)
In between each of the above steps are brief moments of simplicity, where the only boundary conditions are those covered in previous sections. These gaps use the aforementioned HSSBound.loadOverheads
. The carriage boundaries are only actually used when the carriages are over the build bed, it is assumed that if they are moving but not over the bed then they have no impact on the boundary conditions so the HSSBound.loadOverheads
can be used instead.
It is also worth noting, that when a carriage is in over the top of the build bed, the build bed is shadowed from the overheads, which is modeled in each of the four carriage loads.
Boundary Parameters
HSSSimulations.Boundary.BasicProblemParams
— TypeA basic implementation of a Types.AbstractProblemParams
struct to go along with basicLoad
. For a more elaborate example see HSSParams
Fields
condCoef::Float64
: The contact conduction coefficent for the bottom facecondTemp::Any
: The temperature of the surface in contact with the bottom faceconvCoef::Float64
: The convection coefficent for the top face to the air aboveconvTemp::Any
: The temperature of the air in contact with the top face
HSSSimulations.HSSBound.HSSParams
— MethodHSSParams(Geometry; kwargs...) -> HSSParams
The geometry
(of type Geometry
) should be the same one used for the simulation. If the piston target temperature is chaneged then the piston path will need to be changed to curves that will match the target temperature. The same applies if the preheat bed is thicker than the normal ≈3 mm.
This is intended to proved the required parameters for the load sets produced by HSSLoads
.
See HSSParams
for information on the fields of the created struct.
Arguments
name = "HSS example problem boundary"
: A name for the parameter set, only used for user reference.pistonPath = joinpath(artifact"HSS", "HSS_Piston.jld2")
: Where to find the piston heat up and cool down curves data file.airPath = joinpath(artifact"HSS", "HSS_Surface.jld2")
: Where to find the machine's internal surface heat up and cool down curves data file.surfacePath = joinpath(artifact"HSS", "HSS_Air.jld2")
: Where to find the machine's internal air heat up and cool down curves data file.conductionCoef = 7500.0
: The conduction coefficient of the top surface of the bed, in W/m²k.lampVector = [0.0, 1, 2, 2, 2, 2, 2, 1, 0]
: The y-axis distribution of the lamp power, seelampMaker
for more details.lampWidth = 0.100
: The width of the lamps power distribution, in meters.lampOffset = 0.175
: The distance between the left edge of the lamp carriage and th left edge of the lamp's distribution, in meters.carriageWidth = 0.275
: The width of the lamp/recoater carriage, in meters.recoatOffset = 0.045
: The offset between the left edge of the recoater carriage and the left edge of the recoater, in meters.printCarriageWidth = 0.180
: The width of the print head carriage, in meters.printOffset = 0.110
: The offset between the left edge of the print head carriage and the left edge of the print nozzles.surfaceTarget = 160.0
: The target temperature of the top surface of the powder bed. Used to control the overhead heaters, in °C.surfaceTol = 1.0
: The tolerance of the bed surface temperature when compared to the target temperature, in °C.overheadLayerStep = 3
: How often, in number of layers, to update the overhead power based on the bed surface temperature.overheadPercentStep = 1.0
: How big of a step in overhead power to make each time they are updated, in % of total power.overheadTemp = 25.0
: The starting temperature of the overhead heaters themselves, in °C.overheadPower = 0.6 * 300
: The starting input power of the overhead heaters, in watts.overheadPowerOut = T -> (0.596T - 12.2)
: A function that takes the current overhead heater temperature and returns the output power of the heater.overheadHeatCapacity = 118.923
: The heat capacity of the overhead heaters, in J/K.overheadMaxPower = 300.0
: The maximum input power of the overhead heaters.convectionCoef = 4.0
: The convection coefficient of the top surface of the bed, in W/m²k.sinterPower = 2000.0
: The output power of the sinter lamp during the sinter stroke.recoatPower = 0.0
: The output power of the sinter lamp during the recoat stroke.lastUpdatedOverhead = 0
: The last layer the overhead heaters were updated on.percentOverhead = 0.2125
: The percentage of the surfaces that the top surface of the powder bed is exposed to that is the overhead heaters, as opposed to the other internal surfaces of the machine.powderTempDelta = 25
: The difference between the surface temperature of the machine and the temperature of the powder being deposited.overheadHeatupFunc
: A function that takes an input power and a previous overhead heater temperature and returns the temperature for the overhead heaters for the next time step. This usesHSSBound.overheadTempFunc
by default, using the function shown below:
overheadHeatupFunc = function (powerIn::Float64, prevOverheadTemp::Float64, _)
return overheadTempFunc(
powerIn,
overheadPowerOut,
overheadHeatCapacity,
geometry.Δt,
prevOverheadTemp,
)
end
Settings
HSSSimulations.Types.Options
— Typestruct Options
Fields
compress::Union{Bool, TranscodingStreams.Codec}
: How to compress the results file, can be set to true (to compress), false (to leave uncompressed) or to a specific compression algorithm (see the JLD2 documentation for more details)debug::Union{Bool, Vector{String}}
: Whether or not to log debug information, can accept a list of strings to select only some debugging groups, seepackage_groups
showProgress::Union{Bool, Float64}
: Whether or not to show the progres meter, if a number is given that is used as the update intervalnotify::Bool
: If true, simulations finishing will send a system notification using Alert.jl
The debug
option is passed to the logGroups option of Solver.makeLogger
, check that out for more information and package_groups
for what log groups are available by default.
Depending on settings, the debug option might log a lot of things, the log file could end up somewhere in the region of 4x the size of the compressed results file, so make sure you clean them up after you're done.
HSSSimulations.Types.package_groups
— ConstantA list of all of the log groups used in this package. They log the following things:
core
: the start of a problem, loadstep, load or timestep has startedsolver
: the fdm solvermat
: material modelbound
: boundary conditionb_adv
: recoating and moving object boundarieshss
: HSS example functions
Results File Structure
The last part of the API to cover is the format of the simulation results that are saved. These use the JLD2
package to save to a Hierarchical Data Format version 5 (HDF5) based format. Most of the information stored should be readable by any HDF5 compatible software or libraries, except the input problem, which requires the software to understand Julia types (the easiest way is to just use Julia, it's mostly saved in case it needs to be rerun).
By default, this is stored compressed using the ZlibCompressor
compressor.
The results have the following structure:
Tree Description
Results
│
├─ Description - An overview of the problem that has been solved
├─ Input - The full problem struct that has been solved
├─ Start_Time - The computer's clock time at the end of the simulation
├── Results
│ ├─ MeltMax - The maximum melt state reached in the simulation
│ │
│ ├── Preheat-1 - List of loads run during the preheating load set
│ │ ├─ 1
│ │ ├─ 2
│ │ └─ ⋯ (3 more entries)
│ │
│ ├── Layer-2 - 1st Layer
│ │ ├─ 1 - 1st Layer's 1st Load
│ │ ├─ 2 - 1st Layer's 2nd Load
│ │ └─ ⋯ (6 more entries) - And so on for the remaining loads
│ │
│ ├── Layer-3 - 2nd Layer
│ │ ├─ 1 (5 entries)
│ │ ├─ 2
│ │ └─ ⋯ (6 more entries)
│ │
│ ├─ ⋯ (25 more entries) - And so on for the remaining layers
│ │
│ └── Cooldown-28 - List of loads run during the cooldown load set
│ ├─ 1
│ ├─ 2
│ └─ ⋯ (3 more entries)
│
├─ Results_Index - A list of all load results indices within this file
├─ Finish_Time - The computer's clock time at the end of the simulation
└─ _types - Ignore (Used internally by JLD2)
Where each of the loads has these fields:
Tree Description
Load
├─ name - The load's name
├─ time - 1D array of the times of the load's time steps
├─ T - 4D array of temperatures (X, Y, Z, time step)
├─ M - 4D array of melt states (X, Y, Z, time step)
└─ C - 4D array of consolidation states (X, Y, Z, time step)
Results.loadStepSaver
, Solver.otherResults
, are the two functions used for saving simulation results to the file. so looking at their implementation might help with if anything is not covered here. Solver.startMetadata
and Solver.finishMetadata
are also used to save a few extra bits of metadata to the file. I'd also recommend a tool like HDFView to get an idea for the structure of the results.