# Diagnose UFL ArityMismatch Errors in FEniCS: Read the Argument Tuples First

Use this when form assembly or `solve()` dies with
`ufl.algorithms.check_arities.ArityMismatch`. The error message names the two
sides' form arguments — e.g. `('v_0',) vs ('v_0', 'v_1')` — and those tuples
identify the bug directly. Applies to both legacy FEniCS (DOLFIN, `dolfin`,
e.g. 2019.1.0) and FEniCSx (DOLFINx, `dolfinx` >= 0.6): UFL's arity checker is
shared, and the messages are identical. Do not mix the two solver APIs, but the
diagnosis below works for both.

## 1. Read the two argument tuples

Real messages seen on the FEniCS Discourse:

```
ufl.algorithms.check_arities.ArityMismatch:
  Adding expressions with non-matching form arguments ('v_0',) vs ('v_0', 'v_1').
```

```
ufl.algorithms.check_arities.ArityMismatch:
  Adding expressions with non-matching form arguments ('v_0',) vs ('conj(v_0)',).
```

```
ufl.algorithms.check_arities.ArityMismatch: Failure to conjugate test function in complex Form
```

```
ufl.algorithms.check_arities.ArityMismatch: Multiplying expressions with
  overlapping form arguments ('v_0', 'v_1') vs ('v_0',).
```

Notation: `v_0` is the argument numbered 0 — by convention the **test**
function; `v_1` is argument number 1 — the **trial** function. `conj(v_0)`
is the conjugated test function (complex mode). `v_0^1` means test function,
part 1 of a mixed space. The two tuples come from two terms you added or
multiplied together; the term whose tuple differs from the rest is the bug.

## 2. Classify the mismatch

Match your message to one of these patterns before editing anything.

**Pattern A: `('v_0',) vs ('v_0', 'v_1')` — a TrialFunction leaked into a linear form (or vice versa).**
One term contains a trial function the other terms don't. Usual causes:

- The linear form `L` contains a `TrialFunction`. `L` may only contain the
  test function and known data.
- You built the bilinear form with a `Function` (the unknown, known data at
  assembly time) instead of a `TrialFunction`, then combined it with a term
  that does use `TrialFunction`. A form built from `Function` has arity 1;
  from `TrialFunction` + `TestFunction` it has arity 2.

```python
# WRONG: first term has arity 2 (TrialFunction), second has arity 1 (Function)
u_trial, u_known = TrialFunction(V), Function(V)
a = inner(grad(u_trial), grad(v))*dx + inner(grad(u_known), grad(v))*dx
# RIGHT: the unknown is a TrialFunction everywhere in the bilinear form
u = TrialFunction(V)
a = inner(grad(u), grad(v))*dx
```

**Pattern B: `('v_0',) vs ('v_1',)` or two different `v_0` from different spaces — test functions from two different spaces in one form.**
You used test functions belonging to different function spaces, e.g. `v`
from `V` and `w` from `W`, in a single form. Either the problem genuinely
couples the spaces (use one `MixedElement` space and `TestFunctions(W)`)
or the terms belong in separate forms.

**Pattern C: `('v_0',) vs ('conj(v_0)',)` or "Failure to conjugate test function in complex Form" — complex-mode conjugation.**
Your form is compiled for complex scalars (DOLFINx complex build) but you
multiplied a test function with plain `*` instead of `ufl.inner`, so one
side conjugates the test function and the other doesn't. Rule: in complex
mode, **always use `ufl.inner` for anything multiplied by a test or trial
function** — never bare `*`.

```python
# WRONG in complex mode: v appears both conjugated and not
F = v*f*dx + inner(grad(u), grad(v))*dx
# RIGHT
F = inner(v, f)*dx + inner(grad(u), grad(v))*dx
```

**Pattern D: `() vs (Argument(...),)` — a zero-arity term added to a form.**
One summand contains no form arguments at all: a stray scalar, a `Constant`
added directly to `F`, or an expression where every argument got substituted
away. Find the term and either give it its test function or move it out of
the form.

**Pattern E: "Multiplying expressions with overlapping form arguments" — `test*test` or `trial*trial`.**
You multiplied two expressions that share an argument number, e.g. squaring
a form fragment like `(u*v)*(u*v)`, or `v*v*dx`. Restructure so each
multiplication combines at most one test-side and one trial-side factor.

**Pattern F: `('v_0^1', 'v_1^0') vs ('v_0^0', 'v_1^1')` — mixed-space parts inconsistent across terms.**
In a mixed form, one term was built with test/trial functions unpacked in a
different order than another term (common when combining `split()` pieces
with `TrialFunctions`/`TestFunctions` unpacked manually). Define each test
and trial function exactly once and reuse the same objects in every term.

## 3. Find the offending term mechanically

Don't eyeball a 40-term form. Print each term's arguments with UFL's own
introspection and diff them:

```python
import ufl
terms = {
    "diffusion": inner(grad(u), grad(v))*dx,
    "reaction":  c*u*v*dx,          # suspect term
    "source":    f*v*dx,
}
for name, t in terms.items():
    print(name, ufl.Form(t).arguments())
```

`Form.arguments()` returns the tuple of `Argument` objects UFL sees. The term
whose tuple differs from the intended arity (1 for `L`/`F`, 2 for `a`/`J`)
is the one to fix. This works unchanged in legacy `dolfin` and `dolfinx`.

## 4. The three rules that prevent recurrence

1. **One definition site per argument.** Create `TestFunction`/`TrialFunction`
   once per space and reuse; never create a second `TestFunction(V)` mid-form
   (a fresh `Argument` object compares unequal and triggers Pattern B/F).
2. **Arity by role:** residual/nonlinear form `F` and linear form `L` contain
   only test functions (arity 1); bilinear form `a` and Jacobian `J` contain
   trial + test (arity 2). For Jacobians prefer `J = derivative(F, u, du)`
   over hand-differentiation — a hand-written `J` with a wrong argument is
   the most common source of Pattern A.
3. **Complex builds: `inner`, always.** Any product involving a test or trial
   function uses `ufl.inner`; reserve `*` for scalar-times-scalar.

## 5. Checklist

1. Copy the two argument tuples from the error message.
2. Match to Pattern A–F above.
3. Run the per-term `Form(t).arguments()` probe to confirm which term is odd.
4. Fix at the definition site (wrong object type, wrong space, missing
   `inner`), not by patching the sum.
5. Re-assemble; if a *different* arity error appears, repeat — large forms
   often contain two independent mistakes.
