motulator_bearingless.utils
===========================

.. py:module:: motulator_bearingless.utils

.. autoapi-nested-parse::

   Utilities for six-phase bearingless machine drives.

   The utilities include the machine parameters and the magnetic models [#Das2026]_,
   [#Sok2019]_, the vector-space decomposition of the six-phase winding [#Ahm2023]_, and
   plotting functions. The base-value classes and the plot style of motulator are
   re-exported for convenience.

   .. rubric:: References

   .. [#Das2026] Das, Martin, Kontodinas, Hinkkanen, "Direct force control of six-phase
      bearingless synchronous machines," Proc. ICEM, 2026,
      https://doi.org/10.1109/ICEM68283.2026.11757980

   .. [#Sok2019] Sokolov, Gruber, Saarakkala, Hinkkanen, "Modeling of a bearingless
      synchronous reluctance motor with combined windings," Proc. IEEE ECCE, 2019,
      https://doi.org/10.1109/ECCE.2019.8913000

   .. [#Ahm2023] Ahmed, Martin, Das, Hinkkanen, "Analytical modeling of a six-phase
      bearingless synchronous reluctance machine using winding function theory," Proc. IEEE
      ECCE, 2023, https://doi.org/10.1109/ECCE53617.2023.10361945

   ..
       !! processed by numpydoc !!


Classes
-------

.. autoapisummary::

   motulator_bearingless.utils.BaseBearinglessMachinePars
   motulator_bearingless.utils.BearinglessMagneticModel
   motulator_bearingless.utils.BearinglessSynchronousMachinePars
   motulator_bearingless.utils.FluxMapFcn
   motulator_bearingless.utils.SaturatedBearinglessSynchronousMachinePars
   motulator_bearingless.utils.SpatialSaturatedBearinglessSynchronousMachinePars


Functions
---------

.. autoapisummary::

   motulator_bearingless.utils.c2v
   motulator_bearingless.utils.fourpole_pars
   motulator_bearingless.utils.inv_vsd
   motulator_bearingless.utils.plot
   motulator_bearingless.utils.plot_phase_currents
   motulator_bearingless.utils.sixphase_currents
   motulator_bearingless.utils.torque
   motulator_bearingless.utils.v2c
   motulator_bearingless.utils.vsd


Module Contents
---------------

.. py:class:: BaseBearinglessMachinePars

   Bases: :py:obj:`Protocol`


   
   Base class for the machine parameters, as used by the machine model.

   The magnetic model is a lossless mapping between the stator flux linkages, the
   stator currents, the rotor displacement, and the radial force, consistent with a
   magnetic energy function `W(psi_s, q_M, theta_M)`. All space vectors are in rotor
   coordinates, and the methods accept scalars as well as arrays of the same shape.















   ..
       !! processed by numpydoc !!

   .. py:method:: i_s(psi_s1, psi_s2, q_M, exp_j_theta_M = None)

      
      Currents (A) and radial force (N) from flux linkages (Vs).
















      ..
          !! processed by numpydoc !!


   .. py:method:: magnetic_map(psi_s1, psi_s2, q_M, exp_j_theta_M = None)

      
      Currents, radial force, and torque from the flux linkages.

      :param psi_s1: Stator flux linkages (Vs) of the two-pole and four-pole planes.
      :type psi_s1: complex | ndarray
      :param psi_s2: Stator flux linkages (Vs) of the two-pole and four-pole planes.
      :type psi_s2: complex | ndarray
      :param q_M: Rotor displacement (m).
      :type q_M: complex | ndarray
      :param exp_j_theta_M: Complex exponential of the mechanical rotor angle, needed by models with
                            spatial harmonics.
      :type exp_j_theta_M: complex | ndarray, optional

      :returns: * **i_s1, i_s2, f_M** (*complex | ndarray*) -- Stator currents (A) of the two planes and the radial force (N).
                * **tau_M** (*float | ndarray*) -- Electromagnetic torque (Nm).















      ..
          !! processed by numpydoc !!


.. py:class:: BearinglessMagneticModel

   Bases: :py:obj:`BaseBearinglessMachinePars`, :py:obj:`Protocol`


   
   Base class for the machine parameters, as used by the control system.

   In addition to the machine model, the control system needs the flux map `psi_s`, the
   force map `f_M`, the incremental inductance matrix `incr_ind_mat`, and the
   sensitivities `di_dq` and `df_dq` to the displacement, all as functions of the
   currents and the displacement.















   ..
       !! processed by numpydoc !!

   .. py:method:: df_dq(i_s1, i_s2, q_M)

      
      Force sensitivity `d f_M/d q_M` at constant flux, shape (..., 2, 2).
















      ..
          !! processed by numpydoc !!


   .. py:method:: di_dq(i_s1, i_s2, q_M)

      
      Current sensitivity `d i_s/d q_M` at constant flux, shape (..., 4, 2).
















      ..
          !! processed by numpydoc !!


   .. py:method:: f_M(i_s1, i_s2, q_M)

      
      Radial force (N) as a function of currents (A) and displacement (m).
















      ..
          !! processed by numpydoc !!


   .. py:method:: incr_ind_mat(i_s1, i_s2, q_M)

      
      Incremental inductance matrix (H), shape (..., 4, 4), `d psi_s/d i_s`.
















      ..
          !! processed by numpydoc !!


   .. py:method:: psi_s(i_s1, i_s2, q_M)

      
      Flux linkages (Vs) as functions of currents (A) and displacement (m).
















      ..
          !! processed by numpydoc !!


.. py:class:: BearinglessSynchronousMachinePars

   Bases: :py:obj:`BearinglessMagneticModel`


   
   Constant parameters of a six-phase bearingless synchronous machine.

   The four-pole rotor produces the torque with the four-pole field (`h = 2`) and
   the radial force with the two-pole field (`h = 1`). Linear magnetics are
   assumed. The flux linkages are `psi_s = L_s(q_M)*i_s`, where the inductance
   matrix depends linearly on the rotor displacement via the radial force constants::

       L_s = [[L_1*I, L_21.T], [L_21, diag(L_d, L_q)]]
       L_21 = [[m_d*q_i, -m_d*q_j], [m_q*q_j, m_q*q_i]]

   The corresponding radial force is `f_M = 1.5*i_s.T*(dL_s/dq_M)*i_s`, i.e.::

       f_i = 3*(m_d*i_2d*i_1d + m_q*i_2q*i_1q)
       f_j = 3*(m_q*i_2q*i_1d - m_d*i_2d*i_1q)

   :param R_s: Stator resistance (Ω).
   :type R_s: float
   :param L_1: Inductance (H) of the two-pole plane, isotropic for the four-pole rotor.
   :type L_1: float
   :param L_d: d-axis inductance (H) of the four-pole plane.
   :type L_d: float
   :param L_q: q-axis inductance (H) of the four-pole plane.
   :type L_q: float
   :param m_d: d-axis radial force constant (H/m).
   :type m_d: float
   :param m_q: q-axis radial force constant (H/m).
   :type m_q: float

   .. rubric:: Notes

   The negative stiffness (unbalanced magnetic pull proportional to the displacement)
   is a second-order effect of the displacement on the inductances, which is not
   included in this linear model.















   ..
       !! processed by numpydoc !!

   .. py:method:: df_dq(i_s1, i_s2, q_M)

      
      Force sensitivity `d f_M/d q_M` at constant flux, shape (..., 2, 2).
















      ..
          !! processed by numpydoc !!


   .. py:method:: di_dq(i_s1, i_s2, q_M)

      
      Current sensitivity `d i_s/d q_M` at constant flux, shape (..., 4, 2).
















      ..
          !! processed by numpydoc !!


   .. py:method:: f_M(i_s1, i_s2, q_M)

      
      Radial force (N) as a function of currents (A) and displacement (m).
















      ..
          !! processed by numpydoc !!


   .. py:method:: i_s(psi_s1, psi_s2, q_M, exp_j_theta_M = None)

      
      Currents (A) and radial force (N) from flux linkages (Vs).
















      ..
          !! processed by numpydoc !!


   .. py:method:: incr_ind_mat(i_s1, i_s2, q_M)

      
      Incremental inductance matrix (H), shape (..., 4, 4).
















      ..
          !! processed by numpydoc !!


   .. py:method:: ind_mat(q_M)

      
      Inductance matrix (H), shape (..., 4, 4), at the displacement `q_M`.
















      ..
          !! processed by numpydoc !!


   .. py:method:: psi_s(i_s1, i_s2, q_M)

      
      Flux linkages (Vs) as functions of currents (A) and displacement (m).
















      ..
          !! processed by numpydoc !!


.. py:class:: FluxMapFcn

   Bases: :py:obj:`Protocol`


   
   Flux map with its Jacobian, e.g., :class:`~motulator_bearingless.gradnet.FluxMap`.

   The map gives the flux linkages (Vs) of the two planes and the radial force (N) as
   functions of the currents (A) of the two planes and the displacement (m).















   ..
       !! processed by numpydoc !!

   .. py:method:: jacobian(i_s1, i_s2, q_M)

      
      Jacobian matrix of the outputs with respect to the inputs, (..., 6, 6).
















      ..
          !! processed by numpydoc !!


.. py:class:: SaturatedBearinglessSynchronousMachinePars

   Bases: :py:obj:`BearinglessMagneticModel`


   
   Parameters of a saturated bearingless synchronous machine.

   The class corresponds to
   :class:`motulator.drive.model.SaturatedSynchronousMachinePars`. The saturation model
   is specified as a current map. Optionally, to be used only in control systems, a
   flux map can be provided.

   :param R_s: Stator resistance (Ω).
   :type R_s: float
   :param i_s_fcn: Currents (A) and radial force (N) as functions of the flux linkages (Vs) of the
                   two planes and the displacement (m), e.g.,
                   :class:`~motulator_bearingless.gradnet.CurrentMap`. Needed in the machine model.
   :type i_s_fcn: Callable[[Any, Any, Any], tuple[Any, Any, Any]], optional
   :param psi_s_fcn: Flux linkages (Vs) and radial force (N) as functions of the currents (A) of the
                     two planes and the displacement (m), with the Jacobian, e.g.,
                     :class:`~motulator_bearingless.gradnet.FluxMap`. Needed in the control systems.
   :type psi_s_fcn: FluxMapFcn, optional















   ..
       !! processed by numpydoc !!

   .. py:method:: df_dq(i_s1, i_s2, q_M)

      
      Force sensitivity `d f_M/d q_M` at constant flux, shape (..., 2, 2).
















      ..
          !! processed by numpydoc !!


   .. py:method:: di_dq(i_s1, i_s2, q_M)

      
      Current sensitivity `d i_s/d q_M` at constant flux, shape (..., 4, 2).
















      ..
          !! processed by numpydoc !!


   .. py:method:: f_M(i_s1, i_s2, q_M)

      
      Radial force (N) as a function of currents (A) and displacement (m).
















      ..
          !! processed by numpydoc !!


   .. py:method:: i_s(psi_s1, psi_s2, q_M, exp_j_theta_M = None)

      
      Currents (A) and radial force (N) from flux linkages (Vs).
















      ..
          !! processed by numpydoc !!


   .. py:method:: incr_ind_mat(i_s1, i_s2, q_M)

      
      Incremental inductance matrix (H), shape (..., 4, 4).
















      ..
          !! processed by numpydoc !!


   .. py:method:: linearized(i_s2)

      
      Constant parameters of the linear model at an operating point.

      The inductances of the four-pole plane are the secant inductances at the given
      current and those of the two-pole plane are the incremental inductances at a
      centric rotor without the two-pole current. The force constants are fitted to
      the sensitivity of the radial force to the two-pole current.

      :param i_s2: Current (A) of the four-pole plane in rotor coordinates. Its both components
                   must be nonzero.
      :type i_s2: complex

      :returns: Parameters of the linear model.
      :rtype: BearinglessSynchronousMachinePars















      ..
          !! processed by numpydoc !!


   .. py:method:: psi_s(i_s1, i_s2, q_M)

      
      Flux linkages (Vs) as functions of currents (A) and displacement (m).
















      ..
          !! processed by numpydoc !!


.. py:class:: SpatialSaturatedBearinglessSynchronousMachinePars

   Bases: :py:obj:`BaseBearinglessMachinePars`


   
   Parameters of a saturated bearingless synchronous machine with spatial harmonics.

   The class corresponds to
   :class:`motulator.drive.model.SpatialSaturatedSynchronousMachinePars`. This version
   is intended for the machine model, while the control methods do not support spatial
   harmonics.

   :param R_s: Stator resistance (Ω).
   :type R_s: float
   :param magnetic_map_fcn: Currents (A), radial force (N), and torque (Nm) as functions of the flux
                            linkages (Vs) of the two planes, the displacement (m), and the complex
                            exponential of the mechanical rotor angle, e.g.,
                            :class:`~motulator_bearingless.gradnet.CurrentMapWithHarmonics`.
   :type magnetic_map_fcn: Callable[[Any, Any, Any, Any], tuple[Any, Any, Any, Any]]















   ..
       !! processed by numpydoc !!

   .. py:method:: i_s(psi_s1, psi_s2, q_M, exp_j_theta_M = None)

      
      Currents (A) and radial force (N) from flux linkages (Vs).
















      ..
          !! processed by numpydoc !!


   .. py:method:: magnetic_map(psi_s1, psi_s2, q_M, exp_j_theta_M = None)

      
      Currents (A), radial force (N), and torque (Nm) from flux linkages (Vs).
















      ..
          !! processed by numpydoc !!


.. py:function:: c2v(*z)

   
   Stack complex numbers (or arrays) into a real vector along the last axis.
















   ..
       !! processed by numpydoc !!

.. py:function:: fourpole_pars(par)

   
   Parameters of the four-pole plane as an equivalent three-phase machine.

   For a centric rotor, the four-pole plane is a three-phase synchronous reluctance
   machine with two pole pairs, whose two three-phase sets are connected in parallel:
   the voltage and the flux linkage of the equivalent machine are those of the plane,
   while its current is twice the current of the plane, i.e., the sum of the set
   currents. Hence, the resistance and the inductances of the plane are halved, cf.
   the common mode of multi-three-phase machines in `motulator_multiphase`. The
   equivalent machine produces the total torque `6*Im(i_s2*conj(psi_s2))`. The
   coupling of the planes via the rotor displacement is omitted.

   :param par: Magnetic model. A saturated model is sliced at zero two-pole current and at a
               centric rotor.
   :type par: BearinglessMagneticModel

   :returns: Parameters of the equivalent three-phase machine.
   :rtype: SynchronousMachinePars | SaturatedSynchronousMachinePars

   .. rubric:: Examples

   >>> from motulator_bearingless.utils import (
   ...     BearinglessSynchronousMachinePars,
   ...     fourpole_pars,
   ... )
   >>> par = BearinglessSynchronousMachinePars(
   ...     R_s=0.2, L_1=0.03, L_d=0.03, L_q=0.01, m_d=10.0, m_q=0.2
   ... )
   >>> fourpole_pars(par)
   SynchronousMachinePars(n_p=2, R_s=0.1, L_d=0.015, L_q=0.005, psi_f=0.0, G_c=0.0)















   ..
       !! processed by numpydoc !!

.. py:function:: inv_vsd(x_s1_ab, x_s2_ab)

   
   Inverse vector-space decomposition, see :func:`vsd`.

   :param x_s1_ab: Space vector in the two-pole plane, in stator coordinates.
   :type x_s1_ab: complex | ndarray
   :param x_s2_ab: Space vector in the four-pole plane, in stator coordinates.
   :type x_s2_ab: complex | ndarray

   :returns: **x_A_ab, x_B_ab** -- Space vectors of the two three-phase sets.
   :rtype: complex | ndarray

   .. rubric:: Examples

   >>> from motulator_bearingless.utils import inv_vsd, vsd
   >>> x_A_ab, x_B_ab = inv_vsd(0.1j, 1 - 0.5j)
   >>> [complex(round(x.real, 12), round(x.imag, 12)) for x in vsd(x_A_ab, x_B_ab)]
   [0.1j, (1-0.5j)]















   ..
       !! processed by numpydoc !!

.. py:function:: plot(res, base = None, subplots = None, t_lims = None, t_ticks = None, y_lims = None, y_ticks = None, latex = False, save_path = None, **savefig_kwargs)

   
   Plot the simulation results of a bearingless machine drive.

   :param res: Simulation results.
   :type res: SimulationResults
   :param base: Base values for scaling the electrical waveforms. If not given, the waveforms
                are plotted in SI units. Forces and displacements are always in SI units.
   :type base: BaseValues, optional
   :param subplots: Subplots to include. Valid names: "speed", "torque", "flux", "force",
                    "current", "displacement". Defaults to all but "displacement".
   :type subplots: list[str], optional
   :param t_lims: Time axis limits.
   :type t_lims: tuple[float, float], optional
   :param t_ticks: Time axis tick locations.
   :type t_ticks: ArrayLike, optional
   :param y_lims: y-axis limits for each subplot.
   :type y_lims: Sequence[tuple[float, float] | None], optional
   :param y_ticks: y-axis tick locations for each subplot.
   :type y_ticks: Sequence[ArrayLike | None], optional
   :param latex: Use LaTeX fonts for the labels, defaults to False.
   :type latex: bool, optional
   :param save_path: Path to save the figure. If None, the figure is not saved.
   :type save_path: str | Path, optional
   :param \*\*savefig_kwargs: Additional keyword arguments passed to `plt.savefig()`.















   ..
       !! processed by numpydoc !!

.. py:function:: plot_phase_currents(t, i_abc, t_lims = None, y_lims = None, latex = False, save_path = None, **savefig_kwargs)

   
   Plot the six phase currents.

   :param t: Time (s).
   :type t: ndarray
   :param i_abc: Phase currents (A), see :func:`sixphase_currents`.
   :type i_abc: ndarray, shape (6, N)
   :param t_lims: Time axis limits.
   :type t_lims: tuple[float, float], optional
   :param y_lims: Current axis limits.
   :type y_lims: tuple[float, float], optional
   :param latex: Use LaTeX fonts for the labels, defaults to False.
   :type latex: bool, optional
   :param save_path: Path to save the figure. If None, the figure is not saved.
   :type save_path: str | Path, optional
   :param \*\*savefig_kwargs: Additional keyword arguments passed to `plt.savefig()`.















   ..
       !! processed by numpydoc !!

.. py:function:: sixphase_currents(i_c_ab)

   
   Phase currents from the space vectors of the two sets.

   :param i_c_ab: Space vectors of the first (phases 1, 3, 5) and second (phases 2, 4, 6) sets.
   :type i_c_ab: ndarray, shape (2,) or (2, N)

   :returns: Phase currents `[i_1, ..., i_6]`.
   :rtype: ndarray, shape (6,) or (6, N)















   ..
       !! processed by numpydoc !!

.. py:function:: torque(i_s1, i_s2, psi_s1, psi_s2, q_M, f_M)

   
   Electromagnetic torque (Nm) without the term due to the spatial harmonics.
















   ..
       !! processed by numpydoc !!

.. py:function:: v2c(v)

   
   Inverse of `c2v`.
















   ..
       !! processed by numpydoc !!

.. py:function:: vsd(x_A_ab, x_B_ab)

   
   Vector-space decomposition of the two three-phase sets into the two planes.

   The symmetrical six-phase winding consists of phases 1...6 at 60° intervals. The
   first three-phase set consists of the phases 1, 3, 5 and the second set of the
   phases 2, 4, 6. Each set is described by its standard peak-valued space vector
   `abc2complex([x_1, x_3, x_5])` and `abc2complex([x_2, x_4, x_6])`, where the vector
   of the second set is expressed in its own coordinates (its a-phase axis is at 60°).
   The planes `h = 1` (two-pole) and `h = 2` (four-pole) result from the
   vector-space decomposition with peak-value scaling,
   `x_sh = (1/3)*sum(x_k*alpha**(h*(k - 1)))`, where `alpha = exp(1j*pi/3)`
   [#Ahm2023]_. Zero-sequence components are omitted.

   :param x_A_ab: Space vector of the first three-phase set (phases 1, 3, 5).
   :type x_A_ab: complex | ndarray
   :param x_B_ab: Space vector of the second three-phase set (phases 2, 4, 6).
   :type x_B_ab: complex | ndarray

   :returns: **x_s1_ab, x_s2_ab** -- Space vectors in the two-pole and four-pole planes, in stator coordinates.
   :rtype: complex | ndarray

   .. rubric:: Notes

   The four-pole plane sees the phases in reversed sequence, which shows up as the
   complex conjugate in the transformation.

   .. rubric:: Examples

   >>> from motulator_bearingless.utils import ALPHA, vsd
   >>> # Equal currents in the opposite phases (i_1 = i_4, i_3 = i_6, i_5 = i_2)
   >>> # produce only the four-pole component
   >>> i_A_ab = 1 + 1j
   >>> i_s1_ab, i_s2_ab = vsd(i_A_ab, ALPHA**2 * i_A_ab)
   >>> round(abs(i_s1_ab), 12)
   0.0
   >>> complex(round(i_s2_ab.real, 12), round(i_s2_ab.imag, 12))
   (1-1j)















   ..
       !! processed by numpydoc !!

