Ferrite.jl release announcements

Ferrite version 1.2.0

We have recently release Ferrite version 1.2.0, the second feature release in the 1.X release series. This release contains some new features but mostly bugfixes and enhancements to the documentation. The full list of changes can be found in CHANGELOG.md.

Thanks to everyone who contributed!

Ferrite versions 1.3 and 1.4

Since we missed updating this thread about the last releases, let me leave a brief summary. The main features of these two releases are the new MultiFieldCellValues to optimize the field evaluations on elements containing multiple fields, we now have proper support for mixed tensors on embedded elements (before these releases static arrays have been returned) and the assembly with rectangular matrix shapes has been added. We have also added more convenience features and optimized the performance of many operations. More details are given in the CHANGELOG.md.

Thanks to everyone who contributed!

FerriteViz.jl v0.3.0 — a ParaView-style pipeline for Ferrite.jl results

I’m happy to announce FerriteViz.jl v0.3.0, a full rewrite of the internals of the visualization package for Ferrite.jl.

The short version: plotting a finite element result is no longer “call a recipe with a pile of keyword arguments”. It follows the model ParaView uses — a source holds your solution, filters transform it, and representations draw the result:

FEData(dh, u)  |>  filter  |>  filter  |>  solutionplot(...)
    source          transformations         representation
using Ferrite, FerriteViz, GLMakie

dh, u = solve_problem()
ds = FEData(dh, u)                       # the source

solutionplot(ds)                         # plot it
solutionplot(ds |> WarpByVector(:u) |> Gradient(:u) |> VonMises(); color = :vonMises)

Filters map an FEData to an FEData, so they compose freely, and the whole chain stays reactive: FerriteViz.update! anywhere in a pipeline propagates through every filter into every open plot — which is what makes live plotting during a solve work.

One pipeline, three stages, on a plastified cantilever (J2 plasticity, trilinear hexahedra). (a) the raw source, (b) warped by the displacement field, (c) plastic work density built from two quadrature point quantities. All three panels share one bounding box. In (c) the white lines are the FE element outlines, the coloured patches the quadrature point partition — see below.


Filters compose, and derived quantities are first class

Gradient turns a field into its piecewise discontinuous gradient, and Derive maps that through an arbitrary function — which is where your constitutive law goes. Because the pipeline carries named arrays, a quantity you solved for and one you derived live in the same dataset and can be plotted side by side:

mixed = FEData(dh, u) |>
        Gradient(:u; copy_fields = [:p]) |>
        Derive(∇u -> vonmises(stress(∇u)); input = :gradient, output = :σvM)

solutionplot(mixed; color = :p)      # solved for
solutionplot(mixed; color = :σvM)    # derived

Cook’s membrane, mixed displacement/pressure formulation (Q2/Q1). The visible faceting in
(c) is not an artifact to be ashamed of — the gradient is kept element-wise discontinuous instead of being L2-projected onto a nodal field, because those inter-element jumps are one of the better indicators of an under-resolved discretization. Colour limits in (c) are clipped at the 90th percentile; the clamped corner is singular.

Filters currently shipping: WarpByVector, Gradient, CrinkleClip, Refine,
FirstOrderRefinement, ExtractComponent, Magnitude, Norm1, VonMises, Deviator, Threshold, Derive, AddQuadraturePointData.

Internal variables without averaging or smoothing

This is the feature I’d most like people to try. Internal variables — plastic strain, damage, stress in a history-dependent material — are known only at the quadrature points, with no interpolation defining them anywhere else. The two usual ways of drawing them both destroy information: averaging per cell throws away the sub-element variation, and projecting onto a nodal field invents smoothness that smears out exactly the localization you were looking for.

AddQuadraturePointData instead partitions every cell into the Voronoi regions of its quadrature points and fills each region with that point’s value.

A localisation band sampled at 2×2 quadrature points per cell on a 12×12 mesh. (a) cell average, (b) the quadrature point partition, (c) the underlying field. Same mesh, same data, same colour limits.

The result is ordinary point data, so it feeds straight back into the rest of the pipeline. Two such filters sharing a quadrature rule produce the same vertex layout, which is what lets their arrays be combined afterwards — for instance into a plastic work density:

ds |> AddQuadraturePointData(qr, states; extract = s -> s.σ,  output = :σ) |>
      AddQuadraturePointData(qr, states; extract = s -> s.ϵᵖ, output = :εᵖ) |>
      Derive((σ, εᵖ) -> σ ⊡ εᵖ; input = [:σ, :εᵖ], output = :wᵖ) |>
      WarpByVector(:u, 2.0)

extract pulls the quantity out of your material state struct, so states can be handed
over exactly as it comes out of your solver.

High order fields

High order solutions are still resolved by refinement rather than by curved rendering, but
Refine now improves the geometry too, not just the solution:

A Q2 heat problem, clipped open with CrinkleClip to show the interior. (a) flattening the quadratic ansatz onto the element corners produces the star-shaped artifact; (b) and (c) progressively resolve it. FirstOrderRefinement is the cheaper alternative that replaces the high order field by a first order one spanned by its nodes.

The viewer is composable now

ferriteviewer(ds) still gives you the familiar single panel with menus and toggles, but it is no longer hard-wired. The whole thing is described declaratively with Makie.SpecApi: a layout(ds, state) -> GridLayoutSpec hook, pluggable Controls that feed the view state, and spec helpers that assemble panels.

function twopanels(ds, state)
    disp = solutionplotspec(ds; color = :default, colormap = state.colormap)
    pres = solutionplotspec(ds; color = :p,       colormap = state.colormap)
    return S.GridLayout([
        panelspec(disp; colorbar = disp, dim = 2, axis = (; title = "displacement magnitude"))
        panelspec(pres; colorbar = pres, dim = 2, axis = (; title = "pressure"))
    ])
end

ferriteviewer(ds; layout = twopanels, controls = [ColormapMenu(), DeformationToggle()])

Structural state (colormap, which panels exist, anything the layout reads) re-diffs the spec, so Makie updates only the attributes of the plots it reuses. Data streaming — update! and the deformation scale — bypasses the spec entirely and mutates the shared GPU buffers, which is why live plotting stays cheap.

All Makie backends

GLMakie for interactive work, WGLMakie in Pluto/Jupyter, CairoMakie for vector graphics.
The same pipeline code, the same figure:

CairoMakie in particular is working again: its mesh path cannot accept the ShaderAbstractions.Buffers the pipeline shares with the GPU, so the representations now unwrap them to plain arrays when CairoMakie is the active backend. Thanks to PeturBryde for diagnosing and fixing that one (#146, fixes #118).

Timings

Uniform hexahedral grids, vector-valued Q1 field, everything measured after warm-up so no
compilation time is included (Julia 1.12, single machine, GLMakie):

cells dofs triangles FEData(dh,u) Gradient|>Derive first solutionplot update! with an open plot
1 000 3 993 24 000 0.04 s 0.13 s 0.04 s 3.8 ms
8 000 27 783 192 000 0.34 s 1.18 s 0.37 s 26 ms
27 000 89 373 648 000 1.24 s 4.26 s 1.20 s 74 ms
64 000 206 763 1 536 000 2.79 s 10.0 s 3.09 s 144 ms

The last column is the one that matters for live plotting: pushing a new solution vector into an open plot costs milliseconds, because it streams through the shared buffers instead of rebuilding anything. Setting up the tessellation is the one-off cost. Gradient |> Derive is the expensive filter — it evaluates your constitutive function at every tessellation vertex — and is worth applying downstream of a CrinkleClip if you only want to look at a cut.

Breaking changes

v0.3.0 is a breaking release. The main renames:

before now
MakiePlotter(dh, u) FEData(dh, u)
solutionplot(p, field = :p) solutionplot(ds, color = :p)
solutionplot(p, deformation_field = :u) solutionplot(ds |> WarpByVector(:u))
solutionplot(p, field = :gradient, process = f) solutionplot(ds |> Gradient(:u) |> Derive(f, output = :name), color = :name)
crinkle_clip(!) CrinkleClip filter
uniform_refinement Refine
for_discretization FirstOrderRefinement
arrows arrowplot
wireframe meshplot

FEData(dh, u) copies u, so update! no longer mutates your solution vector. transfer_solution’s process keyword, postprocess, x₁/x₂/x₃ and l1/l2 are gone — use the derivation filters instead.

Adding your own cell type is now a single method, reference_tessellation(::Type{<:AbstractRefShape}); Wedge and Pyramid work out of the box, and there is a worked example for cohesive/interface elements in the docs.

Links

Feedback, bug reports and feature requests very welcome — especially from anyone with history-dependent material models, since the quadrature point machinery is new and I’d like to know how it holds up on real problems.

Ferrite version 1.6.0

heat_adaptivity-dark

Refined mesh from heat equation with adaptive mesh refinement

We have just released Ferrite version 1.6.0, the biggest feature release since 1.0.0. The full list of changes can be found in CHANGELOG.md, but the main highlights are summarized below.

Adaptive mesh refinement

The biggest feature of this release is adaptive mesh refinement (AMR) for quadrilateral and hexahedral grids, based on a p4est-style forest of octrees. This is a pure Julia implementation of the p4est algorithms and not just a wrapper around the C library. This means that everything down to the octree data structures is accessible, extensible, and debuggable from Julia. This feature has been under development for a long time. The pull request (#780) was opened almost exactly three years ago and the implementation has been presented at our yearly FerriteCon both in 2023 and 2024. It is great to see it merged finally!

Note that the feature is marked experimental for now: the API may change in minor releases without following semantic versioning. To get started, see the new AMR topic guide, the tutorial on Heat equation with adaptive mesh refinement, and the example Linear elasticity with adaptive mesh refinement in the code gallery. Feedback from early adopters is very welcome!

Higher order elements

  • New interpolations Lagrange{RefTetrahedron, 3}, Lagrange{RefTetrahedron, 4}, and Lagrange{RefHexahedron, 3}. Supporting these required teaching the dof distribution to handle multiple nodal dofs on faces shared between cells by taking the relative orientation of the face into account.
  • New quadrature rule type :polyquad for RefTetrahedron supporting orders 1 to 10 (previously the maximum order was 5), with positive weights and points strictly inside the reference tetrahedron.

Export to the VTKHDF file format

The new VTKHDFGridFile is the counterpart of VTKGridFile for the HDF5-based VTKHDF file format, provided through a package extension that loads together with the new VTKHDF.jl package. It supports the same data functions as VTKGridFile (write_solution, write_cell_data, write_projection, …), but unlike the XML-based formats a whole simulation can be stored in a single file, with the grid written only once for time series on a fixed mesh.

Multi-threaded assembly without grid coloring

start_assemble(K, f; atomic = true) returns an assembler that accumulates into K and f using atomic additions. This makes it safe to assemble from multiple concurrent tasks without first partitioning the cells into independent sets (“grid coloring”), at the cost of some accumulation overhead. The how-to on multi-threaded assembly has been updated to show both approaches.

New tutorials and documentation improvements

  • New tutorial: Darcy flow using the H(div)-conforming Raviart-Thomas interpolations that were introduced in Ferrite 1.1.0.
  • New tutorial: Elastodynamics and modal analysis of a cantilever beam (mass matrix assembly, generalized eigenvalue problem, Rayleigh damping, and Newmark time integration).
  • Code blocks in the documentation now have line numbers, and individual lines can be selected and linked to, similar to code on GitHub, using the new DocumenterCodeBlocks package.
  • The figures and animations for the tutorials and code gallery are now rendered programmatically from the generated vtk output files (instead of via print screen from within ParaView) to give them a consistent look.
  • The overview page for the tutorials and code gallery has been made visually nicer.
  • Other figures in the documentation have also been improved, especially for dark mode.

FerriteCon 2026

Finally, a reminder that FerriteCon 2026, the fifth annual Ferrite user and developer conference, takes place on September 24 in Braunschweig, Germany, hosted by Technische Universität Braunschweig. Attendance is free, but registration is required (deadline September 18) and if you want to present something please submit a short abstract before August 28 (see the conference page for details). Talks will also be live-streamed and recorded for those who cannot attend in person. We hope to see you there!

Thanks to everyone that contributed!