Brakke’s samples: drops and capillarity

Liquid volumes against walls: contact angles, gravity, curved tanks, and the forces a liquid bridge exerts.

Each sample is run twice from its datafile: once with the recipe Brakke wrote into it (its gogo command, or the usage its comments describe), once with a default ev.relax() given as many refinements as the recipe does (levels). The table shows the energy each ends with, the residual (how far from equilibrium, see Relaxing a surface), the facet count, the time, and what relax() reports: converged, stable, and any health issues. A lower energy on the same mesh is not always the better answer: a coarse mesh can lower its energy in ways the smooth surface can’t, so where an exact value is known, the error against it is the measure. Times are from the documentation build and vary.

This gallery assumes Getting started and Relaxing a surface; its pages can be read in any order.

import numpy as np
import pyvista as pv
import pysurfaceevolver as pyse
import brakke_helpers as bh

pv.set_jupyter_backend("static")

mound.fe: a drop on a table

A drop of fixed volume on the plane \(z = 0\) with a contact angle (here 90 degrees, a parameter) and gravity. The contact angle comes from an energy integral on the contact line; the table top is drawn with fixed facets. re refines the contact line only.

print(bh.recipe("mound.fe"))
runs = bh.compare("mound.fe")
bh.show(runs)
re; g 5; r; g 5; r; g 5; hessian; hessian;
energyresidualfacetssecondsconvergedstablenotes
recipe: re; g 5; r; g 5; r; g 5; hessian; hessian;3.8467589043.1e-053880.00
relax(levels=2)3.8467842592.1e-093240.02yesyes
_images/c8faf6497e698a51061a4a8b8a5fd2c629c33da8ed9dfa53683726561d097265.png

The recipe’s re refines the edges on the table first, so its mesh has more facets there; the energies agree to about 1e-5. The file’s other recipes change the contact angle and gravity; gogo2 sets 45 degrees:

runs = bh.compare("mound.fe", "gogo2", levels=2,
                  setup=lambda ev: ev.parameters.update(angle=45))
bh.show(runs)
energyresidualfacetssecondsconvergedstablenotes
recipe: gogo21.8758930824.1e-043880.00
relax(levels=2)1.8767434591.4e-093240.12yesyes
_images/5122de6e6e11b3257a70effe5d837f203d5a4bfb08445274fcf9c767e6ecf4d2.png

addload_example.fe: nine drops at once

On loading, the file’s setup script loads the mound eight more times next to the first (addload), each drop with its own contact angle on its edges (from 50 to 130 degrees).

runs = bh.compare("addload_example.fe")
bh.show(runs)
energyresidualfacetssecondsconvergedstablenotes
recipe: re; g 5; r; g 5; r; g 5; hessian; hessian;32.976599913.8e-0434920.03
relax(levels=2)32.975196682.7e-0929160.25yesyes
_images/86d4330236091900c4eea9686d33832e7403319cb02e63ba1668178d13b27f07.png

column.fe: a column of solder

A liquid solder column between two pads, its upper pad offset sideways (a skewed catenoid shape with a volume and gravity; units cgs). The file goes on to compute the forces on the pads by central differences.

runs = bh.compare("column.fe")
bh.show(runs)
energyresidualfacetssecondsconvergedstablenotes
recipe: u; g 5; r; g 5 ; r; g 5; hessian; hessian; do_yforce; do_zforce;10.488842222.0e-053840.01
relax(levels=2)10.488715344.7e-113840.08yesyes
_images/f3820a6553b62b0b7c1377da70b240a14a3042d54b6f971947805223c1d8ee81.png

sphere.fe: liquid in a spherical tank

A spherical tank partly filled, with a contact angle on the wall. Straight edges can’t follow the curved wall, so the file gives the gaps between them and the wall an energy and volume (a “convex” constraint). Evolver has no Hessian for that gap energy, so relax() uses gradient steps only; the recipe uses conjugate gradients at the end.

print(bh.recipe("sphere.fe"))
runs = bh.compare("sphere.fe")
bh.show(runs)
g 5; r; g 5; r; g 5; r; g 10; conj_grad; g 20;
energyresidualfacetssecondsconvergedstablenotes
recipe: g 5; r; g 5; r; g 5; r; g 10; conj_grad; g 20;2.6017394223.2e-025120.04
relax(levels=3)2.6010584331.8e-025128.68no
_images/f812cc1a0463f79ce4abee2e8d1761ac13925832555d12b63747361a75305131.png

Neither run reaches a residual near zero: gradient steps converge slowly, and the energies agree to about 3e-4.

tankex.fe: liquid in a cylindrical tank

A flat-ended cylindrical tank on its side, gravity pulling the liquid to the wall, gap energies as in sphere.fe. The file has no recipe; here two refinements and plain gradient steps (it starts with 8 facets, too coarse to keep its shape on its own). With no Hessian, relax() goes on with gradient rounds while no facet is degenerate; from fewer refinements (levels=1) its facets degenerate first, and it stops there and says so instead of sliding into what the degenerate mesh allows.

runs = bh.compare("tankex.fe", "r; r; g 1000", levels=2)
bh.show(runs)
energyresidualfacetssecondsconvergedstablenotes
recipe: r; r; g 10001.5322718639.4e-011286.47degenerate facets (smallest angle 8.5e-07 degrees, 4 below 5)
relax(levels=2), 4 collapsed edges1.5050227046.8e-011227.50no
_images/91350c03a3e563764f6a1d6d9da39da33a717fc0a9f27b0967890ad75d3b9267.png

Solder joints: bga-1.fe, cbga1.fe, cbga2.fe

Three files from Brakke’s online examples (included with pySE), in cgs units (surface tension 430-480 erg/cm²). bga-1.fe is the first of his ball grid array tutorial: solder between two round pads.

runs = bh.compare("bga-1.fe")
bh.show(runs)
energyresidualfacetssecondsconvergedstablenotes
recipe: u; g 5; r; g 5; r; g 5; hessian; hessian0.15475761481.9e-075760.00
relax(levels=2)0.15475676573.0e-095760.03yesyes
_images/c56b01f9f879bb737248b1bcdce53c61382322d25c61a0ffb5864fdb21c88afc.png

cbga1.fe holds solder between a pad and a sphere, its contact with the sphere as edge integrals. It starts as a hexagonal prism of quads that Evolver splits at their centres; the recipe first swaps the six vertical edges by hand (edgeswap), and without that, gradient steps settle at an energy of 11 on the badly triangulated prism. relax() equiangulates after its first step, which swaps them. (The recipe ends unconverged.)

runs = bh.compare("cbga1.fe")
bh.show(runs)
energyresidualfacetssecondsconvergedstablenotes
recipe: edgeswap edge where id >= 13 and id <= 18; g 5; r; g 5; V; g 5; V; g 5; r; g 22; fixred;4.0828648823.9e+015040.02
relax(levels=2)4.0954192622.3e-115760.26yesnounstable: 2 direction(s) lower the energy
_images/bf161ff871f077983bea421a5714778e1812701f50c2125f5d39a2877c39424b.png

cbga2.fe is the same joint with explicit contact facets on the sphere. Its comments say that at high contact angles the symmetric bulge is unstable, bulging to one side, and suggest Evolver’s saddle to move off it. relax() finds the symmetric bulge and reports it unstable; ev.saddle() steps along the unstable mode, and relaxing from there finds the drop leaning over, at a lower energy.

import warnings
ev = pyse.Evolver("cbga2.fe")
with warnings.catch_warnings():
    warnings.simplefilter("ignore", pyse.UnstableEquilibriumWarning)
    r = ev.relax(levels=2)
print(f"symmetric: energy {ev.total_energy:.6f}, {r.negative_modes} unstable directions")
print(f"saddle() lowers the energy by {ev.saddle():.4f}")
r = ev.relax()
print(f"leaning:   energy {ev.total_energy:.6f}, stable: {r.stable}")
ev.plot(color="sandybrown", show_edges=False, smooth_shading=True)
symmetric: energy 3.943787, 2 unstable directions
saddle() lowers the energy by 0.0361
leaning:   energy 3.835180, stable: True
_images/99d0cb0810715bcaa4af1d5fbaf76a4f5b7e72a94e5499b71712fa6158e57d2d.png

Next

Energies other than surface tension: Brakke’s samples: crystals and elastic membranes. The same physics built from arrays, with pyse.constraints writing the contact-angle integrals: the liquid bridge and the slit droplet. Brakke’s ball grid array series in full: solder joints.