|
| 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=}") |
0 commit comments