SMT solvers are quite good at solving these sorts of constraints and there are many to choose from. See https://smtlib.cs.uiowa.edu for an overview.
One implementation is Microsoft's z3, which can easily handle queries like the one you posed: https://github.com/Z3Prover/z3
SMT solvers can be programmed in the so called SMTLib syntax (see first link above), or they provide APIs in many languages for you to program them in; including C/C++/Java/Python/Haskell etc. Furthermore, most theorem provers these days (including Isabelle/HOL) come with tactics that use these provers behind the scenes; so many integrations are possible.
As I mentioned, z3 can be programmed in various languages, a good paper to review is https://theory.stanford.edu/~nikolaj/programmingz3.html, which uses Python to do so.
A Solution using Z3 in Haskell
For your particular problem, here's how it'd be solved using z3 in Haskell, using the SBV layer (http://leventerkok.github.io/sbv/):
import Data.SBV
problem :: IO AllSatResult
problem = allSatWith z3{allSatPrintAlong = True} $ do
[bi0, bi1, bi2] <- sBools ["bi0", "bi1", "bi2"]
[ai0, ai1, ai2] <- sReals ["ai0", "ai1", "ai2"]
cbrt_ai0 <- sReal "cbrt_ai0"
let cube x = x * x * x
constrain $ cube cbrt_ai0 .== ai0
let b0 = bi0 .&& bi1
a1 = ite b0 ai0 cbrt_ai0
a2 = ite bi2 a1 ai1
pure $ a2 .> ai2
When I run this, I get:
*Main> problem
Solution #1:
bi0 = False :: Bool
bi1 = False :: Bool
bi2 = False :: Bool
ai0 = 0.125 :: Real
ai1 = -0.5 :: Real
ai2 = -2.0 :: Real
cbrt_ai0 = 0.5 :: Real
Solution #2:
bi0 = True :: Bool
bi1 = True :: Bool
bi2 = True :: Bool
ai0 = 0.0 :: Real
ai1 = 0.0 :: Real
ai2 = -1.0 :: Real
cbrt_ai0 = 0.0 :: Real
Solution #3:
bi0 = True :: Bool
bi1 = False :: Bool
bi2 = True :: Bool
ai0 = 0.0 :: Real
ai1 = 0.0 :: Real
ai2 = -1.0 :: Real
cbrt_ai0 = 0.0 :: Real
...[many more lines deleted...]
I interrupted the output since it seems there's a lot of assignments (possibly infinite by looking at the structure of your problem).
A solution using Z3 in Python
Another popular choice is to use Python to program the same. The code is a bit more verbose than Haskell in this case, there's some boiler-plate to get all-solutions and other Python gotchas, but otherwise follow a similar path:
from z3 import *
s = Solver()
bi0, bi1, bi2 = Bools('bi0 bi1 bi2')
ai0, ai1, ai2 = Reals('ai0 ai1 ai2')
cbrt_ai0 = Real('cbrt_ai0')
def cube(x):
return x*x*x
s.add(cube(cbrt_ai0) == ai0)
b0 = And(bi0, bi1)
a1 = If(b0, ai0, cbrt_ai0)
a2 = If(bi2, a1, ai1)
s.add(a2 > ai2)
def all_smt(s, initial_terms):
def block_term(s, m, t):
s.add(t != m.eval(t, model_completion=True))
def fix_term(s, m, t):
s.add(t == m.eval(t, model_completion=True))
def all_smt_rec(terms):
if sat == s.check():
m = s.model()
yield m
for i in range(len(terms)):
s.push()
block_term(s, m, terms[i])
for j in range(i):
fix_term(s, m, terms[j])
yield from all_smt_rec(terms[i:])
s.pop()...
yield from all_smt_rec(list(initial_terms))
for model in all_smt(s, [ai0, ai1, ai2, bi0, bi1, bi2]):
print(model)
Again, when run this continuously spits out solutions.