Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tutorial: partitioned oscillator #297

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
12eb25b
Add mass-spring tutorial.
BenjaminRodenberg Sep 30, 2022
9e595b1
Fix bare URL.
BenjaminRodenberg Sep 30, 2022
1ad6353
Add post-processing script.
BenjaminRodenberg Sep 30, 2022
2a1d0e6
Add image text.
BenjaminRodenberg Sep 30, 2022
7741ed9
Formatting autopep8.
BenjaminRodenberg Sep 30, 2022
8414456
Fix structure.
BenjaminRodenberg Sep 30, 2022
fc70b27
Add helper scripts.
BenjaminRodenberg Sep 30, 2022
be00d9a
Follow naming conventions.
BenjaminRodenberg Sep 30, 2022
8a96a2f
Fix order of plots.
BenjaminRodenberg Oct 3, 2022
53824c0
Add schematic drawing.
BenjaminRodenberg Oct 3, 2022
a636156
Rename mass-spring-1d to oscillator.
BenjaminRodenberg Oct 3, 2022
1c31544
Also write final result to output.
BenjaminRodenberg Oct 3, 2022
ddcc966
Add changelog entry.
BenjaminRodenberg Oct 3, 2022
d153c52
Merge branch 'develop' into add-partitioned-mass-spring-system
BenjaminRodenberg Oct 3, 2022
c1f530b
Rename.
BenjaminRodenberg Oct 3, 2022
1d430cb
Apply suggestions from code review
BenjaminRodenberg Nov 2, 2022
960321a
Fix documentation.
BenjaminRodenberg Nov 2, 2022
132f14f
Fix cleanup script.
BenjaminRodenberg Nov 2, 2022
dbb055d
Add note on coupling scheme
BenjaminRodenberg Nov 2, 2022
5653021
Rename Mass-One and Mass-Two to Mass-Left and Mass-Right
BenjaminRodenberg Nov 2, 2022
a65ca03
Remove unnecessary.
BenjaminRodenberg Nov 2, 2022
3fbd538
Make tutorial compatible with v2.
BenjaminRodenberg Nov 2, 2022
f0d364b
Remove unnecessary change.
BenjaminRodenberg Nov 2, 2022
6b17baa
Change order of operations for time step computation.
BenjaminRodenberg Nov 4, 2022
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
58 changes: 58 additions & 0 deletions mass-spring-1d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: 1D mass-spring system
permalink: tutorials-mass-spring-1d.html
keywords: Python, 1D
summary: We solve an oscillator with two masses in a partitioned fashion. Each mass is solved by an independent process.
---

{% note %}
Get the [case files of this tutorial](https://github.com/precice/tutorials/tree/master/mass-spring-1d). Read how in the [tutorials introduction](https://www.precice.org/tutorials.html).
{% endnote %}

## Setup

The setup is taken from [1].

## Available solvers

This tutorial is only available in python. You will need to have preCICE and the python bindings installed on your system.

- *Python*: An example solver using the preCICE [Python bindings](https://www.precice.org/installation-bindings-python.html). This solver also depends on the Python libraries `numpy`, which you can get from your system package manager or with `pip3 install --user <package>`.

## Running the Simulation

### Python

Open two separate terminals and start each participant by calling:

```bash
python3 mass-spring.py MassOne
```

and

```bash
python3 mass-spring.py MassTwo
```

## Post-processing

Each simulation run will create two files containing position and velocity of the two masses over time. These files are called `trajectory-MassOne.csv` and `trajectory-MassTwo.csv`. You can find a script for post-processing in `python/plot-trajectory.py`. Type `python3 python/plot-trajectory --help` to see available options. You can, for example plot the trajectory by running

```bash
python3 python/plot-trajectory.py python/trajectory-MassOne.csv TRAJECTORY
```

This allows you to study the effect of different time stepping schemes on energy conservation. Newmark beta conserves energy:

![](trajectory_Newmark_beta.png)

Generalized alpha does not conserve energy:
BenjaminRodenberg marked this conversation as resolved.
Show resolved Hide resolved

![](trajectory_generalited_alpha.png)

For details, refer to [1].

## References

[1] V. Schüller, B. Rodenberg, B. Uekermann and H. Bungartz, A Simple Test Case for Convergence Order in Time and Energy Conservation of Black-Box Coupling Schemes, in: WCCM-APCOM2022. [URL](https://www.scipedia.com/public/Rodenberg_2022a)
63 changes: 63 additions & 0 deletions mass-spring-1d/precice-config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?xml version="1.0"?>
<precice-configuration>
<log enabled="1">
<sink filter="%Severity% > debug" />
</log>

<solver-interface dimensions="2" >
<!-- Use this to activate waveforms
<solver-interface dimensions="2" experimental="true">
-->
<data:scalar name="forceOne" />
<data:scalar name="forceTwo" />

<mesh name="MeshOne">
<use-data name="forceOne" />
<use-data name="forceTwo" />
</mesh>

<mesh name="MeshTwo">
<use-data name="forceOne" />
<use-data name="forceTwo" />
</mesh>

<participant name="MassOne">
<use-mesh name="MeshOne" provide="yes"/>
<write-data name="forceOne" mesh="MeshOne" />
<read-data name="forceTwo" mesh="MeshOne" />
<!-- Use this to activate first order interpolation
<read-data name="forceTwo" mesh="MeshOne" waveform-order="1" />
-->
</participant>

<participant name="MassTwo">
<use-mesh name="MeshOne" from="MassOne"/>
<use-mesh name="MeshTwo" provide="yes"/>
<write-data name="forceTwo" mesh="MeshTwo" />
<read-data name="forceOne" mesh="MeshTwo" />
<!-- Use this to activate first order interpolation
<read-data name="forceOne" mesh="MeshTwo" waveform-order="1" />
-->
<mapping:nearest-neighbor direction="write" from="MeshTwo" to="MeshOne" constraint="conservative" />
<mapping:nearest-neighbor direction="read" from="MeshOne" to="MeshTwo" constraint="conservative" />
</participant>

<m2n:sockets from="MassOne" to="MassTwo" exchange-directory=".." />

<coupling-scheme:serial-implicit>
<participants first="MassOne" second="MassTwo" />
<max-time value="1" />
<time-window-size value="0.01" />
<max-iterations value="200" />
<min-iteration-convergence-measure min-iterations="1" data="forceOne" mesh="MeshOne"/>
<relative-convergence-measure data="forceOne" mesh="MeshOne" limit="1e-10"/>
<relative-convergence-measure data="forceTwo" mesh="MeshOne" limit="1e-10"/>
<exchange data="forceOne" mesh="MeshOne" from="MassOne" to="MassTwo" />
<!-- Use this for higher accuracy with waveforms and preCICE v3
<exchange data="forceOne" mesh="MeshOne" from="MassOne" to="MassTwo" initialize="true" />
-->
<exchange data="forceTwo" mesh="MeshOne" from="MassTwo" to="MassOne" initialize="true"/>
</coupling-scheme:serial-implicit>
</solver-interface>

</precice-configuration>
202 changes: 202 additions & 0 deletions mass-spring-1d/python/mass-spring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
from __future__ import division

import argparse
import numpy as np
from numpy.linalg import eig
import precice
from enum import Enum
import csv

class Scheme(Enum):
NEWMARK_BETA = "Newmark_beta"
GENERALIZED_ALPHA = "generalized_alpha"

parser = argparse.ArgumentParser()
parser.add_argument("participantName", help="Name of the solver.", type=str)
parser.add_argument("-ts", "--time-stepping", help="Time stepping scheme being used.", type=str, choices=[s.value for s in Scheme], default=Scheme.NEWMARK_BETA.value)

try:
args = parser.parse_args()
except SystemExit:
print("")
print("Usage: python ./mass-spring participant-name")
quit()

participant_name = args.participantName

m_1, m_2 = 1, 1
k_1, k_2, k_12 = 4*np.pi**2, 4*np.pi**2, 16*(np.pi**2)

M = np.array([[m_1, 0], [0, m_2]])
K = np.array([[k_1 + k_12, -k_12], [-k_12, k_2 + k_12]])

# system:
# m ddu + k u = f
# compute analytical solution from eigenvalue ansatz

eigenvalues, eigenvectors = eig(K)
omega = np.sqrt(eigenvalues)
A, B = eigenvectors

# can change initial displacement
u0_1 = 1
u0_2 = 0

# cannot change initial velocities!
v0_1 = 0
v0_2 = 0

c = np.linalg.solve(eigenvectors, [u0_1, u0_2])

if participant_name == 'MassOne':
write_data_name = 'forceOne'
read_data_name = 'forceTwo'
mesh_name = 'MeshOne'

mass = m_1
stiffness = k_1 + k_12
u0, v0, f0, d_dt_f0 = u0_1, v0_1, k_12 * u0_2, k_12 * v0_2
u_analytical = lambda t: c[0]*A[0] * np.cos(omega[0] * t) + c[1]*A[1] * np.cos(omega[1] * t)
v_analytical = lambda t: -c[0]*A[0]*omega[0] * np.sin(omega[0] *t) - c[1]*A[1]*omega[1] * np.sin(omega[1] * t)

elif participant_name == 'MassTwo':
read_data_name = 'forceOne'
write_data_name = 'forceTwo'
mesh_name = 'MeshTwo'

mass = m_2
stiffness = k_2 + k_12
u0, v0, f0, d_dt_f0 = u0_2, v0_2, k_12 * u0_1, k_12 * v0_1
u_analytical = lambda t: c[0]*B[0] * np.cos(omega[0] * t) + c[1]*B[1] * np.cos(omega[1] * t)
v_analytical = lambda t: -c[0]*B[0]*omega[0] * np.sin(omega[0] *t) - c[1]*B[1]*omega[1] * np.sin(omega[1] * t)

else:
raise Exception(f"wrong participant name: {participant_name}")

num_vertices = 1 # Number of vertices

solver_process_index = 0
solver_process_size = 1

configuration_file_name = "../precice-config.xml"

interface = precice.Interface(participant_name, configuration_file_name, solver_process_index, solver_process_size)

mesh_id = interface.get_mesh_id(mesh_name)
dimensions = interface.get_dimensions()

vertex = np.zeros(dimensions)
read_data = np.zeros(num_vertices)
write_data = k_12 * u0 * np.ones(num_vertices)

vertex_id = interface.set_mesh_vertex(mesh_id, vertex)
read_data_id = interface.get_data_id(read_data_name, mesh_id)
write_data_id = interface.get_data_id(write_data_name, mesh_id)

if interface.is_action_required(precice.action_write_initial_data()):
interface.write_scalar_data(write_data_id, vertex_id, write_data)
interface.mark_action_fulfilled(precice.action_write_initial_data())

precice_dt = interface.initialize()
my_dt = precice_dt # use my_dt < precice_dt for subcycling
dt = np.min([precice_dt, my_dt])

# Initial Conditions
a0 = (f0 - stiffness * u0) / mass
u = u0
v = v0
a = a0
t = 0

# Generalized Alpha Parameters
if args.time_stepping == Scheme.GENERALIZED_ALPHA.value:
alpha_f = 0.4
alpha_m = 0.2
elif args.time_stepping == Scheme.NEWMARK_BETA.value:
alpha_f = 0.0
alpha_m = 0.0

gamma = 0.5 - alpha_m + alpha_f
beta = 0.25 * (gamma + 0.5)

m = 3*[None]
m[0] = (1-alpha_m)/(beta*dt**2)
m[1] = (1-alpha_m)/(beta*dt)
m[2] = (1-alpha_m-2*beta)/(2*beta)
k_bar = stiffness * (1 - alpha_f) + m[0] * mass

positions = []
velocities = []
times = []

u_write = [u]
v_write = [v]
t_write = [t]

while interface.is_coupling_ongoing():
if interface.is_action_required(precice.action_write_iteration_checkpoint()):
u_cp = u
v_cp = v
a_cp = a
t_cp = t
interface.mark_action_fulfilled(precice.action_write_iteration_checkpoint())

# store data for plotting and postprocessing
positions += u_write
velocities += v_write
times += t_write

# # use this with waveform relaxation
# read_time = (1-alpha_f) * dt
# read_data = interface.read_scalar_data(read_data_id, vertex_id, read_time)
read_data = interface.read_scalar_data(read_data_id, vertex_id)
f = read_data

# do generalized alpha step
u_new = (f - alpha_f * stiffness * u + mass*(m[0]*u + m[1]*v + m[2]*a)) / k_bar
a_new = 1.0 / (beta * dt**2) * (u_new - u - dt * v) - (1-2*beta) / (2*beta) * a
v_new = v + dt * ((1-gamma)*a+gamma*a_new)

write_data = k_12 * u_new

interface.write_scalar_data(write_data_id, vertex_id, write_data)

precice_dt = interface.advance(dt)
dt = np.min([precice_dt, my_dt])

if interface.is_action_required(precice.action_read_iteration_checkpoint()):
u = u_cp
v = v_cp
a = a_cp
t = t_cp
interface.mark_action_fulfilled(precice.action_read_iteration_checkpoint())

# empty buffers for next window
u_write = []
v_write = []
t_write = []

else:
u = u_new
v = v_new
a = a_new
t += dt

# write data to buffers
u_write.append(u)
v_write.append(v)
t_write.append(t)

interface.finalize()

# print errors
error = np.max(abs(u_analytical(np.array(times))-np.array(positions)))
print("Error w.r.t analytical solution:")
print(f"{my_dt},{error}")

# output trajectory
with open(f'trajectory-{participant_name}.csv', 'w') as file:
csv_write = csv.writer(file,delimiter=';')
csv_write.writerow(['time','position','velocity'])
for t, u, v in zip(times, positions, velocities):
csv_write.writerow([t, u, v])
33 changes: 33 additions & 0 deletions mass-spring-1d/python/plot-trajectory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pandas as pd
from matplotlib import pyplot as plt
import argparse
from enum import Enum

class PlotType(Enum):
U_OVER_T = "position over time"
V_OVER_T = "velocity over time"
TRAJECTORY = "velocity over position (trajectory)"

parser = argparse.ArgumentParser()
parser.add_argument("csvFile", help="CSV file.", type=str)
parser.add_argument("plotType", help="Plot type.", type=str, choices=[pt.name for pt in PlotType])
args = parser.parse_args()

filename = args.csvFile
df = pd.read_csv(filename, delimiter=';')

if args.plotType == PlotType.U_OVER_T.name:
plt.plot(df['time'], df['position'])
plt.title(PlotType.U_OVER_T.value)
elif args.plotType == PlotType.V_OVER_T.name:
plt.plot(df['time'], df['velocity'])
plt.title(PlotType.V_OVER_T.value)
elif args.plotType == PlotType.TRAJECTORY.name:
print([df['position'][0]], [df['velocity'][0]])
plt.plot(df['position'], df['velocity'])
plt.scatter([df['position'][0]], [df['velocity'][0]], label=f"(u,v) at t={df['time'][0]}")
plt.scatter([df['position'].iloc[-1]], [df['velocity'].iloc[-1]], label=f"(u,v) at t={df['time'].iloc[-1]}", marker="*")
plt.title(PlotType.TRAJECTORY.value)
plt.legend()

plt.show()
Binary file added mass-spring-1d/trajectory_Newmark_beta.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mass-spring-1d/trajectory_generalited_alpha.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.