More generally, SAT solving is useful whenever you have something that can be represented as a circuit, which is an extremely large class of problems. Indeed, SAT can be proven to be polynomial-time equivalent to any problem in NP (which you can read as "any decision problem whose yes solution is efficiently verifiable"), which includes many useful problems: common examples include the traveling salesman problem, detecting hamiltonian cycles, subgraph isomorphism (interestingly, graph isomorphism is no longer believed to be NP-complete), graph coloring, and many others (which in turn often have easy reductions from other extremely useful problems). Since SAT solvers have received so much optimization work, in practical cases these problems are very often solved by reducing them to some variant of a satisfiability problem, rather than trying to solve them directly.
It is even more useful when combined with theories that can compute special cases (like arithmetic on natural numbers) much more efficiently, which is known as an SMT solver (satisfiability modulo theory). Sometimes, the theories are undecidable, which means the SMT solver is allowed to report "don't know" or run forever in some cases (in practice, people then massage the inputs to try to get them within the solvable cases). This is the form in which SAT solvers are used most often. There are a ton of applications, but an immediate one is determining whether version constraints in a package manager (which may include equalities, inequalities, etc.) can all be simultaneously satisfied (and producing a satisfying assignment). There are a lot of good examples, though, which is why SMT solvers are so ubiquitous in the literature when it comes to "how do we do this really hard thing?" (e.g. they are often critical components of program synthesis).
Sorry if this wasn't that helpful--it's a very broad topic and I'm sure many people here can give you a much better answer!
"If I can frame the problem as X, then I can get a SAT solver to solve it." What would you call X ?
---
Now the long version...
There's a very precise answer here, actually, since SAT is what's called an NP-complete problem (a very important and large class of problems). The easiest way to explain what you can use with SAT is to first define NP, then define NP-completeness.
Let X be some finite "alphabet" of symbols in your system, e.g. 0 and 1 for binary inputs (but as it turns out, for our purposes it could be any finite alphabet and it doesn't really change the definition).
We define a decision problem on finite strings of symbols in X, as a predicate on strings of symbols in X, P: X* -> Prop (strings in X are just a finite number of concatenated symbols of X, e.g. "", "0", "00", "10", etc.); another way of thinking about this is just saying that P is a subset of the set of all strings of symbols in X. It can be pretty complicated to actually check whether a string is actually in P (you can define P as "the set of all programs that don't halt!"), so that is not that useful computationally.
Intuitively, since we're doing computer science, we want to characterize the properties we can actually check. Let's narrow down decision problems just a bit. First, we'll define "computable" functions f : A -> B for any types A and B, as functions / algorithms that take inputs of type A, return inputs of type B, and halt on all inputs (you can define halting formally but it's a pain so we'll skip that). These are a much better fit for computers to run than arbitrary predicates, because they have an answer and a defined algorithm.
Now, we'll narrow down the set of decision problems to the class of what are called "semidecidable problems," which is basically the largest class of functions we can say something useful about computationally. For a decision problem P : X* -> Prop, we say P has a semidecision procedure if there exists an alphabet Y and a computable function f : (X, Y) -> bool, such that for any string x : X, P x holds if and only if there exists some string y: Y such that f(x, y) returns true.
Clearly, not every predicate has a semidecision procedure: the stupid counterexample we talked about before (the set of all prgorams that don't halt) doesn't have one. But, to show how broad this class is, the halting problem actually is semidecidable! That's because if a program halts, by definition, it must halt in a finite number of steps; so you can make Y* just be a natural number represented in binary (or whatever your favorite base is), pass the max number of steps to compute to f (a function that simulates a computer for y steps), and then the function returns true if f halts within y steps and false otherwise. Pretty incredible, right? This is one of many reasons why people are usually mistaken when they say something is impossible because of the halting problem; in practical cases, you can almost always work around it with a semidecision procedure and an appropriate certificate.
One really neat aspect of semidecision procedures is that any semidecidable problem P has an algorithm that, given x: X, will always* terminate and return true if P x holds (but may run forever if it doesn't, so this is called a ). How? Because Y is finite, every string Y* can be represented as a natural number in base Y. So we can just run through every natural number, in order, convert each one into its equivalent string in Y, and then run f (the semidecision procedure for P) on (x,y). If it returns true, we're done, and we know x is in the set (and if x is in the set, we know it will eventually return true for some y, so this always terminates if x is in the set). If it doesn't return true, we're not done, but that's okay because we're only a semidecision procedure :)
There's a bunch of other interesting stuff about semidecision procedures, but we're now going to narrow it down even further... because the problem with the definition I just gave is that there's no restriction on how big y can be. It could be very large, arbitrarily larger than X. So I could just give you a ridiculous number as the upper bound on how long it takes to halt, for instance, and tell you to keep trying for that number of steps, and you couldn't realistically prove me wrong, but also obviously couldn't run that many steps. Additionally, the semiprocedure f just needs to be computable, but nobody said it needs to be fast... as long as you can prove that it halts, it can be basically arbitrarily slow! And of course, the algorithm I mentioned for finding the certificate is incredibly impractical even for relatively small certificates.
So a useful class of problems are those for which there exists an "efficient" semidecision procedure, where the total time required to run f on x and y is bounded in some way by the length of the original input, x. "Efficient" can mean a lot of things, but most people agree that stuff that's exponential in the length of x is probably not efficient, so a popular definition is that a decision procedure is "efficient" if it can run in time polynomial in the length of the input (note that this is the length of the input string, so for example if x is a number represented in binary, it needs to be polynomial in the logarithm of the number, not the number itself). Note that this obviously also places a bound on how much bigger the useful part of y can be than x: it has to be at most polynomially bigger, otherwise the algorithm definitely can't run in time polynomial in the length of x since it has to spend longer than that just reading the parts of the certificate it needs!
Using polynomial time (also called P) is nice because it erases away a bunch of stuff, like log factors (i.e. the form in which the input is represented), conversion procedures, etc. Polynomial time is also closed under most operations: if two procedures can run in polynomial time, then basically any composition you can think of that uses them also runs in polynomial time. It can also hide huge hidden terms though, so "polynomial time reducible" or "polynomial doesn't necessarily actually mean "efficient", it's just a convenient category that restricts things so they don't blow up in insane ways. In practice, most stuff people actually care about has a pretty small polynomial.
The class of problems I just defined (problems with semidecision procedures that run in time polynomial in the input?). That's NP! And that's the set of problems you can encode as SAT instances :) Why? The answer is pretty interesting... first, remember how we defined an algorithm that always terminates with "yes" if x is in the set, for any semidecision procedure at all? Well, with the restriction that our semidecision procedure runs in polynomial time and that the certificate is at most polynomially larger than X, we can ∂efine a new procedure, called a decision procedure, for the problems, as follows:
Given x, run through every y up to a polynomial greater than the maximum certificate blowup (we know this polynomial exists, and what it is, by the definition of problems in NP, as I mentioned earlier, so we know that if a certificate exists, we'll find it). For each y, run our decision procedure f(x, y) on it; this runs in polynomial time, also by definition. If f(x, y) returns true, then we return true. If it didn't return true for any y up to our upper bound, we return false. This new algorithm is said to decide P: it halts on all inputs, not just the ones that return yes, and returns true if x is in P and false otherwise. Problems with this property are called decidable, so we can see that all problems in NP are decidable.
How does this relate to SAT? Well, first, let's observe we can convert the problem from base |X| and |Y| to base 2 (and back) in polynomial time, with at most polynomial input blowup (proof omitted, it's not that interesting). Once we've done that, the algorithm we gave above looks a lot like a naive algorithm for solving SAT: if we think of every bit in the certificate as a boolean variable, then we're just running through all possible assignments to the boolean variables, and then checking f(x,y) on each assignment to see if any are true. So if we can somehow abstract f(x, _) into a boolean circuit (that uses at most polynomially more space than the original input), we're all set!
As it turns out, we can do that! The actual way we do it is kind of subtle, especially if we want to be efficient, and depends heavily on the program representation you've chosen, but the Wikipedia article on the Cook Levin theorem (https://en.wikipedia.org/wiki/Cook%E2%80%93Levin_theorem) gives one approach. So, any problem in NP can be converted to SAT in polynomial time :). Since SAT is also in NP (the certificate is the string of satisfying assignments, and checking it just requires running the boolean expression on the certificate), we call SAT (and other problems like it) "NP-complete." Any problem in NP that you can convert SAT into in polynomial time is also NP-complete, so this is a very natural class of problems, and it contains a whole bunch of important stuff (basically, anything where you need to search the input space for an answer, but can verify it easily).
SAT solver is a tool that determines whether a given boolean formula can be True (it is called SAT) or not. If it can be True, a SAT solver provides values for variables so that you can check that they are correct.
For example, a Boolean formula
a & (b | c)
Can be True if a = True, b = True and c = False.SAT solver can be useful for resolving dependencies in a package manager. Imagine if a user wants to install package A with version >= 1.0, but package A depends on other packages and might conflicts with some packages as well. The package manager converts these requirements into a boolean formula like this: let's say that if variable A₁₀ is True, then package A version 1.0 is installed, if A₁₁ is True, then package A version 1.1 is installed and so on.
First, let's express user's desire to have any version of A installed. Here is the rule:
A₁₀ | A₁₁ must be True
Let's say A version 1.0 requires B version 1.0 or 1.1. We can then write a rule, that if A 1.0 is installed, then B 1.0 or 1.1 must be installed as well: !A₁₀ | (B₁₀ | B₁₁) must be True
Now, let' say that B 1.0 conflicts with C 1.0 and both cannot be installed at the same time. We can express that as a formula as well: !(B₁₀ & C₁₀) must be True
Finally, we cannot install two versions of the same package, so let's write that too: !(A₁₀ & A₁₁) & !(B₁₀ & B₁₁) must be True
Now we can combine all the rules into a single formula. All the rules must be true, so we join them using AND operator: (A₁₀ | A₁₁) & (!A₁₀ | (B₁₀ | B₁₁)) & !(B₁₀ & C₁₀) & !(A₁₀ & A₁₁) & !(B₁₀ & B₁₁)
Now we can give the formula to the SAT solver. If will solve it and give a set of versions that need to be installed in order to satisfy all requirements. Or it will prove that this is impossible.Another use of SAT solver is verification of programs: if you have two pieces of code without loops and you want to make sure that they are equivalent, you can convert them into boolean formulas and use SAT to prove that for any given input they produce the same output.
https://zeta-two.com/assets/other/nixucon18-slides.pdf https://github.com/TechSecCTF/z3_splash_class