waveguide.py

class waveguicsx.waveguide.Waveguide(comm: _MPI.Comm, M: Mat, K0: Mat, K1: Mat, K2: Mat)[source]

Bases: object

A class for solving complex waveguide problems based on SLEPc eigensolver.

The full documentation is entirely defined in the `waveguide.py’ module.

The following matrix problem is considered: (K0-omega**2*M + 1j*k*(K1-K1^T) + k**2*K2)*U=F. This kind of problem typically stems from the so-called SAFE (Semi-Analytical Finite Element) method.

The class enables to deal with complex waveguides, two-dimensional (e.g. plates) or three-dimensional (arbitrarily shaped cross-section), inhomogeneous in the transverse directions, anisotropic. Complex-valued problems can be handled including the effects of non-propagating modes (evanescent, inhomogeneous), viscoelastic loss (complex material properties) or perfectly matched layers (PML) to simulate buried waveguides.

The free response (F=0) is an eigenvalue problem, solved iteratively by varying the parameter which can be the angular frequency omega or the wavenumber k. In the former case, the eigenvalue is k, while in the latter case, the eigenvalue is omega^2. The loops over the parameter (angular frequency or wavenumber) can be parallelized, as shown in some tutorials (using mpi4py).

Various modal properties (energy velocity, group velocity, excitability…) can be post-processed as a function of the frequency and plotted as dispersion curves.

The forced reponse (F is not 0) is solved in the frequency domain by expanding the solution as a sum of eigenmodes using biorthogonality relationship, leading to very fast computations of excited wavefields.

Example:

# In this example, the matrices M, K0, K1, K2 and the excitation vector F are supposed to be dimensional for simplicity
# Yet, in practice, the problem would better be normalized to avoid ill-conditioning (see tutorials)

from waveguicsx.waveguide import Waveguide

# Definition of the excitation signal (here, a toneburst)
excitation = Signal()
excitation.toneburst(fs=400e3, T=2e-3, fc=100e3, n=8) #central frequency 100 kHz, 8 cycles, duration 2 ms, sampling frequency 400 kHz
excitation.plot() #plot time signal
excitation.plot_spectrum() #plot spectrum
omega = 2*np.pi*excitation.frequency #angular frequency range

# Initialization of waveguide
wg = Waveguide(MPI.COMM_WORLD, M, K0, K1, K2)
wg.set_parameters(omega=omega) #set the parameter range (here, angular frequency)

# Free response (dispersion curves)
wg.solve(nev=20, target=0) #solution of eigenvalue problem (iteration over the parameter omega), 20 eigenvalues requested at each frequency
wg.compute_energy_velocity() #post-process energy velocity
wg.plot() #plot k vs. omega
wg.plot_energy_velocity() #plot ve vs. omega

# Computation of modal coefficients due to an excitation vector F
wg.compute_response_coefficient(F=F) #F should be a PETSc vector
wg.plot_coefficient() #plot modal coefficients vs. omega

# Forced response at degree of freedom dof and axial coordinates z (dof should be an integer)
frequency, response = wg.compute_response(dof=dof, z=[0.5, 1., 1.5, 2.], spectrum=excitation.spectrum, plot=False) #response in the frequency domain
response = Signal(frequency=frequency, spectrum=response) #define response as a Signal object
response.plot_spectrum() #plot frequency response
response.ifft() #response in the time domain
response.plot() #plot time response
plt.show()

Attributes

commmpi4py.MPI.Intracomm

MPI communicator (parallel processing)

M, K0, K1, K2petsc4py.PETSc.Mat

SAFE matrices

Fpetsc4py.PETSc.Vec

SAFE excitation vector

problem_typestr

problem_type is “omega” if the varying parameter is omega, “wavenumber” if this is k

two_sidedbool

if True, left eigenvectors will be also computed (otherwise, only right eigenvectors are computed)

target: complex number or user-defined function of the parameter

target around which eigenpairs are looked for (see method solve)

omega or wavenumbernumpy.ndarray

the parameter range specified by the user (see method set_parameters)

evpPEP or EPS instance (SLEPc object)

eigensolver parameters

eigenvalueslist of numpy arrays

list of wavenumbers or angular frequencies, access to components with eigenvalues[ip][imode] (ip: parameter index, imode: mode index)

eigenvectorslist of PETSc matrices

list of mode shapes, access to components with eigenvectors[ik][idof,imode] (ik: parameter index, imode: mode index, idof: dof index) or eigenvectors[ik].getColumnVector(imode)

eigenforceslist of PETSc matrices

list of eigenforces (acces to components: see eigenvectors)

opposite_goinglist of numpy arrays

list of opposite-going mode (acces to components: see eigenvectors)

energy_velocitylist of numpy arrays

list of energy velocity (access to component: see eigenvalues)

group_velocitylist of numpy arrays

list of group velocity (access to component: see eigenvalues)

traveling_directionlist of numpy arrays

list of traveling_direction (access to component: see eigenvalues)

pml_ratiolist of numpy arrays

list of pml ratio, used for filtering out PML modes (access to component: see eigenvalues)

coefficientlist of numpy arrays

list of response coefficient to excitation vector F (access to component: see eigenvalues)

excitabilitylist of numpy arrays

list of excitability to excitation vector F (access to component: see eigenvalues)

complex_powerlist of numpy arrays

list of complex power flow of individual modes (access to component: see eigenvalues)

plot_scalerdictionnary

dictionnary containing the scaling factors of various modal properties, useful to plot results in a dimensional form

Methods

__init__(comm:’_MPI.Comm’, M:PETSc.Mat, K0:PETSc.Mat, K1:PETSc.Mat, K2:PETSc.Mat):

Constructor, initialization of waveguide

set_parameters(omega=None, wavenumber=None, two_sided=False):

Set problem type (problem_type), the parameter range (omega or wavenumber) as well as default parameters of SLEPc eigensolver (evp); set two_sided to True to compute left eigenvectors also (left eigenvectors are the opposite-going modes)

solve(nev=1, target=0):

Solve the eigenvalue problem repeatedly for the parameter range, solutions are stored as attributes (names: eigenvalues, eigenvectors)

compute_eigenforce():

Compute the eigenforces for the whole parameter range and store them as an attribute (name: eigenforces)

compute_poynting_normalization():

Normalization of eigenvectors and eigenforces, so that U’=U/sqrt(|P|), where P is the normal component of complex Poynting vector

compute_opposite_going(plot=False):

Compute opposite-going mode pairs based on on wavenumber and biorthogonality for the whole parameter range and store them as attributes (name: opposite_going), set plot to True to visualize the biorthogonality values of detected pairs

compute_energy_velocity():

Compute the energy velocities for the whole parameter range and store them as an attribute (name: energy_velocity)

compute_group_velocity():

Compute the group velocities for the whole parameter range and store them as an attribute (name: energy_velocity)

compute_traveling_direction():

Compute the traveling directions for the whole parameter range and store them as an attribute (name: traveling_direction)

compute_pml_ratio():

Compute the pml ratios for the whole parameter range and store them as an attribute (name: pml_ratio)

compute_response_coefficient(F, spectrum=None, wavenumber_function=None, dof=None):

Compute the response coefficients due to excitation vector F for the whole parameter range and store them as an attribute (name: coefficient)

compute_complex_power(self):

Compute the individual complex power flow of modes given by P=-1j*omega/2*U^H*F

compute_response(dof, z, spectrum=None, wavenumber_function=None, plot=False):

Compute the response at the degree of freedom dof and the axial coordinate z for the whole frequency range

track_mode(omega_index, mode_index, threshold=0.9, plot=False):

Track a mode over the whole frequency range thanks to eigenvector similarity. The mode is specified by its index, mode_index, at a given angular frequency index, omega_index.

plot(direction=None, pml_threshold=None, ax=None, color=”k”, marker=”o”, markersize=2, linestyle=””, **kwargs):

Plot dispersion curves Re(omega) vs. Re(wavenumber) using matplotlib

plot_phase_velocity(direction=None, pml_threshold=None, ax=None, color=”k”, marker=”o”, markersize=2, linestyle=””, **kwargs):

Plot phase velocity dispersion curves, vp=Re(omega)/Re(wavenumber) vs. Re(omega)

plot_attenuation(direction=None, pml_threshold=None, ax=None, color=”k”, marker=”o”, markersize=2, linestyle=””, **kwargs):

Plot attenuation dispersion curves, Im(wavenumber) vs. Re(omega) if omega is the parameter, or Im(omega) vs. Re(omega) if wavenumber is the parameter

plot_energy_velocity(direction=None, pml_threshold=None, ax=None, color=”k”, marker=”o”, markersize=2, linestyle=””, **kwargs):

Plot energy velocity dispersion curves, ve vs. Re(omega)

plot_group_velocity(direction=None, pml_threshold=None, ax=None, color=”k”, marker=”o”, markersize=2, linestyle=””, **kwargs):

Plot group velocity dispersion curves, vg vs. Re(omega)

plot_coefficient(direction=None, pml_threshold=None, ax=None, color=”k”, marker=”o”, markersize=2, linestyle=””, **kwargs):

Plot response coefficients as a function of frequency, |q| vs. Re(omega)

plot_excitability(direction=None, pml_threshold=None, ax=None, color=”k”, marker=”o”, markersize=2, linestyle=””, **kwargs):

Plot excitability as a function of frequency, |e| vs. Re(omega)

plot_spectrum(index=0, ax=None, color=”k”, marker=”o”, markersize=2, linestyle=””, **kwargs):

Plot the spectrum, Im(eigenvalues) vs. Re(eigenvalues), for the parameter index specified by the user

set_plot_scaler(length=1, time=1, mass=1, dim=3):

Define the characteristic length, time, mass, as well as dim, and calculate the scaling factors of modal properties, which are stored in the attribute name plot_scaler (useful to visualize plots in a dimensional form)

set_parameters(omega: Optional[ndarray[complex]] = None, wavenumber: Optional[ndarray[complex]] = None, two_sided: bool = False)[source]

Set the parameter range (omega or wavenumber) as well as default parameters of the SLEPc eigensolver (evp). The user must specify the parameter omega or wavenumber, but not both. This method generates the attributes omega (or wavenumber) and evp. After calling this method, various SLEPc parameters can be set by changing the attribute evp manually. Set two_sided=True for solving left eigenvectors also.

Parameters

omega or wavenumbernumpy.ndarray

the parameter range specified by the user

two_sidedbool

False if left eigenvectiors are not needed, True if they must be solved also

solve(nev=1, target=0)[source]

Solve the dispersion problem, i.e. the eigenvalue problem repeatedly for the parameter range (omega or wavenumber). The solutions are stored in the attributes eigenvalues and eigenvectors. If two_sided is True, left eigensolutions are also solved.

Note: left eigensolutions correspond to opposite-going modes and are hence added to the right eigensolutions (i.e. in eigenvalues and eigenvectors) after removing any possible duplicates.

Parameters

nevint

number of eigenpairs requested

targetcomplex number or user-defined function of the parameter, optional (default: 0)

target around which eigenpairs are looked for a small shift might sometimes prevent errors (e.g. zero pivot with dirichlet bc)

compute_eigenforces()[source]

Post-process the eigenforces F=(K1^T+1j*k*K2)*U for every mode in the whole parameter range

compute_poynting_normalization()[source]

Post-process the normalization of eigenvectors and eigenforces, so that U’=U/sqrt(|P|), where P is the normal component of complex Poynting vector (P=-1j*omega/2*U^H*F). After normalization, every mode is such that |P|=1 and the attribute _poynting_normalization is set to True. Normalization is not mandatory but, when applied, has to be done before any response coefficient computation.

compute_energy_velocity()[source]

Post-process the energy velocity ve=Re(P)/Re(E) for every mode in the whole parameter range, where P is the normal component of complex Poynting vector and E is the total energy (cross-section time-averaged). Warning in case of PML: the integration is currently applied over the whole cross-section (including PML), the so-defined energy velocity is questionable.

compute_opposite_going(plot=False)[source]

Post-process the pairing of opposite-going modes, based on wavenumber and biorthogonality criteria, and store them as a an attribute (name: opposite_going, -1 value for unpaired modes). Compute their biorthogonality normalization factors, Um^T*F-m - U-m^T*Fm, where m and -m denote opposite-going modes, for the whole parameter range and store them as an attribute (name: _biorthogonality_factor). If plot is set to True, the biorthogonality criterion found by the algorithm is plotted as a function of frequency index, allowing visual check that there is no values close to zero (a factor close to zero probably means a lack of biorthogonality). The biorthogonality criterion is defined as |biorthogonality_factor*omega/4|.

Notes:

  • when an unpaired mode is found, the value -1 is stored in opposite_going (and NaN value in _biorthogonality_factor), meaning that this mode will be discarded in the computation of group velocity, traveling direction, coefficient and excitability (NaN values stored)

  • if modes with lack of biorthogonality or two many unpaired modes occur, try to recompute the eigenproblem by increasing the accuracy (e.g. reducing the tolerance)

  • lack of biorthogonality may be also due to multiple modes (*); in this case, try to use an unstructured mesh instead

  • if two_sided is True, lack of biorthogonolity may occur for specific target: try another target (e.g. add a small imaginary part)

(*) e.g. flexural modes in a cylinder with structured mesh

compute_group_velocity()[source]

Post-process the group velocity, vg=1/Re(dk/domega) for every mode in the whole parameter range (opposite-going modes required). For unpaired modes, NaN values are set.

compute_traveling_direction(delta=0.01)[source]

Post-process the traveling direction, +1 or -1, for every mode in the whole parameter range, using the sign of Im(k + 1j*delta/v) where delta is the imaginary shift used for analytical continuation of k, and v is the group velocity (or, if not available, the energy velocity). This criterion is based on the limiting absorption principle (theoretically, vg should be used instead of ve). For unpaired modes, NaN values are set.

compute_pml_ratio()[source]

Post-process the pml ratio (useful to filter out PML mode), given by 1-Im(Ek)/|Ek| where Ek denotes the “complex” kinetic energy, for every mode in the whole parameter range. Reminder: the pml ratio tends to 1 for mode shapes vanishing inside the PML.

compute_response_coefficient(F, spectrum=None, wavenumber_function=None, dof=None)[source]

Computation of modal coefficients due to the excitation vector F for every mode in the whole omega range (opposite-going eigenvectors are required). Modal coefficients qm are defined from: U(z,omega) = sum qm(omega)*Um(omega)*exp(i*km*z), m=1…M, omega denotes the angular frequency. For unpaired modes, NaN values are set. Assumption: the source is centred at z=0.

Note: spectrum and wavenumber_function can be specified in compute_response(…) instead of compute_response_coefficient(…), but not in both functions in the same time (otherwise the excitation will be modulated twice)

Parameters

FPETSc vector

SAFE excitation vector

spectrumnumpy.ndarray

when specified, spectrum is a vector of length omega used to modulate F in terms of frequency (default: 1 for all frequencies)

wavenumber_function: python function

when specified, wavenumber_function is a python function used to modulate F in terms of wavenumber (example: wavenumber_function = lambda x: np.sin(x), default: 1 for all wavenumbers, i.e. source localized at z=0)

dofint

when specified, it calculates the modal excitability (stored in the attribute excitability), i.e. qm*Um at the degree of freedom dof and for a unit excitation vector (i.e. such that the sum of the elements of F is equal to 1)

compute_complex_power()[source]

Post-process the individual complex power flow of modes given by P=-1j*omega/2*U^H*F (normal component of complex Poynting vector), where U and F denote the eigenvector and eigenforce of a single mode. The ‘usual’ power is given by the real part.

Notes:

  • For lossy media (e.g. with viscoelasticity or with PML), it should be reminded that the individual power should be carefully handled: the power of a sum is not equal to the sum of powers because Auld’s complex biorthogonality relation does no longer hold

  • This inequality also generally applies between two multiple modes if any(*); in this case, try to use an unstructured mesh instead?

  • Warning in case of PML: integration is currently done over the whole cross-section (see also compute_energy_velocity), the so-defined complex power flow is questionable

(*) e.g. flexural modes in a cylinder with structured mesh, whether the medium is lossy or lossless

compute_response(dof, z, omega_index=None, spectrum=None, wavenumber_function=None, plot=False)[source]

Post-process the response (modal expansion) at the degree of freedom dof and the axial coordinate z, for the whole frequency range. The outputs are frequency, a numpy 1d array of size len(omega), and response, a numpy 2d array of size len(dof or z)*len(omega). dof and z cannot be both vectors, except if omega_index is specified or omega is scalar (single frequency computation): in that case, the array response is of size len(z)*len(dof), which can be useful to plot the whole field at a single frequency.

The response at each frequency omega is calculated from: U(z,omega) = sum qm(omega)*Um(omega)*exp(i*km*z), m=1…M, where z is the receiver position along the waveguide axis. M is the number of modes traveling in the proper direction, positive if z is positive, negative if z is negative. The pairing of opposite-going eigenvectors is required, unpaired modes are discarded from the expansion.

The outputs frequency and response are made dimensional when values in plot_scaler are not set to 1.

Assumption: the source is assumed to be centred at z=0.

Warning: the response calculation is only valid if z lies oustide the source region.

Note: spectrum and wavenumber_function can be specified in compute_response_coefficient(…) instead of compute_response(…), but not in both functions in the same time (otherwise the excitation will be modulated twice).

Parameters

dofnumpy array of integer

dof where the response is computed

znumpy array

axial coordinate where the response is computed

omega_indexint

omega index to compute the response at a single frequency, allowing the consideration of multiple dof and z

spectrumnumpy.ndarray

when specified, spectrum is a vector of length omega used to modulate F in terms of frequency (default: 1 for all frequencies)

wavenumber_function: python function

when specified, wavenumber_function is a python function used to modulate F in terms of wavenumber (example: wavenumber_function = lambda x: np.sin(x), default: 1 for all wavenumbers, i.e. source localized at z=0)

plotbool

if set to True, the magnitude and phase of response are plotted as a function of frequency

Returns

frequencynumpy 1d array

the frequency vector, i.e. omega/(2*pi)

responsenumpy array (1d or 2d)

the matrix response

ll_abs : matplotlib list of lines for magnitude plot when plot is set to True ll_angle : matplotlib list of lines for phase plot when plot is set to True

track_mode(omega_index, mode_index, threshold=0.9, plot=False)[source]

Track a mode over the whole frequency range. The mode is specified by its index, mode_index, at a given angular frequency index, omega_index. Tracking is performed thanks to similarity between eigenvectors and eigenforces (value between 0 and 1). Tracking is stopped if similarity becomes lower than threshold. It returns mode, the index list identifying the mode position at each frequency (index is set to -1 for frequencies at which the mode has not been successfully tracked due to low similarity). If plot is set to True, the real and imaginary parts of eigenvalue are plotted w.r.t. frequency index, for visual check that the desired mode has been properly tracked.

plot_phase_velocity(**kwargs)[source]

Plot phase velocity dispersion curves, vp vs. Re(omega), where omega is replaced with frequency for dimensional results. Parameters and Returns: see plot(…).

plot_attenuation(**kwargs)[source]

Plot attenuation dispersion curves, Im(wavenumber) vs. Re(omega) if omega is the parameter, or Im(omega) vs. Re(wavenumber) if wavenumber is the parameter, where omega is replaced with frequency for dimensional results. Parameters and Returns: see plot(…).

plot_energy_velocity(**kwargs)[source]

Plot energy velocity dispersion curves, ve vs. Re(omega), where omega is replaced with frequency for dimensional results. Parameters and Returns: see plot(…).

plot_group_velocity(**kwargs)[source]

Plot group velocity dispersion curves, vg vs. Re(omega), where omega is replaced with frequency for dimensional results. Parameters and Returns: see plot(…).

plot_coefficient(**kwargs)[source]

Plot response coefficients as a function of frequency, |q| vs. Re(omega), where omega is replaced with frequency for dimensional results. Parameters and Returns: see plot(…).

plot_excitability(**kwargs)[source]

Plot excitability as a function of frequency, |e| vs. Re(omega), where omega is replaced with frequency for dimensional results. Parameters and Returns: see plot(…).

plot_complex_power(**kwargs)[source]

Plot complex power flow of individual modes as a function of frequency, Re(P) and Im(P) vs. Re(omega), where omega is replaced with frequency for dimensional results. Gray color is used for the imaginary part. Parameters and Returns: see plot(…).

plot(x=None, y=None, c=None, direction=None, pml_threshold=None, mode=None, ax=None, color='k', marker='o', markersize=2, **kwargs)[source]

Plot dispersion curves y[1](y[0]) vs. x[1](x[0]) as scatter plot. If the index list, mode, is specified by the user, a single mode is plotted as a continuous single colored curve (the index list mode can be obtained from method track_mode(…)).

Parameters

x, y, c: list

x[0], y[0], c[0] are strings corresponding to modal properties for the x-axis, y-axis and marker colors respectively (these strings can be: ‘omega’, ‘wavenumber’, ‘energy_velocity’, ‘group_velocity’, ‘pml_ratio’, ‘eigenvalues’, ‘excitability’, ‘eigenvectors’, ‘eigenforces’, ‘coefficient’, ‘power flow’, ‘frequency’, ‘attenuation’, ‘phase_velocity’), x[1], y[1], c[1] are the functions applied to x[0], y[0] and c[0] respectively (e.g. np.abs, np.angle, np.real, np.imag, etc.). If x is None but not y, x is set to [‘omega’, np.real] if results are normalized, or set to [‘frequency’, np.real] if they are dimensional. If both x and are None, plot dispersion curves Re(omega) or Re(frequency) vs. Re(wavenumber). If c is None, a single color is used for coloring markers (given by the input variable color).

direction: int

+1 for positive-going modes, -1 for negative-going modes, None for plotting all modes

pml_threshold: float

threshold to filter out PML modes (modes such that pml_ratio<pml_threshold)

mode: index list

index list identifying the mode position at each frequency

ax: matplotlib axis

the matplotlib axis on which to plot data (created if None)

color: str, marker: str, markersize: int, linestyle: str, **kwargs are passed to ax.plot

Returns

sc: the matplotlib collection

set_plot_scaler(length=1, time=1, mass=1, dim=3)[source]

Define the characteristic length, time and mass in order to visualize plots in a dimensional form (by default, they are equal to 1). Set dim=3 for three-dimensional waveguides, dim=2 for two-dimensional waveguides (e.g. plates). Scaling factors for ‘omega’, ‘wavenumber’, ‘energy_velocity’, ‘group_velocity’, ‘pml_ratio’, ‘eigenvalues’, ‘excitability’, ‘eigenvectors’, ‘eigenforces’, ‘coefficient’, ‘complex_power’, ‘frequency’, ‘attenuation’, ‘phase_velocity’ are stored in the attribute name plot_scaler. If poynting normalization has already been applied, then the scalers for ‘eigenvectors’, ‘eigenforces’ and ‘coefficient’ are such that the dimensional cross-section power flow of eigenmodes is equal to 1 Watt (if no poynting normalization applied, these scalers are left to 1). Reminder: while the dimension of U (displacement) is in meter, the dimension of F (force) is in Newton for 3D waveguides and in Newton/meter for 2D waveguides (F is in mass*length**(dim-2)/time**2).

plot_spectrum(index=0, c=None, ax=None, color='k', marker='o', markersize=2, **kwargs)[source]

Plot the spectrum, Im(k) vs. Re(k) computed for omega[index] (if the parameter is the frequency), or Im(omega) vs. Re(omega) for wavenumber[index] (if the parameter is the wavenumber).

Parameters

index: int

parameter index

c: list

c[0] is a string (must be an attribute of self) and c[1] is a function used for coloring markers, a single color (given by the input variable color) is used if c is None

ax: matplotlib axis

the matplotlib axis on which to plot data (created if None)

color: str, marker: str, markersize: int, linestyle: str, **kwargs are passed to ax.plot

Returns

sc: the matplotlib collection

class waveguicsx.waveguide.Signal(time=None, waveform=None, frequency=None, spectrum=None, alpha=0)[source]

Bases: object

A class for handling signals in the time domain and in the frequency domain

Reminder:

  • the sampling frequency fs must be at least twice the highest excited frequency (fs>=2fmax)

  • the time duration T must be large enough to capture the slowest wave at z, the source-receiver distance

Fourier transform definition used: X(f) = 2/T * integral of x(t)*exp(+i*omega*t)*dt

Two remarks:

  1. this is not the numpy fft function convention, which is in exp(-i*omega*t)

  2. the true amplitude of the Fourier transform, when needed, has to be obtained by multiplying the output (spectrum) by the scalar T/2, where T is the duration of the time signal (with the above definition: the division by T simplifies dimensionless analyses, and the factor 2 is used because only the positive part of the spectrum is considered)

Complex Fourier transform:

A complex Fourier transform is applied if alpha is set to a nonzero value. The frequency vector has then an imaginary part, constant and equal to alpha/(2*pi). Complex frequency computations can be useful for the analysis of long time duration signals (avoids aliasing). A good choice is alpha = log(50)/T. Note that the first frequency component is kept in that case (the frequency has a zero real part but non-zero imaginary part).

Example:

mysignal = Signal(alpha=0*np.log(50)/5e-4)
mysignal.toneburst(fs=5000e3, T=5e-4, fc=100e3, n=5)
mysignal.plot()
mysignal.fft()
mysignal.plot_spectrum()
mysignal.ifft(coeff=1)
mysignal.plot()
plt.show()

Attributes

timenumpy 1d array

time vector

waveformnumpy nd array

waveform vectors stacked as rows (waveform is an array of size number_of_signals*len(time))

frequencynumpy 1d array

frequency vector

spectrumnumpy nd array

spectrum vectors stacked as rows (spectrum is an array of size number_of_signals*len(frequency))

alphafloat

decaying parameter to apply complex Fourier transform (useful for long time duration signal)

Methods

__init__(time=None, waveform=None, frequency=None, spectrum=None, alpha=0):

Constructor, initialization of signal (specify either waveform vs. time or spectrum vs. frequency)

fft():

Compute Fourier transform, results are stored as attributes (names: frequency, spectrum)

ifft(coeff=1):

Compute inverse Fourier transform, results are stored as attributes (names: time, waveform)

ricker(fs, T, fc):

Generate a Ricker signal

toneburst(fs, T, fc, n):

Generate a toneburst signal

chirp(fs, T, f0, f1, chirp_duration):

Generate a chirp signal

plot(ax=None, color=”k”, linewidth=1, linestyle=”-”, **kwargs):

Plot time waveform (waveform vs. time)

plot_spectrum(ax=None, color=”k”, linewidth=1, linestyle=”-”, **kwargs):

Plot the spectrum (spectrum vs. frequency), in magnitude and phase

fft()[source]

Compute Fourier transform (positive frequency part only, time waveform are assumed to be real). If the number of time steps is odd, one point is added. The zero frequency, if any, is suppressed. Results are stored as attributes (names: frequency, spectrum). spectrum is an array of size number_of_signals*len(frequency)

ifft(coeff=1)[source]

Compute inverse Fourier transform (only the positive frequency part is needed, time waveform are assumed to be real). Zero padding is applied in the low-frequency range (if missing) and in the high-frequency range (if coeff is greater than 1). Zero padding in the high frequency range is applied up to the frequency coeff*max(frequency). Results are stored as attributes (names: time, waveform). waveform is an array of size number_of_signals*len(time).

ricker(fs, T, fc)[source]

Generate a Ricker wavelet signal of unit amplitude (fs: sampling frequency, T: time duration, fc: Ricker central frequency)

Note that for better accuracy:

  • fs is rounded so that fs/fc is an integer

  • T is adjusted so that the number of points is even

toneburst(fs, T, fc, n)[source]

Generate a toneburst signal (fs: sampling frequency, T: time duration, fc: central frequency, n: number of cycles). This signal is a Hanning-modulated n cycles sinusoidal toneburst centred at fc Hz (with unit amplitude). For this kind of excitation, fmax can be considered as 2*fc roughly, hence one should choose fs>=4fc.

Note that for better accuracy:

  • fs is rounded so that fs/fc is an integer

  • T is adjusted so that the number of points is even

chirp(fs, T, f0, f1, chirp_duration)[source]

Generate a chirp of unit amplitude (fs: sampling frequency, T: time duration, f0: first frequency, f1: last frequency, chirp_duration: time to sweep from f0 to f1). Note that for better accuracy, T is adjusted so that the number of points is even.

plot(ax=None, color='k', linewidth=1, linestyle='-', **kwargs)[source]

Plot time waveform (waveform vs. time)

Parameters

ax: matplotlib axis

the matplotlib axis on which to plot data (created if None)

color: str, linewidth: int, linestyle: str, **kwargs are passed to ax.plot

Returns

ll: the matplotlib list of lines

plot_spectrum(ax=None, color='k', linewidth=1, linestyle='-', **kwargs)[source]

Plot the spectrum (spectrum vs. frequency), in magnitude and phase

Parameters

ax: matplotlib axis

the matplotlib axis on which to plot data (created if None)

color: str, linewidth: int, linestyle: str, **kwargs are passed to ax.plot

Returns

ll_abs: the matplotlib list of lines for magnitude plot ll_angle: same but for phase plot

scattering.py

class waveguicsx.scattering.Scattering(comm: _MPI.Comm, M: Mat, K: Mat, C: Mat, tbcs: list)[source]

Bases: object

A class for solving scattering problems by local inhomogeneities in complex waveguides based on PETSc.

The full documentation is entirely defined in the `scattering.py’ module.

The following matrix problem is considered: (K-omega**2*M-1j*omega*C)*U=F. This kind of problem typically typically stems from a finite element (FE) model of a small portion of waveguide including a local inhomogeneity (e.g. defects). The cross-section extremities of the truncated FE model are then handled as transparent boundary conditions (BCs) to reproduce semi-infinite waveguides. The so-obtained scattering problem is solved repeatedly for each frequency. The loops over the angular frequency can be parallelized, as shown in some tutorials (using mpi4py).

This class enables to deal with scattering in complex waveguides, two-dimensional (e.g. plates) or three-dimensional (arbitrarily shaped cross-section), inhomogeneous in the transverse directions, anisotropic. Complex-valued problems can be handled including the effects of non-propagating modes (evanescent, inhomogeneous), viscoelastic loss (complex material properties) or perfectly matched layers (PML) to simulate buried waveguides.

Transparent BCs are Waveguide objects, which must have been solved prior to the scattering problem solution, yielding the following object attributes: omega, eigenvalues, eigenvectors, eigenforces and traveling direction (see waveguide.py module for details). Transparent BCs are localized by their degrees of freedom in the global vector U. This means that the local degree of freedom i of a given eigenvector/eigenforce stored in a Waveguide object is located at the global degree of freedom dofs[i] of the FE model.

The user must supply the following inputs: - K, M, C, the global FE matrices (stiffness, mass and viscous damping) - F and F_spectrum, the global FE vector of internal excitation (i.e. sources inside the FE model), if any,

and its spectrum

  • tbcs, a list of pairs (name, dofs) which characterize the transparent BCs, where name is a string specifying the attribute name of a given transparent BC (this attribute will be a Waveguide object) and dofs is a numpy array of the global degrees of freedom for this transparent BC.

  • the ingoing mode coefficients, specified by the attribute coefficient in each transparent BC

Important convention: if the waveguide axis of a transparent BC is oriented outward (i.e. outside the FE box), dofs are positive, but if oriented inward (i.e. inside the FE box), a negative sign has to be assigned to dofs by the user.

The solution to the scattering problem yields: - the displacement U of the FE model for each angular frequency omega - the outgoing modal coefficients of every transparent BC and for each omega - the energy balance post-processed for each angular frequency

which enables to check the error due to the modal truncature introduced in the transparent BCs.

See Attributes below for more details.

Example:

# This simple example involves only one transparent boundary condition (e.g. waveguide scattering by a free edge)
# The tbc (the "inlet") is supposed to be at the left-hand side of the FE box so that its outward normal is negative

from waveguicsx.waveguide import Waveguide
from waveguicsx.scattering import Scattering

# Input parameters
omega = 2*np.sqrt(3)*np.linspace(1.48, 1.60, num=100) #normalized angular frequency range
nev = 30 #tbc number of eigenvalues requested at each frequency

# Scattering initialization
ws = Scattering(MPI.COMM_WORLD, M, K, 0*M, [('waveguide0', -tbc_dofs)]) #M and K are the mass and stiffness matrices of the FE box
#reminder: tbc_dofs are the global degrees of freedom, set negative by convention when the normal is negative (here, we suppose n=-ey)

#Solve waveguide problem associated with the tbc
ws.waveguide0 = Waveguide(MPI.COMM_WORLD, Ms, K0, K1, K2) #Ms, K0, K1 and K2 are SAFE matrices associated with the tbc (here, named 'waveguide0')
ws.waveguide0.set_parameters(omega=omega)
ws.waveguide0.solve(nev)
ws.waveguide0.compute_traveling_direction()
ws.waveguide0.compute_poynting_normalization()

# Solving scattering problem
mode = ws.waveguide0.track_mode(frequency_index, mode_index, threshold=0.98, plot=True) #track a mode, specified by its index at a given frequency, over the whole frequency range
ws.set_ingoing_mode('waveguide0', mode) #set mode as a single ingoing mode, coeff is 1 (here, power is also 1 thanks to poynting normalization)
ws.set_parameters()
ws.solve()

# Plot reflected power coefficients vs. angular frequency
ws.waveguide0.compute_complex_power()
ws.waveguide0.plot(y=('complex_power', lambda x:np.abs(np.real(x))), direction=-1)

Attributes

commmpi4py.MPI.Intracomm

MPI communicator (parallel processing)

M, K, Cpetsc4py.PETSc.Mat

FE matrices

Fpetsc4py.PETSc.Vec

FE force vector (internal excitation)

F_spectrumnumpy.ndarray

the modulation spectrum of F (size must be the same as omega)

tbclist of pairs (name, dofs)

name is a string corresponding to the desired attribute name of a tbc (Waveguide object), dofs is a numpy array of the degrees of freedom of the tbc (positive if outward negative if inward)

omeganumpy.ndarray

the angular frequency range, specified by the user in tbcs (Waveguide objects)

ksp: KSP object (PETSc object)

solver parameters

displacementlist of PETSc vectors

for each angular frequency, the FE displacement vector U (solution of the scattering problem)

energy_balancelist of 1d numpy array

for each angular frequency, energy_balance[i] gives the following three-component array, [Pin-i*omega/2*U^H*F, Ptot, -i*omega*U^H*D*U], where P=-i*omega/2*U^H*T: - the term Pin-i*omega/2*U^H*F represents the input power, supplied by ingoing modes and internal

forces (this term should have a negative real part)

  • the term Ptot is the complex power flow of the sum of ingoing and ougoing modes

  • the term -i*omega*U^H*D*U is related to the kinetic, potential and dissipated energies in the volume (the dissipated energy, defined as positive, is equal to the real part of this term divided by -2*omega).

  • a perfect energy balance is achived if Ptot = -i*omega*U^H*D*U

name.coefficientlist of numpy.ndarray, where name is a Waveguide object associated with a given transparent BC

this transparent BC attribute stores modal coefficients at each frequency: - the coefficients of ingoing modes are considered as inputs, specified by the user (excitation in the scattering problem) - the coefficients of outgoing modes are considered as initially unknown, solution of the scattering problem Any non-zero outgoing amplitudes specified in name.coefficient prior to solving the scattering problem will hence be discarded and replaced with the scattering solution. If the attribute coefficient is empty prior to scattering (no specified ingoing modes), zero ingoing amplitudes will be considered by default.

Methods

__init__(comm:’_MPI.Comm’, M:PETSc.Mat, K:PETSc.Mat, C:PETSc.Mat, tbcs:list):

Constructor, initialization of scattering problem

set_ingoing_mode(tbc_name, mode, spectrum=None):

For a given tbc, specified by the string tbc_name, set the coefficient of a single mode to 1. mode is a list of indices identifying the mode position at each frequency.

set_internal_excitation(F, F_spectrum=None):

Set the internal excitation vector F and its spectrum F_spectrum

set_parameters(solver=’iterative’):

Set default parameters of KSP solver (stored in attribute ksp)

solve():

Solve the scattering problem repeatedly for the angular frequency range, solutions are stored as attributes (names: displacement, energy_balance)

plot_energy_balance():

Plot the three terms of energy_balance (complex modulus) at each frequency index for checking modal tbc truncature

set_ingoing_mode(tbc_name, mode, spectrum=None)[source]

For a given tbc, specified by the string tbc_name, set the coefficient of a single mode to 1. The coefficients of all other modes are set to 0. mode is a list of indices identifying the mode position at each frequency (see also method track_mode() of class Waveguide). Please ensure that this mode is an ingoing mode (non-zero outgoing amplitudes will be discarded). When specified, spectrum is a vector of length omega (numpy.array) used to modulate the coefficient in terms of frequency (default: 1 for all frequencies).

set_internal_excitation(F, F_spectrum=None)[source]

Set the internal excitation vector F (petsc4py.PETSc.Vec). When specified, F_spectrum is a vector of length omega (numpy.array) used to modulate F in terms of frequency (default: 1 for all frequencies). F and F_spectrum are stored as attributes.

set_parameters(solver='iterative')[source]

Set default parameters of KSP solver (stored into the attribute ksp.) The preselected methods are CGS (iterative method) and MUMPS (direct method). CGS is used by default, set solver=’direct’ to use MUMPS instead. After calling this method, various PETSc parameters can be set by changing the attribute ksp manually.

solve()[source]

Solve the scattering problem, i.e. the linear system repeatedly for the angular frequency range. The solutions are stored in the attributes displacement and energy_balance. The FE problem D*U=F is transformed into: Bout^T*(D*Bout-Tout)*Uout = Bout^T*(Tin-D*Bin)*Uin where: - D is the dynamic stiffness matrix (D=K-omega**2*M-1j*omega*C) - Bin and Bout (resp. Tin and Tout) are bases containing ingoing and outgoing modal

displacements (resp. forces) so that: U=Bin*Uin+Bout*Uout, F=Tin*Uin+Tout*Uout

  • Uin contains internal forces and known ingoing amplitudes (zero by default, if not specified)

  • Uout is the solution containing outgoing modal amplitudes and internal dofs

plot_energy_balance(ax=None, color='k', linewidth=1, linestyle='-', **kwargs)[source]

Plot energy balance at each frequency index for checking modal tbc truncature. The energy balance is defined as the difference between tbc net flux and volume power divided by the input power (values close to zero indicate that enough modes have been included in the transparent BCs).

Parameters

ax: matplotlib axis

the matplotlib axis on which to plot data (created if None)

color: str, linewidth: int, linestyle: str, **kwargs are passed to ax.plot

Returns

ll: the matplotlib list of lines