Skip to content

Commit c769abf

Browse files
committed
Merge branch 'new_solver'
2 parents 428f58b + edc5492 commit c769abf

19 files changed

Lines changed: 1925 additions & 75 deletions

‎.gitignore‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,6 @@ xcode/
3838
externals/
3939
.vscode/
4040
.venv
41-
.wav
41+
*.wav
42+
*.h5
43+
*.pdf

‎python/base_model.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import numpy as np
2+
3+
class Model():
4+
"""General Model class to pass to the SAVSolver.
5+
"""
6+
def __init__(self, N = 10):
7+
# Number of dofs
8+
self.N = N
9+
# Number of inputs
10+
self.Nu = 1
11+
# Diagonal system matrices
12+
self.J0 = np.ones(self.N)
13+
self.M = self.J0
14+
# Rmid is diagonal but can depend on the state
15+
self.Rmid = lambda q: (0 * np.ones(self.N))
16+
# Non-diagonal system matrices as operators
17+
self.K_op = lambda x: (x)
18+
self.Rsv_op = lambda x: (0 * x)
19+
# Input matrix, may depend on the state
20+
self.G = lambda q: (np.ones((self.N, self.Nu)))
21+
# Nonlinear functions
22+
self.Enl = lambda q: (0.25 * np.sum(q**4)) * 1
23+
self.Fnl = lambda q: (q**3) * 1
24+
# Both functions are called at the same time in the solver,
25+
# in some cases it is then computationally interesting
26+
# to compute both in the same function.
27+
self.EandFnl = lambda q: (self.Enl(q), self.Fnl(q))
28+
29+
def setting(self):
30+
return {"Name": self.__class__.__name__, "N": self.N}

‎python/convergence_dafx_25.py‎

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
'''
2+
Run this file to reproduce the figures 2 and 3 from the DAFX25 paper. The only difference between both
3+
is the kappa parameter which tunes the relationship bewtween the spatial timestep and the stability condition.
4+
5+
Important: with the new solver (for JAES), the reference scheme is now a classical Stormer-Verlet scheme.
6+
In Dafx25, we used an alternative energy-stable schemes for systems with cubic nonlinearity. This reference shceme
7+
and the exact code used for production of the figure in the Dafx paper has been moved in the oldd_solver_dafx25 directory.
8+
9+
Execution of this file show that depsites this difference the reuslts are similar. This proves
10+
that both implementations of the sav solver are equivalent. The new one is intended to comply
11+
with notations from the JAES paper.
12+
'''
13+
import numpy as np
14+
import h5py
15+
import os
16+
import json
17+
import matplotlib.pyplot as plt
18+
from matplotlib.ticker import ScalarFormatter, NullFormatter
19+
20+
from sav_solver import SAVSolver
21+
from fd_string_model import FD_string_model, DEFAULT_STRING_PARAMS
22+
from plotter import Plotter, DEFAULT_PLOTTER_CONFIG, NO_PLOTTER_CONFIG
23+
from results_storage import ResultsStorage, STATE_STORAGE_CONFIG, DEFAULT_STORAGE_CONFIG
24+
from helper_plots import set_size
25+
26+
#%% Settings: same than in dafx.
27+
# Physical parameters (table 2):
28+
string_params = {
29+
"l0": 1.1,
30+
"rho": 8000,
31+
"T": 60,
32+
"E": 2e11,
33+
"eta_0": 0.9,
34+
"eta_1": 4e-4,
35+
"Ra": 0.4e-3,
36+
"NL_type": "GE4"
37+
}
38+
39+
# Exmerimental setting
40+
A0 = 8e-3 # initial amplitude
41+
duration = 0.1 # This parameter was missing in the Dafx paper, but presented figure indeed corresponds to
42+
# a duration of 0.1s.
43+
44+
# Solver parameters
45+
lambda0s = [0, 1000]
46+
sr0 = 20000
47+
B = 8
48+
OF_ref = 4
49+
50+
kappas = [0.9, 1]
51+
52+
# Storage
53+
result_folder = "results/conv_dafx"
54+
55+
#%% Compute and store results
56+
srs = sr0 * np.pow(2, np.arange(B))
57+
sr_ref = srs[-1] * OF_ref
58+
dts = 1 / srs
59+
60+
d = os.path.dirname(os.path.abspath(os.path.join(result_folder, "settings.h5")))
61+
if d and not os.path.exists(d):
62+
os.makedirs(d, exist_ok=True)
63+
64+
with h5py.File(os.path.join(result_folder, "settings.h5"), "w") as f:
65+
f.attrs["sr0"] = json.dumps(sr0)
66+
f.attrs["srs"] = json.dumps([float(sr) for sr in srs])
67+
f.attrs["lambda0s"] = json.dumps([float(lambda0) for lambda0 in lambda0s])
68+
f.attrs["sr_ref"] = json.dumps(float(sr_ref))
69+
f.attrs["A0"] = json.dumps(A0)
70+
71+
for kappa in kappas:
72+
for i, sr in enumerate(srs):
73+
model = FD_string_model(sr0, **string_params)
74+
75+
model.recompute_stability(sr, kappa = kappa)
76+
77+
storage = STATE_STORAGE_CONFIG
78+
storage["q_idx"] = np.array([model.N//2 + 1])
79+
storage["p_idx"] = None
80+
81+
def u_func(t):
82+
return np.zeros(model.Nu)
83+
84+
# Create the initial condition vector: first mode on the displacement
85+
# End point are fixed and there are N points in excluding endpoints
86+
q0 = np.sin(np.pi * np.arange(model.N+2) / (model.N+1))[1:-1] * A0
87+
u0 = np.zeros(model.N)
88+
89+
d = os.path.dirname(os.path.abspath(os.path.join(result_folder, f"{kappa}")))
90+
if d and not os.path.exists(d):
91+
os.makedirs(d, exist_ok=True)
92+
93+
for lambda0 in lambda0s:
94+
solver = SAVSolver(model, float(sr), float(lambda0))
95+
solver.integrate(q0, u0, u_func, duration, ConstantRmid=True, plotter_config=NO_PLOTTER_CONFIG, storage_config=storage)
96+
solver.storage.write(os.path.join(result_folder, f"{kappa}/sr{sr}_lambda{lambda0}.h5"))
97+
print(f"Finished computing for sr = {sr}")
98+
99+
#%% Compute references by oversampling sr_ref_OF times
100+
model = FD_string_model(sr0, **string_params)
101+
model.recompute_stability(sr_ref, 0.9)
102+
103+
storage = STATE_STORAGE_CONFIG
104+
storage["q_idx"] = np.array([model.N//2 + 1])
105+
storage["p_idx"] = None
106+
107+
def u_func(t):
108+
return np.zeros(model.Nu)
109+
110+
# Create the initial condition vector: first mode on the displacement
111+
# End point are fixed and there are N points in excluding endpoints
112+
q0 = np.sin(np.pi * np.arange(model.N+2) / (model.N + 1))[1:-1] * A0
113+
u0 = np.zeros(model.N)
114+
115+
solver = SAVSolver(model, float(sr_ref), float(0))
116+
solver.integrate_verlet(q0, u0, u_func, duration, ConstantRmid=True, plotter_config=NO_PLOTTER_CONFIG, storage_config=storage)
117+
solver.storage.write(os.path.join(result_folder, f"ref.h5"))
118+
print(f"Finished computing ref")
119+
120+
121+
#%% Plot results
122+
for kappa in kappas:
123+
#%% Retrieve reference data at midpoint
124+
with h5py.File(os.path.join(result_folder, f"ref.h5"), "r") as f:
125+
q_ref = f["q"][...]
126+
model_setting = json.loads(f.attrs['model_dict'])
127+
q_ref = q_ref[::int(sr_ref / sr0), 0]
128+
129+
# %% For each file, compute and store the L2 error on the midpoint of the string at sr0
130+
def compute_error(q, qref):
131+
return np.linalg.norm(q - qref, 2) / np.linalg.norm(qref, 2)
132+
133+
errors = np.zeros((len(srs), len(lambda0s)))
134+
hs = np.zeros(len(srs))
135+
136+
for i, sr in enumerate(srs):
137+
for j, lambda0 in enumerate(lambda0s):
138+
with h5py.File(os.path.join(result_folder, f"{kappa}/sr{int(sr)}_lambda{int(lambda0)}.h5"), "r") as f:
139+
q = f["q"][...]
140+
model_setting = json.loads(f.attrs['model_dict'])
141+
q = q[::int(sr / sr0), 0]
142+
errors[i, j] = compute_error(q, q_ref)
143+
144+
hs[i] = model_setting["h"]
145+
146+
#%% Plot
147+
fig = plt.figure(figsize = set_size("DAFx", fraction=0.5, height_ratio=0.8))
148+
ax = plt.gca()
149+
ax.set_xscale("log")
150+
ax.set_yscale("log")
151+
for i, lambda0 in enumerate(lambda0s):
152+
ax.plot(hs, errors[:, i], label=r"$\lambda_0 = $" + f"{lambda0}", ls="dotted", marker = '+', markersize = 5)
153+
154+
ax.legend(frameon = True) #, ncol=3, bbox_to_anchor=(0.5, 1.05), loc = "upper center")
155+
ax.set_ylim([np.min(errors) * 0.5, np.max(errors) * 2])
156+
157+
ax.plot([hs[-1], hs[0]], [errors[-1, 0], errors[-1, 0]/ (hs[-1] / hs[0])**2], linestyle = "--", color="gray")
158+
159+
# Set x-axis ticks to align with data points
160+
stride = 2
161+
ax.xaxis.set_minor_formatter(NullFormatter())
162+
ax.xaxis.set_tick_params(which='minor', size=0)
163+
ax.xaxis.set_tick_params(which='minor', width=0)
164+
ax.set_xticks(hs[::stride])
165+
ax.set_xticklabels([f"{x:.2e}" for x in hs[::stride]])
166+
# Add a secondary x axis with sampling frequency
167+
def forward(x):
168+
return x
169+
def backward(x):
170+
return x
171+
secax = plt.gca().secondary_xaxis('top', functions=(forward, backward))
172+
secax.set_xlabel('sr (Hz)')
173+
secax.set_xticks(hs[::stride])
174+
secax.set_xticklabels([f"{x:.2e}" for x in srs[::stride]])
175+
secax.xaxis.set_minor_formatter(NullFormatter())
176+
secax.xaxis.set_tick_params(which='minor', size=0)
177+
secax.xaxis.set_tick_params(which='minor', width=0)
178+
plt.xlabel("h (m)")
179+
plt.ylabel("Relative error e")
180+
plt.tight_layout()
181+
plt.show()

0 commit comments

Comments
 (0)