Automatic thresholds for projectability disentanglement (dis_proj_auto) - #650
Conversation
New keywords dis_proj_auto (logical, default .false.) and dis_proj_auto_classes (integer, default 5, >= 3) determine dis_proj_min/dis_proj_max automatically for projectability disentanglement: the per-band projectabilities sum_j |A_nj(k)|^2 are pooled over all bands and k-points, histogrammed into 64 equal-width bins, and split into dis_proj_auto_classes classes by maximising the between-class variance (multi-Otsu); dis_proj_min/max are set to the first/last thresholds. The algorithm follows skimage.filters.threshold_multiotsu (bin-centre thresholds, index-weighted moments, exhaustive ascending search with first-found tie-break), verified term-by-term against the scikit-image source. The pooling is rank-uniform (a_matrix is replicated), so thresholds are identical on all ranks with no communication. dis_proj_auto requires dis_froz_proj = .true. and is incompatible with explicit dis_proj_min/dis_proj_max (input error); a distribution that cannot support the requested number of classes is a fatal error, not a silent fallback. Validation: Fortran thresholds match a pure-python reference of the same specification exactly on synthetic fixtures (classes 3-6, bins 32/64/128) including degenerate and exactly-N-populated-bins cases; graphene PDWF control run is byte-identical to the committed benchmark; auto mode on graphene selects [0.03895, 0.94248] (hand-tuned reference [0.01, 0.85]) with total spread improving 15.80 -> 14.00 A^2; all four input-error paths and the end-to-end degenerate path exit cleanly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The number of resolvable classes is bounded by the number of populated histogram bins: each class needs its own cluster of projectability values, and cuts can only fall in the gaps between clusters. Requesting more classes than clusters (easy to do on small systems - the graphene test populates only 12 of 64 bins) previously died with a fatal error. dis_otsu_thresholds now returns nclasses_eff = min(requested, populated bins) and computes that many classes; dis_windows_proj prints a root-only note whenever the count is reduced, so the adjustment is never silent. The fatal error remains only where thresholds are genuinely undefined: fewer than three populated bins, or all projectabilities equal. Verified: graphene classes=13 reduces to 12 with the printed note and thresholds byte-identical to an explicit classes=12 run; the default classes=5 case is unchanged; synthetic fixtures (including an exactly-3-populated-bins case reducing from 5) match the python reference of the same specification exactly; all-equal and two-cluster distributions still fail loudly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New test testw90_graphene_dis_proj_auto exercises the default dis_proj_auto path on the graphene PDWF system (inputs symlinked from testw90_graphene_pdwf); the benchmark was negative-tested by perturbing a spread and confirming the comparison fails. Document dis_proj_auto and dis_proj_auto_classes in the user guide, parameter tables and parameters.xml, including the adaptive class-count reduction and the orthonormal-projector assumption of projectability disentanglement (previously undocumented; guaranteed by pw2wannier90's default atom_proj_ortho = .true.). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Independent verification against scikit-image 0.25.2 showed two things. First, our moment weighting does not reproduce threshold_multiotsu: the scikit-image lookup table initialises first_moment[0] = prob[0], giving effective bin weights (1,1,2,3,...) rather than the textbook 0-based indices, and the two conventions select different thresholds on about 11% of clustered test distributions. We keep the textbook weights deliberately. Second, an adaptive [min,max] histogram range makes near-degenerate distributions (e.g. a manifold with no entanglement, projectabilities 0.99 +/- 1e-6) stretch over several bins and yield meaningless thresholds that slice inside a single physical cluster. Since projectability has an absolute scale, histogram over a fixed [0,1] range instead: bins acquire a physical resolution (1/64 ~ 0.016), values closer than one bin are deliberately indistinguishable, and the near-degenerate case now collapses into one populated bin and stops with the existing fewer-than-three-clusters error. Both deviations from scikit-image are documented in the code and the user guide, which no longer claims equivalence with threshold_multiotsu. The graphene thresholds move to the absolute bin grid ([0.03906, 0.94531], previously [0.03895, 0.94248]); the test benchmark is regenerated accordingly and re-verified, including the negative test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
otsu_lower_bound, otsu_upper_bound and otsu_nbins replace the hardwired 0/1/64 and are threaded through dis_otsu_thresholds as arguments, so exposing them as input keywords later only requires parser wiring. Not user-visible; graphene benchmarks unchanged (1/64 is a power of two, so the arithmetic is bit-identical). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The omegaI and omegaOD of testw90_graphene_dis_proj_auto differ from the serial benchmark by ~1e-6 in parallel runs, right at the default tolerance. Use per-test tolerances of 5e-6 for these quantities. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eacba87 to
e4aa872
Compare
…proj_max The disentangle summary now prints the Disentangled window upper bound as dis_manifold%proj_max (commit b4a6d80) instead of a hardcoded 1.00000. Both committed benchmarks still showed "Disentangled: ... to 1.00000", internally inconsistent with their own "Frozen: proj_max to 1.00000" line (dis_proj_auto: 0.94531; pdwf: 0.85000). Updated the one stale print line in each. Verified via testcode that all parser-compared physics (omegaI/omegaD/omegaOD, final centres/spreads) is byte-identical to the old benchmarks; both tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The exhaustive multi-Otsu search enumerates C(nbins-1, classes-1) tuples (nbins = 64): classes = 8 is ~5.5e8 evaluations (seconds-to-minutes), classes = 9 ~3.9e9, classes = 10 ~2.4e10, and classes ~16 effectively hangs. readwrite only checked classes >= 3, so a large value silently hung the run. Reject classes > 8 at parse time with an io_error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks @elinscott, very nice results!
Yes I'd like to see it set as default, making things more automated is better. |
There was a problem hiding this comment.
I think here it should be 1.0 not 0.85? Because all states with projectability between 0.01 and 1.0 participate into disentanglement.
There was a problem hiding this comment.
Good catch -- fixed
The projectability-windows banner again reports the disentangled window as [dis_proj_min, 1.0], as on develop: frozen high-projectability states are part of the disentanglement window (PR wannier-developers#650 review). The testw90_graphene_pdwf benchmark is restored to the develop version and the testw90_graphene_dis_proj_auto benchmark regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
b97f74a to
12d4095
Compare
- dis_proj_min/dis_proj_max no longer have default values: they are either set explicitly or determined automatically - explicit thresholds disable the (defaulted) automatic thresholding; combining them with an explicit dis_proj_auto = .true. is an input error - dis_froz_proj with automatic thresholding disabled requires both thresholds; providing only one of them is an input error - dis_proj_auto without dis_froz_proj is no longer an error (it simply has no effect) - dis_proj_auto_classes is renamed to dis_proj_auto_num_classes, and the local flags recording which projectability keywords were provided are given descriptive names - documentation, parameter tables and CHANGELOG updated; the CHANGELOG entry is shortened to match the style of existing entries Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
12d4095 to
b773377
Compare
Thanks @qiaojunfeng ! OK, if we're going to enable this by default then I have modified the keyword precedence so that this change gives minimal surprising behaviour:
Other changes:
|
The looser omegaI/omegaOD tolerances were the only inline tolerance override in jobconfig; they now live in userconfig as a named program section, as for the other tests that need non-default tolerances. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Thanks for the explanation, all looks good! |
Motivation
Projectability disentanglement (Qiao et al. 2023) selects and freezes states based on the per-band projectabilities$p_{n\mathbf{k}} = \sum_j |A_{nj}(\mathbf{k})|^2$ , controlled by
dis_proj_minanddis_proj_max. Good values for these thresholds are far less material-dependent than those for energy-based disentanglement, permitting high-throughput Wannierisation, but there is still some dependence that can't be ignored, especially when the PAO basis is not used and consequently projectabilities are lower than they would be otherwise (seepw2wannier90'satom_proj_ext). Default thresholds can give rise to concrete failures (see e.g. Supplementary Figure 20 of the original paper; replicated below).This PR adds an option to determine both thresholds automatically at runtime from the projectability distribution itself.
What this PR does
Setting
pools the projectabilities of all bands and k-points, histograms them into 64 equal-width bins, and splits the distribution into
dis_proj_auto_classesclasses (default 5, minimum 3) by multi-level Otsu thresholding, i.e. the class boundaries that maximise the between-class variance.dis_proj_minis set to the lowest threshold anddis_proj_maxto the highest. More classes widen the disentanglement window between the two thresholds, sodis_proj_auto_classesbecomes the one remaining tunable parameter (larger = fewer bands frozen and discarded; smaller = more).Design decisions
dis_proj_autorequiresdis_froz_proj = .true.and is an input error in combination with explicitdis_proj_min/dis_proj_max.Examples
Graphene
OpenMX s+p+s atomic projectors on C, comparing....
dis_proj_minand_max("default")dis_proj_auto = True("auto")Bulk Si
OpenMX s+p+d atomic projectors on Si
With dis_froz_max = CBM + 2 eV
dis_proj_auto_classes=7Without energy disentanglement
N.B. the left-hand-panel is the fail-case given in the Supp. Mat. of Qiao et al. 2023. In that case the authors painfully created a new pseudopotential in order to get a reliable Wannierization. Here instead we get a reliable Wannierization automatically.
dis_proj_auto_classes=7Takeaways
dis_proj_autois robustValidation
testw90_graphene_pdwf, the automatic thresholds come out as[0.039, 0.942](the hand-tuned values in the test are[0.01, 0.85]), and the total spread improves from 15.80 to 14.00 Ų.dis_froz_proj, invalid class count, conflicting explicit thresholds, degenerate distribution) exit with clear messages.Tests and documentation
testw90_graphene_dis_proj_auto(inputs symlinked fromtestw90_graphene_pdwf)dis_proj_autoanddis_proj_auto_classesdocumented in the user guide, parameter tables andparameters.xml.Decision for the maintainers
Do we set
dis_proj_autotoTrueby default? As we have seen, the algorithm outperforms the default thresholds.AI Declaration
I used Claude to help me write and test this code. I have reviewed the code myself.