{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import pyvista\npyvista.OFF_SCREEN = True\npyvista.set_jupyter_backend('static')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Modal cantilever \u2014 first modal example\n\nA 60-second introduction to free-vibration analysis in femorph-solver: build\na steel plate, clamp one edge, extract the first 5 modes, render the\nlowest mode shape.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from __future__ import annotations\n\nimport numpy as np\nimport pyvista as pv\n\nimport femorph_solver as fs"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Build a 20 \u00d7 20 \u00d7 2 hex plate\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "LX, LY, LZ = 1.0, 1.0, 0.01\n\ngrid = pv.StructuredGrid(\n    *np.meshgrid(\n        np.linspace(0.0, LX, 21),\n        np.linspace(0.0, LY, 21),\n        np.linspace(0.0, LZ, 3),\n        indexing=\"ij\",\n    )\n).cast_to_unstructured_grid()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Wrap, stamp steel, clamp the x=0 edge\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "m = fs.Model.from_grid(grid)\nm.assign(fs.ELEMENTS.HEX8, material={\"EX\": 2.0e11, \"PRXY\": 0.30, \"DENS\": 7850.0})\nm.fix(where=np.asarray(grid.points)[:, 0] < 1e-9)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Extract 5 modes\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "res = m.solve_modal(n_modes=5)\n\nprint(\"Mode     f [Hz]\")\nfor i, f in enumerate(res.frequency, start=1):\n    print(f\"{i:>3}   {f:>10.3f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Visualise mode 1\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "grid_plot = fs.io.modal_result_to_grid(m, res)\nphi1 = grid_plot.point_data[\"mode_1_disp\"]\nfactor = 0.15 / (np.max(np.abs(phi1)) + 1e-30)\n\nplotter = pv.Plotter(off_screen=True)\nplotter.add_mesh(grid_plot, style=\"wireframe\", color=\"gray\", opacity=0.3)\nplotter.add_mesh(\n    grid_plot.warp_by_vector(\"mode_1_disp\", factor=factor),\n    scalars=\"mode_1_magnitude\",\n    show_scalar_bar=True,\n    scalar_bar_args={\"title\": f\"mode 1 ({res.frequency[0]:.1f} Hz)\"},\n)\nplotter.add_axes()\nplotter.show()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "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.12.3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}