# Video: Solving 2D Equations Using Color, a Story of Winding Numbers and Composition

Grant Sanderson • 3Blue1Brown • Boclips

Solving 2D Equations Using Color, a Story of Winding Numbers and Composition

22:34

### Video Transcript

There’s two things here, the main topic and the meta topic. So the main topic is gonna be this really neat algorithm for solving two-dimensional equations. Things that have two unknown real numbers or also those involving a single unknown which is a complex number. So for example, if you wanted to find the complex roots of a polynomial, or maybe some of those million dollar zeros of the Riemann zeta function, this algorithm would do it for you.

And this method is super pretty, since a lot of colors are involved. And more importantly, the core underlying idea applies to all sorts of math well beyond this algorithm for solving equations, including a bit of topology which I’ll talk about afterwards. But what really makes this worth 20 minutes or so of your time is that it illustrates a lesson much more generally useful throughout math. Which is, try to define constructs that compose nicely with each other. You’ll see what I mean by that as the story progresses.

To motivate the case with functions that have 2D inputs and 2D outputs, let’s start off simpler, with functions that just take in a real number and spit out a real number. If you wanna know when a function, 𝑓 of 𝑥, equals some another function, 𝑔 of 𝑥, you might think of this as searching for when the graphs of these functions intersect, right? I mean that gives you an input where both functions have the same output.

To take a very simple example, imagine 𝑓 of 𝑥 is 𝑥 squared and 𝑔 of 𝑥 is the constant function two. In other words, you want to find the square root of two. Even if you know almost nothing about finding square roots, you can probably see that one squared is less than two and two squared is bigger than two. So you realize, “Ah, there’s gonna be some solution in between those two values.” And then, if you wanted to narrow it down further, maybe you try squaring the halfway point, 1.5. And this comes out to be 2.25, a little bit too high. So you would focus on the region between one and 1.5, and so on. You can probably see how this would keep going. You keep computing at the midpoint and then chopping your search space in half.

Now another way to think about this, which is gonna make it easier for us once we get up to higher dimensions, is to instead focus on the equivalent question for when the difference between these two functions is zero. In those terms, we found a region of inputs where that difference was negative on one end. And it was positive on another end. And then we split it into two. And the half that we narrowed our attention to was the one where the outermost points had varying signs. And like this, we were able to keep going forever, taking each region with varying signs on the border. Finding a smaller such region among its halves. Knowing that ultimately, we have to be narrowing in on a point which is gonna be exactly zero.

So in short, solving equations can always be framed as finding when a certain function is equal to zero. And to do that, we have this heuristic. If 𝑓 is positive at one point and negative at another point, you can find some place in between where it’s zero. At least if everything changes smoothly with no sudden jumps. Now The amazing thing that I wanna show you is that you can extend this kind of thinking into two-dimensional equations, equations between functions whose inputs and outputs are both two-dimensional. For example, complex numbers are 2D. And this tool that we’re developing is perfect for finding solutions to complex equations.

Now, since we’re gonna be talking about these 2D functions so much, let’s take a brief sidestep and consider how do we illustrate these. I mean, graphing a function with a 2D input and a 2D output would require four dimensions. And that’s not really gonna work so well in our 3D world or on our 2D screens. But, we still have a couple good options. One is to just look at both the input space and the output space side by side. Each point in the input space moves to a particular point in the output space. And I can show how moving around that input point corresponds to certain movements in the output space. All of the functions we consider will be continuous, in the sense that small little changes to the input only correspond to small little changes in the output. There’s no sudden jumps.

Now another option we have is to imagine the arrow from the origin of the output space to that output point and to attach a miniature version of that arrow to the input point. This can give us a sense at a glance for where a given input point goes or where many different input points go by drawing the full vector field. And unfortunately, when you do this at a lot of points, it can get pretty cluttered. So here, let me make all of the arrows the same size. And what this means is we can get a sense just of the direction of each output point.

But perhaps the prettiest way to illustrate two dimensional functions, and the one we’ll be using most this video, is to associate each point in that output space with a color. Here, we’ve used hues — that is, where the color falls along a rainbow or a color wheel — to correspond to the direction away from the origin. And we’re using darkness or brightness to correspond to the distance from the origin. For example, focusing just on this ray of outputs, all of these points are red. But the ones closer to the origin are a little darker and the ones further away are a little brighter. And focusing just on this ray of outputs, all of the points are green. And again, closer to the origin means darker; farther away means lighter, and so on. All we’re doing here is assigning a specific color to each direction, all changing continuously.

You might notice the darkness and brightness differences here are pretty subtle. But for this video, all we care about is the direction of outputs not the magnitudes, the hues not the brightness. The one important thing about brightness for you to notice is that near the origin, which has no particular direction, all of the colors fade to black. So if we’re thinking about functions, now that we’ve decided on a color for each output. We can visualize 2D functions by coloring each point in the input space based on the color of the point where it lands in the output space.

I like to imagine many different points from that input space hopping over to their corresponding outputs in the output space. Then getting painted based on the color of the point where they land. And then hopping back to where they came from in the input space. Doing this for every point in the input space, you can get a sense just by looking at that input space for roughly where the function takes each point. For example, this stripe of pink points on the left tells us that all of those points get mapped somewhere in the pink direction, that lower left of the output space. Also, those three points which are black with lots of colors around them are the ones that go to zero.

Alright, so just like the 1D case, solving equations of two-dimensional functions can always be reframed by asking when a certain function is equal to zero. So that’s our challenge right now. Create an algorithm that finds which input points of a given 2D function go to zero. Now, you might point out that if you’re looking at a color map like this, by seeing those black dots, you already know where the zeros of the function are. So, does that count? Well, keep in mind that to create a diagram like, we’ve had the computer compute the function at all of the pixels on the plane. But our goal is to find a more efficient algorithm that only requires computing the function at as few points as possible, only having a limited view of the colors, so to speak. And also, from a more theoretical standpoint, it’d be nice to have a general construct that tells us the conditions for whether or not a zero exists inside a given region.

Now remember, in one dimension, the main insight was that if a continuous function is positive at one point and negative at another, then somewhere in between, it must be zero. So how do we extend that into two dimensions? We need some sort of analog of talking about signs. Well, one way to think about what signs even are is directions. Positive means you’re pointing to the right along the number line. And negative means you’re pointing to the left. Two-dimensional quantities also have direction. But for them, the options are much wider. They can point anywhere along a whole circle of possibilities. So the same way that in one dimension we were asking whether a given function is positive or negative on the boundary of a range, which is just two points. For 2D functions, we’re gonna be looking at the boundary of a region, which is a loop, and ask about the direction of the function’s output along that boundary.

For example, we see that along this loop around this zero, the output goes through every possible direction, all of the colors of the rainbow. Red, yellow, green, blue, and back to red, and everything in between along the way. But along this loop over here, with no zeros inside of it, the output doesn’t go through every color. It goes through some of the orangish ones but never, say, green or blue. And this is promising. It looks a lot like how things worked in one dimension. Maybe in the same way that if a 1D function takes both possible signs on the boundary of a 1D region. There was a zero somewhere inside. We might hypothesize that if a 2D function hits outputs of all possible directions, all possible colors, along the boundary of a 2D region, then somewhere inside that region it must go to zero. So, that’s our guess. And take a moment to think about if this should be true, and if so, why?

If we start thinking about a tiny loop around some input point, we know that since everything is continuous, our function takes it to some tiny loop near the corresponding output. But look, for most tiny loops, the output barely varies in color. If you pick any output point other than zero and draw a sufficiently tight loop near it, the loop’s colors are all gonna be about the same color as that point. A tight loop over here is all bluish. A tight loop over here is gonna be all yellowish. You certainly aren’t gonna get every color of the rainbow. The only point where you can tighten loops around it while still getting all of the colors is the colorless origin, zero itself. So it is indeed the case that if you have loops going through every color of the rainbow, tightening and tightening, narrowing in on a point, then that point must in fact be a zero.

And so, let’s set up a 2D equation solver just like our one-dimensional equation solver. When we find a large region whose border goes through every color, split it into two. And then look at the colors on the boundary of each half. In the example shown here, the border on the left half doesn’t actually go through all colors. There are no points that map to the orangish-yellowish directions, for example. So I’ll grey out this area as a way of saying we don’t wanna search it any further. Now the right half does go through all of the colors, spends a lot of time in the green direction then passes through yellow, orange, red, as well as blue, violet, pink.

Now remember, what that means is that points of this boundary get mapped to outputs of all possible directions. So we’ll explore it further, subdividing again and checking the boundary for each region. And the boundary of the top right is all green, so we’ll stop searching there. But the bottom is colorful enough to deserve a subdivision. And just continue like this. Check which subregion has a boundary covering all possible colors, meaning points of that boundary get mapped to all possible directions. And keep chopping those regions in half like we did for the one-dimensional case. Eventually leading us to a zero of the fun — oh, actually hang on a second. What happened here? Neither of those last subdivisions on the bottom right passed through all the colors. So our algorithm stopped cause it didn’t wanna search through either of those. But it also didn’t find a zero.

Okay, clearly something’s wrong here. And that’s okay! Being wrong is a regular part of doing math. If we look back, we had this hypothesis. And it led us to this proposed algorithm. So, we were mistaken somewhere. And being good at math is not about being right the first time. It’s about having the resilience to carefully look back and understand the mistakes and understand how to fix them. Now The problem here is that we had a region whose border went through every color. But when we split it in the middle, neither subregion’s border went through every color. We had no options for where to keep searching next. And that broke the zero finder.

Now in one dimension, this sort of thing never happened. Any time you had an interval whose endpoints had different signs, if you split it up, you know that you’re guaranteed to get some subinterval whose endpoints also have different signs. Or put another way, any time you have two intervals whose endpoints don’t change signs, if you combine them, you’ll get a bigger interval whose endpoints also don’t change sign. But in two dimensions, it’s possible to find two regions whose borders don’t go through every color but whose boundaries combine to give a region whose border does go through every color. And in just this way, our proposed zero-finding algorithm broke. In fact, if you think about it, you can find a big loop whose border goes through every possible color without there being a zero inside of it.

Now that’s not to say that we were wrong in our claims about tiny loops, when we said that a forever-narrowing loops going through every color has to be narrowing in on a zero. But what made a mess of things for us is that this “does my border go through every color or not” property doesn’t combine in a nice predictable way when you combine regions. But don’t worry! It turns out, we can modify this slightly to a more sophisticated property that does combine to give us what we want.

The idea is that instead of simply asking whether we can find a color at some point along the loop. Let’s keep more careful track of how those colors change as we walk around that loop. Let me show what you I mean with an example. I’ll keep a little color wheel up here in the corner to help us keep track. When the colors along a path of inputs move through the rainbow in a specific direction — from red to yellow, yellow to green, green to blue, or blue to red — the output is swinging clockwise. But on the other hand, if the colors move the other way through the rainbow — from blue to green, green to yellow, yellow to red, or red to blue — the output is swinging counterclockwise.

So walking along this short path here, the colors wind a fifth of the way clockwise through the color wheel. And walking along this path here, the colors wind another fifth of the way clockwise through the color wheel. And of course, that means that if you go through both paths, one after another, the colors wind a total of two-fifths of a full turn clockwise. The total amount of winding just adds up, and this is gonna be key. This is the kind of straightforward combining that will be useful to us. Now when I say total amount of winding, I want you to imagine an old-fashioned odometer that ticks forward as the arrow spins clockwise but backwards as the arrow spins counterclockwise. So counterclockwise winding counts as negative clockwise winding. The outputs may turn a lot. But if some of that turning is in the opposite direction, it cancels out.

For example, if you move forward along this path and then move backwards along that same path, the total amount of winding ends up just being zero. The backwards movement literally rewinds through the previously seen colors. Reversing all the previous winding and returning the odometer back to where it started. For our purposes, we’ll care most about looking at the winding along loops. For example, let’s say we walk around this entire loop clockwise. The outputs that we come across wind around a total of three full clockwise turns. The colors swung through the rainbow, ROYGBIV, in order, from red to red again and then again and again. In the jargon mathematicians use, we say that along this loop, the total winding number is three.

Now for other loops, it could be any other whole number. Maybe a larger one if the output swings around many times as the input walks around a single loop. Or could be a smaller number if the output only swings around once or twice. Or that winding number could even be a negative integer. If the output was swinging counterclockwise as we walk clockwise around the loop. But along any loop, this total amount of winding has to be a whole number. I mean, by the time you get back to where you started, you’ll have the same output that you started with. Incidentally, if a path actually contains a point where the output is precisely zero, then technically you can’t define a winding number along that. Since the output has no particular direction.

Now this isn’t gonna be a problem for us because our whole goal is to find zeros. So if this ever comes up, we just lucked out early. Alright, so the main thing to notice about these winding numbers is that they add up nicely when you combine paths into bigger paths. But what we really want is for the winding numbers along the borders of regions to add up nicely when we combine regions to make bigger regions. So do we have that property? Well, take a look! The winding number as we go clockwise around this region on the left is the sum of the winding numbers from these four paths. And the winding as we go clockwise around this region on the right is the sum of the winding numbers from these four paths. And when we combine those two regions into a bigger one, most of those paths become part of the clockwise border of the bigger region.

And as for those two paths that don’t? Well, they cancel out perfectly. One of them is just the reverse, the rewinding, of the other one like we saw before. So the winding numbers along borders of regions add up in just the way that we want them to. Also, side note, this reasoning about oriented borders adding up nicely like this comes up a lot in mathematics. And it often goes under the name Stokes’s Theorem. Those of you who’ve studied multivariable calculus might recognize it from that context.

So now, finally, with winding numbers in hand, we can get back to our equation-solving goals. The problem with the region we saw earlier is that even though its border passed through all possible colors, the winding number was actually zero. The outputs wound around halfway through yellow to red. And then started going counterclockwise back the other direction. And continued going through blue and hitting red from the other way. All in such a way that the total winding netted out to be zero. But if you find a loop which not only hits every color, but it has the stronger condition of a nonzero winding number. Then if you were to split it in half, you’re guaranteed that at least one of those halves has a nonzero winding number as well. Since things add up nicely in the way we want them to.

So in this way, you can keep going, narrowing in further and further onto one point. And as you narrow in onto a point, you’ll be doing so with tiny loops that have nonzero winding numbers. Which implies they go through all possible colors. And therefore, like I said before, the point they’re narrowing in on must be a zero. And that’s it! We have now created a two-dimensional equation solver. And this time, I promise, there are no bugs. Winding numbers are precisely the tool we needed to make this work. We can now solve equations that look like where does 𝑓 of 𝑥 equal 𝑔 of 𝑥 in two dimensions just by considering how the difference between 𝑓 and 𝑔 winds around. Whenever we have a loop whose winding number isn’t zero, we can run this algorithm on it. And we’re guaranteed to find a solution somewhere within it.

And what’s more, just like in one dimension, this algorithm is incredibly efficient. We keep narrowing in on half the size of our region each round. Thus, quickly narrowing in on the zeros. And all the while, we only have to check the value of the function along points of these loops, rather than checking it on the many, many points of the interior. So in some sense, the overall work done is proportional only to the search space’s perimeter not the full area, which is amazing! Now once you understand what’s going on, it is weirdly mesmerizing to just watch this in action. Giving it some function and letting it search for zeros. Like I said before, complex numbers, they’re two-dimensional. So let’s apply it to some equation with complex numbers.

For example, here’s the algorithm finding the zeros of the function 𝑥 to the fifth minus 𝑥 minus one over the complex plane. It started by considering a very large region around the origin, which ended up having a winding number of five. Each time you find a loop with a nonzero winding number, you split it in half and figure out the winding number of the two smaller loops. Either one or both of them is guaranteed to have a nonzero winding number. And when you see this, you know that there is a zero somewhere inside that smaller loop. So, you keep going in the same way, searching the smaller space. We also stop exploring a region if the path that we’re computing along happens to stumble once across a zero. Which actually happened once for this example on the right half here.

Those rare occurrences interfere with our ability to compute winding numbers. But hey, we got a zero! And as for loops whose winding number is zero, you just don’t explore those further. Maybe they have a solution inside; maybe they don’t. We have no guarantees. And letting our equation solver continue in the same way, it eventually converges to lots of zeros for this polynomial. By the way, it is no coincidence that the total winding number in this example happen to be five. With complex numbers, the operation 𝑥 to the 𝑛 directly corresponds to walking around the output’s origin 𝑛 times as you walk around the input’s origin once.

So the polynomial, for large enough inputs, every term other than the leading term becomes insignificant in comparison. So any complex polynomial whose leading term is 𝑥 to the 𝑛 has a winding number of 𝑛 around a large-enough loop. And in that way, our winding number technology actually guarantees that every complex polynomial has a zero. This is such an important fact that mathematicians call it the fundamental theorem of algebra. Having an algorithm for finding numerical solutions to equations like this is extremely practical. But the fundamental theorem of algebra is a good example of how these winding numbers are also quite useful on a theoretical level. Guaranteeing the existence of a solution to a broad class of equations for suitable conditions. Which is much more the kinda thing mathematicians like thinking about.