Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ makedocs(;
"How-to guides" => [
"howto_mpi.md",
"howto_visualization.md",
"howto_parameter-study.md",
],
"Tutorials" => [
joinpath("generated", "tutorial_tension_static.md"),
Expand Down
93 changes: 93 additions & 0 deletions docs/src/Parameter-study.jl
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file should not be included. It is not used inside the documentation and just a reference.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Peridynamics
using Printf
using DelimitedFiles

## PARAMETER STUDY SIMULATIONS
function job_xwave(setup, root)
(; E, nu, rho, T, vmax, npyz, m) = setup
lx, lyz = 0.2, 0.002
Δx = lyz / npyz
pos, vol = uniform_box(lx, lyz, lyz, Δx)
ids = sortperm(pos[1,:])
body = Body(BBMaterial(), pos[:, ids], vol[ids])
horizon = m * Δx
material!(body; horizon, rho, E, nu)
point_set!(x -> x < -lx / 2 + 1.2Δx, body, :left)
velocity_bc!(t -> t < T ? vmax * sin(2π / T * t) : 0.0, body, :left, :x)
vv = VelocityVerlet(time=1e-4, safety_factor=0.7)
simname = @sprintf("xwave_npyz-%d_m-%d", npyz, round(Int, m))
path = joinpath(root, simname)
job = Job(body, vv; path, freq=5, fields=(:displacement,))
return job
end
setups = [
(; E=210e9, nu=0.25, rho=7850.0, T=1.0e-5, vmax=2.0, npyz=4, m=3.015),
(; E=210e9, nu=0.25, rho=7850.0, T=1.0e-5, vmax=2.0, npyz=4, m=4.015),
(; E=210e9, nu=0.25, rho=7850.0, T=1.0e-5, vmax=2.0, npyz=4, m=5.015),
]
root = joinpath("results", "xwave_study")
rm(root; recursive=true, force=true)


study = Study(job_xwave, setups; root)

## SUBMIT STUDY
submit!(study)

## POST-PROCESSING
function calc_velocity(t, x_w, u_w) # Berechnung der Wellengeschwindigkeit anhand der Position und Verschiebung der Welle
u_0 = first(u_w) # Anfangsverschiebung der Welle
valid_until = findfirst(x -> !isapprox(u_0, x; rtol=0.01), u_w)
if !isnothing(valid_until) # Überprüfung, ob ein gültiger Punkt gefunden wurde
x_w = x_w[1:valid_until-1] # Beschränkung der Positions-Daten auf gültige Punkte
t = t[1:valid_until-1] # Beschränkung der Zeit-Daten auf gültige Punkte
end
n = length(t) # Anzahl der Datenpunkte
@assert n == length(x_w) # Sicherstellen, dass die Längen übereinstimmen
t̄ = sum(t) / n #Berechnung des Mittelwerts der Zeit
x̄ = sum(x_w) / n #Berechnung des Mittelwerts der Position
v = sum((t .- t̄) .* (x_w .- x̄)) / sum((t .- t̄) .^ 2) #Berechnung der Steigung der Regressionsgeraden = Geschwindigkeit
return v # Rückgabe der berechneten Geschwindigkeit
end

default_result = (; c_0=NaN, c_w=NaN, dc=NaN, dcp=NaN) # Standard-Ergebnisstruktur für Post-Processing-Ergebnisse
results = process_each_job(study, default_result) do job, setup # Post-Processing für jede Simulation im Parameter-Studium
# extract parameters that are needed for post-processing
(; E, rho, T, npyz) = setup
Δx = 0.002 / npyz

# prepare some output files
post_path = joinpath(job.options.root, "post") # Pfad für Post-Processing-Daten
mkpath(post_path) # required to ensure the directory exists (Erstellt das Verzeichnis für die Post-Processing-Daten)
wave_position_data_file = joinpath(post_path, "wavepos.csv") # Datei zur Speicherung der Wellenpositionsdaten

# get the wave position over time
process_each_export(job; serial=true) do r0, r, id # Liest die Position und Verschiebung der Welle aus den Export-Dateien
t = first(r[:time])
t < 1.5T && return nothing # nur Zeitpunkte größer als 1.5T berücksichtigen
ymax = @views maximum(r0[:position][2, :]) # maximale y-position
pids = findall(eachcol(r0[:position])) do point
isapprox(point[2], ymax; atol=1.01Δx/2)
end
û, pids_id = @views findmax(r[:displacement][1, pids]) # maximale Verschiebung der Welle in x-Richtung
x̂ = r[:position][1, pids[pids_id]] # Position der Welle in x-Richtung zum gegebenen Zeitpunkt
open(wave_position_data_file, "a+") do io # speichert die Zeit, Position und Verschiebung der Welle in CSV-Datei
@printf(io, "%.12f,%.12f,%.12f\n", t, x̂, û)
end
return nothing
end

# calculate wave speed
results = readdlm(wave_position_data_file, ',', Float64)
t, x̂, û = results[:,1], results[:,2], results[:,3]
c_0 = sqrt(E / rho) # theoretische Wellengeschwindigkeit
c_w = calc_velocity(t, x̂, û) # Berechnung der Wellengeschwindigkeit anhand Simulationsdaten
dc = c_w - c_0 # Differenz der Wellengeschwindigkeiten
dcp = 100 * dc / c_0 # prozentuale Abweichung der Wellengeschwindigkeiten
printstyled("\nRESULTS FOR SIMULATION: `$(basename(job.options.root))`:\n", bold=true)
printstyled(@sprintf("c_0 = %8.2f m/s\n", c_0); color=:green, bold=true)
printstyled(@sprintf("c_w = %8.2f m/s\n", c_w); color=:blue, bold=true)
printstyled(@sprintf("Δc = %8.2f m/s (%.3f %%)\n", dc, dcp); color=:red, bold=true)

return (; c_0, c_w, dc, dcp)
end
115 changes: 115 additions & 0 deletions docs/src/howto_parameter-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Perform a parameter study

The following section explains how to perform a parameter study.

### 1. Define the simulation conditions

Here is an example function that creates a peridynamic body and defines the boundary conditions of each simulation.
This simulation is intended to simulate wave propagation in the defined body.

```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
function job_xwave(setup, root)
(; E, nu, rho, T, vmax, npyz, m) = setup
lx, lyz = 0.2, 0.002
Δx = lyz / npyz
pos, vol = uniform_box(lx, lyz, lyz, Δx)
ids = sortperm(pos[1,:])
body = Body(BBMaterial(), pos[:, ids], vol[ids])
horizon = m * Δx
material!(body; horizon, rho, E, nu)
point_set!(x -> x < -lx / 2 + 1.2Δx, body, :left)
velocity_bc!(t -> t < T ? vmax * sin(2π / T * t) : 0.0, body, :left, :x)
vv = VelocityVerlet(time=1e-4, safety_factor=0.7)
simname = @sprintf("xwave_npyz-%d_m-%d", npyz, round(Int, m))
path = joinpath(root, simname)
job = Job(body, vv; path, freq=5, fields=(:displacement,))
return job
end
```
The line
```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
(; E, nu, rho, T, vmax, npyz, m) = setup
```
creates a setup in which the material parameters are defined.


Now various setups are created in which the respective parameters are defined as desired. A separate simulation is performed for each setup. Here the parameter `m`, which controls the peridynamic horizon `h = m * Δx`, varies.

```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
setups = [
(; E=210e9, nu=0.25, rho=7850.0, T=1.0e-5, vmax=2.0, npyz=4, m=3.015),
(; E=210e9, nu=0.25, rho=7850.0, T=1.0e-5, vmax=2.0, npyz=4, m=4.015),
(; E=210e9, nu=0.25, rho=7850.0, T=1.0e-5, vmax=2.0, npyz=4, m=5.015),
]
```

The location where the simulation results are to be stored can be specified with `joinpath`
```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
root = joinpath("results", "xwave_study")
```

### 2. Submit study

To execute the previously defined setups one after the other, first create a study object and then call `submit!`:
```
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
study = Study(job_xwave, setups; root)
submit!(study)
```


### 3. Postprocessing

Now that the simulations are complete, post-processing can begin. This is necessary in order to calculate and display the actual values needed from the raw data generated. In this example the needed values are the theoretical wave velocity `c_0`, the measured wave velocity from the simulation `c_w` and the difference between them two `dc` as well as the percentage deviation `dcp`.

#### 3.1 Postprocessing function

First, a standard result structure for post-processing results is created. Then the `process_each_job` function iterates over all simulations (study) and executes the postprocessing code shown below for each individual setup.

```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
default_result = (; c_0=NaN, c_w=NaN, dc=NaN, dcp=NaN)
results = process_each_job(study, default_result) do job, setup
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This do syntax creates an anonymous function that is called, but in this code block, the whole function body of this anonymous function is not visible. It should be included here and then in the following the code is commented and explained.

```
#### 3.2 Create an output file and write the results to it

To create a CSV file (here `wavepos.csv` resp. `wave_position_data_file`) in a desired path where the data can be saved, the following can be executed.
```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
post_path = joinpath(job.options.root, "post")
mkpath(post_path)
wave_position_data_file = joinpath(post_path, "wavepos.csv")
```

The relevant simulation data (here the values for `t`, `x̂` and `û`) is now saved in the CSV file.
```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
open(wave_position_data_file, "a+") do io
@printf(io, "%.12f,%.12f,%.12f\n", t, x̂, û)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```bash
open(wave_position_data_file, "a+") do io
@printf(io, "%.12f,%.12f,%.12f\n", t, x̂, û)
```julia
open(wave_position_data_file, "a+") do io
@printf(io, "%.12f,%.12f,%.12f\n", t, x̂, û)
end

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think that the usage of an external file is no longer necessary since the update of the process_each_export function to also have combined calculation of a NamedTuple vector. See the Job processing function below:

    ## POST-PROCESSING OF RESULTS -- CALCULATE WAVE SPEEDS
    default_ret = (; c_0=(E/rho), c_w=NaN, dc=NaN, dcp=NaN)
    results = process_each_job(study, default_ret) do job, setup
        # extract parameters that are needed for post-processing
        (; E, rho, T, npyz) = setup
        Δx = 0.002 / npyz

        # get the wave position over time
        default_exp_ret = (; time=NaN, wave_position=NaN, amplitude_ux=NaN)
        res = process_each_export(job, default_exp_ret) do r0, r, id
            t = first(r[:time])
            t < 1.5T && return default_exp_ret
            ymax = @views maximum(r0[:position][2, :])
            pids = findall(eachcol(r0[:position])) do point
                isapprox(point[2], ymax; atol=1.01Δx/2)
            end
            û, pids_id = @views findmax(r[:displacement][1, pids])
            x̂ = r[:position][1, pids[pids_id]]
            return (; time=t, wave_position=x̂, amplitude_ux=û)
        end

        # calculate wave speed
        allres = (; (k => getfield.(res, k) for k in keys(default_exp_ret))...)
        (; time, wave_position, amplitude_ux) = allres
        idxs = findall(x -> !isnan(x), time)
        c_0 = sqrt(E / rho)
        c_w = calc_velocity(time[idxs], wave_position[idxs], amplitude_ux[idxs])
        dc = c_w - c_0
        dcp = 100 * dc / c_0
        @mpiroot begin
            bold = true
            simname = basename(job.options.root)
            printstyled("\nRESULTS FOR SIMULATION: `$(simname)`:\n"; bold)
            printstyled(@sprintf("c_0 = %8.2f m/s\n", c_0); color=:green, bold)
            printstyled(@sprintf("c_w = %8.2f m/s\n", c_w); color=:blue, bold)
            printstyled(@sprintf("Δc  = %8.2f m/s (%.3f %%)\n", dc, dcp); color=:red, bold)
        end
        return (; c_0, c_w, dc, dcp)
    end
    ## SAVE RESULTS TO CSV, ONLY ON THE ROOT RANK
    @mpiroot begin
        df = DataFrame(results)
        df[!, :mat] = [setup.matname for setup in setups]
        df[!, :npyz] = [setup.npyz for setup in setups]
        df[!, :m] = [setup.m for setup in setups]
        CSV.write(joinpath(root, "xwave_$(lowercase(matname))_results.csv"), df)
    end

```


#### 3.3 Calculate and display the values

With the `readdlm` command the created CSV file is read. Then the resulting columns are assigned to `t`, `x̂`, and `û`.

```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
results = readdlm(wave_position_data_file, ',', Float64)
t, x̂, û = results[:,1], results[:,2], results[:,3]
```


Now the actual values can be calculated:

```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
c_0 = sqrt(E / rho)
c_w = calc_velocity(t, x̂, û)
dc = c_w - c_0
dcp = 100 * dc / c_0
```


Finally, the four determined values are displayed in the terminal:

```bash
Comment thread
robertweinbrenner marked this conversation as resolved.
Outdated
printstyled("\nRESULTS FOR SIMULATION: `$(basename(job.options.root))`:\n", bold=true)
printstyled(@sprintf("c_0 = %8.2f m/s\n", c_0); color=:green, bold=true)
printstyled(@sprintf("c_w = %8.2f m/s\n", c_w); color=:blue, bold=true)
printstyled(@sprintf("Δc = %8.2f m/s (%.3f %%)\n", dc, dcp); color=:red, bold=true)
```
Loading