Skip to content

Commit b80509a

Browse files
BenjaminRodenbergNiklasVinMakisH
authored
Add python variant of resonant circuit (#498)
* Add python version of resonator. * Add of python case in README.md * Add requirement.txt and modify run file to use venv --------- Co-authored-by: NiklasV <[email protected]> Co-authored-by: Gerasimos Chourdakis <[email protected]> Co-authored-by: Niklas <[email protected]> Co-authored-by: Benjamin Rodenberg <[email protected]>
1 parent c4eb0ec commit b80509a

File tree

9 files changed

+232
-8
lines changed

9 files changed

+232
-8
lines changed

resonant-circuit/README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Resonant Circuit
3-
keywords: MATLAB
3+
keywords: MATLAB, Python
44
summary: We simulate a two-element LC circuit (one inductor and one capacitor).
55
---
66

@@ -31,6 +31,7 @@ preCICE configuration (image generated using the [precice-config-visualizer](htt
3131

3232
* *MATLAB* A solver using the [MATLAB bindings](https://precice.org/installation-bindings-matlab.html).
3333
Before running this tutorial, follow the [instructions](https://precice.org/installation-bindings-matlab.html) to correctly install the MATLAB bindings.
34+
* *Python* A solver using the preCICE [Python bindings](https://precice.org/installation-bindings-python.html).
3435

3536
## Running the simulation
3637

@@ -63,7 +64,9 @@ By doing that, you can now open two shells and switch into the directories `capa
6364

6465
## Post-processing
6566

66-
The solver for the current also records the current and voltage through time and at the end of the simulation saves a plot with the obtained curves, as well as the analytical solution.
67+
As we defined a watchpoint on the 'Capacitor' participant (see `precice-config.xml`), we can plot it with gnuplot using the script `plot-solution.sh.` You need to specify the directory of the selected solid participant as a command line argument, so that the script can pick-up the desired watchpoint file, e.g. `./plot-solution.sh capacitor-python`. The resulting graph shows the voltage and current exchanged between the two participants.
68+
69+
Additionally, the MATLAB participant `capacitor-matlab` records the current and voltage over time. At the end of the simulation it creates a plot with the computed waveforms of current and voltage, as well as the analytical solution.
6770

6871
After successfully running the coupling, one can find the curves in the folder `capacitor-matlab` as `Curves.png`.
6972

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import precice
2+
import numpy as np
3+
from scipy.integrate import solve_ivp
4+
5+
# Initialize and configure preCICE
6+
participant = precice.Participant("Capacitor", "../precice-config.xml", 0, 1)
7+
8+
# Geometry IDs. As it is a 0-D simulation, only one vertex is necessary.
9+
mesh_name = "Capacitor-Mesh"
10+
11+
dimensions = participant.get_mesh_dimensions(mesh_name)
12+
13+
vertex_ids = np.array([participant.set_mesh_vertex(mesh_name, np.zeros(dimensions))])
14+
15+
# Data IDs
16+
read_data_name = "Current"
17+
write_data_name = "Voltage"
18+
19+
# Simulation parameters and initial condition
20+
C = 2 # Capacitance of the capacitor
21+
L = 1 # Inductance of the coil
22+
t0 = 0 # Initial simulation time
23+
t_max = 10 # End simulation time
24+
Io = np.array([1]) # Initial current
25+
phi = 0 # Phase of the signal
26+
27+
w0 = 1 / np.sqrt(L * C) # Resonant frequency
28+
U0 = -w0 * L * Io * np.sin(phi) # Initial condition for U
29+
30+
31+
def f_U(dt, max_allowed_dt):
32+
if dt > max_allowed_dt: # read_data will fail, if dt is outside of window
33+
return np.nan
34+
35+
I = participant.read_data(mesh_name, read_data_name, vertex_ids, dt)
36+
return -I / C # Time derivative of U
37+
38+
39+
# Initialize simulation
40+
if participant.requires_initial_data():
41+
participant.write_data(mesh_name, write_data_name, vertex_ids, U0)
42+
43+
participant.initialize()
44+
45+
solver_dt = participant.get_max_time_step_size()
46+
47+
# Start simulation
48+
t = t0
49+
while participant.is_coupling_ongoing():
50+
51+
# Record checkpoint if necessary
52+
if participant.requires_writing_checkpoint():
53+
U0_checkpoint = U0
54+
t_checkpoint = t
55+
56+
# Make Simulation Step
57+
precice_dt = participant.get_max_time_step_size()
58+
dt = min([precice_dt, solver_dt])
59+
t_span = [t, t + dt]
60+
sol = solve_ivp(lambda t, y: f_U(t - t_span[0], dt), t_span, U0, dense_output=True, rtol=1e-12, atol=1e-12)
61+
62+
# Exchange data
63+
evals = max(len(sol.t), 3) # at least do 3 substeps to allow cubic interpolation
64+
for i in range(evals):
65+
U0 = sol.sol(t_span[0] + (i + 1) * dt / evals)
66+
participant.write_data(mesh_name, write_data_name, vertex_ids, np.array(U0))
67+
participant.advance(dt / evals)
68+
69+
t = t + dt
70+
71+
# Recover checkpoint if not converged
72+
if participant.requires_reading_checkpoint():
73+
U0 = U0_checkpoint
74+
t = t_checkpoint
75+
76+
# Stop coupling
77+
participant.finalize()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
numpy >1, <2
2+
scipy
3+
pyprecice~=3.0
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
set -e -u
3+
4+
. ../../tools/log.sh
5+
exec > >(tee --append "$LOGFILE") 2>&1
6+
7+
python3 -m venv .venv
8+
. .venv/bin/activate
9+
pip install -r requirements.txt
10+
python3 capacitor.py
11+
12+
close_log

resonant-circuit/coil-python/coil.py

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import precice
2+
import numpy as np
3+
from scipy.integrate import solve_ivp
4+
5+
# Initialize and configure preCICE
6+
participant = precice.Participant("Coil", "../precice-config.xml", 0, 1)
7+
8+
# Geometry IDs. As it is a 0-D simulation, only one vertex is necessary.
9+
mesh_name = "Coil-Mesh"
10+
11+
dimensions = participant.get_mesh_dimensions(mesh_name)
12+
13+
vertex_ids = np.array([participant.set_mesh_vertex(mesh_name, np.zeros(dimensions))])
14+
15+
# Data IDs
16+
read_data_name = "Voltage"
17+
write_data_name = "Current"
18+
19+
# Simulation parameters and initial condition
20+
C = 2 # Capacitance of the capacitor
21+
L = 1 # Inductance of the coil
22+
t0 = 0 # Initial simulation time
23+
Io = np.array([1]) # Initial current
24+
phi = 0 # Phase of the signal
25+
26+
w0 = 1 / np.sqrt(L * C) # Resonant frequency
27+
I0 = Io * np.cos(phi) # Initial condition for I
28+
29+
# to estimate cost
30+
global f_evals
31+
f_evals = 0
32+
33+
34+
def f_I(dt, max_allowed_dt):
35+
global f_evals
36+
f_evals += 1
37+
if dt > max_allowed_dt: # read_data will fail, if dt is outside of window
38+
return np.nan
39+
40+
U = participant.read_data(mesh_name, read_data_name, vertex_ids, dt)
41+
return U / L # Time derivative of I; ODE determining capacitor
42+
43+
44+
# Initialize simulation
45+
if participant.requires_initial_data():
46+
participant.write_data(mesh_name, write_data_name, vertex_ids, I0)
47+
48+
participant.initialize()
49+
50+
solver_dt = participant.get_max_time_step_size()
51+
52+
# Start simulation
53+
t = t0
54+
while participant.is_coupling_ongoing():
55+
56+
# Record checkpoint if necessary
57+
if participant.requires_writing_checkpoint():
58+
I0_checkpoint = I0
59+
t_checkpoint = t
60+
61+
# Make Simulation Step
62+
precice_dt = participant.get_max_time_step_size()
63+
dt = min([precice_dt, solver_dt])
64+
t_span = [t, t + dt]
65+
sol = solve_ivp(lambda t, y: f_I(t - t_span[0], dt), t_span, I0, dense_output=True, rtol=1e-12, atol=1e-12)
66+
67+
# Exchange data
68+
evals = max(len(sol.t), 3) # at least do 3 substeps to allow cubic interpolation
69+
for i in range(evals):
70+
I0 = sol.sol(t_span[0] + (i + 1) * dt / evals)
71+
participant.write_data(mesh_name, write_data_name, vertex_ids, np.array(I0))
72+
participant.advance(dt / evals)
73+
74+
t = t + dt
75+
76+
# Recover checkpoint if not converged
77+
if participant.requires_reading_checkpoint():
78+
I0 = I0_checkpoint
79+
t = t_checkpoint
80+
81+
# Stop coupling
82+
participant.finalize()
83+
84+
85+
def I_analytical(t): return Io * np.cos(t * w0 + phi)
86+
87+
88+
error = I0 - I_analytical(t)
89+
print(f"{error=}")
90+
print(f"{f_evals=}")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
numpy >1, <2
2+
scipy
3+
pyprecice~=3.0

resonant-circuit/coil-python/run.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
set -e -u
3+
4+
. ../../tools/log.sh
5+
exec > >(tee --append "$LOGFILE") 2>&1
6+
7+
python3 -m venv .venv
8+
. .venv/bin/activate
9+
pip install -r requirements.txt
10+
python3 coil.py
11+
12+
close_log

resonant-circuit/plot-solution.sh

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/sh
2+
3+
if [ "${1:-}" = "" ]; then
4+
echo "No target directory specified. Please specify the directory of the participant containing the watchpoint, e.g. ./plot-displacement.sh coil-python."
5+
exit 1
6+
fi
7+
8+
FILE="$1/precice-Capacitor-watchpoint-VoltageCurrent.log"
9+
10+
if [ ! -f "$FILE" ]; then
11+
echo "Unable to locate the watchpoint file (precice-Capacitor-watchpoint-VoltageCurrent.log) in the specified participant directory '${1}'. Make sure the specified directory matches the participant you used for the calculations."
12+
exit 1
13+
fi
14+
15+
gnuplot -p << EOF
16+
set grid
17+
set title 'Voltage and current'
18+
set xlabel 'time [s]'
19+
set ylabel 'Voltage / Current'
20+
plot "$1/precice-Capacitor-watchpoint-VoltageCurrent.log" using 1:4 with linespoints title "Voltage", "$1/precice-Capacitor-watchpoint-VoltageCurrent.log" using 1:5 with linespoints title "Current"
21+
EOF

resonant-circuit/precice-config.xml

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<precice-configuration>
3-
<data:scalar name="Current" />
4-
<data:scalar name="Voltage" />
3+
<data:scalar name="Current" waveform-degree="3" />
4+
<data:scalar name="Voltage" waveform-degree="3" />
55

66
<mesh name="Coil-Mesh" dimensions="2">
77
<use-data name="Current" />
@@ -34,16 +34,19 @@
3434
from="Coil-Mesh"
3535
to="Capacitor-Mesh"
3636
constraint="consistent" />
37+
<watch-point mesh="Capacitor-Mesh" name="VoltageCurrent" coordinate="0.0;0.0" />
3738
</participant>
3839

3940
<m2n:sockets acceptor="Coil" connector="Capacitor" exchange-directory=".." />
4041

4142
<coupling-scheme:serial-implicit>
4243
<participants first="Coil" second="Capacitor" />
4344
<max-time value="10" />
44-
<time-window-size value="0.01" />
45-
<max-iterations value="3" />
46-
<exchange data="Current" mesh="Coil-Mesh" from="Coil" to="Capacitor" />
47-
<exchange data="Voltage" mesh="Coil-Mesh" from="Capacitor" to="Coil" />
45+
<time-window-size value="1" />
46+
<max-iterations value="100" />
47+
<exchange data="Current" mesh="Coil-Mesh" from="Coil" to="Capacitor" substeps="true" />
48+
<exchange data="Voltage" mesh="Coil-Mesh" from="Capacitor" to="Coil" substeps="true" />
49+
<relative-convergence-measure limit="1e-3" data="Current" mesh="Coil-Mesh" />
50+
<relative-convergence-measure limit="1e-3" data="Voltage" mesh="Coil-Mesh" />
4851
</coupling-scheme:serial-implicit>
4952
</precice-configuration>

0 commit comments

Comments
 (0)