Source code for engineeringkit.mechanical_engineering.systems.balancing

r"""Balancing of rotating masses in one and two correction planes.

A mass :math:`m_i` at radius :math:`r_i` and angle :math:`\theta_i` on a
shaft spinning at :math:`\omega` pulls on the bearings with the rotating
force :math:`m_i r_i\omega^2`. Static balance makes the vector sum
:math:`\sum m_i r_i e^{i\theta_i}` vanish, so the shaft rests in any
position. Masses spread along the shaft also produce a rocking couple;
dynamic balance cancels both force and couple, which takes corrections in
two planes. Dalby's reference-plane method takes moments about one
correction plane to find the other.
"""

from __future__ import annotations

import numpy as np

from engineeringkit._validation import finite as _finite
from engineeringkit._validation import nonnegative as _nonnegative
from engineeringkit._validation import vector as _vector
from engineeringkit.mechanical_engineering.core.base import BalanceResult

__all__ = ["unbalance", "balance_rotating_masses"]


def _masses(masses, radii, angles):
    m, r, a = _vector("masses", masses), _vector("radii", radii), _vector("angles", angles)
    if not m.size == r.size == a.size:
        raise ValueError("masses, radii and angles must have the same length")
    _nonnegative(masses=m, radii=r)
    return m * r * np.exp(1j * a)


[docs] def unbalance(masses, radii, angles, positions=None): r"""Resultant unbalance and, optionally, its moment about the axial origin. Parameters ---------- masses : array_like Rotating masses [kg], nonnegative. radii : array_like Their distances from the spin axis [m], nonnegative. angles : array_like Their angular positions [rad]. positions : array_like, optional Their axial positions along the shaft [m]. Returns ------- float or tuple of float Magnitude of :math:`\sum m_i r_i e^{i\theta_i}` [kg m]; with ``positions``, also the magnitude of the couple unbalance :math:`\sum m_i r_i z_i e^{i\theta_i}` [kg m^2]. Multiply by :math:`\omega^2` for the shaking force [N] and moment [N m]. Notes ----- Inputs are raw SI numbers with no dimensional metadata; see :doc:`/guides/mechanical_engineering`. Examples -------- >>> round(unbalance([1.0, 1.0], [0.1, 0.1], [0.0, np.pi]), 12) 0.0 """ u = _masses(masses, radii, angles) if positions is None: return float(abs(u.sum())) z = _vector("positions", positions) if z.size != u.size: raise ValueError("positions must have one entry per mass") return float(abs(u.sum())), float(abs((u * z).sum()))
[docs] def balance_rotating_masses(masses, radii, angles, positions=None, planes=None) -> BalanceResult: r"""Correction masses for static (one-plane) or dynamic (two-plane) balance. Without ``positions`` and ``planes`` one correction cancels the force, :math:`m_c r_c e^{i\theta_c} = -\sum m_i r_i e^{i\theta_i}`. With them, corrections :math:`U_L` and :math:`U_R` at :math:`z_L` and :math:`z_R` cancel force and couple: moments about plane :math:`L` give .. math:: U_R = -\frac{\sum m_i r_i (z_i - z_L) e^{i\theta_i}}{z_R - z_L}, \qquad U_L = -\sum m_i r_i e^{i\theta_i} - U_R Parameters ---------- masses : array_like Rotating masses [kg], nonnegative. radii : array_like Their radii [m], nonnegative. angles : array_like Their angular positions [rad]. positions : array_like, optional Their axial positions [m]; give with ``planes`` for dynamic balance. planes : array_like, optional Axial positions :math:`(z_L, z_R)` of the two correction planes [m], distinct. Returns ------- BalanceResult Correction mass-radius product [kg m] and angle [rad] in each plane. Notes ----- The rotor is rigid: the corrections hold at every speed below the first bending critical speed. Inputs are raw SI numbers with no dimensional metadata; see :doc:`/guides/mechanical_engineering`. References ---------- W. E. Dalby, *The Balancing of Engines* (Edward Arnold, London, 1902). Examples -------- >>> result = balance_rotating_masses([2.0], [0.1], [np.pi / 2]) >>> round(float(result.mass_radius[0]), 12), round(float(result.angle[0]), 12) (0.2, -1.570796326795) """ u = _masses(masses, radii, angles) if (positions is None) != (planes is None): raise ValueError("give positions and planes together for two-plane balance") if positions is None: corrections = np.array([-u.sum()]) plane_positions = np.array([]) else: z = _vector("positions", positions) plane_positions = _vector("planes", planes) if z.size != u.size or plane_positions.size != 2: raise ValueError("positions needs one entry per mass and planes exactly two") zl, zr = plane_positions _finite(planes=plane_positions) if zl == zr: raise ValueError("the two correction planes must be distinct") right = -(u * (z - zl)).sum() / (zr - zl) corrections = np.array([-u.sum() - right, right]) return BalanceResult(mass_radius=np.abs(corrections), angle=np.angle(corrections), plane_positions=plane_positions)