pyfracval.quality ================= .. py:module:: pyfracval.quality .. autoapi-nested-parse:: Final-aggregate quality measurement. Answers two questions about a *finished* aggregate that nothing else in the pipeline asks: 1. **Is the geometry actually valid?** ``success`` in the catalog means "PCA+CCA reached the requested particle count", which says nothing about whether the saved coordinates are overlap-free. Clusters marked successful have been found carrying severe residual overlap (see docs/source/catalog_overlap_leak.md). Measuring the finished geometry once, at the end, closes that whole class of leak structurally rather than by chasing individual entry points. 2. **Did it land on the scaling law?** The per-merge Gamma machinery *aims* at the prescribed Df/kf, but the pairing relaxation factor and the adaptive overlap tolerance both accept merges slightly off-target, and nothing measures the result. Cheap enough to run unconditionally: one O(N^2) pass over a few hundred to a few thousand particles, once per aggregate, against a generation that took seconds to minutes. Module Contents --------------- .. py:function:: max_self_overlap(coords, radii, min_overlap = 1e-12) Largest and total count of residual overlaps within one aggregate. The overlap fraction uses the same normalization as the sticking tolerance ``tol_ov`` - ``((r_i + r_j) - d_ij) / (r_i + r_j)`` - so the result is directly comparable against it. (Note ``densify._self_overlap_pairs_kernel`` normalizes by ``min(r_i, r_j)`` instead, for its own push-apart geometry; the two numbers are not interchangeable.) :param min_overlap: Ignore overlaps at or below this. Particles are placed in point contact, so a correctly-built aggregate has many pairs sitting at ``|overlap| ~ 1e-15`` purely from floating-point round-off in the rigid transforms. Counting those would report every healthy aggregate as having dozens of "overlapping pairs". :type min_overlap: float :returns: ``(max_overlap_fraction, n_overlapping_pairs)``. The fraction is 0.0 when nothing overlaps beyond ``min_overlap``. :rtype: tuple[float, int] .. py:function:: compute_aggregate_quality(coords, radii, df, kf, tol_ov, n_particles_dropped = 0, densities = None) Measure a finished aggregate against what was asked for. :param coords: The final aggregate geometry. :type coords: np.ndarray :param radii: The final aggregate geometry. :type radii: np.ndarray :param df: The prescribed fractal parameters, for the scaling-law target. :type df: float :param kf: The prescribed fractal parameters, for the scaling-law target. :type kf: float :param tol_ov: The overlap tolerance the run was generated under; used to decide ``overlap_ok``. :type tol_ov: float :param n_particles_dropped: Particles removed by drop-rescue, passed through for the record. :type n_particles_dropped: int :returns: ``max_residual_overlap``, ``n_overlapping_pairs``, ``overlap_ok``, ``measured_rg`` (paper Eq. 4), ``scaling_law_rg``, ``rg_error_pct``, ``n_particles``, ``n_particles_dropped``. :rtype: dict