A poor man’s MaxSMT

First, what is incremental SAT? When a SAT-solver is "warmed up", and you want only to alter list of clauses slightly by adding one, why to reset it? In my toy SMT solver, I use incremental SAT for model counting, or getting all models: see picosat_get_all_models() function.

Also, many SAT solvers supports "assumptions". That is you supply a list of clauses + "temporary" clauses that will be dropped each time.

Now a digression: this is my own binary search implementation:

/*

This is yet another explanation of binary search.
However, it works only on array sizes = 2^n

You know, like bank robbers in movies rotating a wheel on a safe (I don't know how it's called correctly)
and find all digits consecutively.
This is like brute-fource.

We do here the same: we try 0/1 for each bit of index value.
We start at 1, and if the array value at this index is too large, we clear the bit to 0 and proceed to the next (lower) bit.

The array here has 32 numbers. The array index has 5 bits ( log2(32)==5 ).
And you can clearly see that one need only 5 steps to find a value in an array of sorted numbers.

The result:

==========================================
testing idx=0x10 or 16
bit 4 is incorrect, it's 0, so we clear it
testing idx=0x8 or 8
bit 3 is correct, it's 1
testing idx=0xc or 12
bit 2 is correct, it's 1
testing idx=0xe or 14
bit 1 is incorrect, it's 0, so we clear it
testing idx=0xd or 13
found, idx=0xd or 13
==========================================

*/

#include 
#include 

// array of sorted random numbers:
int array[32]=
{
	335, 481, 668, 1169, 1288, 1437, 1485, 1523,
	1839, 2058, 2537, 2585, 2698, 2722, 3245, 3675,
	4067, 4139, 4356, 4599, 5578, 6334, 6751, 7244,
	7845, 8220, 8272, 8296, 8297, 8358, 9138, 9650
};

void binsearch (int val_to_find)
{
	int idx=0;

	for (int bit=4; ; bit--)
	{
		// set the bit:
		idx|=1<<bit;

		printf ("testing idx=0x%x or %d\n", idx, idx);

		if (array[idx]==val_to_find)
		{
			printf ("found, idx=0x%x or %d\n", idx, idx);
			exit(0);
		};

		// array[idx] is too small?
		if (array[idx]<val_to_find)
		{
			// do nothing, the current bit correct, proceed to the next bit
			printf ("bit %d is correct, it's 1\n", bit);
		};

		// array[idx] is too big?
		if (array[idx]>val_to_find)
		{
			// clear the current bit, because it's incorrect
			idx&=~(1<<bit);
			printf ("bit %d is incorrect, it's 0, so we clear it\n", bit);
		};
	};
};

int main()
{
	binsearch(2722);
};

You wouldn't use it, but I wrote it because it's fits so nicely on SAT.

To solve an optimization problem, you want to find some "optimum" variable, that is minimized or maximized. Like in my implementation of binary search, I can try bit by bit, but these are added as assumptions.

// poor man's MaxSMT
// if we minimize, first try false, then true
// if we maximize, do contrariwise
bool run_poor_mans_MaxSMT(struct ctx* ctx)
{
	if (verbose>0)
		printf ("%s() begin\n", __FUNCTION__);
	assert (ctx->maxsat==true);
	struct PicoSAT *p=picosat_init ();

	add_clauses_to_picosat(ctx, p);

	if (ctx->write_CNF_file)
	{
		write_CNF(ctx, "tmp.cnf");
		printf ("CNF file written to tmp.cnf\n");
	};

	int array[ctx->min_max_var->width];
	int res;
	// do this starting at the MSB moving towards the LSB:
	for (int idx=ctx->min_max_var->width-1; idx>=0; idx--)
	{
		// try the first false/true
		array[idx]=ctx->min_or_max==false ? 0 : 1;
		if (verbose>0)
			printf ("idx=%d trying %s\n", idx, ctx->min_or_max==false ? "false" : "true");
		picosat_assume (p, (ctx->min_or_max==false ? -1 : 1) * (ctx->min_max_var->SAT_var+idx));

		res=picosat_sat (p,-1);
		if (res==10)
		{
			if (verbose>0)
				printf ("got SAT\n");
			if (idx!=0)
			{
				// add a newly discovered correct bit as a clause
				// but do this only if this is not the last bit
				// if the bit is the last/lowest (idx==0), we want to prevent PicoSAT to be switched out of SAT state
				// (PicoSAT do this after picosat_add() call)
				// because we are fetching result sooner afterwards
				picosat_add (p, (ctx->min_or_max==false ? -1 : 1) * (ctx->min_max_var->SAT_var+idx));
				picosat_add (p, 0);
			}
			// proceed to the next bit:
			continue;
		};
		if (verbose>0)
			printf ("got UNSAT\n");
		assert (res==20); // must be in UNSAT state

		// try the second false/true
		array[idx]=ctx->min_or_max==false ? 1 : 0;
		if (verbose>0)
			printf ("idx=%d trying %s\n", idx, ctx->min_or_max==false ? "true" : "false");
		picosat_assume (p, (ctx->min_or_max==false ? 1 : -1) * (ctx->min_max_var->SAT_var+idx));

		res=picosat_sat (p,-1);
		if (res==10)
		{
			if (verbose>0)
				printf ("got SAT\n");
			if (idx!=0)
			{
				picosat_add (p, (ctx->min_or_max==false ? 1 : -1) * (ctx->min_max_var->SAT_var+idx));
				picosat_add (p, 0);
			};
		}
		else if (res==20)
		{
			if (verbose>0)
			{
				printf ("got UNSAT\n");
				printf ("%s() begin -> false\n", __FUNCTION__);
			};
			// UNSAT for both false and true for this bit at idx, return UNSAT for this instance:
			return false;
		}
		else
		{
			assert(0);
		};
	};
	// must have a solution at this point
	fill_variables_from_picosat(ctx, p);
	// construct a value from array[]:
	ctx->min_max_var->val=SAT_solution_to_value(array, ctx->min_max_var->width);
	if (verbose>0)
		printf ("%s() begin -> true, val=%llu\n", __FUNCTION__, ctx->min_max_var->val);
	return true;
};

( https://github.com/DennisYurichev/MK85/blob/master/MK85.cc )

For the popscicle problem:

idx=15 trying true
got UNSAT
idx=15 trying false
got SAT
idx=14 trying true
got UNSAT
idx=14 trying false
got SAT
idx=13 trying true
got UNSAT
idx=13 trying false
got SAT
idx=12 trying true
got UNSAT
idx=12 trying false
got SAT
idx=11 trying true
got UNSAT
idx=11 trying false
got SAT
idx=10 trying true
got UNSAT
idx=10 trying false
got SAT
idx=9 trying true
got UNSAT
idx=9 trying false
got SAT
idx=8 trying true
got UNSAT
idx=8 trying false
got SAT
idx=7 trying true
got UNSAT
idx=7 trying false
got SAT
idx=6 trying true
got UNSAT
idx=6 trying false
got SAT
idx=5 trying true
got UNSAT
idx=5 trying false
got SAT
idx=4 trying true
got UNSAT
idx=4 trying false
got SAT
idx=3 trying true
got SAT
idx=2 trying true
got SAT
idx=1 trying true
got UNSAT
idx=1 trying false
got SAT
idx=0 trying true
got SAT
...
run_poor_mans_MaxSMT() begin -> true, val=13

It works slower than if using Open-WBO, but sometimes even faster than Z3!

This is as well: https://github.com/DennisYurichev/random_notes/blob/master/beer_can_optimal_size.md.

And this is close to LEXSAT, see exercise and solution from TAOCP 7.2.2.2:


→ [list of blog posts]

Please drop me email about any bug(s) and suggestion(s): dennis(@)yurichev.com.