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