{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Constitutive modelling using JuliaFEM\n",
    "\n",
    "Author(s): Jukka Aho\n",
    "\n",
    "**Abstract**: This is reproduction of the results from notebook [*Ideal plastic Von Mises material*](https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/notebooks/2015-09-24-Ideal%20plastic%20Von%20Mises%20material.ipynb) made by Olli Väinölä. Small strain theory is used, see https://en.wikipedia.org/wiki/Flow_plasticity_theory. The purpose of this notebook is to show how one can easily design and simulate material models using JuliaFEM. This is 2d version. For 3d version see Olli's notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "using JuliaFEM: IntegrationPoint, Field, FieldSet, TimeStep, Increment\n",
    "using ForwardDiff\n",
    "using PyPlot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The atomic structure here is `IntegrationPoint`. It has identical `Field`-structure like elements and can store multidimensional variables which can be time-dependent also. That way one can easily store, for example, measured strain and run material simulation for real measured data and fit material parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "JuliaFEM.DefaultDiscreteField([JuliaFEM.TimeStep(0.0,JuliaFEM.Increment[[0.0]])])"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ip = IntegrationPoint([0.0, 0.0], 1.0)\n",
    "\n",
    "ip.fields[\"total strain\"] = Field()\n",
    "ip.fields[\"plastic strain\"] = Field()\n",
    "ip.fields[\"plastic potential\"] = Field()\n",
    "ip.fields[\"elastic strain\"] = Field()\n",
    "ip.fields[\"effective plastic strain\"] = Field()\n",
    "ip.fields[\"stress\"] = Field()\n",
    "ip.fields[\"young\"] = Field(200.0e9)\n",
    "ip.fields[\"poisson\"] = Field(0.3)\n",
    "ip.fields[\"yield stress\"] = Field(200.0e6)\n",
    "ip.fields[\"plastic rate parameter\"] = Field(0.0) # material parameter"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Accessing parameters is done just like with `FieldSet`s in general:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO: [2.0e11]\n",
      "INFO: [2.0e8]\n"
     ]
    }
   ],
   "source": [
    "info(last(ip.fields[\"young\"]))\n",
    "info(last(ip.fields[\"yield stress\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next to material model: ideal plastic material model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "calculate_stress! (generic function with 1 method)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"\"\" Ideal plastic material model. \"\"\"\n",
    "function calculate_stress!(ip::IntegrationPoint, strain::Matrix, time::Number)\n",
    "\n",
    "    dim = size(strain, 1)\n",
    "    \n",
    "    poisson = last(ip.fields[\"poisson\"])[1]\n",
    "    young = last(ip.fields[\"young\"])[1]\n",
    "    mu = young/(2*(1+poisson))\n",
    "    lambda = young*poisson/((1+poisson)*(1-2*poisson))\n",
    "    if dim == 2\n",
    "        lambda = 2*lambda*mu/(lambda + 2*mu)  # <- correction for 2d\n",
    "    end\n",
    "\n",
    "    # yield function\n",
    "    function f(stress)\n",
    "        # https://en.wikipedia.org/wiki/Yield_surface\n",
    "        # https://en.wikipedia.org/wiki/Von_Mises_yield_criterion\n",
    "        stress_y = last(ip.fields[\"yield stress\"])[1]\n",
    "        s1 = stress[1,1]\n",
    "        s2 = stress[2,2]\n",
    "        s12 = stress[1,2]\n",
    "        stress_v = sqrt(s1^2 - s1*s2 + s2^2 + 3*s12^2)\n",
    "        return stress_v - stress_y\n",
    "    end\n",
    "\n",
    "    strain_plastic_prev = last(ip.fields[\"plastic strain\"])[1]\n",
    "    strain_elastic = strain - strain_plastic_prev\n",
    "    stress_trial = lambda*trace(strain_elastic)*I + 2*mu*(strain_elastic)\n",
    "\n",
    "    if f(stress_trial) <= 0.0\n",
    "        #info(\"time=$time: no yield\")\n",
    "        push!(ip.fields[\"stress\"], TimeStep(time, Increment(Matrix[stress_trial])))\n",
    "        push!(ip.fields[\"total strain\"], TimeStep(time, Increment(Matrix[strain])))\n",
    "        return true\n",
    "    else\n",
    "        #info(\"time=$time: yield, f(stress_trial) = $(f(stress_trial))\")\n",
    "        dt = time - ip.fields[\"total strain\"][end].time\n",
    "\n",
    "        # associated flow rule, plastic potential ψ(σ) = f\n",
    "        psi = f\n",
    "\n",
    "        \"\"\" Calculate equations\n",
    "\n",
    "            dσ - C (dϵₜ - dγ*dΨ/dσ) = 0\n",
    "                         σₑ(σ) - σy = 0\n",
    "\n",
    "            dϵₜ = total strain\n",
    "            dϵₑ = elastic strain\n",
    "            dϵₚ = plastic strain\n",
    "        \n",
    "            dϵₜ = dϵₑ + dϵₚ\n",
    "            => dϵₑ = dϵₜ - dϵₚ = dϵₜ - dγ*dψ/dσ\n",
    "            dσ = λ⋅tr(dϵₑ)I + 2μ⋅dϵₑ\n",
    "            => dσ - λ⋅tr(dϵₑ)I - 2μ⋅dϵₑ = 0\n",
    "        \"\"\"\n",
    "        function residual(params::Vector)\n",
    "            dstress = reshape(params[1:prod(size(strain))], size(strain))\n",
    "            gamma = params[end]\n",
    "            strain_prev = last(ip.fields[\"total strain\"])[1]\n",
    "            stress_prev = last(ip.fields[\"stress\"])[1]\n",
    "            dstrain_total = 1/dt*(strain - strain_prev)\n",
    "            stress_tot = stress_prev + dstress\n",
    "            # derivative of plastic potential ψ(σ) w.r.t 2nd order tensor σ(ϵ)\n",
    "            # https://en.wikipedia.org/wiki/Tensor_derivative_%28continuum_mechanics%29\n",
    "            # \"Derivatives of scalar valued functions of second-order tensors\"\n",
    "            dpsi_dstress = derivative(psi, stress_tot)\n",
    "            dstrain_plastic = gamma*dpsi_dstress\n",
    "            dstrain_elastic = dstrain_total - dstrain_plastic\n",
    "            dstress_elastic = lambda*trace(dstrain_elastic)*I + 2*mu*dstrain_elastic\n",
    "            stress_delta = dstress - dstress_elastic\n",
    "            return [vec(stress_delta); psi(stress_tot)]\n",
    "        end\n",
    "\n",
    "        # solve equations using Newton iterations. Jacobian is calcualated\n",
    "        # using automatic differentiation as usual.\n",
    "        stress_prev = last(ip.fields[\"stress\"])[1]\n",
    "        initial_gamma = last(ip.fields[\"plastic rate parameter\"])[1]\n",
    "        params = [vec(stress_prev); initial_gamma]\n",
    "        dparams = zeros(5)\n",
    "        l = [1, 4, 3, 5] # <-- reorder to voigt\n",
    "        for iterations=1:10\n",
    "            A = ForwardDiff.jacobian(residual, params)[l,l]\n",
    "            b = -residual(params)[l]\n",
    "            dparams = A \\ b\n",
    "            params[l] += dparams\n",
    "            norm(dparams) < 1.0e-7 && break\n",
    "        end\n",
    "        params[2] = params[3]\n",
    "\n",
    "        # save all kind of stuff to integration point\n",
    "\n",
    "        dstress = reshape(params[1:prod(size(strain))], size(strain))\n",
    "        stress = stress_prev + dstress\n",
    "        dpsi_dstress = derivative(psi, stress)\n",
    "        gamma = params[end]\n",
    "        dstrain_plastic = gamma*dpsi_dstress\n",
    "        strain_plastic_prev = last(ip.fields[\"plastic strain\"])[1]\n",
    "        strain_plastic  = strain_plastic_prev + dstrain_plastic\n",
    "\n",
    "        push!(ip.fields[\"stress\"], TimeStep(time, Increment(Matrix[stress])))\n",
    "        push!(ip.fields[\"total strain\"], TimeStep(time, Increment(Matrix[strain])))\n",
    "        push!(ip.fields[\"elastic strain\"], TimeStep(time, Increment(Matrix[strain_elastic])))\n",
    "        push!(ip.fields[\"plastic strain\"], TimeStep(time, Increment(Matrix[strain_plastic])))\n",
    "        push!(ip.fields[\"plastic rate parameter\"], TimeStep(time, Increment(params[end])))\n",
    "        push!(ip.fields[\"plastic potential\"], TimeStep(time, Increment(psi(stress))))\n",
    "        push!(ip.fields[\"derivative of plastic potential\"], TimeStep(time, Increment(Matrix[dpsi_dstress])))\n",
    "    end\n",
    "\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Small \"simulator\" to study the behavior of material model in `IntegrationPoint`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "elapsed time: 0.031678742 seconds\n"
     ]
    }
   ],
   "source": [
    "function run(steps=11)\n",
    "\n",
    "    # initialization\n",
    "    ip = IntegrationPoint([0.0, 0.0], 1.0)\n",
    "    ip.fields[\"total strain\"] = Field()\n",
    "    ip.fields[\"plastic strain\"] = Field(Matrix[zeros(2,2)])\n",
    "    ip.fields[\"plastic potential\"] = Field()\n",
    "    ip.fields[\"derivative of plastic potential\"] = Field()\n",
    "    ip.fields[\"elastic strain\"] = Field()\n",
    "    ip.fields[\"effective plastic strain\"] = Field()\n",
    "    ip.fields[\"stress\"] = Field()\n",
    "    ip.fields[\"young\"] = Field(200.0e9)\n",
    "    ip.fields[\"poisson\"] = Field(0.3)\n",
    "    ip.fields[\"yield stress\"] = Field(200.0e6)\n",
    "    ip.fields[\"plastic rate parameter\"] = Field(0.0) # material parameter\n",
    "\n",
    "    strain = zeros(2, 2)\n",
    "    strain[1,1] = 2.0e-3\n",
    "    strain[2,2] = -0.3*2.0e-3\n",
    "    #strain[1,2] = 1.0e-3\n",
    "    #strain[2,1] = 1.0e-3\n",
    "\n",
    "    # calculate stress in integration point\n",
    "    #for (time, omega) in enumerate(linspace(0, 4*pi, steps))\n",
    "    #    calculate_stress!(ip, sin(omega)*strain, Float64(time))\n",
    "    #end\n",
    "\n",
    "    for (time, k) in enumerate(linspace(0, 1, steps))\n",
    "        calculate_stress!(ip, k*strain, Float64(time))\n",
    "    end\n",
    "\n",
    "    return ip\n",
    "end\n",
    "\n",
    "tic()\n",
    "ip = run()\n",
    "toc();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Visualize results:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 40528 characters]",
      "text/plain": [
       "PyPlot.Figure(PyObject <matplotlib.figure.Figure object at 0x7fa8b80dd450>)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "PyObject <matplotlib.legend.Legend object at 0x7fa8a16155d0>"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "steps = length(ip.fields[\"total strain\"])\n",
    "eps11 = Float64[]\n",
    "sig11 = Float64[]\n",
    "sig22 = Float64[]\n",
    "sig12 = Float64[]\n",
    "principals = Vector{Float64}[]\n",
    "for i=1:steps\n",
    "    # extract from integration points\n",
    "    # field -> timestep -> increment -> (vector of tensors, take first) -> (first component)\n",
    "    strain = ip.fields[\"total strain\"][i][end][1]*1.0e6\n",
    "    stress = ip.fields[\"stress\"][i][end][1]*1.0e-6\n",
    "    push!(eps11, strain[1,1])\n",
    "    push!(sig11, stress[1,1])\n",
    "    push!(sig22, stress[2,2])\n",
    "    push!(sig12, stress[1,2])\n",
    "    push!(principals, eigvals(stress))\n",
    "end\n",
    "\n",
    "PyPlot.figure(figsize=(7, 5))\n",
    "PyPlot.plot(eps11, sig11, \"-bo\", label=\"s11\")\n",
    "PyPlot.plot(eps11, sig22, \"-ro\", label=\"s22\")\n",
    "PyPlot.plot(eps11, sig12, \"-go\", label=\"s12\")\n",
    "PyPlot.title(\"Stress-Strain curve\")\n",
    "PyPlot.xlabel(\"Strain [ustr]\")\n",
    "PyPlot.ylabel(\"Stress [MPa]\")\n",
    "#PyPlot.ylim([-250, 250])\n",
    "#PyPlot.xlim([-2100, 2100])\n",
    "PyPlot.legend(loc=\"best\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 46352 characters]",
      "text/plain": [
       "PyPlot.Figure(PyObject <matplotlib.figure.Figure object at 0x7fa8a1931150>)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "1-element Array{Any,1}:\n",
       " PyObject <matplotlib.lines.Line2D object at 0x7fa8a1513050>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function plot_principal()\n",
    "    PyPlot.figure(figsize=(6, 6))\n",
    "    n = 100\n",
    "    s = linspace(-300, 300, n)\n",
    "    s1 = repmat(s', n, 1)\n",
    "    s2 = repmat(s, 1, n)\n",
    "    sigma = sqrt(s1.^2 + s2.^2 - s1.*s2)\n",
    "    contour(s1, s2, sigma, [200], colors=\"k\")\n",
    "    p1 = [p[1] for p in principals]\n",
    "    p2 = [p[2] for p in principals]\n",
    "    PyPlot.plot(p1, p2, \"-bo\")\n",
    "    axis(\"equal\")\n",
    "    xlabel(\"sigma 1\")\n",
    "    ylabel(\"sigma 2\")\n",
    "    title(\"principal stress\")\n",
    "    dir = last(ip.fields[\"derivative of plastic potential\"])[1]\n",
    "    PyPlot.plot([p1[end], p1[end]+dir[2,2]*50],\n",
    "                [p2[end], p2[end]+dir[1,1]*50], \"-r\")\n",
    "end\n",
    "plot_principal()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Julia 0.4.0",
   "language": "julia",
   "name": "julia-0.4"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "0.4.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
