Source code for oscilate.MMS.visualisation

# -*- 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
from sympy.physics.vector.printing import vlatex
from typing import Union, TYPE_CHECKING
from numpy import ndarray

#%% Classes and functions
[docs] class Backbone_curve: """ Evaluate the backbone curve for given numerical parameters. This transforms the sympy expressions to numpy arrays. They can then be plotted. 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 : list[tuple] A list whose values are tuples with 2 elements: 1. The sympy symbol of a parameter, 2. The numerical value(s) taken by that parameter. """ # Class-level annotations for pyreverse if TYPE_CHECKING: a : np.ndarray omegaMMS : float omega : np.ndarray param : dict def __init__(self, mms, ss, dyn, param): # Information print("Converting sympy BBC expressions to numpy") # Construct a dictionary of substitutions param_dic = {} for ii in range(len(param)): if param[ii][0] == ss.coord.a[ss.sol_bbc.solve_dof]: param_dic["a"] = param[ii] elif param[ii][0] == dyn.forcing.F: param_dic["F"] = param[ii] else: param_dic[f"param_{ii}"] = param[ii] # Initialisation self.a = param_dic["a"][1] self.param = param_dic self.omegaMMS = numpise_omegaMMS(mms, param_dic) # Evaluation of the bbc self.omega = numpise_omega_bbc(mms, ss, param_dic) if ss.sol_bbc.xmax != None: self.xmax = numpise_xmax_bbc(mms, ss, param_dic)
[docs] def plot(self, **kwargs): r""" Plots the backbone curve. Parameters ---------- ss : Steady_state, optional Steady state object. Used to name the axis labels. Returns ------- fig : Figure The amplitude plot :math:`a(\omega)`. """ # Extract the bbc data a = self.__dict__.get("a", np.full(10, np.nan)) omega = self.__dict__.get("omega", np.full_like(a, np.nan)) omegaMMS = self.__dict__.get("omegaMMS", np.nan) # Extract the keyword arguments fig_param = kwargs.get("fig_param", dict()) if "ss" in kwargs.keys(): ss = kwargs.get("ss") amp_name = kwargs.get("amp_name", vlatex(ss.coord.a[ss.sol_forced.solve_dof])) else: amp_name = kwargs.get("amp_name", "amplitude") xlim = kwargs.get("xlim", [coeff*omegaMMS for coeff in (0.9, 1.1)]) if np.isnan(xlim).any(): xlim = [None, None] # Backbone curve fig, ax = plt.subplots(**fig_param) ax.plot(omega, a, c="tab:grey", lw=0.7) ax.axvline(omegaMMS, c="k") ax.set_xlim(xlim) ax.set_xlabel(r"$\omega_{\textrm{nl}}$") ax.set_ylabel(r"${}$".format(amp_name)) ax.margins(y=0) # Return return fig
[docs] class Frequency_response_curve: """ Evaluate the frequency response curves (FRC) and bifurcation curves (if computed) for given numerical parameters. This transforms the sympy expressions to numpy arrays. They can then be plotted. 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 : list[tuple] A list whose values are tuples with 2 elements: 1. The sympy symbol of a parameter, 2. The numerical value(s) taken by that parameter. bif : bool, optional Evaluate the bifurcation curves. Default is `True`. """ # Class-level annotations for pyreverse if TYPE_CHECKING: a : np.ndarray omega : list[ndarray] omegaMMS : float omega_bif : list[ndarray] param : dict phase : list[ndarray] phase_bif : list[ndarray] def __init__(self, mms, ss, dyn, param, bif=True): # Information print("Converting sympy FRC expressions to numpy") # Construct a dictionary of substitutions param_dic = {} for ii in range(len(param)): if param[ii][0] == ss.coord.a[ss.sol_forced.solve_dof]: param_dic["a"] = param[ii] elif param[ii][0] == dyn.forcing.F: param_dic["F"] = param[ii] else: param_dic[f"param_{ii}"] = param[ii] # Initialisation self.a = param_dic["a"][1] self.param = param_dic self.omegaMMS = numpise_omegaMMS(mms, param_dic) # Evaluation of the FRC self.omega = numpise_omega_FRC(mms, ss, param_dic) self.phase = [] for omegai in self.omega: self.phase.append(numpise_phase(mms, ss, dyn, param_dic, omegai, self.param["F"][1])) if bif: self.omega_bif = numpise_omega_bif(mms, ss, param_dic) if isinstance(self.omega_bif, np.ndarray): self.phase_bif = numpise_phase(mms, ss, dyn, param_dic, self.omega_bif, self.param["F"][1]) elif isinstance(self.omega_bif, list): self.phase_bif = [] for omegai in self.omega_bif: self.phase_bif.append(numpise_phase(mms, ss, dyn, param_dic, omegai, self.param["F"][1]))
[docs] def plot(self, bbc=None, **kwargs): r""" Plots the frequency response curves (FRC), both frequency-amplitude and frequency-phase. Also includes the stability information if computed, and the backbone curve if given. Parameters ---------- bbc : Backbone_curve, optional The evaluated backbone curve. Default is None. ss : Steady_state, optional Steady state object. Used to name the axis labels. Returns ------- fig1 : Figure The amplitude plot :math:`a(\omega)`. fig2 : Figure The phase plot :math:`\beta(\omega)`. """ # Extract the FRC data a = self.__dict__.get("a", np.full(10, np.nan)) omega = self.__dict__.get("omega", [np.full_like(a, np.nan)]) omegaMMS = self.__dict__.get("omegaMMS", np.nan) phase = self.__dict__.get("phase", [np.full_like(a, np.nan)]) omega_bif = self.__dict__.get("omega_bif", [np.full_like(a, np.nan)]) phase_bif = self.__dict__.get("phase_bif", [np.full_like(a, np.nan)]) # Extract the backbone curve data if any if bbc != None: a_bbc = bbc.__dict__.get("a" , np.full_like(a, np.nan)) omega_bbc = bbc.__dict__.get("omega", np.full_like(a, np.nan)) if not isinstance(omega_bbc, np.ndarray): omega_bbc = np.full_like(a_bbc, omega_bbc) # Extract the keyword arguments fig_param = kwargs.get("fig_param", dict()) if "ss" in kwargs.keys(): ss = kwargs.get("ss") amp_name = kwargs.get("amp_name", vlatex(ss.coord.a[ss.sol_forced.solve_dof])) phase_name = kwargs.get("phase_name", vlatex(ss.sol_forced.cos_phase[0].args[0])) else: amp_name = kwargs.get("amp_name", "amplitude") phase_name = kwargs.get("phase_name", "phase") xlim = kwargs.get("xlim", [coeff*omegaMMS for coeff in (0.9, 1.1)]) if np.isnan(xlim).any(): xlim = [None, None] # FRC - amplitude fig1, ax = plt.subplots(**fig_param) if bbc != None: ax.plot(omega_bbc, a_bbc, c="tab:grey", lw=0.7) ax.axvline(omegaMMS, 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) if bbc == None: ax.set_ylim(0, 1.2*np.max(a)) # FRC - phase fig2, ax = plt.subplots(**fig_param) ax.axvline(omegaMMS, c="k") ax.axhline(0.5, c="k", lw=0.7) [ax.plot(omegai, phasei/np.pi, c="tab:blue") for (omegai, phasei) in zip(omega, phase)] [ax.plot(omegai, phasei/np.pi, 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"${} \; [\pi]$".format(phase_name)) # Return return fig1, fig2
[docs] class Amplitude_response_curve: """ Evaluate the amplitude response curves (ARC) for given numerical parameters. 'Amplitude' refers to the forcing amplitude. This transforms the sympy expressions to numpy arrays. They can then be plotted. 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 : list[tuple] A list whose values are tuples with 2 elements: 1. The sympy symbol of a parameter, 2. The numerical value(s) taken by that parameter. """ # Class-level annotations for pyreverse if TYPE_CHECKING: F : Union[list[ndarray], ndarray] a : np.ndarray omegaMMS : float param : dict phase : list[ndarray] def __init__(self, mms, ss, dyn, param): # Information print("Converting sympy ARC expressions to numpy") # Construct a dictionary of substitutions param_dic = {} for ii in range(len(param)): if param[ii][0] == ss.coord.a[ss.sol_forced.solve_dof]: param_dic["a"] = param[ii] elif param[ii][0] == mms.omega: param_dic["omega"] = param[ii] else: param_dic[f"param_{ii}"] = param[ii] # Initialisation self.a = param_dic["a"][1] self.param = param_dic self.omegaMMS = numpise_omegaMMS(mms, param_dic) # Evaluation of the ARC self.F = numpise_F_ARC(mms, ss, self.param) if isinstance(self.F, np.ndarray): self.phase = numpise_phase(mms, ss, dyn, self.param, self.param["omega"][1], self.F) elif isinstance(self.F, list): self.phase = [] for Fi in self.F: self.phase.append(numpise_phase(mms, ss, dyn, self.param, self.param["omega"][1], Fi))
[docs] def plot(self, **kwargs): r""" Plots the amplitude-response curves (ARC), both forcing amplitude-amplitude and forcing amplitude-phase. Parameters ---------- ss : Steady_state, optional Steady state object. Used to name the axis labels. 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 = self.__dict__.get("a", np.full(10, np.nan)) F = self.__dict__.get("F", np.full_like(a, np.nan)) phase = self.__dict__.get("phase", np.full_like(a, np.nan)) # Extract the keyword arguments fig_param = kwargs.get("fig_param", dict()) if "ss" in kwargs.keys(): ss = kwargs.get("ss") amp_name = kwargs.get("amp_name", vlatex(ss.coord.a[ss.sol_forced.solve_dof])) phase_name = kwargs.get("phase_name", vlatex(ss.sol_forced.cos_phase[0].args[0])) else: amp_name = kwargs.get("amp_name", "amplitude") phase_name = kwargs.get("phase_name", "phase") if isinstance(F, np.ndarray): xlim = kwargs.get("xlim", [0, np.nanmax(F)]) elif isinstance(F, list): xlim = kwargs.get("xlim", [0, np.nanmax(np.hstack(F))]) if np.isinf(xlim).any(): xlim = [None, None] # ARC - amplitude fig1, ax = plt.subplots(**fig_param) if isinstance(F, np.ndarray): ax.plot(F, a, c="tab:blue") elif isinstance(F, list): [ax.plot(Fi, a, c="tab:blue") for Fi in F] ax.set_xlim(xlim) ax.set_xlabel(r"$F$") ax.set_ylabel(r"${}$".format(amp_name)) ax.margins(x=0, y=0) # ARC - phase fig2, ax = plt.subplots(**fig_param) ax.axhline(0.5, c="k", lw=0.7) if isinstance(F, np.ndarray): ax.plot(F, phase/np.pi, c="tab:blue") elif isinstance(F, list): [ax.plot(Fi, phasei/np.pi, c="tab:blue") for (Fi, phasei) in zip(F, phase)] ax.set_xlim(xlim) ax.set_xlabel(r"$F$") ax.set_ylabel(r"${} \; [\pi]$".format(phase_name)) ax.margins(x=0) # Return return fig1, fig2
[docs] class Transient_response: """ Evaluate the transient response for given numerical parameters. This transforms the sympy expressions to numpy arrays. They can then be plotted in the phase portrait and as time signals. Parameters ---------- mms : Multiple_scales_system The MMS object. param : list[tuple] A list whose values are tuples with 2 elements: 1. The sympy symbol of a parameter, 2. The numerical value(s) taken by that parameter. """ # Class-level annotations for pyreverse if TYPE_CHECKING: a : np.ndarray dxdt : np.ndarray param : dict psi : np.ndarray solve_dof : int t : np.ndarray x : np.ndarray def __init__(self, mms, param): # Information print("Converting sympy transient response expressions to numpy") # Construct a dictionary of substitutions param_dic = {} for ii in range(len(param)): if param[ii][0] == mms.t: param_dic["t"] = param[ii] elif param[ii][0] in mms.sol_transient.IC["a"].values(): param_dic["ai"] = param[ii] elif param[ii][0] in mms.sol_transient.IC["a"].values(): param_dic["betai"] = param[ii] else: param_dic[f"param_{ii}"] = param[ii] # Compute the slow time solutions a, psi, dadt, dpsidt = numpise_transient_slow_time(mms, param_dic) slow_sol = dict() slow_sol["a"] = (mms.coord.at[mms.sol_transient.solve_dof], a) slow_sol["psi"] = (mms.coord.psi, psi) slow_sol["dadt"] = (mms.coord.at[mms.sol_transient.solve_dof].diff(mms.t), dadt) slow_sol["dpsidt"] = (mms.coord.psi.diff(mms.t), dpsidt) # Compute the time signals x, dxdt = numpise_transient_trajectory(mms, param_dic | slow_sol) # Store the results self.param = param_dic self.t = param_dic["t"][1] self.a = a self.psi = psi self.x = x self.dxdt = dxdt self.solve_dof = mms.sol_transient.solve_dof
[docs] def plot_PP(self, c="tab:blue", **kwargs): r""" Plots the transient trajectory in the phase portrait. Parameters ---------- c : str, optional The color of the line. Returns ------- fig : Figure The phase portrait plot. """ # Extract the keyword arguments fig_param = kwargs.get("fig_param", dict()) # Trajectory plot fig, ax = plt.subplots(**fig_param) ax.plot(self.x, self.dxdt, c=c) ax.plot(self.x[0], self.dxdt[0], marker="o", mfc=c, mec="none", ms=4) # Labels ax.set_xlabel(r"$x_{}$".format(self.solve_dof)) ax.set_ylabel(r"$\dot{{x}}_{}$".format(self.solve_dof)) # Return return fig
[docs] def plot_time(self, c="tab:blue", **kwargs): r""" Plots the transient time response. Parameters ---------- c : str, optional The color of the line. Returns ------- fig : Figure The time plot. """ # Extract the keyword arguments fig_param = kwargs.get("fig_param", dict()) # Time plot fig, ax = plt.subplots(**fig_param) ax.plot(self.t, self.x, c=c) # Labels ax.set_xlabel(r"$t$") ax.set_ylabel(r"$x_{}$".format(self.solve_dof)) # Return return fig
[docs] class Limit_cycle: """ Evaluate the limit cycle for given numerical parameters. This transforms the sympy expressions to numpy arrays. They can then be plotted in the phase portrait and as time signals. Parameters ---------- mms : Multiple_scales_system The MMS object. ss : Steady_state The SS object. param : list[tuple] A list whose values are tuples with 2 elements: 1. The sympy symbol of a parameter, 2. The numerical value(s) taken by that parameter. Npts: int, optional Number of time points. Default is 1000. """ # Class-level annotations for pyreverse if TYPE_CHECKING: a : float beta : float dxdt : np.ndarray param : dict solve_dof : int t : np.ndarray x : np.ndarray def __init__(self, mms, ss, param, Npts=1000): # Information print("Converting sympy limit cycle expressions to numpy") # Construct a dictionary of substitutions param_dic = {} for ii in range(len(param)): param_dic[f"param_{ii}"] = param[ii] # Compute the LC amplitude, phase and frequency. a, beta, omega = numpise_LC(mms, ss, param_dic) LC_sol = dict() LC_sol["a"] = (ss.coord.a[ss.sol_LC.solve_dof], a) LC_sol["beta"] = (ss.coord.beta[ss.sol_LC.solve_dof], beta) LC_sol["omega"] = (ss.omega, omega) # Compute the time signals param_dic["t"] = (mms.t, np.linspace(0, 2*np.pi/omega, Npts)) x, dxdt = numpise_LC_trajectory(mms, ss, param_dic | LC_sol) # Store the results self.param = param_dic self.t = param_dic["t"][1] self.a = a self.beta = beta self.x = x self.dxdt = dxdt self.solve_dof = ss.sol_LC.solve_dof
[docs] def plot_PP(self, c="tab:blue", lw=2, **kwargs): r""" Plots the transient trajectory in the phase portrait. Parameters ---------- c : str, optional The color of the line. lw : float, optional The linewidth Default is 2. Returns ------- fig : Figure The phase portrait plot. """ # Extract the keyword arguments fig_param = kwargs.get("fig_param", dict()) # Trajectory plot fig, ax = plt.subplots(**fig_param) ax.plot(self.x, self.dxdt, c=c, lw=lw, zorder=10) # Labels ax.set_xlabel(r"$x_{}$".format(self.solve_dof)) ax.set_ylabel(r"$\dot{{x}}_{}$".format(self.solve_dof)) # Return return fig
[docs] def plot_time(self, c="tab:blue", **kwargs): r""" Plots the transient time response. Parameters ---------- c : str, optional The color of the line. Returns ------- fig : Figure The time plot. """ # Extract the keyword arguments fig_param = kwargs.get("fig_param", dict()) # Time plot fig, ax = plt.subplots(**fig_param) ax.plot(self.t, self.x, c=c) # Labels ax.set_xlabel(r"$t$") ax.set_ylabel(r"$x_{}$".format(self.solve_dof)) # Return return fig
[docs] def numpise_omegaMMS(mms, param): r""" Numpise the frequency around which a solution is sought. Parameters ---------- mms: Multiple_scales_system param: dict See :func:`~oscilate.sympy_functions.sympy_to_numpy`. Returns ------- omegaMMS: float Numpised MMS frequency. """ omegaMMS = sfun.sympy_to_numpy(mms.omegaMMS, param) return omegaMMS
[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:`~oscilate.sympy_functions.sympy_to_numpy`. Returns ------- omega_bbc: numpy.ndarray Numpised backbone curve's frequency. """ omega_bbc = sfun.sympy_to_numpy(rescale(ss.sol_bbc.omega, mms), param) return omega_bbc
[docs] def numpise_xmax_bbc(mms, ss, param): r""" Numpise the peak oscillator's amplitude :math:`x_{\textrm{max}}` on the backbone curve. Parameters ---------- mms: Multiple_scales_system ss: Steady_state param: dict See :func:`~oscilate.sympy_functions.sympy_to_numpy`. Returns ------- xmax: numpy.ndarray Numpised peak amplitude on the backbone curve. """ xmax = sfun.sympy_to_numpy(rescale(ss.sol_bbc.xmax, mms), param) return xmax
[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:`~oscilate.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_forced.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:`~oscilate.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.sol_forced.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:`~oscilate.sympy_functions.sympy_to_numpy`. omega: numpy.ndarray The frequency array. F: numpy.ndarray The forcing amplitude array. Returns ------- phase: numpy.ndarray Numpised phase. """ param_phase = param | dict(omega=(mms.omega, omega), F=(dyn.forcing.F, F)) sin_phase = sfun.sympy_to_numpy( rescale(ss.sol_forced.sin_phase[1], mms), param_phase) cos_phase = sfun.sympy_to_numpy( rescale(ss.sol_forced.cos_phase[1], mms), param_phase) phase = 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:`~oscilate.sympy_functions.sympy_to_numpy`. Returns ------- F: numpy.ndarray Numpised forced response's forcing amplitude. """ if not isinstance(ss.sol_forced.F, list): F = sfun.sympy_to_numpy(rescale(mms.eps**mms.forcing.f_order * ss.sol_forced.F, mms), param) else: F = [sfun.sympy_to_numpy(rescale(mms.eps**mms.forcing.f_order * Fi, mms), param) for Fi in ss.sol_forced.F] return F
[docs] def numpise_transient_slow_time(mms, param): r""" Numpise the slow time transient response. Parameters ---------- mms: Multiple_scales_system param: dict See :func:`~oscilate.sympy_functions.sympy_to_numpy`. Returns ------- a: numpy.ndarray Numpised transient amplitude. psi: numpy.ndarray Numpised transient absolute phase. dadt: numpy.ndarray Numpised time derivative of the transient amplitude. dpsidt: numpy.ndarray Numpised time derivative of the transient absolute phase. """ a = sfun.sympy_to_numpy(rescale(mms.sol_transient.a, mms), param) psi = sfun.sympy_to_numpy(rescale(mms.sol_transient.psi, mms), param) dadt = sfun.sympy_to_numpy(rescale(mms.sol_transient.a.diff(mms.t), mms), param) dpsidt = sfun.sympy_to_numpy(rescale(mms.sol_transient.psi.diff(mms.t), mms), param) return a, psi, dadt, dpsidt
[docs] def numpise_transient_trajectory(mms, param): r""" Numpise the transient oscillator's trajectory Parameters ---------- mms: Multiple_scales_system param: dict See :func:`~oscilate.sympy_functions.sympy_to_numpy`. Returns ------- x: numpy.ndarray Numpised transient motion. dxdt: numpy.ndarray Numpised transient velocity. """ x = sfun.sympy_to_numpy(rescale(mms.sol_transient.x, mms), param) dxdt = sfun.sympy_to_numpy(rescale(mms.sol_transient.x.diff(mms.t), mms), param) return x, dxdt
[docs] def numpise_LC(mms, ss, param): r""" Numpise the limit cycle solution. Parameters ---------- mms: Multiple_scales_system ss: Steady_state_system param: dict See :func:`~oscilate.sympy_functions.sympy_to_numpy`. Returns ------- a: float Numpised LC amplitude. beta: float Numpised LC initial phase. omega: float Numpised LC frequency. """ a = sfun.sympy_to_numpy(rescale(ss.sol_LC.a, mms), param) beta = sfun.sympy_to_numpy(rescale(ss.sol_LC.beta, mms), param) omega = sfun.sympy_to_numpy(rescale(ss.sol_LC.omega, mms), param) return a, beta, omega
[docs] def numpise_LC_trajectory(mms, ss, param): r""" Numpise the oscillator's LC trajectory Parameters ---------- mms: Multiple_scales_system ss: Steady_state_system param: dict See :func:`~oscilate.sympy_functions.sympy_to_numpy`. Returns ------- x: numpy.ndarray Numpised LC motion. dxdt: numpy.ndarray Numpised LC velocity. """ x = sfun.sympy_to_numpy(rescale(ss.sol_LC.x, mms), param) dxdt = sfun.sympy_to_numpy(rescale(ss.sol_LC.x.diff(mms.t), mms), param) return x, dxdt