feat: added DynamicSimulation.jl

Added dynamic simulation for use with distributed systems and incremental simulations
Added module based exports
Added test for distributed systems

[skip ci]
This commit is contained in:
nebmit 2023-12-04 08:23:11 +01:00
parent e7f1e3eb23
commit 7331e45eea
No known key found for this signature in database
10 changed files with 387 additions and 82 deletions

View File

@ -0,0 +1,175 @@
using BenchmarkTools
using CSV
using DataFrames
using Distributed
using Statistics
include("../tug/TUG.jl")
using .TUG
# 1. Environment Setup
function setup_environment(num_procs::Int)
if num_procs > 0
added_procs = addprocs(num_procs)
# Use remotecall to include TUG on each new worker
for proc in added_procs
remotecall_wait(include, proc, "../tug/TUG.jl")
remotecall_wait(eval, proc, :(using .TUG))
end
end
end
# 2. Test Case Definitions
function testBTCS100()::Tuple{Grid,Boundary}
rows::Int = 1024
cols::Int = 1000
alphaX = fill(1.25, rows, cols)
alphaY = fill(1.1, rows, cols)
alphaX[1:100, :] .= 0.5
alphaX[101:200, :] .= 0.8
alphaY[:, 1:200] .= 0.6
alphaY[:, 201:400] .= 0.9
grid::Grid = Grid{Float64}(rows, cols, alphaX, alphaY)
concentrations = fill(0.5, rows, cols)
concentrations[11, 11] = 15000
concentrations[1015, 991] = 7500
concentrations[11, 991] = 7500
concentrations[1015, 11] = 7500
setConcentrations!(grid, concentrations)
bc::Boundary = Boundary(grid)
setBoundarySideClosed!(bc, LEFT)
setBoundarySideClosed!(bc, RIGHT)
setBoundarySideClosed!(bc, TOP)
setBoundarySideClosed!(bc, BOTTOM)
return grid, bc
end
function testBTCS200()::Tuple{Grid,Boundary}
rows::Int = 2027
cols::Int = 1999
alphaX = [sin(i / 100) * cos(j / 100) for i in 1:rows, j in 1:cols]
alphaY = [cos(i / 100) * sin(j / 100) for i in 1:rows, j in 1:cols]
grid::Grid = Grid{Float64}(rows, cols, alphaX, alphaY)
concentrations = [i * j / 1e2 for i in 1:rows, j in 1:cols]
concentrations[11, 11] = 15000
concentrations[2021, 1995] = 7500
concentrations[11, 1995] = 7500
concentrations[2021, 11] = 7500
setConcentrations!(grid, concentrations)
bc::Boundary = Boundary(grid)
setBoundarySideClosed!(bc, LEFT)
setBoundarySideConstant!(bc, RIGHT, 1.5)
setBoundarySideClosed!(bc, TOP)
setBoundarySideConstant!(bc, BOTTOM, 0.75)
return grid, bc
end
function testFTCS500()::Tuple{Grid,Boundary}
rows::Int = 2000
cols::Int = 2000
alphaX = [sin(i / 100) * cos(j / 100) + 1 for i in 1:rows, j in 1:cols]
alphaY = [cos(i / 100) * sin(j / 100) + 1 for i in 1:rows, j in 1:cols]
grid::Grid = Grid{Float64}(rows, cols, alphaX, alphaY)
concentrations = [(i * j) / 1e6 for i in 1:rows, j in 1:cols]
concentrations[1001, 1001] = 2000
setConcentrations!(grid, concentrations)
bc::Boundary = Boundary(grid)
setBoundarySideClosed!(bc, LEFT)
setBoundarySideClosed!(bc, RIGHT)
setBoundarySideClosed!(bc, TOP)
setBoundarySideConstant!(bc, BOTTOM, 0.75)
return grid, bc
end
# 3. Simulation Runners
function run_static_simulation(grid::Grid, bc::Boundary, method, steps::Int, dt::Float64)
simulation = Simulation(grid, bc, method, steps, dt, CONSOLE_OUTPUT_OFF, CSV_OUTPUT_OFF)
TUG.run(simulation)
end
function run_dynamic_simulation(grid::Grid, bc::Boundary, method, steps::Int, dt::Float64, num_procs::Int)
setup_environment(num_procs)
simulation = DynamicSimulation(grid, bc, method, dt)
createGrid(simulation)
for _ in 1:steps
next(simulation)
end
if num_procs > 0
rmprocs(workers())
end
end
# 4. Performance Metrics
function measure_performance(test_case_function, method, steps::Int, dt::Float64, num_procs::Int, dynamic::Bool=false)
grid, bc = test_case_function()
if dynamic
simulation_run = @benchmark run_dynamic_simulation($grid, $bc, $method, $steps, $dt, $num_procs)
else
simulation_run = @benchmark run_static_simulation($grid, $bc, $method, $steps, $dt)
end
time_measurement = mean(simulation_run).time / 1e9
memory_measurement = mean(simulation_run).memory / 1e6
return (time=time_measurement, memory=memory_measurement)
end
# 5. Benchmarking and Comparison
function benchmark_test_case(test_case_function, method, steps::Int, dt::Float64, is_distributed::Bool, num_procs::Int)
performance_metrics = measure_performance(test_case_function, method, steps, dt, num_procs, is_distributed)
return performance_metrics
end
# 6. Reporting
function report_results(results)
for result in results
println("Test Case: $(result.test_case)$(result.is_distributed ? ", Procs: $(result.num_procs)" : ""), Time: $(result.time) s, Memory: $(result.memory) MB")
end
end
# 7. Cleanup
function cleanup()
for filename in readdir()
if occursin(".csv", filename)
rm(filename, force=true)
end
end
end
# Main Function
function main()
test_cases = [(testBTCS100, BTCS, 100, 0.01), (testBTCS200, BTCS, 200, 0.005), (testFTCS500, FTCS, 500, 0.005)]
configurations = [(false, 0), (true, 0), (true, 1), (true, 2)] # Non-distributed, distributed with 1 and 2 additional procs
results = []
for (test_case, method, steps, dt) in test_cases
for (is_distributed, num_procs) in configurations
performance_metrics = benchmark_test_case(test_case, method, steps, dt, is_distributed, num_procs)
push!(results, (test_case=test_case, method=method, is_distributed=is_distributed, num_procs=num_procs, time=performance_metrics.time, memory=performance_metrics.memory))
end
end
cleanup()
report_results(results)
end
main()

View File

@ -44,7 +44,7 @@ def compare_matrices(matrix_a_tuple, matrix_b_tuple, tolerance=0.01):
return True, "Matrices are equal.", None, None, max_diff
def compare_csv_files(file_path1, file_path2, tolerance=0.01):
def compare_csv_files(file_path1, file_path2, tolerance=0):
matrices1 = read_matrices_from_csv(file_path1)
matrices2 = read_matrices_from_csv(file_path2)
max_difference = 0
@ -66,7 +66,7 @@ def main():
parser = argparse.ArgumentParser(description='Compare two CSV files containing matrices.')
parser.add_argument('file_a', help='Path to File A')
parser.add_argument('file_b', help='Path to File B')
parser.add_argument('--tolerance', type=float, default=0.01, help='Tolerance for comparison (default: 0.01)')
parser.add_argument('--tolerance', type=float, default=0, help='Tolerance for comparison (default: 0)')
parser.add_argument('--silent', action='store_true', help='Run in silent mode without printing details')
args = parser.parse_args()

View File

@ -0,0 +1,67 @@
using Printf
include("Boundary.jl")
include("Grid.jl")
@enum APPROACH BTCS FTCS
@enum CONSOLE_OUTPUT CONSOLE_OUTPUT_OFF CONSOLE_OUTPUT_ON CONSOLE_OUTPUT_VERBOSE
@enum CSV_OUTPUT CSV_OUTPUT_OFF CSV_OUTPUT_ON CSV_OUTPUT_VERBOSE CSV_OUTPUT_XTREME
abstract type AbstractSimulation{T} end
function createCSVfile(simulation::AbstractSimulation{T}, grid::Union{Grid{T},Nothing}=nothing)::IOStream where {T}
grid = grid === nothing ? simulation.grid : grid
approachString = string(simulation.approach)
rows = getRows(grid)
cols = getCols(grid)
numIterations = simulation.iterations
filename = string(approachString, "_", rows, "_", cols, "_", numIterations, ".csv")
appendIdent = 0
while isfile(filename)
appendIdent += 1
filename = string(approachString, "_", rows, "_", cols, "_", numIterations, "-", appendIdent, ".csv")
end
# Write boundary conditions if required
if simulation.csvOutput >= CSV_OUTPUT_XTREME
open(filename, "w") do file
writeBoundarySideValuesToCSV(file, simulation.bc, LEFT)
writeBoundarySideValuesToCSV(file, simulation.bc, RIGHT)
if getDim(grid) == 2
writeBoundarySideValuesToCSV(file, simulation.bc, TOP)
writeBoundarySideValuesToCSV(file, simulation.bc, BOTTOM)
end
write(file, "\n\n")
end
end
file = open(filename, "a")
return file
end
function writeBoundarySideValuesToCSV(file::IOStream, bc::Boundary{T}, side) where {T}
values::Vector{BoundaryElement} = getBoundarySide(bc, side)
formatted_values = join(map(getValue, values), " ")
write(file, formatted_values, "\n")
end
function writeConcentrationsToCLI(simulation::AbstractSimulation{T}, grid::Union{Grid{T},Nothing}=nothing) where {T}
grid = grid === nothing ? simulation.grid : grid
println(getConcentrations(grid))
end
function writeConcentrationsToCSV(file::IOStream, simulation::AbstractSimulation{T}, grid::Union{Grid{T},Nothing}=nothing) where {T}
grid = grid === nothing ? simulation.grid : grid
concentrations = getConcentrations(grid)
for row in eachrow(concentrations)
formatted_row = [Printf.@sprintf("%.6g", x) for x in row] # Format each element like is done in the C++ version using Eigen3
println(file, join(formatted_row, " "))
end
println(file)
println(file)
end

View File

@ -3,10 +3,11 @@
# condition at the edges of the diffusion grid.
# Translated from C++'s Boundary.hpp.
include("Grid.jl")
@enum TYPE CLOSED CONSTANT
@enum SIDE LEFT = 1 RIGHT = 2 TOP = 3 BOTTOM = 4
# BoundaryElement class
struct BoundaryElement{T}
type::TYPE

View File

@ -4,9 +4,9 @@
# alternating-direction implicit (ADI) method.
# Translated from C++'s BTCS.hpp.
using Base.Threads
using LinearAlgebra
using SparseArrays
using Base.Threads
include("../Boundary.jl")
include("../Grid.jl")

View File

@ -3,6 +3,10 @@
# solution of diffusion equation in 1D and 2D space.
# Translated from C++'s FTCS.hpp.
using Base.Threads
using LinearAlgebra
using SparseArrays
include("../Boundary.jl")
include("../Grid.jl")
include("Utils.jl")

View File

@ -0,0 +1,69 @@
using Distributed
include("AbstractSimulation.jl")
include("Boundary.jl")
include("Core/BTCS.jl")
include("Core/FTCS.jl")
include("Grid.jl")
struct DynamicSimulation{T} <: AbstractSimulation{T}
grid::Grid{T}
grids::Vector{Grid{T}}
bc::Boundary{T}
approach::APPROACH
iterations::Int
timestep::T
# Constructor
function DynamicSimulation(grid::Grid{T}, bc::Boundary{T}, approach::APPROACH, timestep::T) where {T}
timestep, iterations = adjustTimestep(grid, approach, timestep, 1)
new{T}(grid, Vector{Grid{T}}(), bc, approach, iterations, timestep)
end
end
function createGrid(simulation::DynamicSimulation{T})::Int where {T}
new_grid = clone(simulation.grid)
push!(simulation.grids, new_grid)
return length(simulation.grids)
end
function next(simulation::DynamicSimulation{T}) where {T}
pmap(grid -> runSimulationForGrid(simulation, grid), simulation.grids)
end
function printConcentrations(simulation::DynamicSimulation{T}, gridIndex::Int) where {T}
writeConcentrationsToCLI(simulation, simulation.grids[gridIndex])
end
function printConcentrationsCSV(simulation::DynamicSimulation{T}, gridIndex::Int) where {T}
file = createCSVfile(simulation, simulation.grids[gridIndex])
writeConcentrationsToCSV(file, simulation, simulation.grids[gridIndex])
close(file)
end
function runSimulationForGrid(simulation::DynamicSimulation{T}, grid::Grid{T}) where {T}
if simulation.approach == BTCS
runBTCS(grid, simulation.bc, simulation.timestep, simulation.iterations, () -> nothing)
elseif simulation.approach == FTCS
runFTCS(grid, simulation.bc, simulation.timestep, simulation.iterations, () -> nothing)
else
error("Undefined approach!")
end
end
function getConcentrations(simulation::DynamicSimulation{T}, gridIndex::Int)::Matrix{T} where {T}
getConcentrations(simulation.grids[gridIndex])
end
function setConcentrations!(simulation::DynamicSimulation{T}, gridIndex::Int, concentrations::Matrix{T}) where {T}
setConcentrations!(simulation.grids[gridIndex], concentrations)
end
function setAlphaX!(simulation::DynamicSimulation{T}, gridIndex::Int, alphaX::Matrix{T}) where {T}
setAlphaX!(simulation.grids[gridIndex], alphaX)
end
function setAlphaY!(simulation::DynamicSimulation{T}, gridIndex::Int, alphaY::Matrix{T}) where {T}
setAlphaY!(simulation.grids[gridIndex], alphaY)
end

View File

@ -5,7 +5,7 @@
using LinearAlgebra
# Grid class
# Grid class
struct Grid{T}
cols::Int
rows::Int
@ -15,10 +15,10 @@ struct Grid{T}
deltaCol::T
deltaRow::T
concentrations::Ref{Matrix{T}}
alphaX::Matrix{T}
alphaY::Union{Matrix{T},Nothing}
alphaX_t::Union{Matrix{T},Nothing}
alphaY_t::Union{Matrix{T},Nothing}
alphaX::Ref{Matrix{T}}
alphaY::Union{Ref{Matrix{T}},Nothing}
alphaX_t::Union{Ref{Matrix{T}},Nothing}
alphaY_t::Union{Ref{Matrix{T}},Nothing}
# Constructor for 1D-Grid
function Grid{T}(length::Int, alpha::Matrix{T}) where {T}
@ -47,22 +47,30 @@ struct Grid{T}
new{T}(cols, rows, 2, T(cols), T(rows), T(1), T(1), Ref(fill(T(0), rows, cols)), alphaX, alphaY, alphaX_t, alphaY_t)
end
function Grid{T}(rows::Int, cols::Int, dim::Int, domainCol::T, domainRow::T, deltaCol::T, deltaRow::T, concentrations::Ref{Matrix{T}}, alphaX::Ref{Matrix{T}}, alphaY::Union{Ref{Matrix{T}},Nothing}, alphaX_t::Union{Ref{Matrix{T}},Nothing}, alphaY_t::Union{Ref{Matrix{T}},Nothing}) where {T}
new{T}(cols, rows, dim, domainCol, domainRow, deltaCol, deltaRow, concentrations, alphaX, alphaY, alphaX_t, alphaY_t)
end
end
function clone(grid::Grid{T})::Grid{T} where {T}
Grid{T}(grid.rows, grid.cols, grid.dim, grid.domainCol, grid.domainRow, grid.deltaCol, grid.deltaRow, Ref(copy(grid.concentrations[])), Ref(copy(grid.alphaX[])), Ref(copy(grid.alphaY[])), Ref(copy(grid.alphaX_t[])), Ref(copy(grid.alphaY_t[])))
end
function getAlphaX(grid::Grid{T})::Matrix{T} where {T}
grid.alphaX
grid.alphaX[]
end
function getAlphaY(grid::Grid{T})::Matrix{T} where {T}
grid.alphaY
grid.alphaY[]
end
function getAlphaX_t(grid::Grid{T})::Matrix{T} where {T}
grid.alphaX_t
grid.alphaX_t[]
end
function getAlphaY_t(grid::Grid{T})::Matrix{T} where {T}
grid.alphaY_t
grid.alphaY_t[]
end
function getCols(grid::Grid{T})::Int where {T}
@ -89,6 +97,16 @@ function getRows(grid::Grid{T})::Int where {T}
grid.rows
end
function setAlphaX!(grid::Grid{T}, new_alphaX::Matrix{T}) where {T}
grid.alphaX[] = new_alphaX
grid.alphaX_t[] = new_alphaX'
end
function setAlphaY!(grid::Grid{T}, new_alphaY::Matrix{T}) where {T}
grid.alphaY[] = new_alphaY
grid.alphaY_t[] = new_alphaY'
end
function setConcentrations!(grid::Grid{T}, new_concentrations::Matrix{T}) where {T}
grid.concentrations[] = new_concentrations
end

View File

@ -4,19 +4,14 @@
# options. Simulation object also holds a predefined Grid and Boundary object.
# Translated from C++'s Simulation.hpp.
using Printf
include("Grid.jl")
include("AbstractSimulation.jl")
include("Boundary.jl")
include("Core/BTCS.jl")
include("Core/FTCS.jl")
@enum APPROACH BTCS FTCS
@enum CONSOLE_OUTPUT CONSOLE_OUTPUT_OFF CONSOLE_OUTPUT_ON CONSOLE_OUTPUT_VERBOSE
@enum CSV_OUTPUT CSV_OUTPUT_OFF CSV_OUTPUT_ON CSV_OUTPUT_VERBOSE CSV_OUTPUT_XTREME
include("Grid.jl")
# Simulation class
struct Simulation{T}
struct Simulation{T} <: AbstractSimulation{T}
grid::Grid{T}
bc::Boundary{T}
approach::APPROACH
@ -34,7 +29,7 @@ struct Simulation{T}
end
end
function _adjustTimestep(grid::Grid{T}, approach::APPROACH, timestep::T, iterations::Int)::Tuple{T,Int} where {T}
function adjustTimestep(grid::Grid{T}, approach::APPROACH, timestep::T, iterations::Int)::Tuple{T,Int} where {T}
if approach == FTCS
if getDim(grid) == 1
deltaSquare = getDeltaCol(grid)
@ -64,91 +59,37 @@ function _adjustTimestep(grid::Grid{T}, approach::APPROACH, timestep::T, iterati
return timestep, iterations
end
function _createCSVfile(simulation::Simulation{T})::IOStream where {T}
approachString = string(simulation.approach)
rows = getRows(simulation.grid)
cols = getCols(simulation.grid)
numIterations = simulation.iterations
filename = string(approachString, "_", rows, "_", cols, "_", numIterations, ".csv")
appendIdent = 0
while isfile(filename)
appendIdent += 1
filename = string(approachString, "_", rows, "_", cols, "_", numIterations, "-", appendIdent, ".csv")
end
# Write boundary conditions if required
if simulation.csvOutput >= CSV_OUTPUT_XTREME
open(filename, "w") do file
_writeBoundarySideValues(file, simulation.bc, LEFT)
_writeBoundarySideValues(file, simulation.bc, RIGHT)
if getDim(simulation.grid) == 2
_writeBoundarySideValues(file, simulation.bc, TOP)
_writeBoundarySideValues(file, simulation.bc, BOTTOM)
end
write(file, "\n\n")
end
end
file = open(filename, "a")
return file
end
function _writeBoundarySideValues(file::IOStream, bc::Boundary{T}, side) where {T}
values::Vector{BoundaryElement} = getBoundarySide(bc, side)
formatted_values = join(map(getValue, values), " ")
write(file, formatted_values, "\n")
end
function _printConcentrationsCSV(file::IOStream, simulation::Simulation{T}) where {T}
concentrations = getConcentrations(simulation.grid)
for row in eachrow(concentrations)
formatted_row = [Printf.@sprintf("%.6g", x) for x in row] # Format each element like is done in the C++ version using Eigen3
println(file, join(formatted_row, " "))
end
println(file)
println(file)
end
function _printConcentrations(simulation::Simulation{T}) where {T}
println(getConcentrations(simulation.grid))
end
function run(simulation::Simulation{T}) where {T}
file = nothing
try
if simulation.csvOutput > CSV_OUTPUT_OFF
file = _createCSVfile(simulation)
file = createCSVfile(simulation)
end
function simulationStepCallback()
if simulation.consoleOutput >= CONSOLE_OUTPUT_VERBOSE
_printConcentrations(simulation)
writeConcentrationsToCLI(simulation)
end
if simulation.csvOutput >= CSV_OUTPUT_VERBOSE
_printConcentrationsCSV(file, simulation)
writeConcentrationsToCSV(file, simulation)
end
end
if simulation.approach == BTCS
runBTCS(simulation.grid, simulation.bc, simulation.timestep, simulation.iterations, simulationStepCallback)
elseif simulation.approach == FTCS
timestep, iterations = _adjustTimestep(simulation.grid, simulation.approach, simulation.timestep, simulation.iterations)
timestep, iterations = adjustTimestep(simulation.grid, simulation.approach, simulation.timestep, simulation.iterations)
runFTCS(simulation.grid, simulation.bc, timestep, iterations, simulationStepCallback)
else
error("Undefined approach!")
end
if simulation.consoleOutput >= CONSOLE_OUTPUT_ON
_printConcentrations(simulation)
writeConcentrationsToCLI(simulation)
end
if simulation.csvOutput >= CSV_OUTPUT_ON
_printConcentrationsCSV(file, simulation)
writeConcentrationsToCSV(file, simulation)
end
finally

30
julia/tug/TUG.jl Normal file
View File

@ -0,0 +1,30 @@
module TUG
include("AbstractSimulation.jl")
export AbstractSimulation, APPROACH, CONSOLE_OUTPUT, CSV_OUTPUT, BTCS, FTCS, CONSOLE_OUTPUT_OFF, CONSOLE_OUTPUT_ON, CONSOLE_OUTPUT_VERBOSE, CSV_OUTPUT_OFF, CSV_OUTPUT_ON, CSV_OUTPUT_VERBOSE, CSV_OUTPUT_XTREME
include("Simulation.jl")
export Simulation
export run, setTimestep, setIterations, setOutputConsole, setOutputCSV
include("DynamicSimulation.jl")
export DynamicSimulation
export createGrid, getConcentrations, setConcentrations!, setAlphaX!, setAlphaY!, next, printConcentrationsCSV, printConcentrations
include("Boundary.jl")
export Boundary, BoundaryElement, TYPE, SIDE, BC_TYPE_CLOSED, BC_TYPE_CONSTANT, LEFT, RIGHT, TOP, BOTTOM
export getType, getValue, setValue!, setBoundarySideClosed!, setBoundarySideConstant!, getBoundarySide, getBoundarySideClosed, getBoundarySideConstant
include("Grid.jl")
export Grid
export getAlphaX, getAlphaY, getConcentrations, setConcentrations!, setAlphaX!, setAlphaY!, getDomainCol, getDomainRow, getDeltaCol, getDeltaRow, getDim
include("Core/BTCS.jl")
include("Core/FTCS.jl")
end