# -*- coding: utf-8 -*-
"""
Started on Tue Feb 15 17:25:59 2022
@author: Vincent MAHE
Analyse systems of coupled nonlinear equations using the Method of Multiple Scales (MMS).
This sub-module evaluates the symbolic expressions for given numerical parameters and allows to plot the resulting numerical results.
"""
#%% Imports and initialisation
from .. import sympy_functions as sfun
import numpy as np
import matplotlib.pyplot as plt
from .mms import rescale
#%% Classes and functions
[docs]
def plot_FRC(FRC, **kwargs):
r"""
Plots the frequency response curves (FRC), both frequency-amplitude and frequency-phase.
Also includes the stability information if given.
Parameters
----------
FRC : dict
Dictionary containing the frequency response curves and the bifurcation curves.
Returns
-------
fig1 : Figure
The amplitude plot :math:`a(\omega)`.
fig2 : Figure
The phase plot :math:`\beta(\omega)`.
"""
# Extract the FRC data
a = FRC.get("a", np.full(10, np.nan))
omega_bbc = FRC.get("omega_bbc", np.full_like(a, np.nan))
omega = FRC.get("omega", [np.full_like(a, np.nan)])
phase = FRC.get("phase", [np.full_like(a, np.nan)])
omega_bif = FRC.get("omega_bif", [np.full_like(a, np.nan)])
phase_bif = FRC.get("phase_bif", [np.full_like(a, np.nan)])
# Extract the keyword arguments
fig_param = kwargs.get("fig_param", dict())
amp_name = kwargs.get("amp_name", "amplitude")
phase_name = kwargs.get("phase_name", "phase")
xlim = kwargs.get("xlim", [coeff*np.min(omega_bbc) for coeff in (0.9, 1.1)])
if np.isnan(xlim).any():
xlim = [None, None]
# FRC - amplitude
fig1, ax = plt.subplots(**fig_param)
ax.plot(omega_bbc, a, c="tab:grey", lw=0.7)
ax.axvline(np.min(omega_bbc), c="k")
[ax.plot(omegai, a, c="tab:blue") for omegai in omega]
[ax.plot(omegai, a, c="tab:red", lw=0.7) for omegai in omega_bif]
ax.set_xlim(xlim)
ax.set_xlabel(r"$\omega$")
ax.set_ylabel(r"${}$".format(amp_name))
ax.margins(y=0)
plt.show(block=False)
# FRC - phase
fig2, ax = plt.subplots(**fig_param)
ax.axvline(np.min(omega_bbc), c="k")
ax.axhline(np.pi/2, c="k", lw=0.7)
[ax.plot(omegai, phasei, c="tab:blue") for (omegai, phasei) in zip(omega, phase)]
[ax.plot(omegai, phasei, c="tab:red", lw=0.7) for (omegai, phasei) in zip(omega_bif, phase_bif)]
ax.set_xlim(xlim)
ax.set_xlabel(r"$\omega$")
ax.set_ylabel(r"${}$".format(phase_name))
plt.show(block=False)
# Return
return fig1, fig2
[docs]
@staticmethod
def plot_ARC(ARC, **kwargs):
r"""
Plots the amplitude-response curves (ARC), both forcing amplitude-amplitude and forcing amplitude-phase.
Parameters
----------
ARC : dict
Dictionary containing the amplitude response curves.
Returns
-------
fig1 : Figure
The amplitude plot :math:`a(F)`.
fig2 : Figure
The phase plot :math:`\beta(F)`.
"""
# Extract the FRC data and keyword arguments
a = ARC.get("a", np.full(10, np.nan))
F = ARC.get("F", np.full_like(a, np.nan))
phase = ARC.get("phase", np.full_like(a, np.nan))
# Extract the keyword arguments
fig_param = kwargs.get("fig_param", dict())
amp_name = kwargs.get("amp_name", "amplitude")
phase_name = kwargs.get("phase_name", "phase")
xlim = kwargs.get("xlim", [0, np.max(F)])
# ARC - amplitude
fig1, ax = plt.subplots(**fig_param)
ax.plot(F, a, c="tab:blue")
ax.set_xlim(xlim)
ax.set_xlabel(r"$F$")
ax.set_ylabel(r"${}$".format(amp_name))
ax.margins(x=0, y=0)
plt.show(block=False)
# ARC - phase
fig2, ax = plt.subplots(**fig_param)
ax.axhline(np.pi/2, c="k", lw=0.7)
ax.plot(F, phase, c="tab:blue")
ax.set_xlim(xlim)
ax.set_xlabel(r"$F$")
ax.set_ylabel(r"${}$".format(phase_name))
ax.margins(x=0)
plt.show(block=False)
# Return
return fig1, fig2
[docs]
def numpise_FRC(mms, ss, dyn, param, bbc=True, forced=True, bif=True):
r"""
Evaluate the frequency-response and bifurcation curves at given numerical values.
This transforms the sympy expressions to numpy arrays.
Parameters
----------
mms : Multiple_scales_system
The MMS object.
ss : Steady_state
The MMS results evaluated at steady state.
dyn : Dynamical_system
The initial dynamical system.
param : dict
A dictionary whose values are tuples with 2 elements:
1. The sympy symbol of a parameter,
2. The numerical value(s) taken by that parameter.
The key of the amplitude vector must be ``"a"``.
The key of the forcing amplitude must be ``"F"``.
bbc : bool, optional
Evaluate the backbone curve.
Default is `True`.
forced : bool, optional
Evaluate the forced response.
Default is `True`.
bif : bool, optional
Evaluate the bifurcation curves.
Default is `True`.
Returns
-------
FRC : dict
The frequency-response curves data.
"""
# Information
print("Converting sympy FRC expressions to numpy")
# Initialisation
a = param.get("a")[1]
F_val = param.get("F")[1]
FRC = {"a": a}
# Evaluation of the FRC
if bbc:
FRC["omega_bbc"] = numpise_omega_bbc(mms, ss, param)
if forced:
FRC["omega"] = numpise_omega_FRC(mms, ss, param)
FRC["phase"] = numpise_phase(mms, ss, dyn, param, FRC["omega"], F_val)
if bif:
FRC["omega_bif"] = numpise_omega_bif(mms, ss, param)
FRC["phase_bif"] = numpise_phase(mms, ss, dyn, param, FRC["omega_bif"], F_val)
return FRC
[docs]
def numpise_ARC(mms, ss, dyn, param):
r"""
Evaluate the amplitude-response curves at given numerical values.
This transforms the sympy expressions to numpy arrays.
Parameters
----------
mms : Multiple_scales_system
The MMS object.
ss : Steady_state
The MMS results evaluated at steady state.
dyn : Dynamical_system
The initial dynamical system.
param : dict
A dictionnary whose values are tuples with 2 elements:
1. The sympy symbol of a parameter,
2. The numerical value(s) taken by that parameter.
The key of the amplitude vector must be ``"a"``.
The key of the angular frequency must be ``"omega"``.
Returns
-------
ARC: dict
The amplitude-response curves data.
"""
# Information
print("Converting sympy ARC expressions to numpy")
# Initialisation
a = param.get("a")[1]
omega_val = param.get("omega")[1]
ARC = {"a": a}
# Evaluation of the FRC
ARC["F"] = numpise_F_ARC(mms, ss, param)
ARC["phase"] = numpise_phase(mms, ss, dyn, param, omega_val, ARC["F"])[0]
return ARC
[docs]
def numpise_omega_bbc(mms, ss, param):
r"""
Numpise the backbone curve's frequency :math:`\omega_{\textrm{bbc}}`.
Parameters
----------
mms: Multiple_scales_system
ss: Steady_state
param: dict
See :func:`~MMS.sympy_functions.sympy_to_numpy`.
Returns
-------
omega_bbc: numpy.ndarray
Numpised backbone curve's frequency.
"""
omega_bbc = sfun.sympy_to_numpy(rescale(ss.sol.omega_bbc, mms), param)
return omega_bbc
[docs]
def numpise_omega_FRC(mms, ss, param):
r"""
Numpise the forced response's frequency :math:`\omega`.
Parameters
----------
mms: Multiple_scales_system
ss: Steady_state
param: dict
See :func:`~MMS.sympy_functions.sympy_to_numpy`.
Returns
-------
omega: numpy.ndarray
Numpised forced response's frequency.
"""
omega = [np.real(sfun.sympy_to_numpy(mms.omegaMMS + rescale(mms.eps*sigmai, mms), param)) for sigmai in ss.sol.sigma]
return omega
[docs]
def numpise_omega_bif(mms, ss, param):
r"""
Numpise the bifurcation curves' frequency :math:`\omega_{\textrm{bif}}`.
Parameters
----------
mms: Multiple_scales_system
ss: Steady_state
param: dict
See :func:`~MMS.sympy_functions.sympy_to_numpy`.
Returns
-------
omega_bif: list of numpy.ndarray
Numpised bifurcation curves' frequency.
"""
omega_bif = [np.real(sfun.sympy_to_numpy(mms.omegaMMS + rescale(mms.eps*sigmai, mms), param)) for sigmai in ss.stab.bif_sigma]
return omega_bif
[docs]
def numpise_phase(mms, ss, dyn, param, omega, F):
r"""
Numpise the phase :math:`\beta_i`.
Parameters
----------
mms: Multiple_scales_system
ss: Steady_state
dyn: Dynamical_system
param: dict
See :func:`~MMS.sympy_functions.sympy_to_numpy`.
omega: numpy.ndarray or list of numpy.ndarray
The frequency array.
F: numpy.ndarray
The forcing amplitude array.
Returns
-------
phase: list of numpy.ndarray
Numpised phase.
"""
if not isinstance(omega,list):
omega = [omega]
phase = []
for omegai in omega:
param_phase = param | dict(omega=(mms.omega, omegai), F=(dyn.forcing.F, F))
sin_phase = sfun.sympy_to_numpy( rescale(ss.sol.sin_phase[1], mms), param_phase )
cos_phase = sfun.sympy_to_numpy( rescale(ss.sol.cos_phase[1], mms), param_phase )
phase.append(np.arctan2(sin_phase, cos_phase))
return phase
[docs]
def numpise_F_ARC(mms, ss, param):
r"""
Numpise the forced response's forcing amplitude :math:`F`.
Parameters
----------
mms: Multiple_scales_system
ss: Steady_state
param: dict
See :func:`~MMS.sympy_functions.sympy_to_numpy`.
Returns
-------
F: numpy.ndarray
Numpised forced response's forcing amplitude.
"""
F = sfun.sympy_to_numpy(rescale(mms.eps**mms.forcing.f_order * ss.sol.F, mms), param)
return F