Project: s41467-024-47562-4 | Function: calc_edge_weight (cpp)

system: You are an expert C++ programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow C++ best practices and maintain consistent code style.
Return **only** the complete C++ function code.

user: Complete this C++ function:

**DESCRIPTION:** Calculates edge weights in bubble regions using MEC (Minimum Error Correction) scores for optimal haplotype path selection

**KEY TERMS:** {
  "prob_u": "probability of correct allele call at position u",
  "prob_v": "probability of correct allele call at position v",
  "hap_u": "haplotype assignment at position u",
  "hap_v": "haplotype assignment at position v",
  "allele_u": "allele at position u",
  "allele_v": "allele at position v"
}

**CONTEXT CODE:** 	for (int idx = 0; idx < variants.size(); idx++) {
		if (not variants[idx].flipped) {
			hap1[idx] = (hap1[idx] == '1') ? '0' : '1';
		}
	}
}

double CUT_Bubble::add_logs(double a, double b) {
	double max = std::max(a, b), min = std::min(a, b);
	return ((max-min) >= 15.7) ? max : max + log_sum10_lookup[(int)((max - min) * LOG_SUM10_PCS)];
}

void CUT_Bubble::build_read_variant_graph() {
	for (int r = 0; r < fragments.size(); r++) {
		const auto &f = fragments[r];
		for (int i = 0; i < f.size(); i++) {
			auto &v = variants[f.variant_idx[i]];
			v.read_idx.push_back(r);
			v.allele_idx.push_back(i);
		}
		for (int i = 1; i < f.size(); i++) {
			int node_u = f.variant_idx[i-1], node_v = f.variant_idx[i];
			char allele_u = f.allele_call[i-1], allele_v = f.allele_call[i];
			double prob_u = f.probability[i-1], prob_v = f.probability[i];
			variants[node_u].edges.emplace_back(Edge(node_v, r, allele_u, allele_v, prob_u, prob_v));
			variants[node_v].edges.emplace_back(Edge(node_u, r, allele_v, allele_u, prob_v, prob_u));
		}
	}
	for (auto &v : variants) std::sort(v.edges.begin(), v.edges.end());

	// All fragment and variants belong to a same non-trivial block
}

double CUT_Bubble::calc_fragment_score(const Fragment &f, const char *hap) {
	double prob_RH0 = 0, prob_RH1 = 0;
	for (int i = 0; i < f.size(); i++) {
		if (f.phred_quality[i] < MIN_ALLELE_QUAL) continue;
		double prob_miscall = f.prob_miscall[i]; // Log(p)
		double prob_correct = f.prob_correct[i]; // Log(1-p)
		assert(prob_miscall <= 0 and prob_correct <= 0);
		if (f.allele_call[i] == hap[f.variant_idx[i]]) {
			prob_RH0 += prob_correct; prob_RH1 += prob_miscall;
		} else {
			prob_RH0 += prob_miscall; prob_RH1 += prob_correct;
		}
	}
	return add_logs(prob_RH0, prob_RH1);
}

/** Calculate a edge weight for a given haplotype pair hap_u and hap_v */
static inline double calc_edge_weight(const Edge &k, char hap_u, char hap_v) {
    // MEC scoring: compare haplotype alleles with observed alleles in the edge
    // k.allele_u, k.allele_v are observed alleles
    // hap_u, hap_v are haplotype assignments at positions u and v
    // k.prob_u, k.prob_v are log-probabilities of correct calls

    double prob_RH0 = 0.0; // likelihood if haplotype matches observed alleles
    double prob_RH1 = 0.0; // likelihood if haplotype mismatches observed alleles

    // Position u contribution
    if (hap_u == k.allele_u) {
        prob_RH0 += k.prob_u;             // match: add log-prob correct
        prob_RH1 += std::log10(1.0 - std::pow(10.0, k.prob_u)); // mismatch: add log-prob miscall
    } else {
        prob_RH0 += std::log10(1.0 - std::pow(10.0, k.prob_u)); // mismatch
        prob_RH1 += k.prob_u;             // match to mismatched hap
    }

    // Position v contribution
    if (hap_v == k.allele_v) {
        prob_RH0 += k.prob_v;
        prob_RH1 += std::log10(1.0 - std::pow(10.0, k.prob_v));
    } else {
        prob_RH0 += std::log10(1.0 - std::pow(10.0, k.prob_v));
        prob_RH1 += k.prob_v;
    }

    // Weight is the difference in log-likelihood for matching vs mismatching
    return prob_RH0 - prob_RH1;
}

void CUT_Bubble::init_node_score(const char *hap, int S, int T) {
	for (auto node : {S, T}) {
		if (node < 0) continue; // T might be minus
		for (int i = 0; i < variants[node].size(); i++) {
			auto &f = fragments[variants[node].read_idx[i]];
			int j = variants[node].allele_idx[i]; // The index to allele call on fragment with respect to the node
			if (f.phred_quality[j] < MIN_ALLELE_QUAL) continue;

			double prob_miscall = f.prob_miscall[j];
			double prob_correct = f.prob_correct[j];
			if (node == S) {
				if (hap[S] == f.allele_call[j]) {
					f.prob_RH0 += prob_correct; f.prob_RH1 += prob_miscall;
					f.prob_RC0 += prob_correct; f.prob_RC1 += prob_miscall;
				} else {
					f.prob_RH0 += prob_miscall; f.prob_RH1 += prob_correct;
					f.prob_RC0 += prob_miscall; f.prob_RC1 += prob_correct;
				}
			} else if (node == T) { // T is flipped(cut) compared to S
				if (hap[T] == f.allele_call[j]) {
					f.prob_RH0 += prob_miscall; f.prob_RH1 += prob_correct;
					f.prob_RC0 += prob_miscall; f.prob_RC1 += prob_correct;
				} else {
					f.prob_RH0 += prob_correct; f.prob_RH1 += prob_miscall;
					f.prob_RC0 += prob_correct; f.prob_RC1 += prob_miscall;
				}
			}

			// Update score of every node outside 2 shores(S,T) covered by the fragment
			for (j = 0; j < f.size(); j++) {
				if (f.phred_quality[j] < MIN_ALLELE_QUAL) continue;
				if (f.variant_idx[j] == S or f.variant_idx[j] == T) continue;
				prob_miscall = f.prob_miscall[j];
				prob_correct = f.prob_correct[j];
				double ph0 = f.prob_RH0, ph1 = f.prob_RH1;
				double pc0 = f.prob_RC0, pc1 = f.prob_RC1;
				if (hap[f.variant_idx[j]] == f.allele_call[j]) {
					ph0 += prob_correct; ph1 += prob_miscall; // Add node to S side of cut
					pc0 += prob_miscall; pc1 += prob_correct; // Add node to T side of cut
				} else {
					ph0 += prob_miscall; ph1 += prob_correct; // Allele mismatch so flip probability
					pc0 += prob_correct; pc1 += prob_miscall;
				}

				// old_ll is likelihood of fragment if node is added to S side
				double old_LL = add_logs(ph0, ph1);
				// new_ll is likelihood of fragment if node is added to T side
				double new_LL = add_logs(pc0, pc1);
				variants[f.variant_idx[j]].score += old_LL - new_LL;
			}
		}
	}
}

Max_Heap::Max_Heap(std::vector<Variant> &v, std::vector<int> &e):
		v(v), e(e) {
	for (int i = 0; i < e.size(); i++) v[e[i]].heap_location = i;
	for (int i = (int)e.size() / 2 - 1; i >= 0; i--) heapify(i);
}

void Max_Heap::heapify(int f) {
	int cl = f * 2 + 1;
	int cr = f * 2 + 2;
	int max_idx = f;
	if (cl < e.size() and fabs(v[e[cl]].score) > fabs(v[e[max_idx]].score)) {
		max_idx = cl;
	}
	if (cr < e.size() and fabs(v[e[cr]].score) > fabs(v[e[max_idx]].score)) {
		max_idx = cr;


Complete the function implementation replacing "throw std::runtime_error("Not implemented");".