{ "cells": [ { "cell_type": "markdown", "metadata": { "user_expressions": [] }, "source": [ "\n", "\n", "# Evaluation models: endurance of the actuator (student version)\n", "\n", "*Written by Marc Budinger (INSA Toulouse) and Scott Delbecq (ISAE-SUPAERO), Toulouse, France*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" }, "tags": [], "user_expressions": [] }, "source": [ "\n", "## Endurance specifications\n", "\n", "In the description of the TVC system given in the [following article](http://esmats.eu/esmatspapers/pastpapers/pdfs/2009/vanthuyne.pdf) an endurance test profile composed of multiple sinusoidal displacements is given. \n", "\n", "*Endurance profile for P80 EMA*\n", "\n", "![Endurance profile](./assets/images/EnduranceP80.png)\n", "\n", "> Exercice: declare the magnitude displacement, frequency and number of cycles (divided by 100 here) informations in 1D arrays (numpy) for cycles 1 to 6 in a global a Pandas [dataframe](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html) called 'Profil'." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", "
" ], "text/plain": [ "Empty DataFrame\n", "Columns: []\n", "Index: []" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.display import Markdown as md # enable Markdown printing\n", "import numpy as np\n", "import pandas as pd\n", "from math import pi\n", "\n", "# Declaration of amplitude, frequency and number of cycles 1 to 6\n", "\n", "lever_arm = 1.35 # [m] Lever Arm\n", "stroke = 6 * pi / 180 * lever_arm # [m] Stroke\n", "\n", "profil = pd.DataFrame()\n", "\n", "profil" ] }, { "cell_type": "markdown", "metadata": { "user_expressions": [] }, "source": [ "with: \n", "- A [m], magnitude of stroke;\n", "- f [Hz], frequency;\n", "- Nc [-], number of cycles.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" }, "user_expressions": [] }, "source": [ "## Generation of the test profiles\n", "\n", "The target is now to generate from this specification a set of time vectors representing the displacement, speed and acceleration. \n", "\n", "> Exercice: define a [function](https://anh.cs.luc.edu/python/hands-on/3.1/handsonHtml/functions.html) which take as parameters a dataframe *profil* and a time step and return the desired time vectors.\n", "Remark: 'arange', 'concatenate' and 'array' functions of numpy can be usefull. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'DataFrame' object has no attribute 'f'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/var/folders/8f/0st0j4vx0k5fzpjhfpf2fn3n1r3782/T/ipykernel_8876/1604667484.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 33\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 34\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 35\u001b[0;31m \u001b[0mdf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtest_profiles\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprofil\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprofil\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0;36m20\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 36\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 37\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/sizing_course/lib/python3.8/site-packages/pandas/core/generic.py\u001b[0m in \u001b[0;36m__getattr__\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m 5139\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_info_axis\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_can_hold_identifiers_and_holds_name\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5140\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 5141\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mobject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__getattribute__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5142\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5143\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__setattr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mAttributeError\u001b[0m: 'DataFrame' object has no attribute 'f'" ] } ], "source": [ "def test_profiles(profil, step_size):\n", " tmin = 0\n", " time = np.array([])\n", " position = np.array([])\n", " speed = np.array([])\n", " acceleration = np.array([])\n", " for A, f, Nc in zip(profil.A, profil.f, profil.Nc):\n", " tmax = Nc / f\n", " # time vector\n", " t = np.arange(tmin, tmin + tmax, step_size)\n", " # Position, speed and Acceleration vectors\n", " X = A * np.sin(2 * pi * f * t)\n", " Xp = A * 2 * pi * f * np.cos(2 * pi * f * t)\n", " Xpp = -A * (2 * pi * f) ** 2 * np.sin(2 * pi * f * t)\n", " # Concatenation of multiple cycles\n", " time = np.concatenate((time, t))\n", " position = np.concatenate((position, X))\n", " speed = np.concatenate((speed, Xp))\n", " acceleration = np.concatenate((acceleration, Xpp))\n", " # new start time for the next cycle\n", " tmin = tmin + tmax\n", "\n", " d = {\n", " \"t\": time,\n", " \"position\": position,\n", " \"speed\": speed,\n", " \"acceleration\": acceleration,\n", " }\n", "\n", " df = pd.DataFrame(data=d)\n", "\n", " return df\n", "\n", "\n", "df = test_profiles(profil, 1 / max(profil.f) / 20)\n", "\n", "df" ] }, { "cell_type": "markdown", "metadata": { "user_expressions": [] }, "source": [ "The combination of cycles can now be plot :" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "# Position\n", "plt.figure()\n", "plt.subplot(311, xlabel=\"Time [s]\")\n", "plt.plot(df[\"t\"], df[\"position\"])\n", "plt.ylabel(\"Position [m]\")\n", "\n", "# Speed\n", "plt.subplot(312, xlabel=\"Time [s]\")\n", "plt.plot(df[\"t\"], df[\"speed\"])\n", "plt.ylabel(\"Speed [m/s]\")\n", "\n", "# Acceleration\n", "plt.subplot(313, xlabel=\"Time [s]\")\n", "plt.plot(df[\"t\"], df[\"acceleration\"])\n", "plt.ylabel(\"Acceleration [m/s²]\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Force mission profile\n", "\n", "> Exercice: Knowing the main characteristics of the nozzle (stiffness of 1.52E+04 Nm/deg, viscous damping of 1.74E+02\tNms/deg, inertia of 1.40E+03 kg.m^2), calculate and plot the mechanical force to be applied by the actuation system. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Definition of nozzle equivalent parameters with engineering units\n", "Jnozzle = 1.40e03 # [kg.m2] Inertia\n", "Knozzle = 1.52e04 # [Nm/deg] Stiffness\n", "Fnozzle = 1.74e02 # [Nms/deg] Viscous damping\n", "\n", "# Calculate SI unit values of Knozzle and Fnozzle\n", "# pi value is math.pi\n", "\n", "Knozzle = Knozzle / (pi / 180)\n", "Fnozzle = Fnozzle / (pi / 180)\n", "\n", "# Angular mission profiles (nozzle)\n", "df[\"teta\"] = df[\"position\"] / lever_arm\n", "df[\"tetap\"] = df[\"speed\"] / lever_arm\n", "df[\"tetapp\"] = df[\"acceleration\"] / lever_arm\n", "\n", "# Torque and force calculation\n", "df[\"Fact\"] = \n", "\n", "# Plot force mission profile\n", "plt.plot(df[\"t\"], df[\"Fact\"])\n", "plt.ylabel(\"Force (N)\")\n", "plt.xlabel(\"Time (s)\")" ] }, { "cell_type": "markdown", "metadata": { "user_expressions": [] }, "source": [ "## Rolling fatigue\n", "\n", "The rolling fatigue for a variable mission profile is evaluated in two stages:\n", "- firstly by calculating the number of revolutions and an equivalent rolling fatigue effort called $F_{RMC}$ (RMC for RootMean Cube) \n", "$F_{RMC}=(\\frac{1}{\\int |\\dot{x}| {d}t} \\int |F|^3 |\\dot{x}| {d}t)^{1/3}$\n", "- then by obtaining a fatigue effort $F_d$ equivalent to 1 million rev. \n", "$F_d^3 . N_{ref} = F_{RMC}^3. N_{cycles}$\n", "\n", "We will assume here that the screw/nut system has a pitch of 10 mm/rev. Examples of thrust bearings can be found [here](./PDF/MatchedBearingSKF.pdf).\n", "\n", "> Exercice: Calculate $F_{RMC}$ and $F_d$. Compare relative ratio between $C_0$ and $C_d$. Conclusion. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pitch = 10e-3 # [m/rev] pitch of the roller screw\n", "\n", "# Computation of integrals avec np.trapz\n", "\n", "# Global distance\n", "distance = \n", "\n", "# Cumulative damage\n", "FcubeD = \n", "\n", "# Root Mean Cube\n", "FRMC = \n", "\n", "# Number of rev for the mission profile\n", "Nturn = \n", "\n", "# Dynamic equivalent load\n", "Fd = \n", "\n", "md(\n", " \"\"\"\n", "The Root Mean Cube force is *FRMC* = {:.0f} kN \n", "The number of turns = {:.2g} \n", "The equivalent dynamic load for one million revolutions is *Fd* = {:.0f} kN\n", "\"\"\".format(\n", " FRMC / 1e3, Nturn, Fd / 1e3\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": { "user_expressions": [] }, "source": [ "> Exercice for excel users: calculate RMC force and equivalent dynamic force with the mission profile saved in the `FatigueProfil.xlsx` file." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Profil mission export to excel\n", "df.to_excel(\"FatigueProfil.xlsx\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Analytic calculation of equivalent rolling force\n", "\n", "The following figures show the evolution of the actuator forces as a function of the position, speed and acceleration. \n", "\n", "> Exercice: Derive from it a simplified approach allowing to calculate the rolling fatigue stress in a fast way directly from the table of specifications of fatigue life cycles. Compare your results with the calculations made directly on the mission profile." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot force mission profile\n", "plt.figure(3)\n", "\n", "plt.subplot(131)\n", "plt.plot(df[\"position\"], df[\"Fact\"])\n", "plt.ylabel(\"Force [N]\")\n", "plt.xlabel(\"Position [m]\")\n", "\n", "plt.subplot(132)\n", "plt.plot(df[\"speed\"], df[\"Fact\"])\n", "plt.xlabel(\"Speed [m/s]\")\n", "plt.gca().axes.get_yaxis().set_visible(False)\n", "\n", "plt.subplot(133)\n", "plt.plot(df[\"acceleration\"], df[\"Fact\"])\n", "plt.xlabel(\"Acceleration [m/s²]\")\n", "plt.gca().axes.get_yaxis().set_visible(False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The force is mainly proportional to the position: therefore the effect of the stiffness of the flexible bearing dominates this fatigue cycle. In this case the calculation of the fatigue force can be carried out analytically using the following equations: \n", "$F_{RMC}=(\\frac{1}{\\int |\\dot{x}| {d}t} \\int |F|^3 |\\dot{x}| {d}t)^{1/3}$ \n", "where: \n", "- $F(t)=Kx(t)=F_kcos(\\omega_kt)$ with $x(t)=X_kcos(\\omega_kt)$ and $F_k=KX_k$ \n", "- $\\dot{x}(t)=-X_k\\omega_ksin(\\omega_kt)$ \n", "with:\n", "- $F_k$ magnitude of sinusoidal forces;\n", "- $A_k$ magnitude of sinusoidal displacements;\n", "- $N_k$ " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Thus: \n", "$F_{RMC}^3 = \\frac{\\sum{\\frac{F_k^3}{4}A_k N_k}}{\\sum{A_k N_k}}$ \n", "\n", "We have used following trigonometric formula: \n", "$cos(\\theta)^3sin(\\theta) = cos(\\theta)^2 sin(\\theta)cos(\\theta) = \\frac{1+cos(2\\theta)}{2} \\frac{sin(2\\theta)}{2} $ \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Profil\n", "fatigue_profil = pd.DataFrame(\n", " {\n", " \"A\": np.array([0.95, 0.5, 0.3, 0.1, 0.05, 0.05]) * stroke,\n", " \"f\": np.array([0.3, 0.6, 1, 2, 5, 3]),\n", " \"Nc\": np.array([3700, 4800, 5000, 7000, 8500, 1500]),\n", " }\n", ")\n", "\n", "fatigue_profil[\"max_force\"] = fatigue_profil[\"A\"] * Knozzle / lever_arm**2\n", "fatigue_profil[\"distance\"] = fatigue_profil[\"A\"] * fatigue_profil[\"Nc\"] * 4\n", "\n", "fatigue_profil[\"Feq^3 Distance\"] = (\n", " (fatigue_profil[\"max_force\"]) ** 3 * fatigue_profil[\"distance\"] / 4\n", ")\n", "\n", "fatigue_profil" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Frmc = (fatigue_profil[\"Feq^3 Distance\"].sum() / fatigue_profil[\"distance\"].sum()) ** (1 / 3)\n", "\n", "md(\n", " \"\"\"\n", "The calculated Root Mean Cube force is *FRMC* = {:.0f} kN \n", "which can be compared to previous result.\n", "\"\"\".format(\n", " FRMC / 1e3\n", " )\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 4 }