{
  "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# TRUSS2 reference geometry \u2014 linear axial bar\n\nThe 2-node 3D truss (axial-only spar) maps to the natural\ncoordinate $s \\in [-1, +1]$.  Linear shape functions\ninterpolate the three translational DOFs at each node:\n\n\\begin{align}N_1(s) = \\tfrac{1 - s}{2}, \\qquad\n    N_2(s) = \\tfrac{1 + s}{2}.\\end{align}\n\nAxial strain $\\varepsilon_{xx} = (u_2 - u_1) / L$ is\nconstant across the element; the resulting 6\u00d76 stiffness in\nthe global frame is\n\n\\begin{align}K_e = \\frac{E A}{L} \\begin{bmatrix} +T & -T \\\\ -T & +T \\end{bmatrix},\n    \\qquad T_{ij} = d_i d_j\\end{align}\n\nwith $(d_1, d_2, d_3)$ the unit direction cosines along\nthe I\u2192J axis.\n\n## References\n* Cook, R. D., Malkus, D. S., Plesha, M. E., Witt, R. J. (2002)\n  *Concepts and Applications of Finite Element Analysis*, 4th\n  ed., Wiley, \u00a73.2.\n* Bathe, K.-J. (2014) *Finite Element Procedures*, 2nd ed.,\n  Prentice Hall, \u00a75.3.1.\n* Przemieniecki, J. S. (1968) *Theory of Matrix Structural\n  Analysis*, McGraw-Hill, \u00a75.1.\n\nImplementation: :class:`femorph_solver.elements.truss2.Truss2`.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from __future__ import annotations\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pyvista as pv"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Linear shape functions on [-1, +1]\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "s = np.linspace(-1.0, 1.0, 200)\nN1 = 0.5 * (1.0 - s)\nN2 = 0.5 * (1.0 + s)\n\nfig, ax = plt.subplots(1, 1, figsize=(6.4, 3.6))\nax.plot(s, N1, label=\"$N_1(s) = (1 - s) / 2$\", color=\"#1f77b4\", lw=2)\nax.plot(s, N2, label=\"$N_2(s) = (1 + s) / 2$\", color=\"#ff7f0e\", lw=2)\nax.scatter([-1.0, 1.0], [1.0, 1.0], color=\"black\", zorder=5)\nax.set_xlabel(r\"$s$\")\nax.set_ylabel(\"$N_i(s)$\")\nax.set_title(\"TRUSS2 linear shape functions\")\nax.legend(loc=\"lower center\", ncol=2, fontsize=10)\nax.grid(True, ls=\":\", alpha=0.5)\nax.set_xlim(-1.0, 1.0)\nax.set_ylim(-0.05, 1.1)\nfig.tight_layout()\nfig.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Reference element with the 2-point Gauss-Legendre rule\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cells = np.array([2, 0, 1])\ncell_types = np.array([pv.CellType.LINE], dtype=np.uint8)\ntruss = pv.UnstructuredGrid(cells, cell_types, np.array([[-1.0, 0.0, 0.0], [+1.0, 0.0, 0.0]]))\ng = 1.0 / np.sqrt(3.0)\ngauss = np.array([[-g, 0.0, 0.0], [+g, 0.0, 0.0]])\n\nplotter = pv.Plotter(off_screen=True, window_size=(640, 200))\nplotter.add_mesh(truss, color=\"black\", line_width=4)\nplotter.add_points(\n    np.array([[-1.0, 0.0, 0.0], [+1.0, 0.0, 0.0]]),\n    render_points_as_spheres=True,\n    point_size=18,\n    color=\"black\",\n    label=\"end nodes\",\n)\nplotter.add_points(\n    gauss,\n    render_points_as_spheres=True,\n    point_size=14,\n    color=\"#d62728\",\n    label=\"2-pt Gauss-Legendre\",\n)\nplotter.view_xy()\nplotter.camera.zoom(1.6)\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Sanity \u2014 partition of unity at every Gauss point\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "sums = N1[np.searchsorted(s, [-g, +g])] + N2[np.searchsorted(s, [-g, +g])]\nnp.testing.assert_allclose(sums, 1.0, atol=1e-12)\nprint(\"OK \u2014 N_1(s) + N_2(s) = 1 at every Gauss point.\")"
      ]
    }
  ],
  "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
}