Skip to content

Commit 2baf376

Browse files
authored
Merge pull request #58 from AL-JiongYang/appmc7
The pull request is failing one build check but that should get fixed with the new cmake system.
2 parents 6213fb2 + 1bfa39c commit 2baf376

File tree

4 files changed

+194
-7
lines changed

4 files changed

+194
-7
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
- name: Install zlib for Mac dynamic
4040
if: contains(matrix.os, 'macos') && matrix.staticcompile == 'OFF'
4141
run: |
42-
wget https://www.zlib.net/zlib-1.3.1.tar.gz
42+
wget https://zlib.net/fossils/zlib-1.3.1.tar.gz
4343
tar xzvf zlib-1.3.1.tar.gz
4444
cd zlib-1.3.1
4545
./configure
@@ -50,7 +50,7 @@ jobs:
5050
- name: Install zlib for Mac static
5151
if: contains(matrix.os, 'macos') && matrix.staticcompile == 'ON'
5252
run: |
53-
wget https://www.zlib.net/zlib-1.3.1.tar.gz
53+
wget https://zlib.net/fossils/zlib-1.3.1.tar.gz
5454
tar xzvf zlib-1.3.1.tar.gz
5555
cd zlib-1.3.1
5656
./configure --static

src/appmcconfig.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class Config {
5454
int dump_intermediary_cnf = 0;
5555
int debug = 0;
5656
int force_sol_extension = false;
57+
double appmc7_eps_cutoff = 5;
5758

5859
std::vector<uint32_t> sampl_vars;
5960
bool sampl_vars_set = false;

src/counter.cpp

Lines changed: 183 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,12 @@ void Counter::set_up_probs_threshold_measurements(
370370

371371
verb_print(1, "[appmc] threshold set to " << threshold << " sparse: " << (int)using_sparse);
372372

373-
double p_L = 0;
373+
// Error probability of ApproxMC6
374+
// See Lemma 4 in CAV23 paper "Rounding Meets Approximate Model Counting"
375+
// https://link.springer.com/chapter/10.1007/978-3-031-37703-7_7
376+
377+
// Upper bound on the probability of underestimation event (L)
378+
double p_L;
374379
if (conf.epsilon < sqrt(2)-1) {
375380
p_L = 0.262;
376381
} else if (conf.epsilon < 1) {
@@ -383,13 +388,30 @@ void Counter::set_up_probs_threshold_measurements(
383388
p_L = 0.023;
384389
}
385390

391+
// Upper bound on the probability of overestimation event (U)
386392
double p_U = 0;
387393
if (conf.epsilon < 3) {
388394
p_U = 0.169;
389395
} else {
390396
p_U = 0.044;
391397
}
392398

399+
// Error probability of ApproxMC7
400+
// See AAAI25 paper "Towards Real-Time Approximate Counting"
401+
// https://ojs.aaai.org/index.php/AAAI/article/view/33231
402+
if (conf.epsilon >= conf.appmc7_eps_cutoff) {
403+
// Line 6 in Algorithm 4
404+
threshold = 0;
405+
// Line 4 in Algorithm 4
406+
beta = (1 + sqrt( 1 + 2*(1+conf.epsilon)*(1+conf.epsilon) ))/2;
407+
alpha = beta - 1;
408+
// Lemma 5.b
409+
p_L = 1 / (1 + alpha);
410+
// Lemma 5.a
411+
p_U = 1 / beta;
412+
}
413+
414+
// Algorithm 6 of ApproxMC6 or Algorithm 3 of ApproxMC7
393415
for (measurements = 1; ; measurements+=2) {
394416
if (calc_error_bound(measurements, p_L) + calc_error_bound(measurements, p_U) <= conf.delta) {
395417
break;
@@ -422,8 +444,12 @@ ApproxMC::SolCount Counter::count()
422444
//for Probabilistic Inference: From Linear to Logarithmic SAT Calls"
423445
//https://www.ijcai.org/Proceedings/16/Papers/503.pdf
424446
for (uint32_t j = 0; j < measurements; j++) {
425-
one_measurement_count(prev_measure, j, sparse_data, &hm);
426-
if (prev_measure == 0) {
447+
if (conf.epsilon < conf.appmc7_eps_cutoff) {
448+
one_measurement_count(prev_measure, j, sparse_data, &hm);
449+
} else {
450+
appmc7_one_measurement_count(prev_measure, j, sparse_data, &hm);
451+
}
452+
if (prev_measure == 0 && (conf.epsilon < conf.appmc7_eps_cutoff || num_count_list.back() == 0)) {
427453
// Exact count, no need to measure multiple times.
428454
verb_print(1, "[appmc] Counted without XORs, i.e. we got exact count");
429455
break;
@@ -441,24 +467,43 @@ ApproxMC::SolCount Counter::calc_est_count()
441467
ApproxMC::SolCount ret_count;
442468
if (num_hash_list.empty() || num_count_list.empty()) return ret_count;
443469

444-
// round model counts
445470
if (num_hash_list[0] > 0) {
446471
double pivot = 9.84*(1.0+(1.0/conf.epsilon))*(1.0+(1.0/conf.epsilon));
447472
for (auto cnt_it = num_count_list.begin(); cnt_it != num_count_list.end(); cnt_it++) {
448-
if (conf.epsilon < sqrt(2)-1) {
473+
// Adjust model counts of ApproxMC7 to improve confidence.
474+
// See Line 15 of Algorithm 4 in AAAI25 paper "Towards Real-Time Approximate Counting"
475+
// https://ojs.aaai.org/index.php/AAAI/article/view/33231
476+
// The usage of sqrt(2*alpha/beta) is justified in the proof of Lemma 5, specifically, Equation 7 and 10.
477+
if (conf.epsilon >= conf.appmc7_eps_cutoff) {
478+
assert(alpha != -1);
479+
assert(beta != -1);
480+
*cnt_it *= sqrt(2*alpha/beta);
481+
482+
// Round model counts of ApproxMC6 to improve confidence.
483+
// See Algorithm 5 and Line 5-8 of Algorithm 4 in CAV23 paper "Rounding Meets Approximate Model Counting"
484+
// https://link.springer.com/chapter/10.1007/978-3-031-37703-7_7
485+
// The usage of various rounded values is justified in the proof of Lemma 4.
486+
// The breakpoints in epsilon are explained at the end of Section 5.3.
487+
488+
// Line 1 in Algorithm 5
489+
} else if (conf.epsilon < sqrt(2)-1) {
449490
if (*cnt_it < sqrt(1+2*conf.epsilon)/2 * pivot) {
450491
*cnt_it = sqrt(1+2*conf.epsilon)/2 * pivot;
451492
}
493+
// Line 2 in Algorithm 5
452494
} else if (conf.epsilon < 1) {
453495
if (*cnt_it < pivot / sqrt(2)) {
454496
*cnt_it = pivot / sqrt(2);
455497
}
498+
// Line 3 in Algorithm 5
456499
} else if (conf.epsilon < 3) {
457500
if (*cnt_it < pivot) {
458501
*cnt_it = pivot;
459502
}
503+
// Line 4 in Algorithm 5
460504
} else if (conf.epsilon < 4*sqrt(2)-1) {
461505
*cnt_it = pivot;
506+
// Line 5 in Algorithm 5
462507
} else {
463508
*cnt_it = sqrt(2)*pivot;
464509
}
@@ -632,6 +677,139 @@ void Counter::one_measurement_count(
632677
}
633678
}
634679

680+
// Compute an estimate of model count in ApproxMC7
681+
// See Algorithm 4 (excluding Line 15) in AAAI25 paper "Towards Real-Time Approximate Counting"
682+
// https://ojs.aaai.org/index.php/AAAI/article/view/33231
683+
void Counter::appmc7_one_measurement_count(
684+
int64_t& prev_measure,
685+
const uint32_t iter,
686+
SparseData sparse_data,
687+
HashesModels* hm)
688+
{
689+
if (conf.sampl_vars.empty()) {
690+
auto ret = solver->solve();
691+
assert(ret != l_Undef);
692+
num_hash_list.push_back(0);
693+
num_count_list.push_back(ret == l_True ? 1 : 0);
694+
return;
695+
}
696+
697+
//Tells whether a solution is found at hash number N
698+
//sols_for_hash[N] tells whether a solution is found when N hashes were added
699+
map<uint64_t,int64_t> sols_for_hash;
700+
701+
//threshold_sols[hash_num]==1 tells us that at hash_num number of hashes
702+
//a solution was found
703+
//threshold_sols[hash_num]==0 tells that no solution was found.
704+
map<uint64_t,bool> threshold_sols;
705+
int64_t total_max_xors = conf.sampl_vars.size();
706+
int64_t lower_fib = 0;
707+
int64_t upper_fib = total_max_xors+1;
708+
threshold_sols[lower_fib] = 1;
709+
sols_for_hash[lower_fib] = 1;
710+
threshold_sols[upper_fib] = 0;
711+
sols_for_hash[upper_fib] = 0;
712+
713+
int64_t hash_cnt = prev_measure;
714+
int64_t hash_prev = hash_cnt;
715+
716+
//We are doing a galloping search here (see our paper for more details).
717+
//lower_fib is referred to as loIndex and upper_fib is referred to as hiIndex
718+
//The key idea is that we first do an exponential search and then do binary search
719+
//This is implemented by using two sentinels: lower_fib and upper_fib. The correct answer
720+
// is always between lower_fib and upper_fib. We do exponential search until upper_fib < lower_fib*2
721+
// Once upper_fib < lower_fib*2; we do a binary search.
722+
while (true) {
723+
uint64_t cur_hash_cnt = hash_cnt;
724+
const vector<Lit> assumps = set_num_hashes(hash_cnt, hm->hashes, sparse_data);
725+
726+
verb_print(1, "[appmc] "
727+
"[ " << std::setw(7) << std::setprecision(2) << std::fixed << (cpu_time()-start_time) << " ]"
728+
<< " round: " << std::setw(2) << iter
729+
<< " hashes: " << std::setw(6) << hash_cnt);
730+
SolNum sols = bounded_sol_count(
731+
1, //max no. solutions
732+
&assumps, //assumptions to use
733+
hash_cnt,
734+
iter,
735+
hm
736+
);
737+
const uint64_t num_sols = std::min<uint64_t>(sols.solutions, 1);
738+
assert(num_sols <= 1);
739+
if (num_sols < 1) {
740+
//one less hash count had threshold solutions
741+
//this one has less than threshold
742+
//so this is the real deal!
743+
if (hash_cnt == 0) {
744+
num_hash_list.push_back(0);
745+
num_count_list.push_back(0);
746+
prev_measure = 0;
747+
assert(num_sols == 0 and hash_cnt == 0);
748+
return;
749+
}
750+
if (threshold_sols.find(hash_cnt-1) != threshold_sols.end()
751+
&& threshold_sols[hash_cnt-1] == 1) {
752+
num_hash_list.push_back(hash_cnt-1);
753+
num_count_list.push_back(1);
754+
prev_measure = hash_cnt-1;
755+
assert(num_sols == 0 and threshold_sols[hash_cnt-1] == 1);
756+
return;
757+
}
758+
759+
threshold_sols[hash_cnt] = 0;
760+
sols_for_hash[hash_cnt] = 0;
761+
if (iter > 0 &&
762+
std::abs(hash_cnt - prev_measure) <= 2
763+
) {
764+
//Doing linear, this is a re-count
765+
upper_fib = hash_cnt;
766+
hash_cnt--;
767+
} else {
768+
if (hash_prev > hash_cnt) hash_prev = 0;
769+
upper_fib = hash_cnt;
770+
if (hash_prev > lower_fib) lower_fib = hash_prev;
771+
hash_cnt = (upper_fib+lower_fib)/2;
772+
}
773+
} else {
774+
assert(num_sols == 1);
775+
776+
//success record for +1 hashcount exists and is 0
777+
//so one-above hashcount was below threshold, this is above
778+
//we have a winner -- the one above!
779+
if (threshold_sols.find(hash_cnt+1) != threshold_sols.end()
780+
&& threshold_sols[hash_cnt+1] == 0
781+
) {
782+
num_hash_list.push_back(hash_cnt);
783+
num_count_list.push_back(1);
784+
prev_measure = hash_cnt;
785+
assert(num_sols == 1 and threshold_sols[hash_cnt+1] == 0);
786+
return;
787+
}
788+
789+
threshold_sols[hash_cnt] = 1;
790+
sols_for_hash[hash_cnt] = 1;
791+
if (iter > 0
792+
&& std::abs(hash_cnt - prev_measure) < 2
793+
) {
794+
//Doing linear, this is a re-count
795+
lower_fib = hash_cnt;
796+
hash_cnt++;
797+
} else if (lower_fib + (hash_cnt-lower_fib)*2 >= upper_fib-1) {
798+
799+
// Whenever the above condition is satisfied, we are in binary search mode
800+
lower_fib = hash_cnt;
801+
hash_cnt = (lower_fib+upper_fib)/2;
802+
} else {
803+
// We are in exponential search mode.
804+
const auto old_hash_cnt = hash_cnt;
805+
hash_cnt = lower_fib + (hash_cnt-lower_fib)*2;
806+
if (old_hash_cnt == hash_cnt) hash_cnt++;
807+
}
808+
}
809+
hash_prev = cur_hash_cnt;
810+
}
811+
}
812+
635813
bool Counter::gen_rhs()
636814
{
637815
std::uniform_int_distribution<uint32_t> dist{0, 1};

src/counter.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ class Counter {
139139
SparseData sparse_data,
140140
HashesModels* hm
141141
);
142+
void appmc7_one_measurement_count(
143+
int64_t& prev_measure,
144+
const unsigned iter,
145+
SparseData sparse_data,
146+
HashesModels* hm
147+
);
142148
void call_after_parse();
143149
void ban_one(const uint32_t act_var, const vector<lbool>& model);
144150
void check_model(
@@ -174,6 +180,8 @@ class Counter {
174180
uint32_t cnf_dump_no = 0;
175181
vector<vector<Lit>> cls_in_solver; // needed for accurate dumping
176182
vector<pair<vector<Lit>, bool>> xors_in_solver; // needed for accurate dumping
183+
double alpha = -1;
184+
double beta = -1;
177185
};
178186

179187
}

0 commit comments

Comments
 (0)