<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: emptiesvoid-cloud</title>
    <description>The latest articles on DEV Community by emptiesvoid-cloud (@emptiesvoidcloud).</description>
    <link>https://dev.to/emptiesvoidcloud</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4112676%2F824feb71-37df-4702-b2e1-3a1bef2fe532.png</url>
      <title>DEV Community: emptiesvoid-cloud</title>
      <link>https://dev.to/emptiesvoidcloud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/emptiesvoidcloud"/>
    <language>en</language>
    <item>
      <title>QF Solver 0.2.7: Building an Inspectable Python FEM Solver with Reproducible V&amp;V</title>
      <dc:creator>emptiesvoid-cloud</dc:creator>
      <pubDate>Sun, 06 Sep 2026 18:02:32 +0000</pubDate>
      <link>https://dev.to/emptiesvoidcloud/qf-solver-027-building-an-inspectable-python-fem-solver-with-reproducible-vv-27c2</link>
      <guid>https://dev.to/emptiesvoidcloud/qf-solver-027-building-an-inspectable-python-fem-solver-with-reproducible-vv-27c2</guid>
      <description>&lt;h1&gt;
  
  
  I'm Building an Open-Source Python FEM Solver — Here's Where QF Solver 0.2.7 Stands
&lt;/h1&gt;

&lt;p&gt;For some time now, I've been working on a personal project called &lt;strong&gt;QF Solver&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's an open-source finite-element solver written in Python, mainly focused on structural mechanics and dynamics.&lt;/p&gt;

&lt;p&gt;I'm not going to pretend that it is ready to replace Code_Aster, CalculiX, Abaqus, or every other FEM solver out there.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;But the project has reached a point where I think it's interesting enough to start showing it publicly, explaining what I'm trying to build, what already works, and also what still needs a lot of work.&lt;/p&gt;

&lt;p&gt;So... here it is.&lt;/p&gt;

&lt;p&gt;Please keep in mind that this is still a relatively young solver. I'm trying to make it serious and reproducible, but I'm also learning a lot while building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why am I building another FEM solver?
&lt;/h2&gt;

&lt;p&gt;The short answer is: because I like understanding what actually happens inside a solver.&lt;/p&gt;

&lt;p&gt;A lot of engineering software gives you something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then you get displacements, stresses, eigenfrequencies, etc.&lt;/p&gt;

&lt;p&gt;That's obviously useful.&lt;/p&gt;

&lt;p&gt;But personally, I wanted something where it was easier to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which formulation is actually being used?&lt;/li&gt;
&lt;li&gt;Which solver backend solved the system?&lt;/li&gt;
&lt;li&gt;What tolerance was used?&lt;/li&gt;
&lt;li&gt;What was the final residual?&lt;/li&gt;
&lt;li&gt;Was equilibrium checked?&lt;/li&gt;
&lt;li&gt;Can I reproduce the same result?&lt;/li&gt;
&lt;li&gt;Which parts of the solver are actually validated?&lt;/li&gt;
&lt;li&gt;Which parts are still experimental?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is basically the philosophy behind QF Solver.&lt;/p&gt;

&lt;p&gt;I'm trying to keep the numerical methods relatively visible instead of hiding everything behind a black box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;QF Solver is available on PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;qf-solver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/emptiesvoid-cloud/QF_solver" rel="noopener noreferrer"&gt;https://github.com/emptiesvoid-cloud/QF_solver&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://emptiesvoid-cloud.github.io/QF_solver/" rel="noopener noreferrer"&gt;https://emptiesvoid-cloud.github.io/QF_solver/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PyPI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/qf-solver/" rel="noopener noreferrer"&gt;https://pypi.org/project/qf-solver/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the project currently stands
&lt;/h2&gt;

&lt;p&gt;The current version is &lt;strong&gt;QF Solver 0.2.7&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the moment, the solver covers several areas of structural finite-element analysis, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;linear static analysis;&lt;/li&gt;
&lt;li&gt;TET4, TET10, HEX8 and HEX20 solid elements;&lt;/li&gt;
&lt;li&gt;small-strain J2 plasticity for selected elements;&lt;/li&gt;
&lt;li&gt;modal analysis;&lt;/li&gt;
&lt;li&gt;Newmark time integration;&lt;/li&gt;
&lt;li&gt;harmonic analysis;&lt;/li&gt;
&lt;li&gt;linear buckling;&lt;/li&gt;
&lt;li&gt;selected frictionless contact capabilities;&lt;/li&gt;
&lt;li&gt;experimental WEDGE6 support;&lt;/li&gt;
&lt;li&gt;PETSc/MPI routes for selected large models.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing I'm trying to be careful about is not treating every implemented feature as equally mature.&lt;/p&gt;

&lt;p&gt;Some routes are much better tested than others.&lt;/p&gt;

&lt;p&gt;Some are intentionally labelled as experimental or supported with limitations.&lt;/p&gt;

&lt;p&gt;I'd rather write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;this works within this tested scope&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;this solver supports everything&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;when that isn't actually true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification is probably the part I care about the most
&lt;/h2&gt;

&lt;p&gt;Writing an FEM element is one thing.&lt;/p&gt;

&lt;p&gt;Trusting it is another.&lt;/p&gt;

&lt;p&gt;So a fairly large part of the project is now dedicated to verification and validation.&lt;/p&gt;

&lt;p&gt;I try to keep records of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;numerical residuals;&lt;/li&gt;
&lt;li&gt;equilibrium errors;&lt;/li&gt;
&lt;li&gt;energy checks;&lt;/li&gt;
&lt;li&gt;deterministic replays;&lt;/li&gt;
&lt;li&gt;reference cases;&lt;/li&gt;
&lt;li&gt;solver configuration;&lt;/li&gt;
&lt;li&gt;execution environment;&lt;/li&gt;
&lt;li&gt;limitations;&lt;/li&gt;
&lt;li&gt;resource failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also machine-readable qualification records in the repository.&lt;/p&gt;

&lt;p&gt;It probably looks a little excessive for a personal open-source project, but I actually enjoy this part.&lt;/p&gt;

&lt;p&gt;And in my opinion, numerical software becomes much more interesting once you can start answering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why should I trust this result?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;instead of only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did the code run?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Trying larger model
&lt;/h2&gt;

&lt;p&gt;Another area I've been experimenting with is larger finite-element systems using &lt;strong&gt;PETSc and MPI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Again, I want to be careful with the wording here.&lt;/p&gt;

&lt;p&gt;These are not claims that QF Solver can magicallie solve every multi-million-DOF FEM problem.&lt;/p&gt;

&lt;p&gt;They are recorded results for specific structured TET4 linear-static workloads, using specific solver configurations.&lt;/p&gt;

&lt;p&gt;The current public evidence includes approximately:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model size&lt;/th&gt;
&lt;th&gt;Recorded result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,029,000 DOF&lt;/td&gt;
&lt;td&gt;Two stable PETSc replays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3,000,000 DOF&lt;/td&gt;
&lt;td&gt;Two Silver replays plus bounded Gold Compute evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5,012,640 DOF&lt;/td&gt;
&lt;td&gt;Two Silver replays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10M scale&lt;/td&gt;
&lt;td&gt;Bounded evidence only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 10M case especially should not be interpreted as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;QF Solver fully supports arbitrary 10-million-DOF models.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not what I'm claiming.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5 million DOF case
&lt;/h2&gt;

&lt;p&gt;The largest result I currently feel comfortable describing in more detail is the &lt;strong&gt;5M Silver workload&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't know why, but I really like the terms "Silver" and "Gold" 😅 I guess I'll probably replace them with better metrics in the future.&lt;/p&gt;

&lt;p&gt;The recorded model contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;5,012,640 degrees of freedom&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;9,773,946 TET4 elements&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1,243 solver iterations&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;free residual around &lt;code&gt;9.85e-11&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;equilibrium error around &lt;code&gt;1.39e-9&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;energy error below &lt;code&gt;2e-14&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two recorded runs took approximately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;4,428 seconds&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4,379 seconds&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a PETSc/MPI workload under a recorded environment and solver configuration.&lt;/p&gt;

&lt;p&gt;I really want to emphasize this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;these numbers are not a universal QF Solver performance benchmark.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Change the mesh topology, element type, conditioning, preconditioner, hardware, material model or solver configuration and the behaviour can change completely.&lt;/p&gt;

&lt;p&gt;For now, I'm mostly using these cases to answer a simpler question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I reproduce a stable solution of this particular problem at this scale?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  I also keep the failures
&lt;/h2&gt;

&lt;p&gt;One thing I've tried not to do is quietly delete results that are inconvenient.&lt;/p&gt;

&lt;p&gt;For example, an earlier matrix-free campaign successfully solved structured models up to roughly &lt;strong&gt;750k DOF&lt;/strong&gt;, while an attempt around 1M DOF reached its resource/time limit.&lt;/p&gt;

&lt;p&gt;Later PETSc work managed to go significantly further.&lt;/p&gt;

&lt;p&gt;But that doesn't mean the older 1M experiment suddenly becomes a successful result.&lt;/p&gt;

&lt;p&gt;It stays recorded as a limited attempt.&lt;/p&gt;

&lt;p&gt;Maybe this sounds obvious, but I think it matters.&lt;/p&gt;

&lt;p&gt;Especially in performance work, it's very easy to only keep the nice numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is QF Solver better than SfePy, scikit-fem, CalculiX or Code_Aster?
&lt;/h2&gt;

&lt;p&gt;I don't think that's really the right question.&lt;/p&gt;

&lt;p&gt;They are different tools.&lt;/p&gt;

&lt;p&gt;SfePy and scikit-fem have their own philosophies and ecosystems.&lt;/p&gt;

&lt;p&gt;CalculiX and Code_Aster are much more mature projects with years of development behind them.&lt;/p&gt;

&lt;p&gt;QF Solver is much younger.&lt;/p&gt;

&lt;p&gt;What I'm currently trying to build is something that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easy to inspect;&lt;/li&gt;
&lt;li&gt;Python-friendly;&lt;/li&gt;
&lt;li&gt;useful for experimenting with FEM methods;&lt;/li&gt;
&lt;li&gt;explicit about numerical limitations;&lt;/li&gt;
&lt;li&gt;backed by reproducible verification records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've started writing some comparison pages here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://emptiesvoid-cloud.github.io/QF_solver/comparisons/" rel="noopener noreferrer"&gt;https://emptiesvoid-cloud.github.io/QF_solver/comparisons/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal isn't to write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;QF Solver wins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's more:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have this type of problem, which tool makes the most sense?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The benchmark and reproducibility page
&lt;/h2&gt;

&lt;p&gt;I recently created a more readable public summary of the current performance and verification evidence:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://emptiesvoid-cloud.github.io/QF_solver/benchmarks/" rel="noopener noreferrer"&gt;https://emptiesvoid-cloud.github.io/QF_solver/benchmarks/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The much more detailed verification records are also available directly in the repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/emptiesvoid-cloud/QF_solver/tree/main/docs/verification/0_2_7" rel="noopener noreferrer"&gt;https://github.com/emptiesvoid-cloud/QF_solver/tree/main/docs/verification/0_2_7&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the machine-readable qualification data is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/emptiesvoid-cloud/QF_solver/tree/main/qualification/0_2_7" rel="noopener noreferrer"&gt;https://github.com/emptiesvoid-cloud/QF_solver/tree/main/qualification/0_2_7&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;I'm now starting to think about &lt;strong&gt;QF Solver 0.2.8&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There is still a lot I want to improve.&lt;/p&gt;

&lt;p&gt;Some capabilities need to become more mature.&lt;/p&gt;

&lt;p&gt;Some solver routes need better performance.&lt;/p&gt;

&lt;p&gt;Some parts of the API still need cleanup.&lt;/p&gt;

&lt;p&gt;And there are definitely still things in the codebase that I will probably look at in six months and wonder why I wrote them that way.&lt;/p&gt;

&lt;p&gt;That's part of the process.&lt;/p&gt;

&lt;p&gt;For now, I'm mostly trying to build each release on top of the previous one without pretending the project is more mature than it actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why am I posting this now?
&lt;/h2&gt;

&lt;p&gt;Mostly because I've been working on the project for a while without really talking about it publicly.&lt;/p&gt;

&lt;p&gt;I figured it was probably time to stop keeping everything inside the repository.&lt;/p&gt;

&lt;p&gt;I'm interested in FEM, numerical methods, solver architecture and scientific computing, so if anyone working in those areas happens to look at the project, feedback is welcome.&lt;/p&gt;

&lt;p&gt;And yes, if you open the code and immediately find something questionable... be little kind 😄&lt;/p&gt;

&lt;p&gt;It's still a work in progress.&lt;/p&gt;

&lt;p&gt;But constructive criticism is exactly the kind of thing that can make the solver better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/emptiesvoid-cloud/QF_solver" rel="noopener noreferrer"&gt;https://github.com/emptiesvoid-cloud/QF_solver&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://emptiesvoid-cloud.github.io/QF_solver/" rel="noopener noreferrer"&gt;https://emptiesvoid-cloud.github.io/QF_solver/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmarks and reproducibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://emptiesvoid-cloud.github.io/QF_solver/benchmarks/" rel="noopener noreferrer"&gt;https://emptiesvoid-cloud.github.io/QF_solver/benchmarks/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FEM solver comparisons&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://emptiesvoid-cloud.github.io/QF_solver/comparisons/" rel="noopener noreferrer"&gt;https://emptiesvoid-cloud.github.io/QF_solver/comparisons/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PyPI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/qf-solver/" rel="noopener noreferrer"&gt;https://pypi.org/project/qf-solver/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;I'll probably write another update once QF Solver 0.2.8 is in a state worth showing.&lt;/p&gt;

&lt;p&gt;Small AI disclosure: I used AI to help me with the English and wording of this article because English isn't my strongest language. The project, technical work, results, and ideas are my own.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>science</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
