refactor!: structural changes
Improved julia structs and removed redundant calculations [skip ci]
This commit is contained in:
parent
d6df09ca5f
commit
957f73bb83
@ -27,7 +27,7 @@ int main(int argc, char *argv[])
|
|||||||
// **** SIMULATION ****
|
// **** SIMULATION ****
|
||||||
Simulation simulation = Simulation(grid, bc);
|
Simulation simulation = Simulation(grid, bc);
|
||||||
simulation.setTimestep(1.23);
|
simulation.setTimestep(1.23);
|
||||||
simulation.setIterations(750);
|
simulation.setIterations(75000);
|
||||||
simulation.setOutputCSV(CSV_OUTPUT_VERBOSE);
|
simulation.setOutputCSV(CSV_OUTPUT_VERBOSE);
|
||||||
simulation.setOutputConsole(CONSOLE_OUTPUT_OFF);
|
simulation.setOutputConsole(CONSOLE_OUTPUT_OFF);
|
||||||
|
|
||||||
@ -4,17 +4,16 @@ function main()
|
|||||||
# **** GRID ****
|
# **** GRID ****
|
||||||
rows::Int = 100
|
rows::Int = 100
|
||||||
cols::Int = 100
|
cols::Int = 100
|
||||||
grid::Grid = Grid{Float64}(rows, cols)
|
|
||||||
|
alphaX = fill(1.0, rows, cols)
|
||||||
|
alphaY = fill(1.0, rows, cols)
|
||||||
|
grid::Grid = Grid{Float64}(rows, cols, alphaX, alphaY)
|
||||||
|
|
||||||
concentrations = fill(0.0, rows, cols)
|
concentrations = fill(0.0, rows, cols)
|
||||||
concentrations[11, 11] = 2000
|
concentrations[11, 11] = 2000
|
||||||
concentrations[91, 91] = 2000
|
concentrations[91, 91] = 2000
|
||||||
setConcentrations!(grid, concentrations)
|
setConcentrations!(grid, concentrations)
|
||||||
|
|
||||||
alphaX = fill(1.0, rows, cols)
|
|
||||||
alphaY = fill(1.0, rows, cols)
|
|
||||||
setAlpha!(grid, alphaX, alphaY)
|
|
||||||
|
|
||||||
# **** BOUNDARY ****
|
# **** BOUNDARY ****
|
||||||
bc::Boundary = Boundary(grid)
|
bc::Boundary = Boundary(grid)
|
||||||
setBoundarySideConstant!(bc, LEFT, 1.0)
|
setBoundarySideConstant!(bc, LEFT, 1.0)
|
||||||
|
|||||||
@ -3,15 +3,14 @@ include("../../tug/Simulation.jl")
|
|||||||
function main()
|
function main()
|
||||||
# **** GRID ****
|
# **** GRID ****
|
||||||
cells::Int = 20
|
cells::Int = 20
|
||||||
grid::Grid = Grid{Float64}(cells)
|
|
||||||
|
alpha = fill(1.0, 1, cells)
|
||||||
|
grid::Grid = Grid{Float64}(cells, alpha)
|
||||||
|
|
||||||
concentrations = fill(0.0, 1, cells)
|
concentrations = fill(0.0, 1, cells)
|
||||||
concentrations[1] = 2000
|
concentrations[1] = 2000
|
||||||
setConcentrations!(grid, concentrations)
|
setConcentrations!(grid, concentrations)
|
||||||
|
|
||||||
alpha = fill(1.0, 1, cells)
|
|
||||||
setAlpha!(grid, alpha)
|
|
||||||
|
|
||||||
# **** BOUNDARY ****
|
# **** BOUNDARY ****
|
||||||
bc::Boundary = Boundary(grid)
|
bc::Boundary = Boundary(grid)
|
||||||
setBoundarySideConstant!(bc, LEFT, 0.0)
|
setBoundarySideConstant!(bc, LEFT, 0.0)
|
||||||
|
|||||||
@ -3,16 +3,15 @@ include("../../tug/Simulation.jl")
|
|||||||
function main()
|
function main()
|
||||||
# **** GRID ****
|
# **** GRID ****
|
||||||
cells::Int = 45
|
cells::Int = 45
|
||||||
grid::Grid = Grid{Float64}(cells)
|
|
||||||
|
|
||||||
concentrations = fill(10.0, 1, cells)
|
|
||||||
concentrations[6] = 2000
|
|
||||||
setConcentrations!(grid, concentrations)
|
|
||||||
|
|
||||||
alpha = fill(1.0, 1, cells)
|
alpha = fill(1.0, 1, cells)
|
||||||
alpha[1:15] .= 0.5
|
alpha[1:15] .= 0.5
|
||||||
alpha[31:45] .= 1.5
|
alpha[31:45] .= 1.5
|
||||||
setAlpha!(grid, alpha)
|
grid::Grid = Grid{Float64}(cells, alpha)
|
||||||
|
|
||||||
|
concentrations = fill(10.0, 1, cells)
|
||||||
|
concentrations[6] = 2000
|
||||||
|
setConcentrations!(grid, concentrations)
|
||||||
|
|
||||||
# **** BOUNDARY ****
|
# **** BOUNDARY ****
|
||||||
bc::Boundary = Boundary(grid)
|
bc::Boundary = Boundary(grid)
|
||||||
@ -22,7 +21,7 @@ function main()
|
|||||||
# **** SIMULATION ****
|
# **** SIMULATION ****
|
||||||
simulation::Simulation = Simulation(grid, bc)
|
simulation::Simulation = Simulation(grid, bc)
|
||||||
simulation = setTimestep(simulation, 1.23)
|
simulation = setTimestep(simulation, 1.23)
|
||||||
simulation = setIterations(simulation, 750)
|
simulation = setIterations(simulation, 75000)
|
||||||
simulation = setOutputConsole(simulation, CONSOLE_OUTPUT_OFF)
|
simulation = setOutputConsole(simulation, CONSOLE_OUTPUT_OFF)
|
||||||
simulation = setOutputCSV(simulation, CSV_OUPUT_VERBOSE)
|
simulation = setOutputCSV(simulation, CSV_OUPUT_VERBOSE)
|
||||||
|
|
||||||
@ -4,16 +4,15 @@ function main()
|
|||||||
# **** GRID ****
|
# **** GRID ****
|
||||||
rows::Int = 20
|
rows::Int = 20
|
||||||
cols::Int = 20
|
cols::Int = 20
|
||||||
grid::Grid = Grid{Float64}(rows, cols)
|
|
||||||
|
alphaX = fill(1.0, rows, cols)
|
||||||
|
alphaY = fill(1.0, rows, cols)
|
||||||
|
grid::Grid = Grid{Float64}(rows, cols, alphaX, alphaY)
|
||||||
|
|
||||||
concentrations = fill(0.0, rows, cols)
|
concentrations = fill(0.0, rows, cols)
|
||||||
concentrations[11, 11] = 2000
|
concentrations[11, 11] = 2000
|
||||||
setConcentrations!(grid, concentrations)
|
setConcentrations!(grid, concentrations)
|
||||||
|
|
||||||
alphaX = fill(1.0, rows, cols)
|
|
||||||
alphaY = fill(1.0, rows, cols)
|
|
||||||
setAlpha!(grid, alphaX, alphaY)
|
|
||||||
|
|
||||||
# **** BOUNDARY ****
|
# **** BOUNDARY ****
|
||||||
bc::Boundary = Boundary(grid)
|
bc::Boundary = Boundary(grid)
|
||||||
setBoundarySideClosed!(bc, LEFT)
|
setBoundarySideClosed!(bc, LEFT)
|
||||||
|
|||||||
@ -4,7 +4,14 @@ function main()
|
|||||||
# **** GRID ****
|
# **** GRID ****
|
||||||
rows::Int = 450
|
rows::Int = 450
|
||||||
cols::Int = 670
|
cols::Int = 670
|
||||||
grid::Grid = Grid{Float64}(rows, cols)
|
|
||||||
|
alphaX = fill(1.0, rows, cols)
|
||||||
|
alphaY = fill(1.0, 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.0, rows, cols)
|
concentrations = fill(0.0, rows, cols)
|
||||||
concentrations[11, 11] = 1500
|
concentrations[11, 11] = 1500
|
||||||
@ -14,14 +21,6 @@ function main()
|
|||||||
concentrations[221, 336] = 1500
|
concentrations[221, 336] = 1500
|
||||||
setConcentrations!(grid, concentrations)
|
setConcentrations!(grid, concentrations)
|
||||||
|
|
||||||
alphaX = fill(1.0, rows, cols)
|
|
||||||
alphaY = fill(1.0, rows, cols)
|
|
||||||
alphaX[1:100, :] .= 0.5
|
|
||||||
alphaX[101:200, :] .= 0.8
|
|
||||||
alphaY[:, 1:200] .= 0.6
|
|
||||||
alphaY[:, 201:400] .= 0.9
|
|
||||||
setAlpha!(grid, alphaX, alphaY)
|
|
||||||
|
|
||||||
# **** BOUNDARY ****
|
# **** BOUNDARY ****
|
||||||
bc::Boundary = Boundary(grid)
|
bc::Boundary = Boundary(grid)
|
||||||
setBoundarySideClosed!(bc, LEFT)
|
setBoundarySideClosed!(bc, LEFT)
|
||||||
|
|||||||
@ -28,7 +28,14 @@ def get_max_name_length(directory):
|
|||||||
return max_length
|
return max_length
|
||||||
|
|
||||||
def format_difference(diff):
|
def format_difference(diff):
|
||||||
return '{:.5f}'.format(diff).rjust(8) if diff != 0 else '0'.rjust(8)
|
threshold = 1e-5
|
||||||
|
if diff != 0:
|
||||||
|
if abs(diff) < threshold:
|
||||||
|
return '{:.2e}'.format(diff).rjust(9) # Scientific notation for small values
|
||||||
|
else:
|
||||||
|
return '{:.5f}'.format(diff).rjust(9) # Fixed-point notation for larger values
|
||||||
|
else:
|
||||||
|
return '0'.rjust(9)
|
||||||
|
|
||||||
def run_benchmark(command, runs):
|
def run_benchmark(command, runs):
|
||||||
total_time = 0
|
total_time = 0
|
||||||
@ -38,11 +45,11 @@ def run_benchmark(command, runs):
|
|||||||
total_time += time.time() - start_time
|
total_time += time.time() - start_time
|
||||||
return total_time / runs
|
return total_time / runs
|
||||||
|
|
||||||
def main(tolerance, runs, silent):
|
def main(tolerance, runs, silent, no_clean):
|
||||||
BENCHMARK_DIR = "./cpp_bench"
|
BENCHMARK_DIR = "./cpp_bench"
|
||||||
JULIA_DIR = "./julia_bench"
|
JULIA_DIR = "./julia_bench"
|
||||||
COMPILER = "g++"
|
COMPILER = "g++"
|
||||||
CFLAGS = ["-O3", "-fopenmp", "-I", "/usr/local/include", "-I", "../../include/", "-I", "/usr/include/eigen3"]
|
CFLAGS = ["-O3", "-fopenmp", "-I", "../../include/", "-I", "/usr/include/eigen3"]
|
||||||
BIN_DIR = "./cpp_bin_temp"
|
BIN_DIR = "./cpp_bin_temp"
|
||||||
OUTPUT_DIR = "./csv_temp"
|
OUTPUT_DIR = "./csv_temp"
|
||||||
|
|
||||||
@ -104,12 +111,13 @@ def main(tolerance, runs, silent):
|
|||||||
|
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
remove_non_empty_dir(BIN_DIR)
|
if not no_clean:
|
||||||
remove_non_empty_dir(OUTPUT_DIR)
|
remove_non_empty_dir(BIN_DIR)
|
||||||
|
remove_non_empty_dir(OUTPUT_DIR)
|
||||||
for file in os.listdir('.'):
|
|
||||||
if file.endswith('.csv'):
|
for file in os.listdir('.'):
|
||||||
os.remove(file)
|
if file.endswith('.csv'):
|
||||||
|
os.remove(file)
|
||||||
|
|
||||||
# Print results
|
# Print results
|
||||||
if not silent: print("\n----- Benchmark Results -----")
|
if not silent: print("\n----- Benchmark Results -----")
|
||||||
@ -124,8 +132,9 @@ def main(tolerance, runs, silent):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description='Benchmark and Compare Script')
|
parser = argparse.ArgumentParser(description='Benchmark and Compare Script')
|
||||||
parser.add_argument('--tolerance', type=float, default=0.005, help='Tolerance for CSV comparison')
|
parser.add_argument('--tolerance', type=float, default=0, help='Tolerance for CSV comparison')
|
||||||
parser.add_argument('--runs', type=int, default=1, help='Number of benchmark runs')
|
parser.add_argument('--runs', type=int, default=1, help='Number of benchmark runs')
|
||||||
parser.add_argument('--silent', action='store_true', help='Run in silent mode without printing details')
|
parser.add_argument('--silent', action='store_true', help='Run in silent mode without printing details')
|
||||||
|
parser.add_argument('--no-clean', action='store_true', help='Do not clean up temporary files')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
main(args.tolerance, args.runs, args.silent)
|
main(args.tolerance, args.runs, args.silent, args.no_clean)
|
||||||
|
|||||||
@ -9,26 +9,21 @@ using SparseArrays
|
|||||||
include("../Boundary.jl")
|
include("../Boundary.jl")
|
||||||
include("../Grid.jl")
|
include("../Grid.jl")
|
||||||
|
|
||||||
# Helper functions and types
|
function calcAlphaIntercell(alpha1::T, alpha2::T) where {T}
|
||||||
function calcAlphaIntercell(alpha1::T, alpha2::T, useHarmonic::Bool=true) where {T}
|
return 2 / ((1 / alpha1) + (1 / alpha2))
|
||||||
if useHarmonic
|
|
||||||
return 2 / ((1 / alpha1) + (1 / alpha2))
|
|
||||||
else
|
|
||||||
return 0.5 * (alpha1 + alpha2)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# calculates coefficient for boundary in constant case
|
|
||||||
function calcBoundaryCoeffConstant(alpha_center::T, alpha_side::T, sx::T) where {T}
|
function calcBoundaryCoeffConstant(alpha_center::T, alpha_side::T, sx::T) where {T}
|
||||||
centerCoeff = 1 + sx * (calcAlphaIntercell(alpha_center, alpha_side) + 2 * alpha_center)
|
alpha = calcAlphaIntercell(alpha_center, alpha_side)
|
||||||
sideCoeff = -sx * calcAlphaIntercell(alpha_center, alpha_side)
|
centerCoeff = 1 + sx * (alpha + 2 * alpha_center)
|
||||||
|
sideCoeff = -sx * alpha
|
||||||
return (centerCoeff, sideCoeff)
|
return (centerCoeff, sideCoeff)
|
||||||
end
|
end
|
||||||
|
|
||||||
# calculates coefficient for boundary in closed case
|
|
||||||
function calcBoundaryCoeffClosed(alpha_center::T, alpha_side::T, sx::T) where {T}
|
function calcBoundaryCoeffClosed(alpha_center::T, alpha_side::T, sx::T) where {T}
|
||||||
centerCoeff = 1 + sx * calcAlphaIntercell(alpha_center, alpha_side)
|
alpha = calcAlphaIntercell(alpha_center, alpha_side)
|
||||||
sideCoeff = -sx * calcAlphaIntercell(alpha_center, alpha_side)
|
centerCoeff = 1 + sx * alpha
|
||||||
|
sideCoeff = -sx * alpha
|
||||||
return (centerCoeff, sideCoeff)
|
return (centerCoeff, sideCoeff)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -51,10 +46,13 @@ function createCoeffMatrix(alpha::Matrix{T}, bcLeft::Vector{BoundaryElement{T}},
|
|||||||
end
|
end
|
||||||
|
|
||||||
# inner columns
|
# inner columns
|
||||||
for i in 2:(numCols-1)
|
@inbounds for i in 2:(numCols-1)
|
||||||
cm[i, i-1] = -sx * calcAlphaIntercell(alpha[rowIndex, i-1], alpha[rowIndex, i])
|
alpha_left_here = calcAlphaIntercell(alpha[rowIndex, i-1], alpha[rowIndex, i])
|
||||||
cm[i, i] = 1 + sx * (calcAlphaIntercell(alpha[rowIndex, i], alpha[rowIndex, i+1]) + calcAlphaIntercell(alpha[rowIndex, i-1], alpha[rowIndex, i]))
|
alpha_here_right = alpha[rowIndex, i-1] == alpha[rowIndex, i+1] ? alpha_left_here : calcAlphaIntercell(alpha[rowIndex, i], alpha[rowIndex, i+1]) # calcAlphaIntercell is symmetric, so we can use it for both directions
|
||||||
cm[i, i+1] = -sx * calcAlphaIntercell(alpha[rowIndex, i], alpha[rowIndex, i+1])
|
|
||||||
|
cm[i, i-1] = -sx * alpha_left_here
|
||||||
|
cm[i, i] = 1 + sx * (alpha_here_right + alpha_left_here)
|
||||||
|
cm[i, i+1] = -sx * alpha_here_right
|
||||||
end
|
end
|
||||||
|
|
||||||
# right column
|
# right column
|
||||||
@ -74,37 +72,38 @@ function createCoeffMatrix(alpha::Matrix{T}, bcLeft::Vector{BoundaryElement{T}},
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function calcExplicitConcentrationsBoundaryClosed(conc_center::T, alpha_center::T, alpha_neighbor::T, sy::T) where {T}
|
function calcExplicitConcentrationsBoundaryClosed(conc_center::T, alpha_center::T, alpha_neighbor::T, sy::T) where {T}
|
||||||
sy * calcAlphaIntercell(alpha_center, alpha_neighbor) * conc_center +
|
alpha = calcAlphaIntercell(alpha_center, alpha_neighbor)
|
||||||
(1 - sy * calcAlphaIntercell(alpha_center, alpha_neighbor)) * conc_center
|
sy * alpha * conc_center + (1 - sy * alpha) * conc_center
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function calcExplicitConcentrationsBoundaryConstant(conc_center::T, conc_bc::T, alpha_center::T, alpha_neighbor::T, sy::T) where {T}
|
function calcExplicitConcentrationsBoundaryConstant(conc_center::T, conc_bc::T, alpha_center::T, alpha_neighbor::T, sy::T) where {T}
|
||||||
sy * calcAlphaIntercell(alpha_center, alpha_neighbor) * conc_center +
|
alpha_center_neighbor = calcAlphaIntercell(alpha_center, alpha_neighbor)
|
||||||
(1 - sy * (calcAlphaIntercell(alpha_center, alpha_center) + 2 * alpha_center)) * conc_center +
|
alpha_center_center = alpha_center == alpha_neighbor ? alpha_center_neighbor : calcAlphaIntercell(alpha_center, alpha_center)
|
||||||
|
sy * alpha_center_neighbor * conc_center +
|
||||||
|
(1 - sy * (alpha_center_center + 2 * alpha_center)) * conc_center +
|
||||||
sy * alpha_center * conc_bc
|
sy * alpha_center * conc_bc
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function createSolutionVector(concentrations::Matrix{T}, alphaX::Matrix{T}, alphaY::Matrix{T}, bcLeft::Vector{BoundaryElement{T}}, bcRight::Vector{BoundaryElement{T}}, bcTop::Vector{BoundaryElement{T}}, bcBottom::Vector{BoundaryElement{T}}, length::Int, rowIndex::Int, sx::T, sy::T) where {T}
|
function createSolutionVector(concentrations::Matrix{T}, alphaX::Matrix{T}, alphaY::Matrix{T}, bcLeft::Vector{BoundaryElement{T}}, bcRight::Vector{BoundaryElement{T}}, bcTop::Vector{BoundaryElement{T}}, bcBottom::Vector{BoundaryElement{T}}, length::Int, rowIndex::Int, sx::T, sy::T) where {T}
|
||||||
numRows = size(concentrations, 1)
|
numRows = size(concentrations, 1)
|
||||||
sv = Vector{T}(undef, length)
|
sv = Vector{T}(undef, length)
|
||||||
|
|
||||||
# Inner rows
|
# Inner rows
|
||||||
if rowIndex > 1 && rowIndex < numRows
|
if rowIndex > 1 && rowIndex < numRows
|
||||||
for i = 1:length
|
@inbounds for i = 1:length
|
||||||
sv[i] = sy * calcAlphaIntercell(alphaY[rowIndex, i], alphaY[rowIndex+1, i]) * concentrations[rowIndex+1, i] +
|
alpha_here_below = calcAlphaIntercell(alphaY[rowIndex, i], alphaY[rowIndex+1, i])
|
||||||
(1 - sy * (calcAlphaIntercell(alphaY[rowIndex, i], alphaY[rowIndex+1, i]) + calcAlphaIntercell(alphaY[rowIndex-1, i], alphaY[rowIndex, i]))) * concentrations[rowIndex, i] +
|
alpha_here_above = alphaY[rowIndex+1, i] == alphaY[rowIndex-1, i] ? alpha_here_below : calcAlphaIntercell(alphaY[rowIndex-1, i], alphaY[rowIndex, i]) # calcAlphaIntercell is symmetric, so we can use it for both directions
|
||||||
sy * calcAlphaIntercell(alphaY[rowIndex-1, i], alphaY[rowIndex, i]) * concentrations[rowIndex-1, i]
|
sv[i] = sy * alpha_here_below * concentrations[rowIndex+1, i] +
|
||||||
|
(1 - sy * (alpha_here_below + alpha_here_above)) * concentrations[rowIndex, i] +
|
||||||
|
sy * alpha_here_above * concentrations[rowIndex-1, i]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# First row
|
# First row
|
||||||
if rowIndex == 1
|
if rowIndex == 1
|
||||||
for i = 1:length
|
@inbounds for i = 1:length
|
||||||
if getType(bcTop[i]) == CONSTANT
|
if getType(bcTop[i]) == CONSTANT
|
||||||
sv[i] = calcExplicitConcentrationsBoundaryConstant(concentrations[rowIndex, i], getValue(bcTop[i]), alphaY[rowIndex, i], alphaY[rowIndex+1, i], sy)
|
sv[i] = calcExplicitConcentrationsBoundaryConstant(concentrations[rowIndex, i], getValue(bcTop[i]), alphaY[rowIndex, i], alphaY[rowIndex+1, i], sy)
|
||||||
elseif getType(bcTop[i]) == CLOSED
|
elseif getType(bcTop[i]) == CLOSED
|
||||||
@ -117,7 +116,7 @@ function createSolutionVector(concentrations::Matrix{T}, alphaX::Matrix{T}, alph
|
|||||||
|
|
||||||
# Last row
|
# Last row
|
||||||
if rowIndex == numRows
|
if rowIndex == numRows
|
||||||
for i = 1:length
|
@inbounds for i = 1:length
|
||||||
if getType(bcBottom[i]) == CONSTANT
|
if getType(bcBottom[i]) == CONSTANT
|
||||||
sv[i] = calcExplicitConcentrationsBoundaryConstant(concentrations[rowIndex, i], getValue(bcBottom[i]), alphaY[rowIndex, i], alphaY[rowIndex-1, i], sy)
|
sv[i] = calcExplicitConcentrationsBoundaryConstant(concentrations[rowIndex, i], getValue(bcBottom[i]), alphaY[rowIndex, i], alphaY[rowIndex-1, i], sy)
|
||||||
elseif getType(bcBottom[i]) == CLOSED
|
elseif getType(bcBottom[i]) == CLOSED
|
||||||
@ -141,7 +140,6 @@ function createSolutionVector(concentrations::Matrix{T}, alphaX::Matrix{T}, alph
|
|||||||
return sv
|
return sv
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# solver for linear equation system; A corresponds to coefficient matrix, b to the solution vector
|
# solver for linear equation system; A corresponds to coefficient matrix, b to the solution vector
|
||||||
function LinearAlgebraAlgorithm(A::SparseMatrixCSC{T}, b::Vector{T}) where {T}
|
function LinearAlgebraAlgorithm(A::SparseMatrixCSC{T}, b::Vector{T}) where {T}
|
||||||
return A \ b
|
return A \ b
|
||||||
@ -154,7 +152,7 @@ function BTCS_1D(grid::Grid{T}, bc::Boundary{T}, timestep::T, solverFunc::Functi
|
|||||||
|
|
||||||
b = Vector{T}(undef, length)
|
b = Vector{T}(undef, length)
|
||||||
|
|
||||||
alpha = grid.alphaX[]
|
alpha = getAlphaX(grid)
|
||||||
|
|
||||||
bcLeft = getBoundarySide(bc, LEFT)
|
bcLeft = getBoundarySide(bc, LEFT)
|
||||||
bcRight = getBoundarySide(bc, RIGHT)
|
bcRight = getBoundarySide(bc, RIGHT)
|
||||||
@ -162,9 +160,10 @@ function BTCS_1D(grid::Grid{T}, bc::Boundary{T}, timestep::T, solverFunc::Functi
|
|||||||
concentrations = grid.concentrations[]
|
concentrations = grid.concentrations[]
|
||||||
rowIndex = 1
|
rowIndex = 1
|
||||||
A = createCoeffMatrix(alpha, bcLeft, bcRight, length, rowIndex, sx)
|
A = createCoeffMatrix(alpha, bcLeft, bcRight, length, rowIndex, sx)
|
||||||
for i in 1:length
|
@inbounds for i in 1:length
|
||||||
b[i] = concentrations[1, i]
|
b[i] = concentrations[1, i]
|
||||||
end
|
end
|
||||||
|
|
||||||
if getType(getBoundarySide(bc, LEFT)[1]) == CONSTANT
|
if getType(getBoundarySide(bc, LEFT)[1]) == CONSTANT
|
||||||
b[1] += 2 * sx * alpha[1, 1] * bcLeft[1].value
|
b[1] += 2 * sx * alpha[1, 1] * bcLeft[1].value
|
||||||
end
|
end
|
||||||
@ -174,7 +173,7 @@ function BTCS_1D(grid::Grid{T}, bc::Boundary{T}, timestep::T, solverFunc::Functi
|
|||||||
|
|
||||||
concentrations_t1 = solverFunc(A, b)
|
concentrations_t1 = solverFunc(A, b)
|
||||||
|
|
||||||
for j in 1:length
|
@inbounds for j in 1:length
|
||||||
concentrations[1, j] = concentrations_t1[j]
|
concentrations[1, j] = concentrations_t1[j]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -188,12 +187,11 @@ function BTCS_2D(grid::Grid{T}, bc::Boundary{T}, timestep::T, solverFunc::Functi
|
|||||||
sx = timestep / (2 * grid.deltaCol * grid.deltaCol)
|
sx = timestep / (2 * grid.deltaCol * grid.deltaCol)
|
||||||
sy = timestep / (2 * grid.deltaRow * grid.deltaRow)
|
sy = timestep / (2 * grid.deltaRow * grid.deltaRow)
|
||||||
|
|
||||||
A = spzeros(T, rowMax, rowMax)
|
|
||||||
concentrations_t1 = zeros(T, rowMax, colMax)
|
concentrations_t1 = zeros(T, rowMax, colMax)
|
||||||
row_t1 = Vector{T}(undef, colMax)
|
row_t1 = Vector{T}(undef, colMax)
|
||||||
|
|
||||||
alphaX = grid.alphaX[]
|
alphaX = getAlphaX(grid)
|
||||||
alphaY = grid.alphaY[]
|
alphaY = getAlphaY(grid)
|
||||||
|
|
||||||
bcLeft = getBoundarySide(bc, LEFT)
|
bcLeft = getBoundarySide(bc, LEFT)
|
||||||
bcRight = getBoundarySide(bc, RIGHT)
|
bcRight = getBoundarySide(bc, RIGHT)
|
||||||
@ -202,7 +200,7 @@ function BTCS_2D(grid::Grid{T}, bc::Boundary{T}, timestep::T, solverFunc::Functi
|
|||||||
|
|
||||||
concentrations = grid.concentrations[]
|
concentrations = grid.concentrations[]
|
||||||
|
|
||||||
for i = 1:rowMax
|
@inbounds for i = 1:rowMax
|
||||||
A = createCoeffMatrix(alphaX, bcLeft, bcRight, colMax, i, sx)
|
A = createCoeffMatrix(alphaX, bcLeft, bcRight, colMax, i, sx)
|
||||||
b = createSolutionVector(concentrations, alphaX, alphaY, bcLeft, bcRight, bcTop, bcBottom, colMax, i, sx, sy)
|
b = createSolutionVector(concentrations, alphaX, alphaY, bcLeft, bcRight, bcTop, bcBottom, colMax, i, sx, sy)
|
||||||
|
|
||||||
@ -213,10 +211,10 @@ function BTCS_2D(grid::Grid{T}, bc::Boundary{T}, timestep::T, solverFunc::Functi
|
|||||||
|
|
||||||
concentrations_t1 = copy(transpose(concentrations_t1))
|
concentrations_t1 = copy(transpose(concentrations_t1))
|
||||||
concentrations = copy(transpose(concentrations))
|
concentrations = copy(transpose(concentrations))
|
||||||
alphaX = copy(transpose(alphaX))
|
alphaX = getAlphaX_t(grid)
|
||||||
alphaY = copy(transpose(alphaY))
|
alphaY = getAlphaY_t(grid)
|
||||||
|
|
||||||
for i = 1:colMax
|
@inbounds for i = 1:colMax
|
||||||
# Swap alphas, boundary conditions and sx/sy for column-wise calculation
|
# Swap alphas, boundary conditions and sx/sy for column-wise calculation
|
||||||
A = createCoeffMatrix(alphaY, bcTop, bcBottom, rowMax, i, sy)
|
A = createCoeffMatrix(alphaY, bcTop, bcBottom, rowMax, i, sy)
|
||||||
b = createSolutionVector(concentrations_t1, alphaY, alphaX, bcTop, bcBottom, bcLeft, bcRight, rowMax, i, sy, sx)
|
b = createSolutionVector(concentrations_t1, alphaY, alphaX, bcTop, bcBottom, bcLeft, bcRight, rowMax, i, sy, sx)
|
||||||
@ -231,9 +229,7 @@ function BTCS_2D(grid::Grid{T}, bc::Boundary{T}, timestep::T, solverFunc::Functi
|
|||||||
setConcentrations!(grid, concentrations)
|
setConcentrations!(grid, concentrations)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function BTCS_step(grid::Grid{T}, bc::Boundary{T}, timestep::T, numThreads::Int=1) where {T}
|
||||||
# Entry point for EigenLU solver; differentiate between 1D and 2D grid
|
|
||||||
function BTCS_LU(grid::Grid{T}, bc::Boundary{T}, timestep::T, numThreads::Int=1) where {T}
|
|
||||||
if grid.dim == 1
|
if grid.dim == 1
|
||||||
BTCS_1D(grid, bc, timestep, LinearAlgebraAlgorithm)
|
BTCS_1D(grid, bc, timestep, LinearAlgebraAlgorithm)
|
||||||
elseif grid.dim == 2
|
elseif grid.dim == 2
|
||||||
|
|||||||
@ -9,25 +9,36 @@ struct Grid{T}
|
|||||||
deltaCol::T
|
deltaCol::T
|
||||||
deltaRow::T
|
deltaRow::T
|
||||||
concentrations::Ref{Matrix{T}}
|
concentrations::Ref{Matrix{T}}
|
||||||
alphaX::Ref{Matrix{T}}
|
alphaX::Matrix{T}
|
||||||
alphaY::Ref{Matrix{T}}
|
alphaY::Union{Matrix{T},Nothing}
|
||||||
|
alphaX_t::Union{Matrix{T},Nothing}
|
||||||
|
alphaY_t::Union{Matrix{T},Nothing}
|
||||||
|
|
||||||
# Constructor for 1D-Grid
|
# Constructor for 1D-Grid
|
||||||
function Grid{T}(length::Int) where {T}
|
function Grid{T}(length::Int, alpha::Matrix{T}) where {T}
|
||||||
if length <= 3
|
if length <= 3
|
||||||
throw(ArgumentError("Given grid length too small. Must be greater than 3."))
|
throw(ArgumentError("Given grid length too small. Must be greater than 3."))
|
||||||
end
|
end
|
||||||
|
if size(alpha, 1) != 1 || size(alpha, 2) != length
|
||||||
|
error("Given matrix of alpha coefficients mismatch with Grid dimensions!")
|
||||||
|
end
|
||||||
|
|
||||||
new{T}(length, 1, 1, T(length), 0, T(1), 0, Ref(fill(T(0), 1, length)), Ref(fill(T(0), 1, length)), Ref(fill(T(0), 1, length)))
|
new{T}(length, 1, 1, T(length), 0, T(1), 0, Ref(fill(T(0), 1, length)), alpha, nothing, nothing, nothing)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Constructor for 2D-Grid
|
# Constructor for 2D-Grid
|
||||||
function Grid{T}(row::Int, col::Int) where {T}
|
function Grid{T}(rows::Int, cols::Int, alphaX::Matrix{T}, alphaY::Matrix{T}) where {T}
|
||||||
if row <= 3 || col <= 3
|
if rows <= 3 || cols <= 3
|
||||||
throw(ArgumentError("Given grid dimensions too small. Must each be greater than 3."))
|
throw(ArgumentError("Given grid dimensions too small. Must each be greater than 3."))
|
||||||
end
|
end
|
||||||
|
if size(alphaX) != (rows, cols) || size(alphaY) != (rows, cols)
|
||||||
|
error("Given matrices of alpha coefficients mismatch with Grid dimensions!")
|
||||||
|
end
|
||||||
|
|
||||||
new{T}(col, row, 2, T(col), T(row), T(1), T(1), Ref(fill(T(0), row, col)), Ref(fill(T(0), row, col)), Ref(fill(T(0), row, col)))
|
alphaX_t = alphaX'
|
||||||
|
alphaY_t = alphaY'
|
||||||
|
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -35,25 +46,18 @@ function setConcentrations!(grid::Grid{T}, new_concentrations::Matrix{T}) where
|
|||||||
grid.concentrations[] = new_concentrations
|
grid.concentrations[] = new_concentrations
|
||||||
end
|
end
|
||||||
|
|
||||||
function setAlpha!(grid::Grid{T}, alpha::Matrix{T}) where {T}
|
function getAlphaX(grid::Grid{T})::Matrix{T} where {T}
|
||||||
if grid.dim != 1
|
grid.alphaX
|
||||||
error("Grid is not one dimensional, you should probably use the 2D setter function!")
|
|
||||||
end
|
|
||||||
if size(alpha, 1) != 1 || size(alpha, 2) != grid.cols
|
|
||||||
error("Given matrix of alpha coefficients mismatch with Grid dimensions!")
|
|
||||||
end
|
|
||||||
|
|
||||||
grid.alphaX[] = alpha
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function setAlpha!(grid::Grid{T}, alphaX::Matrix{T}, alphaY::Matrix{T}) where {T}
|
function getAlphaY(grid::Grid{T})::Matrix{T} where {T}
|
||||||
if grid.dim != 2
|
grid.alphaY
|
||||||
error("Grid is not two dimensional, you should probably use the 1D setter function!")
|
end
|
||||||
end
|
|
||||||
if size(alphaX) != (grid.rows, grid.cols) || size(alphaY) != (grid.rows, grid.cols)
|
function getAlphaX_t(grid::Grid{T})::Matrix{T} where {T}
|
||||||
error("Given matrices of alpha coefficients mismatch with Grid dimensions!")
|
grid.alphaX_t
|
||||||
end
|
end
|
||||||
|
|
||||||
grid.alphaX[] = alphaX
|
function getAlphaY_t(grid::Grid{T})::Matrix{T} where {T}
|
||||||
grid.alphaY[] = alphaY
|
grid.alphaY_t
|
||||||
end
|
end
|
||||||
|
|||||||
@ -16,7 +16,6 @@ struct Simulation{T,approach,solver}
|
|||||||
approach::APPROACH
|
approach::APPROACH
|
||||||
solver::SOLVER
|
solver::SOLVER
|
||||||
|
|
||||||
innerIterations::Int
|
|
||||||
iterations::Int
|
iterations::Int
|
||||||
numThreads::Int
|
numThreads::Int
|
||||||
timestep::T
|
timestep::T
|
||||||
@ -25,16 +24,14 @@ struct Simulation{T,approach,solver}
|
|||||||
csvOutput::CSV_OUPUT
|
csvOutput::CSV_OUPUT
|
||||||
|
|
||||||
function Simulation(grid::Grid{T}, bc::Boundary{T}, approach::APPROACH=BTCS,
|
function Simulation(grid::Grid{T}, bc::Boundary{T}, approach::APPROACH=BTCS,
|
||||||
solver::SOLVER=EIGEN_LU_SOLVER, innerIterations::Int=1, iterations::Int=1,
|
solver::SOLVER=EIGEN_LU_SOLVER, iterations::Int=1, numThreads::Int=1, timestep::T=0.1,
|
||||||
numThreads::Int=1, timestep::T=0.1,
|
|
||||||
consoleOutput::CONSOLE_OUTPUT=CONSOLE_OUTPUT_OFF, csvOutput::CSV_OUPUT=CSV_OUPUT_OFF) where {T}
|
consoleOutput::CONSOLE_OUTPUT=CONSOLE_OUTPUT_OFF, csvOutput::CSV_OUPUT=CSV_OUPUT_OFF) where {T}
|
||||||
new{T,APPROACH,SOLVER}(grid, bc, approach, solver, innerIterations, iterations, numThreads, timestep, consoleOutput, csvOutput)
|
new{T,APPROACH,SOLVER}(grid, bc, approach, solver, iterations, numThreads, timestep, consoleOutput, csvOutput)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function createCSVfile(simulation::Simulation{T,approach,solver})::IOStream where {T,approach,solver}
|
||||||
function createCSVfile(simulation::Simulation{T,approach,solver}) where {T,approach,solver}
|
|
||||||
appendIdent = 0
|
appendIdent = 0
|
||||||
approachString = (simulation.approach == BTCS) ? "BTCS" : "UNKNOWN" # Add other approaches as needed
|
approachString = (simulation.approach == BTCS) ? "BTCS" : "UNKNOWN" # Add other approaches as needed
|
||||||
row = simulation.grid.rows
|
row = simulation.grid.rows
|
||||||
@ -47,9 +44,9 @@ function createCSVfile(simulation::Simulation{T,approach,solver}) where {T,appro
|
|||||||
filename = string(approachString, "_", row, "_", col, "_", numIterations, "-", appendIdent, ".csv")
|
filename = string(approachString, "_", row, "_", col, "_", numIterations, "-", appendIdent, ".csv")
|
||||||
end
|
end
|
||||||
|
|
||||||
open(filename, "w") do file
|
# Write boundary conditions if required
|
||||||
# Write boundary conditions if required
|
if simulation.csvOutput == CSV_OUPUT_XTREME
|
||||||
if simulation.csvOutput == CSV_OUPUT_XTREME
|
open(filename, "w") do file
|
||||||
writeBoundarySideValues(file, simulation.bc, LEFT)
|
writeBoundarySideValues(file, simulation.bc, LEFT)
|
||||||
writeBoundarySideValues(file, simulation.bc, RIGHT)
|
writeBoundarySideValues(file, simulation.bc, RIGHT)
|
||||||
|
|
||||||
@ -62,7 +59,8 @@ function createCSVfile(simulation::Simulation{T,approach,solver}) where {T,appro
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return filename
|
file = open(filename, "a")
|
||||||
|
return file
|
||||||
end
|
end
|
||||||
|
|
||||||
function writeBoundarySideValues(file, bc::Boundary{T}, side) where {T}
|
function writeBoundarySideValues(file, bc::Boundary{T}, side) where {T}
|
||||||
@ -71,17 +69,15 @@ function writeBoundarySideValues(file, bc::Boundary{T}, side) where {T}
|
|||||||
write(file, formatted_values, "\n")
|
write(file, formatted_values, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function printConcentrationsCSV(simulation::Simulation{T,approach,solver}, file::IOStream) where {T,approach,solver}
|
||||||
function printConcentrationsCSV(simulation::Simulation{T,approach,solver}, filename::String) where {T,approach,solver}
|
|
||||||
concentrations = simulation.grid.concentrations[]
|
concentrations = simulation.grid.concentrations[]
|
||||||
|
|
||||||
open(filename, "a") do file # Open file in append mode
|
for row in eachrow(concentrations)
|
||||||
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(row, " "))
|
println(file, join(formatted_row, " "))
|
||||||
end
|
|
||||||
println(file) # Add extra newlines for separation
|
|
||||||
println(file)
|
|
||||||
end
|
end
|
||||||
|
println(file) # Add extra newlines for separation
|
||||||
|
println(file)
|
||||||
end
|
end
|
||||||
|
|
||||||
function printConcentrations(simulation::Simulation{T,approach,solver}) where {T,approach,solver}
|
function printConcentrations(simulation::Simulation{T,approach,solver}) where {T,approach,solver}
|
||||||
@ -89,52 +85,59 @@ function printConcentrations(simulation::Simulation{T,approach,solver}) where {T
|
|||||||
end
|
end
|
||||||
|
|
||||||
function run(simulation::Simulation{T,approach,solver}) where {T,approach,solver}
|
function run(simulation::Simulation{T,approach,solver}) where {T,approach,solver}
|
||||||
filename::String = ""
|
file = nothing
|
||||||
if simulation.csvOutput > CSV_OUPUT_OFF
|
try
|
||||||
filename = createCSVfile(simulation)
|
if simulation.csvOutput > CSV_OUPUT_OFF
|
||||||
end
|
file = createCSVfile(simulation)
|
||||||
|
end
|
||||||
|
|
||||||
if simulation.approach == BTCS
|
if simulation.approach == BTCS
|
||||||
if simulation.solver == EIGEN_LU_SOLVER
|
if simulation.solver == EIGEN_LU_SOLVER
|
||||||
for i in 1:(simulation.iterations*simulation.innerIterations)
|
for _ in 1:(simulation.iterations)
|
||||||
if simulation.consoleOutput == CONSOLE_OUTPUT_VERBOSE
|
if simulation.consoleOutput >= CONSOLE_OUTPUT_VERBOSE
|
||||||
printConcentrations(simulation)
|
printConcentrations(simulation)
|
||||||
|
end
|
||||||
|
|
||||||
|
if simulation.csvOutput >= CSV_OUPUT_VERBOSE
|
||||||
|
printConcentrationsCSV(simulation, file)
|
||||||
|
end
|
||||||
|
|
||||||
|
BTCS_step(simulation.grid, simulation.bc, simulation.timestep, simulation.numThreads)
|
||||||
end
|
end
|
||||||
|
else
|
||||||
if simulation.csvOutput >= CSV_OUPUT_VERBOSE
|
error("Undefined solver!")
|
||||||
printConcentrationsCSV(simulation, filename)
|
|
||||||
end
|
|
||||||
|
|
||||||
BTCS_LU(simulation.grid, simulation.bc, simulation.timestep, simulation.numThreads)
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
error("Undefined solver!")
|
error("Undefined approach!")
|
||||||
end
|
end
|
||||||
else
|
|
||||||
error("Undefined approach!")
|
|
||||||
end
|
|
||||||
|
|
||||||
if simulation.consoleOutput == CONSOLE_OUTPUT_ON || simulation.consoleOutput == CONSOLE_OUTPUT_VERBOSE
|
if simulation.consoleOutput == CONSOLE_OUTPUT_ON || simulation.consoleOutput == CONSOLE_OUTPUT_VERBOSE
|
||||||
printConcentrations(simulation)
|
printConcentrations(simulation)
|
||||||
end
|
end
|
||||||
|
|
||||||
if simulation.csvOutput == CSV_OUPUT_ON || simulation.csvOutput == CSV_OUPUT_VERBOSE || simulation.csvOutput == CSV_OUPUT_XTREME
|
if simulation.csvOutput == CSV_OUPUT_ON || simulation.csvOutput == CSV_OUPUT_VERBOSE || simulation.csvOutput == CSV_OUPUT_XTREME
|
||||||
printConcentrationsCSV(simulation, filename)
|
printConcentrationsCSV(simulation, file)
|
||||||
|
end
|
||||||
|
|
||||||
|
finally
|
||||||
|
if file !== nothing
|
||||||
|
close(file)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function setTimestep(simulation::Simulation{T,approach,solver}, timestep::T) where {T,approach,solver}
|
function setTimestep(simulation::Simulation{T,approach,solver}, timestep::T) where {T,approach,solver}
|
||||||
return Simulation(simulation.grid, simulation.bc, simulation.approach, simulation.solver, simulation.innerIterations, simulation.iterations, simulation.numThreads, timestep, simulation.consoleOutput, simulation.csvOutput)
|
return Simulation(simulation.grid, simulation.bc, simulation.approach, simulation.solver, simulation.iterations, simulation.numThreads, timestep, simulation.consoleOutput, simulation.csvOutput)
|
||||||
end
|
end
|
||||||
|
|
||||||
function setIterations(simulation::Simulation{T,approach,solver}, iterations::Int) where {T,approach,solver}
|
function setIterations(simulation::Simulation{T,approach,solver}, iterations::Int) where {T,approach,solver}
|
||||||
return Simulation(simulation.grid, simulation.bc, simulation.approach, simulation.solver, simulation.innerIterations, iterations, simulation.numThreads, simulation.timestep, simulation.consoleOutput, simulation.csvOutput)
|
return Simulation(simulation.grid, simulation.bc, simulation.approach, simulation.solver, iterations, simulation.numThreads, simulation.timestep, simulation.consoleOutput, simulation.csvOutput)
|
||||||
end
|
end
|
||||||
|
|
||||||
function setOutputConsole(simulation::Simulation{T,approach,solver}, consoleOutput::CONSOLE_OUTPUT) where {T,approach,solver}
|
function setOutputConsole(simulation::Simulation{T,approach,solver}, consoleOutput::CONSOLE_OUTPUT) where {T,approach,solver}
|
||||||
return Simulation(simulation.grid, simulation.bc, simulation.approach, simulation.solver, simulation.innerIterations, simulation.iterations, simulation.numThreads, simulation.timestep, consoleOutput, simulation.csvOutput)
|
return Simulation(simulation.grid, simulation.bc, simulation.approach, simulation.solver, simulation.iterations, simulation.numThreads, simulation.timestep, consoleOutput, simulation.csvOutput)
|
||||||
end
|
end
|
||||||
|
|
||||||
function setOutputCSV(simulation::Simulation{T,approach,solver}, csvOutput::CSV_OUPUT) where {T,approach,solver}
|
function setOutputCSV(simulation::Simulation{T,approach,solver}, csvOutput::CSV_OUPUT) where {T,approach,solver}
|
||||||
return Simulation(simulation.grid, simulation.bc, simulation.approach, simulation.solver, simulation.innerIterations, simulation.iterations, simulation.numThreads, simulation.timestep, simulation.consoleOutput, csvOutput)
|
return Simulation(simulation.grid, simulation.bc, simulation.approach, simulation.solver, simulation.iterations, simulation.numThreads, simulation.timestep, simulation.consoleOutput, csvOutput)
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user