Chapter 21
22 min read
Section 178 of 353

Exact Equations

First-Order Differential Equations

Learning Objectives

After this section you should be able to:

  1. Recognize a first-order ODE written in differential form M(x,y)dx+N(x,y)dy=0M(x,y)\,dx + N(x,y)\,dy = 0 and decide whether it is exact.
  2. Explain why the test M/y=N/x\partial M/\partial y = \partial N/\partial x is the right test, in terms of Clairaut's theorem on mixed partial derivatives.
  3. Solve an exact equation by constructing the potential function F(x,y)F(x,y) and writing F(x,y)=CF(x,y) = C.
  4. Visualize the solution as a family of level curves of FF, with the vector (M,N)(M, N) as the gradient pointing across them.
  5. Repair a non-exact equation with an integrating factor, and verify everything in Python with SymPy and PyTorch.

The Big Picture: Walking on Level Curves

Imagine you are standing on a hillside. The height at each point is given by some function F(x,y)F(x, y). If you want to walk so your altitude never changes, you are forced to walk along a level curve F(x,y)=CF(x, y) = C.

At any point on that curve, the direction you must step in satisfies one condition: the total change in height must be zero. Using the chain rule, a tiny step (dx,dy)(dx, dy) changes F by dF=Fxdx+FydydF = \dfrac{\partial F}{\partial x}\,dx + \dfrac{\partial F}{\partial y}\,dy, so “height does not change” becomes:

Fxdx+Fydy=0\frac{\partial F}{\partial x}\,dx + \frac{\partial F}{\partial y}\,dy = 0

If we set M=F/xM = \partial F/\partial x and N=F/yN = \partial F/\partial y, this is exactly the differential form Mdx+Ndy=0M\,dx + N\,dy = 0. So the question “is this ODE exact?” is the same as the question “is the left-hand side the differential of some height function F?”

The one-line idea

An ODE Mdx+Ndy=0M\,dx + N\,dy = 0 is exact when its left-hand side is the total differential dFdF of some function F. The solution is then simply F(x,y)=CF(x, y) = C — the level curves of that hidden potential.


Where the Idea Comes From

Almost every first-order ODE of the form dydx=f(x,y)\dfrac{dy}{dx} = f(x, y) can be rewritten in differential form by clearing the fraction:

f(x,y)dxdy=0M(x,y)dx+N(x,y)dy=0.f(x, y)\,dx - dy = 0 \quad\Longleftrightarrow\quad M(x,y)\,dx + N(x,y)\,dy = 0.

For some choices of M and N, the left-hand side is the perfect differential of a height function F. When that happens we are extremely lucky — we can integrate the equation in one swoop without any tricks. This is why exact equations are usually the first “easy case” after separable equations: the equation has already done the work of telling us what its own conserved quantity is.

Many laws of physics arrive naturally in this form. Conservation of energy is literally dE=0dE = 0; conservation of mass is dM=0dM = 0. The differential form is the language of conservation, and an exact ODE is the calculus statement that says “some quantity F is conserved along solutions.”

Definition: Exact Differential Equation

Let M and N be continuous on an open rectangle RR2R \subset \mathbb{R}^2. The first-order differential equation

M(x,y)dx+N(x,y)dy=0M(x, y)\,dx + N(x, y)\,dy = 0

is called exact on R if there exists a continuously differentiable function F:RRF : R \to \mathbb{R} such that

Fx=MandFy=N.\frac{\partial F}{\partial x} = M \quad \text{and} \quad \frac{\partial F}{\partial y} = N.

In that case FF is called a potential for the equation, and the general solution is the family of level curves

F(x,y)=C,CR.F(x, y) = C, \qquad C \in \mathbb{R}.
Each value of CC picks out one specific integral curve. An initial condition y(x0)=y0y(x_0) = y_0 just plugs in to fix that constant: C=F(x0,y0)C = F(x_0, y_0).

The Test for Exactness

How do we know whether the F we are hoping for exists, without already knowing F? Answer: we use one of the cleanest theorems in multivariable calculus.

Clairaut's theorem. If F has continuous second partials, then 2Fyx=2Fxy\dfrac{\partial^2 F}{\partial y\, \partial x} = \dfrac{\partial^2 F}{\partial x\, \partial y}. Mixed partials commute.

Apply Clairaut to our hoped-for potential F. We assumed M=F/xM = \partial F/\partial x and N=F/yN = \partial F/\partial y. Differentiate the first identity with respect to y and the second with respect to x:

My=2Fyx,Nx=2Fxy.\frac{\partial M}{\partial y} = \frac{\partial^2 F}{\partial y\, \partial x}, \qquad \frac{\partial N}{\partial x} = \frac{\partial^2 F}{\partial x\, \partial y}.

Clairaut says these two right-hand sides are equal. So if F exists, then M/y=N/x\partial M/\partial y = \partial N/\partial x. The converse also holds on a simply-connected region. We get the clean test:

Test for exactness

Mdx+Ndy=0 is exact    My=Nx.M\,dx + N\,dy = 0 \text{ is exact} \iff \frac{\partial M}{\partial y} = \frac{\partial N}{\partial x}.

That is one line of partial differentiation. If it agrees, you can always construct F; if it disagrees, no F exists and we need a different technique (an integrating factor, a substitution, etc.).


Interactive: Vector Fields and Level Curves

The picture below makes the “walking on level curves” intuition concrete. The yellow arrows are the vector field (M,N)(M, N); when the equation is exact these are the gradient F\nabla F. The blue curves are the level curves F=CF = C — i.e. the solutions. Switch between the presets and watch how the arrows always sit perpendicular to the solution curves.

Loading exact-equation explorer...
Try the not-exact preset. The arrows (M,N)=(y,2x)(M, N) = (y, 2x) are not perpendicular to any consistent family of level curves — that is what failing the test looks like geometrically. There is no height function whose gradient is this vector field.

Solving an Exact Equation (Four Steps)

Once we know the equation is exact, recovering F is a small piece of calculus. We will derive the method here so that nothing feels like a recipe.

  1. Step 1. Since we want F/x=M\partial F/\partial x = M, integrate M with respect to x, treating y as a constant:
    F(x,y)=M(x,y)dx+g(y).F(x, y) = \int M(x, y)\,dx + g(y).
    The “constant of integration” is allowed to depend on yy, because anything that vanishes when you differentiate with respect to x — including a function of y alone — is a legal piece of F. Hence g(y)g(y).
  2. Step 2. The other constraint is F/y=N\partial F/\partial y = N. Differentiate the F you just built with respect to y:
    y ⁣(Mdx)+g(y)=N(x,y).\frac{\partial}{\partial y}\!\left(\int M\,dx\right) + g'(y) = N(x, y).
    Solve this equation for g(y)g'(y). Here is the crucial point: because the equation is exact, every xx on the right-hand side cancels and you are left with an expression in yy only. That is the algebraic shadow of My=NxM_y = N_x.
  3. Step 3. Integrate g(y)g'(y) in y to obtain g(y)g(y). There is one additive constant, which we absorb into CC later.
  4. Step 4. Assemble F and write the implicit solution:
    F(x,y)=Mdx+g(y)=C.F(x, y) = \int M\,dx + g(y) = C.
    If an initial condition y(x0)=y0y(x_0) = y_0 is given, evaluate F at that point to pin down CC.
You may instead start from F=Ndy+h(x)F = \int N\,dy + h(x) in Step 1 and recover h from the x-derivative in Step 2. Either order works; choose whichever integral is easier to do by hand.

Worked Example (Try by Hand)

Let's solve a representative problem completely. The example is chosen so that everything — test, integration, an x2x^2 cancellation, an initial condition — appears in one go. Work it out yourself first, then expand the panel to compare.

Problem. Solve (2xy+3)dx+(x2+4y)dy=0(2xy + 3)\,dx + (x^2 + 4y)\,dy = 0 subject to y(0)=1y(0) = 1.

Show the full hand-worked solution
Step 0. Read off M and N.
M=2xy+3,N=x2+4y.M = 2xy + 3, \qquad N = x^2 + 4y.
Step 1. Test exactness.
My=2x,Nx=2x.\frac{\partial M}{\partial y} = 2x, \qquad \frac{\partial N}{\partial x} = 2x. \quad\checkmark
They agree, so we proceed.
Step 2. Integrate M in x.
F(x,y)=(2xy+3)dx=x2y+3x+g(y).F(x, y) = \int (2xy + 3)\,dx = x^2 y + 3x + g(y).
Step 3. Differentiate in y, match to N.
Fy=x2+g(y)=x2+4y.\frac{\partial F}{\partial y} = x^2 + g'(y) = x^2 + 4y.
The x2x^2 cancels — confirmation that the equation really is exact — and we get g(y)=4yg'(y) = 4y.
Step 4. Integrate in y.
g(y)=4ydy=2y2.g(y) = \int 4y\,dy = 2y^2.
Step 5. Assemble.
F(x,y)=x2y+3x+2y2=C.F(x, y) = x^2 y + 3x + 2y^2 = C.
Step 6. Apply the initial condition. Plug in (x0,y0)=(0,1)(x_0, y_0) = (0, 1): 0+0+2=2=C0 + 0 + 2 = 2 = C. So the particular solution is
x2y+3x+2y2=2.x^2 y + 3x + 2y^2 = 2.
Sanity check. Plug (0,1)(0, 1) into the left-hand side: 01+0+21=20 \cdot 1 + 0 + 2 \cdot 1 = 2. ✓ This curve passes through (0,1)(0, 1), as required.

Interactive: Step-by-Step Solver

Pick a problem and reveal the steps one at a time. Try Problem 3 first — it is intentionally non-exact, and seeing how the procedure breaks down is the cleanest way to feel why we need the test.

Loading step-by-step solver...

When It Is Not Exact: Integrating Factors

Most ODEs you run into in the wild fail the exactness test. The repair strategy is to multiply both sides by a function μ(x,y)\mu(x, y) chosen so that the new equation

μMdx+μNdy=0\mu M\,dx + \mu N\,dy = 0

becomes exact. μ\mu is called an integrating factor. The exactness condition for the multiplied equation is (μM)y=(μN)x(\mu M)_y = (\mu N)_x, which expands to

μyM+μMy=μxN+μNx.\mu_y M + \mu M_y = \mu_x N + \mu N_x.

In full generality that is itself a PDE in μ\mu. Two special cases solve cleanly:

SituationIntegrating factor
(M_y − N_x) / N depends only on xμ(x) = exp ∫ (M_y − N_x)/N dx
(N_x − M_y) / M depends only on yμ(y) = exp ∫ (N_x − M_y)/M dy

Notice that this generalizes the integrating-factor trick you already saw for first-order linear ODEs in Section 1: the linear case is just the special case where N1N \equiv 1.

After multiplying by μ\mu, always re-verify exactness on the new equation before integrating. It is very easy to make an algebra slip when computing μ\mu.

Python: Verify Exactness Symbolically

Doing the bookkeeping by hand is great for understanding, but SymPy can run the whole pipeline — test, integrate, assemble — in eight short lines. Read each card before running it. The explanations describe what every single line is doing, why it is needed, and what value the line produces on the running example.

Solving an Exact ODE Symbolically with SymPy
🐍exact_equations.py
1Import SymPy

SymPy is a Python library for symbolic algebra. It manipulates expressions like 2xy + 3 the same way you do on paper — no numerical approximation.

3Create the symbols

We declare x and y as real symbols. Every later expression knows that x and y stand for real numbers, so SymPy can simplify things like √(x²) → |x| correctly.

6Define M(x, y)

This is the coefficient of dx in our ODE M dx + N dy = 0. Concretely M = 2xy + 3.

7Define N(x, y)

This is the coefficient of dy. Here N = x² + 4y. The whole ODE is therefore (2xy + 3) dx + (x² + 4y) dy = 0.

10Compute ∂M/∂y

sp.diff(M, y) treats x as constant and differentiates with respect to y. For M = 2xy + 3 the answer is 2x. The constant 3 differentiates to 0 because it does not contain y.

11Compute ∂N/∂x

Same idea, the other way around. For N = x² + 4y we get 2x. Notice that 4y vanishes because there is no x in it.

12Decide exactness

If sp.simplify(M_y - N_x) reduces to 0, the mixed partials agree and the equation IS exact. Otherwise it is not. Using simplify is essential — SymPy might not immediately see that two algebraically equivalent expressions are equal until you simplify the difference.

18Integrate M w.r.t. x

sp.integrate(M, x) does ∫(2xy + 3) dx = x²y + 3x. Note that SymPy will NOT add a + g(y) for us — it returns just one antiderivative. We will add g(y) ourselves on the next line.

21Solve for g'(y)

Differentiating F_partial w.r.t. y gives x². Subtracting that from N leaves 4y, which is exactly g'(y). The x² cancels because the equation is exact — that cancellation is the algebraic shadow of M_y = N_x.

24Integrate g'(y)

∫4y dy = 2y². The constant of integration is absorbed into the final C in F(x,y) = C, so we don't write it.

25Assemble F

F = x²y + 3x + 2y² is the potential function. The implicit solution of the ODE is the family of level curves F(x, y) = C.

26Print the answer

Expected output: M_y = 2*x, N_x = 2*x, Exact? True, Implicit solution: F(x, y) = x**2*y + 3*x + 2*y**2 = C.

14 lines without explanation
1import sympy as sp
2
3x, y = sp.symbols("x y", real=True)
4
5# Equation:  (2xy + 3) dx + (x^2 + 4y) dy = 0
6M = 2 * x * y + 3
7N = x ** 2 + 4 * y
8
9# Step 1.  Test for exactness:  d M / d y  ==  d N / d x ?
10M_y = sp.diff(M, y)
11N_x = sp.diff(N, x)
12is_exact = sp.simplify(M_y - N_x) == 0
13print("M_y =", M_y)
14print("N_x =", N_x)
15print("Exact?", is_exact)
16
17# Step 2.  Integrate M with respect to x.  Treat y as a constant.
18F_partial = sp.integrate(M, x)            # -> x**2 * y + 3*x
19
20# Step 3.  Differentiate w.r.t. y, set equal to N, solve for g'(y).
21g_prime = sp.simplify(N - sp.diff(F_partial, y))   # -> 4*y
22
23# Step 4.  Integrate g'(y) to get g(y), then assemble F.
24g = sp.integrate(g_prime, y)              # -> 2*y**2
25F = F_partial + g
26print("Implicit solution:  F(x, y) =", F, "= C")
Expected console output:
M_y = 2*x
N_x = 2*x
Exact? True
Implicit solution:  F(x, y) = x**2*y + 3*x + 2*y**2 = C

PyTorch: Mixed Partials by Autograd

Once you have F in hand, the test My=NxM_y = N_x is just Clairaut's theorem applied to F. The same engine that trains neural networks can be asked to differentiate F twice, in either order, and confirm the two answers numerically match. Doing this gives you a feel for autograd as a generic “numerical chain-rule machine,” which is exactly what backpropagation is.

Mixed Partials with PyTorch Autograd
🐍exact_equations_torch.py
1Import PyTorch

We use PyTorch's autograd engine to take derivatives numerically rather than symbolically. This is the same engine that trains every modern neural network — exact equations are a microcosm of the same idea.

4Define F(x, y)

We expose a single Python function that returns F as a torch tensor. Every operation inside (multiplication, addition, **2) is differentiable by autograd, so PyTorch will be able to compute partials of F automatically.

8Create x with requires_grad

requires_grad=True tells autograd to record every operation involving x onto the computation graph. Without it, torch.autograd.grad would refuse to differentiate with respect to x.

9Create y the same way

Same setup for y. We pick (x, y) = (1, 2) as a concrete test point — autograd evaluates partials at a particular point, unlike SymPy which works symbolically.

12Forward pass: evaluate F

F_val is the numerical value of F(1, 2) = 1·2 + 3·1 + 2·4 = 13. Internally PyTorch also stores the computation graph that produced this number.

13First-order partials

torch.autograd.grad(F_val, (x, y), create_graph=True) returns the tuple (∂F/∂x, ∂F/∂y) at the current point. create_graph=True keeps the graph alive so we can differentiate M and N again on the next step.

14Print M

Expected output: M = dF/dx = 7.0, because M = 2xy + 3 and at (1, 2) we get 2·1·2 + 3 = 7.

15Print N

N = dF/dy = 9.0, because N = x² + 4y = 1 + 8 = 9.

18Mixed partial ∂M/∂y

Now we differentiate M (which is itself a tensor on the graph) with respect to y. retain_graph=True is needed because we will use the graph one more time on the next line to compute ∂N/∂x.

19Mixed partial ∂N/∂x

Same trick on the other side. After this call PyTorch is allowed to free the graph.

22Verify exactness

torch.allclose compares the two tensors with a tolerance — necessary because the partials come from floating-point arithmetic. Expected output: Exact? True. This is Clairaut's theorem holding numerically.

11 lines without explanation
1import torch
2
3# Potential function  F(x, y) = x^2 y + 3x + 2 y^2
4def F(x, y):
5    return x ** 2 * y + 3 * x + 2 * y ** 2
6
7# A point to evaluate everything at
8x = torch.tensor(1.0, requires_grad=True)
9y = torch.tensor(2.0, requires_grad=True)
10
11# First derivatives:  M = dF/dx,  N = dF/dy
12F_val = F(x, y)
13M, N = torch.autograd.grad(F_val, (x, y), create_graph=True)
14print("M = dF/dx =", M.item())   # 2xy + 3  at (1,2)  -> 7
15print("N = dF/dy =", N.item())   # x^2 + 4y at (1,2)  -> 9
16
17# Mixed partials by differentiating once more
18M_y = torch.autograd.grad(M, y, retain_graph=True)[0]
19N_x = torch.autograd.grad(N, x)[0]
20print("dM/dy =", M_y.item())     # 2x at x=1  -> 2
21print("dN/dx =", N_x.item())     # 2x at x=1  -> 2
22print("Exact?", torch.allclose(M_y, N_x))
Expected output:
M = dF/dx = 7.0
N = dF/dy = 9.0
dM/dy = 2.0
dN/dx = 2.0
Exact? True

Applications

Conservative Force Fields in Physics

A force F=(Fx,Fy)\mathbf{F} = (F_x, F_y) is conservative precisely when the work form Fxdx+FydyF_x\,dx + F_y\,dy is exact. The potential energy U with F=U\mathbf{F} = -\nabla U is exactly the F we have been building, up to a sign. Closed-loop work is zero because dU=0\oint dU = 0.

Thermodynamic State Functions

In thermodynamics, internal energy U, entropy S, enthalpy H, and Gibbs energy G are state functions: their differentials are exact. The first law dU=TdSpdVdU = T\,dS - p\,dV is an exact form, which is why mixed Maxwell relations like (T/V)S=(p/S)V(\partial T/\partial V)_S = -(\partial p/\partial S)_V hold. Heat δQ\delta Q and work δW\delta W, on the other hand, are not exact — they are path-dependent.

Electrostatics

For a static electric field E=(Ex,Ey)\mathbf{E} = (E_x, E_y), the relation E=φ\mathbf{E} = -\nabla\varphi says precisely that Exdx+EydyE_x\,dx + E_y\,dy is exact. The potential φ\varphi is the F of this section.

Machine Learning

A loss function L(θ)L(\theta) is automatically a potential for its gradient field L\nabla L, so gradient descent is the discretization of the exact equation Ldθ=0\nabla L \cdot d\theta = 0 with a sign flip. When you train a model, you are flowing along the gradient of an exact form. The Hessian symmetry condition you see in second-order optimizers (Newton, L-BFGS) is the very same Clairaut identity.


Common Pitfalls

  • Mixing up M and N. M is the coefficient of dx, N is the coefficient of dy. Test M/y\partial M/\partial y against N/x\partial N/\partial x, not the other way around.
  • Forgetting g(y) in Step 1. When integrating M with respect to x, the constant of integration must be allowed to depend on y. If you drop it, you will be unable to satisfy F/y=N\partial F/\partial y = N.
  • Forgetting to re-test after multiplying by μ. An integrating factor is a guess — verify the multiplied equation is now exact before integrating it.
  • Hidden domain issues. The theorem “exactness ⇔ mixed partials agree” needs a simply-connected region. On a punctured plane (e.g. dropping the origin), an ODE can locally look exact and still fail to have a single-valued potential — this is the same obstruction that makes the differential form (ydx+xdy)/(x2+y2)(-y\,dx + x\,dy)/(x^2 + y^2) locally but not globally exact.
  • Plugging into the wrong equation. When you apply an initial condition, plug it into F(x,y)=CF(x, y) = C, not into the original differential form — the original form is zero at every point on a solution and tells you nothing about C.

Summary

ConceptWhat to remember
Differential formM(x,y) dx + N(x,y) dy = 0 packages an ODE without dividing by anything
ExactThere exists F with ∂F/∂x = M and ∂F/∂y = N. Solutions are F(x,y) = C.
Test∂M/∂y = ∂N/∂x (Clairaut)
MethodF = ∫M dx + g(y); fix g(y) from ∂F/∂y = N; write F = C.
Geometry(M, N) is ∇F. The solution curves are the level curves of F, always perpendicular to (M, N).
Not exact?Multiply by an integrating factor μ(x) or μ(y) and re-test.
Cross-checkSymPy verifies M_y vs N_x symbolically; PyTorch autograd does it numerically by double-differentiating F.
Coming next: in Section 21.3 we will tackle ODEs that are neither linear nor exact, using clever substitutions (Bernoulli, homogeneous, and others) to reduce them to one of the two cases we now know how to solve.
Loading comments...