mixed results with more C++ module experimentation

November 22, 2025 C/C++ development and debugging. No comments , , , , ,

Here is some followup from my earlier attempt to use bleeding edge C++ module support.

First experiment: do I need to import all of the std library?

I tried this:

import iostream;

int main() {

  std::cout << "hello world\n";

  return 0;
}

I didn’t know if gcm.cache/std.gcm (already built from my previous experiment) would supply that export, but I get:

g++ -std=c++23 -fmodules   -c -o broken.o broken.cc
In module imported at broken.cc:1:1:
iostream: error: failed to read compiled module: No such file or directory
iostream: note: compiled module file is ‘gcm.cache/iostream.gcm’
iostream: note: imports must be built before being imported
iostream: fatal error: returning to the gate for a mechanical issue
compilation terminated.
make: *** [: broken.o] Error 1

so it appears the answer is no. Also, /usr/include/c++/15/bits/ only appears to have a std.cc, and no iostream.cc:

> find  /usr/include/c++/15/bits/ -name "*.cc"
/usr/include/c++/15/bits/std.cc
/usr/include/c++/15/bits/std.compat.cc

so it appears, for the time being, g++-15 is all or nothing with respect to std imports. However, when using precompiled headers, you usually want a big pre-generated pch that has just about everything, and this is similar, so maybe that’s not so bad (other than namespace pollution.)

Second experiment. Adding a non-std import/export.

I moved a variant of Stroustrup’s collect_lines function into a separate module, like so:

// stuff.cc
export module stuff;
import std;

namespace stuff {

void helper() { std::cout << "call to a private function\n"; }

export
std::vector<std::string> collect_lines(std::istream &is) {

  helper();

  std::unordered_set<std::string> s;
  for (std::string line; std::getline(is, line);)
    s.insert(line);

  //return std::vector<std::string>(s.begin(), s.end());
  return std::vector{std::from_range, s};
}
} // namespace stuff

It turns out that I needed the export keyword on ‘module stuff’, as well as for any function that I wanted to export. Without that I get:

> make 
g++ -std=c++23 -fmodules -c /usr/include/c++/15/bits/std.cc
g++ -std=c++23 -fmodules   -c -o stuff.o stuff.cc
g++ -std=c++23 -fmodules   -c -o try.o try.cc
try.cc: In function ‘int main()’:
try.cc:8:12: error: ‘stuff’ has not been declared
    8 |   auto v = stuff::collect_lines(std::cin);
      |            ^~~~~
make: *** [: try.o] Error 1

The compile error is not very good. It doesn’t complain that collect_lines is not exported, but instead complains that stuff, the namespace itself, is not declared.

I can export the namespace, which is the naive resolution to the compiler diagnostic presented, for example:

export module stuff;
import std;

export namespace stuff {

void helper() { std::cout << "call to a private function\n"; }

//export
std::vector<std::string> collect_lines(std::istream &is) {

  helper();

  std::unordered_set<std::string> s;
  for (std::string line; std::getline(is, line);)
    s.insert(line);

  //return std::vector<std::string>(s.begin(), s.end());
  return std::vector{std::from_range, s};
}
} // namespace stuff

However, that means that the calling code can now call stuff::helper, which was not my intent.

There also does not appear to be any good way to enumerate exports available in the gcm.cache. nm output for the symbol is not any different with or without the export keyword:

> nm stuff.o | grep collect_lines | c++filt
0000000000000028 T stuff::collect_lines@stuff[abi:cxx11](std::basic_istream<char, std::char_traits >&)

This is a critically important tooling failure if modules are going to be used in production. Anybody who has programmed with windows dlls or AIX shared objects, or Linux shared objects with symbol versioning, knows about the resulting hellish nature of the linker error chase, when an export is missed from such an enumeration. Hopefully, there’s some external tool that can enumerate gcm.cache exports. Both grok and chatgpt were unsuccessful advising about tools for this sort of task. The best answer was chatgpt’s recommendation for -fmodule-dump:

> g++ -std=c++23 -fmodules -save-temps -fdump-lang-module   -c -o stuff.o stuff.cc 
fedoravm:/home/peeter/physicsplay/programming/module> ls
broken.cc  gcm.cache  makefile  makefile.clang  std.o  stuff.cc  stuff.cc.002l.module  stuff.ii  stuff.o  stuff.s  try.cc

but that *.module output doesn’t have anything that obviously distinguishes exported vs. non-exported symbols:

> grep -2e stuff::helper -e stuff::collect_lines *.module
Wrote section:28 named-by:'::std::vector<::std::__cxx11::basic_string@std:1<char,::std::char_traits@std:1,::std::allocator@std:1>,::std::allocator<::std::__cxx11::basic_string@std:1<char,::std::char_traits@std:1,::std::allocator@std:1>>>'
Writing section:29 2 depsets
 Depset:0 decl entity:403 function_decl:'::stuff::collect_lines'
 Wrote declaration entity:403 function_decl:'::stuff::collect_lines'
 Depset:1 binding namespace_decl:'::stuff::collect_lines'
Wrote section:29 named-by:'::stuff::collect_lines'
Writing section:30 2 depsets
 Depset:0 decl entity:404 function_decl:'::stuff::helper'
 Wrote declaration entity:404 function_decl:'::stuff::helper'
 Depset:1 binding namespace_decl:'::stuff::helper'
Wrote section:30 named-by:'::stuff::helper'
Writing section:31 4 depsets
 Depset:0 specialization entity:405 type_decl:'::std::__replace_first_arg<::std::allocator<::std::__detail::_Hash_node<::std::__cxx11::basic_string@std:1<char,::std::char_traits@std:1,::std::allocator@std:1>,0x1>>,::std::__cxx11::basic_string@std:1<char,::std::char_traits@std:1,::std::allocator@std:1>>'
--
Writing binding table
 Bindings '::std::operator==' section:8
 Bindings '::stuff::collect_lines' section:29
 Bindings '::stuff::helper' section:30
 Bindings '::std::swap' section:35
Writing pending-entities

Chatgpt summarizes this as follows:

“This is confirmed by overwhelming evidence:

  • GCC bug 113590
  • GCC mailing list discussion July 2024
  • Confirmation from module implementers: “GCC BMIs do not currently record export flags.”

This is intentional (for now): GCC’s binary module interface tracks reachable declarations, not exported ones.”

Trying clang

After considerable experimentation, and both grok and chatgpt help, I was finally able to get a working compile and link sequence using the clang toolchain:

fedoravm:/home/peeter/physicsplay/programming/module> make -f *.clang clean
rm -f *.o *.pcm try
fedoravm:/home/peeter/physicsplay/programming/module> make -f *.clang 
clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -Wno-reserved-module-identifier --precompile /usr/share/libc++/v1/std.cppm -o std.pcm
clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm --precompile stuff.cppm -o stuff.pcm
clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm -fmodule-file=stuff=stuff.pcm -c try.cc -o try.o
clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm -c stuff.cc -o stuff.o
clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm -fmodule-file=stuff=stuff.pcm try.o stuff.o -o try

Unlike g++, I have to build both the module and the object code for stuff.cc (and facilitated that with a clang.cppm -> clang.cc symlink), but unlike g++, I didn’t need a std.o (for reasons that I don’t understand.)

Dumping the clang-AST appears to be the closest that we can get to enumerating exports. Example:

> clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -fmodule-file=std=std.pcm  -Xclang -ast-dump -fsyntax-only stuff.cc | less -R

This shows output like:

Screenshot

This is not terribly user friendly, and not something that a typical clang front end user would attempt to do.

This hints that the “way” do dump exports would be to write a clang-AST visitor that dumps all the ExportDecl’s that are encountered (or a complex grep script that attempts to mine the -ast-dump output)

C++ sample code with modules!

November 21, 2025 C/C++ development and debugging. ,

Screenshot

A coworker shared the Stroustrup paper titled “21st Century C++”. I was reading a PDF version, but a search turns up an online version too.

This paper included use of C++ with modules. I’ve had my eyes on those since working on DB2, which suffered from include file hell (DB2’s include file hierarchy was a fully connected graph). However, until today, I didn’t realize that there were non-experimental compilers that included module support.

Here’s a sample program that uses modules (Stroustrup’s, with a main added)

import std;

using namespace std;

vector<string> collect_lines(istream &is) {
  unordered_set<string> s;
  for (string line; getline(is, line);)
    s.insert(line);

  return vector{from_range, s};
}

int main() {
  auto v = collect_lines(cin);
  for (const auto &i : v) {
    cout << format("{}\n", i);
  }

  return 0;
}

A first attempt to compile this, even with -std=c++23 bombs:

fedoravm:/home/peeter/physicsplay/programming/module> g++ -std=c++23 -o try try.cc 2>&1 | head -5
try.cc:1:1: error: ‘import’ does not name a type
    1 | import std;
      | ^~~~~~
try.cc:1:1: note: C++20 ‘import’ only available with ‘-fmodules’, which is not yet enabled with ‘-std=c++20’
try.cc:5:8: error: ‘string’ was not declared in this scope

but we get a hint about what is needed (-fmodules). However, that’s not enough by itself:

fedoravm:/home/peeter/physicsplay/programming/module> g++ -std=c++23 -fmodules -o try try.cc 2>&1 | head -5
In module imported at try.cc:1:1:
std: error: failed to read compiled module: No such file or directory
std: note: compiled module file is ‘gcm.cache/std.gcm’
std: note: imports must be built before being imported
std: fatal error: returning to the gate for a mechanical issue

Here’s the magic sequence that we need, which includes a build of the C++ std export too:

g++ -std=c++23 -fmodules -c /usr/include/c++/15/bits/std.cc
g++ -std=c++23 -fmodules   -c -o try.o try.cc
g++ -std=c++23 -fmodules -o try std.o try.o  

On this VM, I have g++-15 installed, which is sufficient to build and run this little program, modules and all.

Curl of Curl. Tensor and GA expansion, and GA equivalent identity.

November 12, 2025 math and physics play , , , , , , , , , , ,

[Click here for a PDF version of this post]

In this blog post, we will expand \(\spacegrad \cross \lr{ \spacegrad \cross \Bf } = -\spacegrad^2 \Bf + \spacegrad \lr{ \spacegrad \cdot \Bf } \) two different ways, using tensor index gymnastics and using geometric algebra.

The tensor way.

To expand the curl using a tensor expansion, let’s first expand the cross product in coordinates
\begin{equation}\label{eqn:curlcurl2:20}
\begin{aligned}
\Ba \cross \Bb
&=
\lr{ \Be_r \cross \Be_s } a_r b_s \\
&=
\Be_t \cdot \lr{ \Be_r \cross \Be_s } \Be_t a_r b_s \\
&=
\epsilon_{rst} a_r b_s \Be_t.
\end{aligned}
\end{equation}
Here \( \epsilon_{rst} \) is the completely antisymmetric (Levi-Civita) tensor, and allows us to compactly express the geometrical nature of the triple product.

We can then expand the curl of the curl by applying this twice
\begin{equation}\label{eqn:curlcurl2:40}
\begin{aligned}
\spacegrad \cross \lr{ \spacegrad \cross \Bf }
&=
\epsilon_{rst} \partial_r \lr{ \spacegrad \cross \Bf }_s \Be_t \\
&=
\epsilon_{rst} \partial_r \lr{ \epsilon_{uvw} \partial_u f_v \Be_w }_s \Be_t \\
&=
\epsilon_{rst} \partial_r \epsilon_{uvs} \partial_u f_v \Be_t.
\end{aligned}
\end{equation}

It turns out that there’s a nice identity to reduce the single index contraction of a pair of Levi-Civita tensors.
\begin{equation}\label{eqn:curlcurl2:60}
\epsilon_{abt} \epsilon_{cdt} = \delta_{ac} \delta_{bd} – \delta_{ad} \delta_{bc}.
\end{equation}
To show this, consider the \( t = 1 \) term of this sum \( \epsilon_{ab1} \epsilon_{cd1} \). This is non-zero only for \( a,b,c,d \in \setlr{2,3} \). If \( a,b = c,d \), this is one, and if \( a,b = d,c \), this is minus one. We may summarize that as
\begin{equation}\label{eqn:curlcurl2:80}
\epsilon_{ab1} \epsilon_{cd1} = \delta_{ac} \delta_{bd} – \delta_{ad} \delta_{bc},
\end{equation}
but this holds for \( t = 2,3 \) too, so \ref{eqn:curlcurl2:60} holds generally.

We may now contract the tensors to find
\begin{equation}\label{eqn:curlcurl2:100}
\begin{aligned}
\spacegrad \cross \lr{ \spacegrad \cross \Bf }
&=
\epsilon_{rst} \epsilon_{uvs} \Be_t \partial_r \partial_u f_v \\
&=
-\epsilon_{rts} \epsilon_{uvs} \Be_t \partial_r \partial_u f_v \\
&=
-\lr{ \delta_{ru} \delta_{tv} – \delta_{rv} \delta_{tu} } \Be_t \partial_r \partial_u f_v \\
&=
– \Be_v \partial_u \partial_u f_v
+ \Be_u \partial_v \partial_u f_v \\
&=
-\spacegrad^2 \Bf + \spacegrad \lr{ \spacegrad \cdot \Bf }.
\end{aligned}
\end{equation}

Using geometric algebra.

Now let’s pull out the GA toolbox. We start with introducing a no-op grade-1 selection, and using the identity \( \Ba \cross \Bb = -I \lr{ \Ba \wedge \Bb } \)
\begin{equation}\label{eqn:curlcurl2:120}
\begin{aligned}
\spacegrad \cross \lr{ \spacegrad \cross \Bf }
&=
\gpgradeone{
\spacegrad \cross \lr{ \spacegrad \cross \Bf }
} \\
&=
\gpgradeone{
-I \lr{ \spacegrad \wedge \lr{ \spacegrad \cross \Bf } }
} \\
\end{aligned}
\end{equation}
We can now expand \( \Ba \wedge \Bb = \Ba \Bb – \Ba \cdot \Bb \)
\begin{equation}\label{eqn:curlcurl2:140}
\spacegrad \cross \lr{ \spacegrad \cross \Bf }
=
\gpgradeone{
-I \spacegrad \lr{ \spacegrad \cross \Bf }
+I \lr{ \spacegrad \cdot \lr{ \spacegrad \cross \Bf } }
}
\end{equation}
but that dot product is a scalar, leaving just a pseudoscalar, which has a zero grade-1 selection. This leaves
\begin{equation}\label{eqn:curlcurl2:160}
\begin{aligned}
\spacegrad \cross \lr{ \spacegrad \cross \Bf }
&=
\gpgradeone{
-I \spacegrad \lr{ -I \lr{ \spacegrad \wedge \Bf } }
} \\
&=
-\gpgradeone{
\spacegrad \lr{ \spacegrad \wedge \Bf }
}.
\end{aligned}
\end{equation}
We use \( \Ba \wedge \Bb = \Ba \Bb – \Ba \cdot \Bb \) once more
\begin{equation}\label{eqn:curlcurl2:180}
\begin{aligned}
\spacegrad \cross \lr{ \spacegrad \cross \Bf }
&=
-\gpgradeone{
\spacegrad \lr{ \spacegrad \Bf }
-\spacegrad \lr{ \spacegrad \cdot \Bf }
}
\\
&=
-\spacegrad^2 \Bf
+\spacegrad \lr{ \spacegrad \cdot \Bf }.
\end{aligned}
\end{equation}

GA identity.

It’s also worth noting that there’s a natural GA formulation of the curl of a curl. From the Laplacian and divergence relationship that we ended up with, we need only factor out the gradient
\begin{equation}\label{eqn:curlcurl2:200}
\begin{aligned}
\spacegrad \cross \lr{ \spacegrad \cross \Bf }
&=
-\spacegrad^2 \Bf +\spacegrad \lr{ \spacegrad \cdot \Bf } \\
&=
-\spacegrad \lr{ \spacegrad \Bf – \spacegrad \cdot \Bf } \\
&=
-\spacegrad \lr{ \spacegrad \wedge \Bf }.
\end{aligned}
\end{equation}
Because \( \spacegrad \wedge \lr{ \spacegrad \wedge \Bf } = 0 \), we may also write this as
\begin{equation}\label{eqn:curlcurl2:220}
\boxed{
\spacegrad \cdot \lr{ \spacegrad \wedge \Bf } = -\spacegrad \cross \lr{ \spacegrad \cross \Bf }.
}
\end{equation}
From the GA LHS, we see by inspection that
\begin{equation}\label{eqn:curlcurl2:240}
\spacegrad \cdot \lr{ \spacegrad \wedge \Bf } = \spacegrad^2 \Bf – \spacegrad \lr{ \spacegrad \cdot \Bf }.
\end{equation}

A fun application of Green’s functions and geometric algebra: Residue calculus

November 2, 2025 math and physics play , , , , , , , , , , , , , , , , , ,

[Click here for a PDF version of this post]

Motivation.

A fun application of both Green’s functions and geometric algebra is to show how the Cauchy integral equation can be expressed in terms of the Green’s function for the 2D gradient. This is covered, almost as an aside, in [1]. I found that treatment a bit hard to understand, so I am going to work through it here at my own pace.

Complex numbers in geometric algebra.

Anybody who has studied geometric algebra is likely familiar with a variety of ways to construct complex numbers from geometric objects. For example, complex numbers can be constructed for any plane. If \( \Be_1, \Be_2 \) is a pair of orthonormal vectors for some plane in \(\mathbb{R}^N\), then any vector in that plane has the form
\begin{equation}\label{eqn:residueGreens:20}
\Bf = \Be_1 u + \Be_2 v,
\end{equation}
has an associated complex representation, by simply multiplying that vector one of those basis vectors. For example, if we pre-multiply \( \Bf \) by \( \Be_1 \), forming
\begin{equation}\label{eqn:residueGreens:40}
\begin{aligned}
z
&= \Be_1 \Bf \\
&= \Be_1 \lr{ \Be_1 u + \Be_2 v } \\
&= u + \Be_1 \Be_2 v.
\end{aligned}
\end{equation}

We may identify the unit bivector \( \Be_1 \Be_2 \) as an imaginary, designed by \( i \), since it has the expected behavior
\begin{equation}\label{eqn:residueGreens:60}
\begin{aligned}
i^2 &=
\lr{\Be_1 \Be_2}^2 \\
&=
\lr{\Be_1 \Be_2}
\lr{\Be_1 \Be_2} \\
&=
\Be_1 \lr{\Be_2
\Be_1} \Be_2 \\
&=
-\Be_1 \lr{\Be_1
\Be_2} \Be_2 \\
&=
-\lr{\Be_1 \Be_1}
\lr{\Be_2 \Be_2} \\
&=
-1.
\end{aligned}
\end{equation}

Complex numbers are seen to be isomorphic to even grade multivectors in a planar subspace. The imaginary is the grade-two pseudoscalar, and geometrically is an oriented unit area (bivector.)

Cauchy-equations in terms of the gradient.

It is natural to wonder about the geometric algebra equivalents of various complex-number relationships and identities. Of particular interest for this discussion is the geometric algebra equivalent of the Cauchy equations that specify required conditions for a function to be differentiable.

If a complex function \( f(z) = u(z) + i v(z) \) is differentiable, then we must be able to find the limit of
\begin{equation}\label{eqn:residueGreens:80}
\frac{\Delta f(z_0)}{\Delta z} = \frac{f(z_0 + h) – f(z_0)}{h},
\end{equation}
for any complex \( h \rightarrow 0 \), for any possible trajectory of \( z_0 + h \) toward \( z_0 \). In particular, for real \( h = \epsilon \),
\begin{equation}\label{eqn:residueGreens:100}
\lim_{\epsilon \rightarrow 0} \frac{u(x_0 + \epsilon, y_0) + i v(x_0 + \epsilon, y_0) – u(x_0, y_0) – i v(x_0, y_0)}{\epsilon}
=
\PD{x}{u(z_0)} + i \PD{x}{v(z_0)},
\end{equation}
and for imaginary \( h = i \epsilon \)
\begin{equation}\label{eqn:residueGreens:120}
\lim_{\epsilon \rightarrow 0} \frac{u(x_0, y_0 + \epsilon) + i v(x_0, y_0 + \epsilon) – u(x_0, y_0) – i v(x_0, y_0)}{i \epsilon}
=
-i\lr{ \PD{y}{u(z_0)} + i \PD{y}{v(z_0)} }.
\end{equation}
Equating real and imaginary parts, we see that existence of the derivative requires
\begin{equation}\label{eqn:residueGreens:140}
\begin{aligned}
\PD{x}{u} &= \PD{y}{v} \\
\PD{x}{v} &= -\PD{y}{u}.
\end{aligned}
\end{equation}
These are the Cauchy equations. When the derivative exists in a given neighbourhood, we say that the function is analytic in that region. If we use a bivector interpretation of the imaginary, with \( i = \Be_1 \Be_2 \), the Cauchy equations are also satisfied if the gradient of the complex function is zero, since
\begin{equation}\label{eqn:residueGreens:160}
\begin{aligned}
\spacegrad f
&=
\lr{ \Be_1 \partial_x + \Be_2 \partial_y } \lr{ u + \Be_1 \Be_2 v } \\
&=
\Be_1 \lr{ \partial_x u – \partial_y v } + \Be_2 \lr{ \partial_y u + \partial_x v }.
\end{aligned}
\end{equation}
We see that the geometric algebra equivalent of the Cauchy equations is simply
\begin{equation}\label{eqn:residueGreens:200}
\spacegrad f = 0.
\end{equation}
Roughly speaking, we may say that a function is analytic in a region, if the Cauchy equations are satisfied, or the gradient is zero, in a neighbourhood of all points in that region.

A special case of the fundamental theorem of geometric calculus.

Given an even grade multivector \( \psi \in \mathbb{R}^2 \) (i.e.: a complex number), we can show that
\begin{equation}\label{eqn:residueGreens:220}
\int_A \spacegrad \psi d^2\Bx = \oint_{\partial A} d\Bx \psi.
\end{equation}
Let’s get an idea why this works by expanding the area integral for a rectangular parameterization
\begin{equation}\label{eqn:residueGreens:240}
\begin{aligned}
\int_A \spacegrad \psi d^2\Bx
&=
\int_A \lr{ \Be_1 \partial_1 + \Be_2 \partial_2 } \psi I dx dy \\
&=
\int \Be_1 I dy \evalrange{\psi}{x_0}{x_1}
+
\int \Be_2 I dx \evalrange{\psi}{y_0}{y_1} \\
&=
\int \Be_2 dy \evalrange{\psi}{x_0}{x_1}

\int \Be_1 dx \evalrange{\psi}{y_0}{y_1} \\
&=
\int d\By \evalrange{\psi}{x_0}{x_1}

\int d\Bx \evalrange{\psi}{y_0}{y_1}.
\end{aligned}
\end{equation}
We took advantage of the fact that the \(\mathbb{R}^2\) pseudoscalar commutes with \( \psi \). The end result, is illustrated in fig. 1, shows pictorially that the remaining integral is an oriented line integral.

fig. 1. Oriented multivector line integral.

 

If we want to approximate a more general area, we may do so with additional tiles, as illustrated in fig. 2. We may evaluate the area integral using the line integral over just the exterior boundary using such a tiling, as any overlapping opposing boundary contributions cancel exactly.

fig. 2. A crude circular tiling approximation.

 

The reason that this is interesting is that it allows us to re-express a complex integral as a corresponding multivector area integral. With \( d\Bx = \Be_1 dz \), we have
\begin{equation}\label{eqn:residueGreens:260}
\oint dz\, \psi = \Be_1 \int \spacegrad \psi d^2\Bx.
\end{equation}

The Cauchy kernel as a Green’s function.

We’ve previously derived the Green’s function for the 2D Laplacian, and found
\begin{equation}\label{eqn:residueGreens:280}
\tilde{G}(\Bx, \Bx’) = \inv{2\pi} \ln \Abs{\lr{\Bx – \Bx’}},
\end{equation}
which satisfies
\begin{equation}\label{eqn:residueGreens:300}
\delta^2(\Bx – \Bx’) = \spacegrad^2 \tilde{G}(\Bx, \Bx’) = \spacegrad \lr{ \spacegrad \tilde{G}(\Bx, \Bx’) }.
\end{equation}
This means that \( G(\Bx, \Bx’) = \spacegrad \tilde{G}(\Bx, \Bx’) \) is the Green’s function for the gradient. That Green’s function is
\begin{equation}\label{eqn:residueGreens:320}
\begin{aligned}
G(\Bx, \Ba)
&= \inv{2 \pi} \frac{\spacegrad \Abs{\Bx – \Ba}}{\Abs{\Bx – \Ba}} \\
&= \inv{2 \pi} \frac{\Bx – \Ba}{\Abs{\Bx – \Ba}^2}.
\end{aligned}
\end{equation}
We may cast this Green’s function into complex form with \( z = \Be_1 \Bx, a = \Be_1 \Ba \). In particular
\begin{equation}\label{eqn:residueGreens:340}
\begin{aligned}
\inv{z – a}
&=
\frac{(z – a)^\conj}{\Abs{z – a}^2} \\
&=
\frac{(z – a)^\conj}{\Abs{z – a}^2} \\
&=
\frac{\Bx – \Ba}{\Abs{\Bx – \Ba}^2} \Be_1 \\
&=
2 \pi G(\Bx, \Ba) \Be_1.
\end{aligned}
\end{equation}

Cauchy’s integral.

With
\begin{equation}\label{eqn:residueGreens:360}
\psi = \frac{f(z)}{z – a},
\end{equation}
using \ref{eqn:residueGreens:260}, we can now evaluate
\begin{equation}\label{eqn:residueGreens:265}
\begin{aligned}
\oint dz\, \frac{f(z)}{z – a}
&= \Be_1 \int \spacegrad \frac{f(z)}{z – a} d^2\Bx \\
&= \Be_1 \int \lr{ \frac{\spacegrad f(z)}{z – a} + \lr{ \spacegrad \inv{z – a}} f(z) } I dA \\
&= \Be_1 \int f(z) \spacegrad 2 \pi G(\Bx – \Ba) \Be_1 I dA \\
&= 2 \pi \Be_1 \int \delta^2(\Bx – \Ba) \Be_1 f(\Bx) I dA \\
&= 2 \pi \Be_1^2 f(\Ba) I \\
&= 2 \pi I f(a),
\end{aligned}
\end{equation}
where we’ve made use of the analytic condition \( \spacegrad f = 0 \), and the fact that \( f \) and \( 1/(z-a) \), both even multivectors, commute.

The Cauchy integral equation
\begin{equation}\label{eqn:residueGreens:380}
f(a) = \inv{2 \pi I} \oint dz\, \frac{f(z)}{z – a},
\end{equation}
falls out naturally. This sort of residue calculation always seemed a bit miraculous. By introducing a geometric algebra encoding of complex numbers, we get a new and interesting interpretation. In particular,

  1. the imaginary factor in the geometric algebra formulation of this identity is an oriented unit area coming directly from the area element,
  2. the factor of \( 2 \pi \) comes directly from the Green’s function for the gradient,
  3. the fact that this particular form of integral picks up only the contribution at the point \( z = a \) is no longer mysterious seeming. This is directly due to delta-function filtering.

Also, if we are looking for an understanding of how to generalize the Cauchy equation to more general multivector functions, we now also have a good clue how that would be done.

References

[1] C. Doran and A.N. Lasenby. Geometric algebra for physicists. Cambridge University Press New York, Cambridge, UK, 1st edition, 2003.

Summary of some gradient related Green’s functions

October 28, 2025 math and physics play , , , , , ,

[Click here for a PDF version of this post]

Here is a summary of Green’s functions for a number of gradient related differential operators (many of which are of interest for electrodynamics, and most of them have been derived recently in blog posts.) These Green’s functions all satisfy
\begin{equation}\label{eqn:deltaFunctions:120}
\delta(\Bx – \Bx’) = L G(\Bx, \Bx’).
\end{equation}

Let \( \Br = \Bx – \Bx’ \), \( r = \Norm{\Br} \), \( \mathbf{\hat{r}} = \Br/r \), and \( \tau = t – t’ \), then

  1. Gradient operator, \( L = \spacegrad \), in 1D, 2D and 3D respectively
    \begin{equation}\label{eqn:deltaFunctions:25}
    \begin{aligned}
    G\lr{ \Bx, \Bx’ } &= \frac{\mathbf{\hat{r}}}{2} \\
    G\lr{ \Bx, \Bx’ } &= \frac{1}{2 \pi} \frac{\mathbf{\hat{r}}}{r} \\
    G\lr{ \Bx, \Bx’ } &= \inv{4 \pi} \frac{\mathbf{\hat{r}}}{r^2}.
    \end{aligned}
    \end{equation}

  2. Laplacian operator, \( L = \spacegrad^2 \), in 1D, 2D and 3D respectively
    \begin{equation}\label{eqn:deltaFunctions:20}
    \begin{aligned}
    G\lr{ \Bx, \Bx’ } &= \frac{r}{2} \\
    G\lr{ \Bx, \Bx’ } &= \frac{1}{2 \pi} \ln r \\
    G\lr{ \Bx, \Bx’ } &= -\frac{1}{4 \pi r}.
    \end{aligned}
    \end{equation}

  3. Second order Helmholtz operator, \( L = \spacegrad^2 + k^2 \) for 1D, 2D and 3D respectively
    \begin{equation}\label{eqn:deltaFunctions:60}
    \begin{aligned}
    G\lr{ \Bx, \Bx’ } &= \pm \frac{1}{2 j k} e^{\pm j k r} \\
    G(\Bx, \Bx’) &= \frac{1}{4 j} H_0^{(1)}(\pm k r) \\
    G\lr{ \Bx, \Bx’ } &= -\frac{1}{4 \pi} \frac{e^{\pm j k r }}{r}.
    \end{aligned}
    \end{equation}

  4. First order Helmholtz operator, \( L = \spacegrad + j k \), in 1D, 2D and 3D respectively

    \begin{equation}\label{eqn:deltaFunctions:80}
    \begin{aligned}
    G\lr{ \Bx, \Bx’ } &= \frac{j}{2} \lr{ \mathbf{\hat{r}} \mp 1 } e^{\pm j k r} \\
    G\lr{ \Bx, \Bx’ } &= \frac{k}{4} \lr{ \pm j \mathbf{\hat{r}} H_1^{(1)}(\pm k r) – H_0^{(1)}(\pm k r) } \\
    G\lr{ \Bx, \Bx’ } &= \frac{e^{\pm j k r}}{4 \pi r} \lr{ jk \lr{ 1 \mp \mathbf{\hat{r}} } + \frac{\mathbf{\hat{r}}}{r} }.
    \end{aligned}
    \end{equation}

    This is also the Green’s function for a left acting operator \( G(\Bx, \Bx’) \lr{ – \lspacegrad + j k } = \delta(\Bx – \Bx’) \).

  5. Wave equation, \( \spacegrad^2 – (1/c^2) \partial_{tt} \), in 1D, 2D and 3D respectively
    \begin{equation}\label{eqn:deltaFunctions:140}
    \begin{aligned}
    G(\Br, \tau) &= -\frac{c}{2} \Theta( \pm \tau – r/c ) \\
    G(\Br, \tau) &= -\inv{2 \pi \sqrt{ \tau^2 – r^2/c^2 } } \Theta( \pm \tau – r/c ) \\
    G(\Br, \tau) &= -\inv{4 \pi r} \delta( \pm \tau – r/c ),
    \end{aligned}
    \end{equation}
    The positive sign is for the retarded solution, and the negative for advancing.

  6. Spacetime gradient \( L = \spacegrad + (1/c) \partial_t \), satisfying \( L G(\Bx – \Bx’, t – t’) = \delta(\Bx – \Bx’) \delta(t – t’) \), in 1D, 2D, and 3D respectively
    \begin{equation}\label{eqn:deltaFunctions:100}
    \begin{aligned}
    G(\Br, \tau)
    &= \inv{2} \lr{ \mathbf{\hat{r}} \pm 1 } \delta(\pm \tau – r/c) \\
    G(\Br, \tau)
    &=
    \frac{
    \lr{\tau^2 – r^2/c^2}^{-3/2}
    }{2 \pi c^2}
    \lr{
    c \lr{ \mathbf{\hat{r}} \pm 1 }
    \lr{\tau^2 – r^2/c^2}
    \delta(\pm \tau – r/c)
    -\lr{ \Br + c \tau }
    \Theta(\pm \tau – r/c)
    }
    \\
    G(\Br, \tau)
    &= \inv{4 \pi r} \delta(\pm \tau – r/c)
    \lr{
    \frac{\mathbf{\hat{r}}}{r}
    +
    \lr{ \mathbf{\hat{r}} \pm 1} \inv{c} \PD{t’}{}
    }
    \end{aligned}
    \end{equation}
    The plus sign is for the retarded solution, and negative for advanced.