@@ -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+
635813bool Counter::gen_rhs ()
636814{
637815 std::uniform_int_distribution<uint32_t > dist{0 , 1 };
0 commit comments