| """Exact graph statistics and candidate-level molecular measurements.""" |
|
|
| import numpy as np |
| from .exact import endpoint_distribution, expected_cost, solve |
|
|
|
|
| def graph_metrics(graph, forward, log_rewards, temperature): |
| """Return exact endpoint TV, joint KL and mean cost on the stored DAG.""" |
| ref = solve(graph, log_rewards, temperature) |
| p = endpoint_distribution(graph, forward) |
| mass = np.zeros(len(graph.nodes)) |
| mass[graph.root_index] = 1.0 |
| joint_kl = 0.0 |
| for u in graph.order: |
| for i in graph.outgoing[u]: |
| f = mass[u] * forward[i] |
| if f > 0: |
| joint_kl += f * np.log(forward[i] / ref.forward[i]) |
| mass[graph.node_index[graph.edges[i].target]] += f |
| endpoint_kl = sum(p[y] * np.log(p[y] / ref.target[y]) for y in p if p[y] > 0) |
| return { |
| "endpoint_tv": 0.5 * sum(abs(p[y] - ref.target[y]) for y in p), |
| "joint_kl": float(joint_kl), |
| "conditional_kl": max(0.0, float(joint_kl - endpoint_kl)), |
| "mean_cost": expected_cost(graph, forward), |
| "conditional_free_energy_gap": temperature |
| * max(0.0, float(joint_kl - endpoint_kl)), |
| } |
|
|
|
|
| def hypervolume_2d(points): |
| """Dominated area for utility points clipped to [0,1]^2, reference (0,0).""" |
| a = np.clip(np.asarray(points, dtype=float), 0, 1) |
| if a.size == 0: |
| return 0.0 |
| a = a[np.argsort(-a[:, 0])] |
| area = 0.0 |
| height = 0.0 |
| for x, y in a: |
| if y > height: |
| area += x * (y - height) |
| height = y |
| return float(area) |
|
|