Skip to content

Open the original notebook on GitHub

Powerfit reports and record exports

This notebook focuses on the plain-record and nested-report helpers around low-level fits, realized-pair matching, and the self-consistent active-set solver.

import numpy as np

import pyvoro2 as pv
import pyvoro2.inverse as inverse
import pyvoro2.inverse.separator as separator

1) Resolve a small candidate set

We use explicit integer ids so that exported rows already carry the labels that downstream code wants to show.

points = np.array(
    [
        [0.0, 0.0, 0.0],
        [2.0, 0.0, 0.0],
        [4.0, 0.0, 0.0],
    ],
    dtype=float,
)
ids = np.array([100, 101, 102], dtype=int)
box = pv.Box(((-1.0, 5.0), (-2.0, 2.0), (-2.0, 2.0)))

observations = inverse.resolve_separator_observations(
    points,
    [(0, 1, 0.35), (1, 2, 0.55), (0, 2, 0.50)],
    measurement="fraction",
    domain=box,
    ids=ids,
)
observations.to_records(use_ids=True)
Output

({'constraint_index': 0,
  'site_i': 100,
  'site_j': 101,
  'shift': (0, 0, 0),
  'target': 0.35,
  'confidence': 1.0,
  'measurement': 'fraction',
  'distance': 2.0,
  'target_fraction': 0.35,
  'target_position': 0.7,
  'input_index': 0,
  'explicit_shift': False},
 {'constraint_index': 1,
  'site_i': 101,
  'site_j': 102,
  'shift': (0, 0, 0),
  'target': 0.55,
  'confidence': 1.0,
  'measurement': 'fraction',
  'distance': 2.0,
  'target_fraction': 0.55,
  'target_position': 1.1,
  'input_index': 1,
  'explicit_shift': False},
 {'constraint_index': 2,
  'site_i': 100,
  'site_j': 102,
  'shift': (0, 0, 0),
  'target': 0.5,
  'confidence': 1.0,
  'measurement': 'fraction',
  'distance': 4.0,
  'target_fraction': 0.5,
  'target_position': 2.0,
  'input_index': 2,
  'explicit_shift': False})

2) Fit power weights and export low-level reports

model = separator.FitModel(
    mismatch=separator.SquaredLoss(),
    feasible=separator.Interval(0.0, 1.0),
    penalties=(
        separator.ExponentialBoundaryPenalty(
            lower=0.0,
            upper=1.0,
            margin=0.05,
            strength=0.2,
            tau=0.02,
        ),
    ),
)

fit = inverse.fit_weights_from_separators(
    points,
    observations,
    model=model,
)

fit_rows = fit.to_records(observations, use_ids=True)
fit_report = fit.to_report(observations, use_ids=True)
fit_report["summary"]

fit_report["weight_shift"]
Output

0.0

3) Check realized pairs against the actual power tessellation

realized = separator.match_realized_pairs(
    points,
    domain=box,
    weights=fit.state.mathematical_weights,
    constraints=observations,
    return_boundary_measure=True,
    return_tessellation_diagnostics=True,
)

realized_rows = realized.to_records(observations, use_ids=True)
realized_report = realized.to_report(observations, use_ids=True)
realized_report["summary"]
Output

{'n_constraints': 3,
 'n_realized': 2,
 'n_same_shift': 2,
 'n_other_shift': 0,
 'n_unrealized': 1,
 'n_unaccounted_pairs': 0}

4) Run the self-consistent active-set solver

Final-state vs optimization-path reports

solve_report["connectivity"] and solve_report["realized"] describe the final returned solution. solve_report["path_summary"] and the optional history rows capture transient disconnectivity or candidate-absent realized pairs that occurred during the outer iterations.

result = separator.solve_self_consistent_power_weights(
    points,
    observations,
    domain=box,
    model=model,
    options=separator.ActiveSetOptions(
        add_after=1,
        drop_after=2,
        relax=0.5,
        max_iter=12,
        cycle_window=6,
    ),
    return_history=True,
    return_boundary_measure=True,
    return_tessellation_diagnostics=True,
)

result_rows = result.to_records(use_ids=True)
solve_report = result.to_report(use_ids=True)
solve_report["summary"]

solve_report["path_summary"]
Output

{'n_iterations': 12,
 'ever_fit_active_graph_disconnected': False,
 'ever_fit_active_effective_graph_disconnected': False,
 'ever_fit_active_offsets_unidentified_by_data': False,
 'ever_unaccounted_pairs': False,
 'max_fit_active_graph_components': 1,
 'max_fit_active_effective_graph_components': 1,
 'max_n_unaccounted_pairs': 0,
 'first_fit_active_graph_disconnected_iter': None,
 'first_fit_active_effective_graph_disconnected_iter': None,
 'first_unaccounted_pairs_iter': None}

5) Serialize the report bundle

text = separator.dumps_report_json(solve_report, sort_keys=True)
text[:200]
Output

'{\n  "connectivity": {\n    "active_effective_graph": {\n      "connected_components": [\n        [\n          100,\n          101,\n          102\n        ]\n      ],\n      "fully_connected": true,\n      "iso'
The numerical API stays array-oriented, while the report helpers make it easy to hand plain Python dictionaries or rows to downstream packages.