{ "cells": [ { "cell_type": "markdown", "id": "brakke_capillarity-0", "metadata": {}, "source": [ "# Brakke's samples: drops and capillarity\n", "\n", "\n", "Liquid volumes against walls: contact angles, gravity, curved tanks, and the forces a\n", "liquid bridge exerts.\n", "\n", "Each sample is run twice from its datafile: once with the recipe Brakke wrote into\n", "it (its `gogo` command, or the usage its comments describe), once with a default\n", "`ev.relax()` given as many refinements as the recipe does (`levels`). The table\n", "shows the energy each ends with, the residual (how far from equilibrium, see\n", "[Relaxing a surface](relaxing.md)), the facet count, the time, and what `relax()`\n", "reports: converged, stable, and any health issues. A lower energy on the same mesh\n", "is not always the better answer: a coarse mesh can lower its energy in ways the\n", "smooth surface can't, so where an exact value is known, the error against it is\n", "the measure. Times are from the documentation build and vary.\n", "\n", "This gallery assumes [Getting started](getting_started.md) and\n", "[Relaxing a surface](relaxing.md); its pages can be read in any order." ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-1", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pyvista as pv\n", "import pysurfaceevolver as pyse\n", "import brakke_helpers as bh\n", "\n", "pv.set_jupyter_backend(\"static\")" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-2", "metadata": {}, "source": [ "## `mound.fe`: a drop on a table\n", "\n", "A drop of fixed volume on the plane $z = 0$ with a contact angle (here 90 degrees, a\n", "parameter) and gravity. The contact angle comes from an energy integral on the\n", "contact line; the table top is drawn with fixed facets. `re` refines the contact line\n", "only." ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-3", "metadata": {}, "outputs": [], "source": [ "print(bh.recipe(\"mound.fe\"))\n", "runs = bh.compare(\"mound.fe\")\n", "bh.show(runs)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-4", "metadata": {}, "source": [ "The recipe's `re` refines the edges on the table first, so its mesh has more facets\n", "there; the energies agree to about 1e-5. The file's other recipes change the contact\n", "angle and gravity; `gogo2` sets 45 degrees:" ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-5", "metadata": {}, "outputs": [], "source": [ "runs = bh.compare(\"mound.fe\", \"gogo2\", levels=2,\n", " setup=lambda ev: ev.parameters.update(angle=45))\n", "bh.show(runs)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-6", "metadata": {}, "source": [ "## `addload_example.fe`: nine drops at once\n", "\n", "On loading, the file's `setup` script loads the mound eight more times next to the\n", "first (`addload`), each drop with its own contact angle on its edges (from 50 to 130\n", "degrees)." ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-7", "metadata": {}, "outputs": [], "source": [ "runs = bh.compare(\"addload_example.fe\")\n", "bh.show(runs)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-8", "metadata": {}, "source": [ "## `column.fe`: a column of solder\n", "\n", "A liquid solder column between two pads, its upper pad offset sideways (a skewed\n", "catenoid shape with a volume and gravity; units cgs). The file goes on to compute the\n", "forces on the pads by central differences." ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-9", "metadata": {}, "outputs": [], "source": [ "runs = bh.compare(\"column.fe\")\n", "bh.show(runs)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-10", "metadata": {}, "source": [ "## `sphere.fe`: liquid in a spherical tank\n", "\n", "A spherical tank partly filled, with a contact angle on the wall. Straight edges can't\n", "follow the curved wall, so the file gives the gaps between them and the wall an\n", "energy and volume (a \"convex\" constraint). Evolver has no Hessian for that gap energy,\n", "so `relax()` uses gradient steps only; the recipe uses conjugate gradients at the\n", "end." ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-11", "metadata": {}, "outputs": [], "source": [ "print(bh.recipe(\"sphere.fe\"))\n", "runs = bh.compare(\"sphere.fe\")\n", "bh.show(runs)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-12", "metadata": {}, "source": [ "Neither run reaches a residual near zero: gradient steps converge slowly, and the\n", "energies agree to about 3e-4." ] }, { "cell_type": "markdown", "id": "brakke_capillarity-13", "metadata": {}, "source": [ "## `tankex.fe`: liquid in a cylindrical tank\n", "\n", "A flat-ended cylindrical tank on its side, gravity pulling the liquid to the wall, gap\n", "energies as in `sphere.fe`. The file has no recipe; here two refinements and plain\n", "gradient steps (it starts with 8 facets, too coarse to keep its shape on its own).\n", "With no Hessian, `relax()` goes on with gradient rounds while no facet is degenerate;\n", "from fewer refinements (`levels=1`) its facets degenerate first, and it stops there and\n", "says so instead of sliding into what the degenerate mesh allows." ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-14", "metadata": {}, "outputs": [], "source": [ "runs = bh.compare(\"tankex.fe\", \"r; r; g 1000\", levels=2)\n", "bh.show(runs)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-15", "metadata": {}, "source": [ "## Solder joints: `bga-1.fe`, `cbga1.fe`, `cbga2.fe`\n", "\n", "Three files from Brakke's online examples (included with pySE), in cgs units\n", "(surface tension 430-480 erg/cm²). `bga-1.fe` is the first of his ball grid array\n", "tutorial: solder between two round pads." ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-16", "metadata": {}, "outputs": [], "source": [ "runs = bh.compare(\"bga-1.fe\")\n", "bh.show(runs)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-17", "metadata": {}, "source": [ "`cbga1.fe` holds solder between a pad and a sphere, its contact with the sphere as\n", "edge integrals. It starts as a hexagonal prism of quads that Evolver splits at their\n", "centres; the recipe first swaps the six vertical edges by hand (`edgeswap`), and\n", "without that, gradient steps settle at an energy of 11 on the badly triangulated prism.\n", "`relax()` equiangulates after its first step, which swaps them. (The recipe ends\n", "unconverged.)" ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-18", "metadata": {}, "outputs": [], "source": [ "runs = bh.compare(\"cbga1.fe\")\n", "bh.show(runs)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-19", "metadata": {}, "source": [ "`cbga2.fe` is the same joint with explicit contact facets on the sphere. Its comments\n", "say that at high contact angles the symmetric bulge is unstable, bulging to one side,\n", "and suggest Evolver's `saddle` to move off it. `relax()` finds the symmetric bulge and\n", "reports it unstable; `ev.saddle()` steps along the unstable mode, and relaxing from\n", "there finds the drop leaning over, at a lower energy." ] }, { "cell_type": "code", "execution_count": null, "id": "brakke_capillarity-20", "metadata": {}, "outputs": [], "source": [ "import warnings\n", "ev = pyse.Evolver(\"cbga2.fe\")\n", "with warnings.catch_warnings():\n", " warnings.simplefilter(\"ignore\", pyse.UnstableEquilibriumWarning)\n", " r = ev.relax(levels=2)\n", "print(f\"symmetric: energy {ev.total_energy:.6f}, {r.negative_modes} unstable directions\")\n", "print(f\"saddle() lowers the energy by {ev.saddle():.4f}\")\n", "r = ev.relax()\n", "print(f\"leaning: energy {ev.total_energy:.6f}, stable: {r.stable}\")\n", "ev.plot(color=\"sandybrown\", show_edges=False, smooth_shading=True)" ] }, { "cell_type": "markdown", "id": "brakke_capillarity-21", "metadata": {}, "source": [ "## Next\n", "\n", "Energies other than surface tension: [Brakke's samples: crystals and elastic\n", "membranes](brakke_crystals_membranes.ipynb). The same physics built from arrays, with\n", "`pyse.constraints` writing the contact-angle integrals: the\n", "[liquid bridge](liquid_bridge.ipynb) and the [slit droplet](slit_droplet.ipynb).\n", "Brakke's ball grid array series in full: [solder joints](solder_joints.ipynb)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }