Skip to content

Elite Archive (CPU)

The MAP-Elites survey over tree space. run_archive fills the archive niche by niche and halts on discovery saturation. EliteArchive holds the elites and renders coordinates, surfaces, and dataframes.

run_archive

run_archive(r1: HifukuTree, r2: HifukuTree, r3: HifukuTree, alignment, model, start_trees, triangle: AnchorTriangle, taxon_table: TaxonTable, cell: float = ARCHIVE_CELL, origin: float = 0.0, n_iters: int = ARCHIVE_N_ITERS, seed: int = 0, sigma_slide: float = DEFAULT_SIGMA_SLIDE, scale_width: float = DEFAULT_SCALE_WIDTH, batch: int = ARCHIVE_BATCH, converge: bool = True, force_full_run: bool = False, gate_window: int = ARCHIVE_GATE_WINDOW, coverage_eps: float = ARCHIVE_COVERAGE_EPS, precision_eps: float = ARCHIVE_PRECISION_EPS, logl_margin: float | None = ARCHIVE_LOGL_MARGIN, logl_floor: float | None = None) -> ArchiveResult

Run a CPU elite-archive survey for one alignment.

The three anchor trees fix the chart. The archive is seeded with the anchors and the start trees, then the survey iterates: select a random occupied niche, vary its elite, evaluate the offspring fitness and niche, and place it. Fitness is the alignment log-likelihood.

The survey runs in batches of batch variations up to n_iters. When converge is set, it halts on discovery saturation: the trailing-window coverage rate (new niches per batch) and precision rate (elite gain per batch as a fraction of the elite range) both fall below their thresholds. Coverage is the crisp signal; precision uses a loose threshold because within-niche refinement never fully stops. force_full_run disables the halt.

Parameters:

Name Type Description Default
r1 HifukuTree

Anchor trees that fix the chart. Used only for CV distances.

required
r2 HifukuTree

Anchor trees that fix the chart. Used only for CV distances.

required
r3 HifukuTree

Anchor trees that fix the chart. Used only for CV distances.

required
alignment

The alignment surveyed and its substitution model.

required
model

The alignment surveyed and its substitution model.

required
start_trees list[HifukuTree]

Extra seed trees, typically the anchors for the surveyed gene.

required
triangle AnchorTriangle

Precomputed anchor geometry, carrying the chart's metric.

required
taxon_table TaxonTable

Shared namespace.

required
cell float

Niche side in barycentric units (the grid is unbounded).

ARCHIVE_CELL
origin float

Grid origin on both axes.

0.0
n_iters int

The survey budget cap in variations.

ARCHIVE_N_ITERS
seed int

RNG seed.

0
sigma_slide float

Proposal widths for the branch moves.

DEFAULT_SIGMA_SLIDE
scale_width float

Proposal widths for the branch moves.

DEFAULT_SIGMA_SLIDE
batch int

Variations per convergence batch.

ARCHIVE_BATCH
converge bool

Halt on discovery saturation when set.

True
force_full_run bool

Run the full budget regardless of the halt.

False
gate_window int

Trailing window (batches) for the halt rates.

ARCHIVE_GATE_WINDOW
coverage_eps float

Halt thresholds on the windowed coverage and precision rates.

ARCHIVE_COVERAGE_EPS
precision_eps float

Halt thresholds on the windowed coverage and precision rates.

ARCHIVE_COVERAGE_EPS
logl_margin float

Relative log-likelihood gate: a variation creates a niche only when its tree is within this many nats of the best tree found. The floor follows the best. Defaults to ARCHIVE_LOGL_MARGIN; pass None for an unbounded survey limited only by the budget.

ARCHIVE_LOGL_MARGIN
logl_floor float

Absolute log-likelihood gate: a variation creates a niche only when its tree scores at least this value. Useful for a common domain across genes. Mutually exclusive with logl_margin.

None

Returns:

Type Description
ArchiveResult

The archive, its per-batch history, and the halt state.

Source code in src/hifuku/archive.py
def run_archive(
    r1: HifukuTree,
    r2: HifukuTree,
    r3: HifukuTree,
    alignment,
    model,
    start_trees,
    triangle: AnchorTriangle,
    taxon_table: TaxonTable,
    cell: float = ARCHIVE_CELL,
    origin: float = 0.0,
    n_iters: int = ARCHIVE_N_ITERS,
    seed: int = 0,
    sigma_slide: float = DEFAULT_SIGMA_SLIDE,
    scale_width: float = DEFAULT_SCALE_WIDTH,
    batch: int = ARCHIVE_BATCH,
    converge: bool = True,
    force_full_run: bool = False,
    gate_window: int = ARCHIVE_GATE_WINDOW,
    coverage_eps: float = ARCHIVE_COVERAGE_EPS,
    precision_eps: float = ARCHIVE_PRECISION_EPS,
    logl_margin: float | None = ARCHIVE_LOGL_MARGIN,
    logl_floor: float | None = None,
) -> ArchiveResult:
    """Run a CPU elite-archive survey for one alignment.

    The three anchor trees fix the chart.  The archive is seeded with the anchors
    and the start trees, then the survey iterates: select a random occupied
    niche, vary its elite, evaluate the offspring fitness and niche, and place
    it.  Fitness is the alignment log-likelihood.

    The survey runs in batches of ``batch`` variations up to ``n_iters``.  When
    ``converge`` is set, it halts on discovery saturation: the trailing-window
    coverage rate (new niches per batch) and precision rate (elite gain per batch
    as a fraction of the elite range) both fall below their thresholds.  Coverage
    is the crisp signal; precision uses a loose threshold because within-niche
    refinement never fully stops.  ``force_full_run`` disables the halt.

    Parameters
    ----------
    r1, r2, r3 : HifukuTree
        Anchor trees that fix the chart.  Used only for CV distances.
    alignment, model
        The alignment surveyed and its substitution model.
    start_trees : list[HifukuTree]
        Extra seed trees, typically the anchors for the surveyed gene.
    triangle : AnchorTriangle
        Precomputed anchor geometry, carrying the chart's metric.
    taxon_table : TaxonTable
        Shared namespace.
    cell : float
        Niche side in barycentric units (the grid is unbounded).
    origin : float
        Grid origin on both axes.
    n_iters : int
        The survey budget cap in variations.
    seed : int
        RNG seed.
    sigma_slide, scale_width : float
        Proposal widths for the branch moves.
    batch : int
        Variations per convergence batch.
    converge : bool
        Halt on discovery saturation when set.
    force_full_run : bool
        Run the full budget regardless of the halt.
    gate_window : int
        Trailing window (batches) for the halt rates.
    coverage_eps, precision_eps : float
        Halt thresholds on the windowed coverage and precision rates.
    logl_margin : float, optional
        Relative log-likelihood gate: a variation creates a niche only when its
        tree is within this many nats of the best tree found.  The floor follows
        the best.  Defaults to ``ARCHIVE_LOGL_MARGIN``; pass None for an unbounded
        survey limited only by the budget.
    logl_floor : float, optional
        Absolute log-likelihood gate: a variation creates a niche only when its
        tree scores at least this value.  Useful for a common domain across
        genes.  Mutually exclusive with ``logl_margin``.

    Returns
    -------
    ArchiveResult
        The archive, its per-batch history, and the halt state.
    """
    if logl_margin is not None and logl_floor is not None:
        raise ValueError("pass logl_margin or logl_floor, not both")
    rng = np.random.default_rng(seed)
    archive = EliteArchive(cell=cell, origin=origin, anchors=triangle,
                           taxon_table=taxon_table, metric=triangle.tree_metric)

    def _floor():
        """Current log-likelihood gate floor, or -inf when the gate is off."""
        if logl_floor is not None:
            return logl_floor
        if logl_margin is not None:
            return archive.best_logL() - logl_margin
        return float("-inf")

    def evaluate_and_place(tree, gate=True):
        """Return (is_new_niche, elite_gain) for a candidate tree.

        When ``gate`` is set and the tree is more than the margin below the best,
        it is not placed, so exploration cannot create a niche outside the
        log-likelihood domain.  Seed trees are placed ungated.
        """
        lam1, lam2, z = locate_tree(tree, taxon_table, triangle)
        key = archive.niche_of(lam1, lam2)
        logL = float(log_likelihood(tree, alignment, model))
        if gate and logL < _floor():
            return (False, 0.0)
        prev = archive.elites.get(key)
        if prev is None:
            archive.place(key, Elite(logL=logL, tree=tree, z=z))
            return (True, 0.0)
        if logL > prev.logL:
            gain = logL - prev.logL
            archive.place(key, Elite(logL=logL, tree=tree, z=z))
            return (False, gain)
        return (False, 0.0)

    for tree in (r1, r2, r3, *start_trees):
        evaluate_and_place(tree, gate=False)
    if archive.n_filled == 0:
        raise ValueError("no seed tree landed on the chart grid")

    n_batches = max(1, n_iters // batch)
    batch_new: list[int] = []
    batch_gain: list[float] = []
    history = [(0, archive.n_filled, archive.best_logL(), np.nan, np.nan)]
    converged = False
    halt_iter = -1
    total = 0

    for _ in range(n_batches):
        n_new = 0
        gain = 0.0
        for _ in range(batch):
            parent = archive.elites[archive.keys[int(rng.integers(len(archive.keys)))]]
            child = _vary(parent.tree, taxon_table, rng, sigma_slide, scale_width)
            if child is None:
                continue
            is_new, g = evaluate_and_place(child)
            n_new += int(is_new)
            gain += g
        total += batch
        batch_new.append(n_new)
        batch_gain.append(gain)

        vals = [e.logL for e in archive.elites.values()]
        elite_range = (max(vals) - min(vals)) or 1.0
        coverage_rate, precision_rate = _windowed_rates(
            batch_new, batch_gain, elite_range, gate_window, archive.n_filled)
        history.append((total, archive.n_filled, archive.best_logL(),
                        coverage_rate, precision_rate))

        if (converge and not force_full_run
                and _survey_converged(coverage_rate, precision_rate,
                                      coverage_eps, precision_eps)):
            converged = True
            halt_iter = total
            break

    return ArchiveResult(
        archive=archive,
        history=np.array(history, dtype=np.float64),
        converged=converged,
        halt_iter=halt_iter,
    )

EliteArchive dataclass

A map from niche to elite over an unbounded 2D chart grid.

A niche is a square cell of side cell, indexed by integer coordinates (i, j) from origin. The grid has no window: any chart point has a niche. Exploration is bounded by the log-likelihood gate in the survey, not by the grid, and the filled niches form the map's footprint.

anchors (an AnchorTriangle), taxon_table, and metric hold the context the archive was built against. The accessors fall back to anchors when their argument is omitted. :meth:locate uses all three to place a new tree in the same frame as the map. A later re-anchoring step can use the anchors to stitch archives on different anchor planes into one frame.

Source code in src/hifuku/archive.py
@dataclass
class EliteArchive:
    """A map from niche to elite over an unbounded 2D chart grid.

    A niche is a square cell of side ``cell``, indexed by integer coordinates
    ``(i, j)`` from ``origin``.  The grid has no window: any chart point has a
    niche.  Exploration is bounded by the log-likelihood gate in the survey, not
    by the grid, and the filled niches form the map's footprint.

    ``anchors`` (an ``AnchorTriangle``), ``taxon_table``, and ``metric`` hold the
    context the archive was built against.  The accessors fall back to ``anchors``
    when their argument is omitted.  :meth:`locate` uses all three to place a new
    tree in the same frame as the map.  A later re-anchoring step can use the
    anchors to stitch archives on different anchor planes into one frame.
    """

    cell: float = ARCHIVE_CELL
    origin: float = 0.0
    anchors: object = field(default=None, repr=False)      # the AnchorTriangle used
    taxon_table: object = field(default=None, repr=False)  # namespace for locate()
    metric: object = field(default=None, repr=False)       # tree metric used
    tree_source: object = field(default=None, repr=False)   # dict (i,j) -> newick, for loaded archives
    elites: dict = field(default_factory=dict)   # (i, j) -> Elite
    keys: list = field(default_factory=list)     # occupied niches, insertion order

    @property
    def n_filled(self) -> int:
        return len(self.elites)

    def tree_at(self, key):
        """Return the elite tree at niche ``key``, parsing its stored newick on
        first access.  For a live archive the tree is already present; for a
        loaded archive it is materialized from ``tree_source`` and cached."""
        elite = self.elites[key]
        if elite.tree is not None:
            return elite.tree
        if self.tree_source is None or key not in self.tree_source:
            raise KeyError(f"no tree available for niche {key}")
        from hifuku.tree_io import read_tree
        t = read_tree(self.tree_source[key], self.taxon_table, from_string=True)
        _set_global_leaf_indices(t, self.taxon_table)
        elite.tree = t
        return t

    def trees(self):
        """Iterate ``((i, j), HifukuTree)`` over all elites, parsing lazily."""
        for key in self.keys:
            yield key, self.tree_at(key)

    def niche_of(self, lam1: float, lam2: float):
        """Return the ``(i, j)`` niche of a chart point.  Unbounded: never None."""
        i = int(np.floor((lam1 - self.origin) / self.cell))
        j = int(np.floor((lam2 - self.origin) / self.cell))
        return (i, j)

    def niche_center(self, key):
        """Chart coordinates ``(lam1, lam2)`` of a niche's center."""
        i, j = key
        return (self.origin + (i + 0.5) * self.cell,
                self.origin + (j + 0.5) * self.cell)

    def place(self, key, elite: Elite) -> bool:
        """Store the elite when its niche is empty or the elite is better."""
        cur = self.elites.get(key)
        if cur is None:
            self.elites[key] = elite
            self.keys.append(key)
            return True
        if elite.logL > cur.logL:
            self.elites[key] = elite
            return True
        return False

    def best_logL(self) -> float:
        return max((e.logL for e in self.elites.values()), default=float("-inf"))

    def index_bounds(self):
        """``(i_min, i_max, j_min, j_max)`` over filled niches, or None if empty."""
        if not self.elites:
            return None
        iis = [k[0] for k in self.elites]
        jjs = [k[1] for k in self.elites]
        return (min(iis), max(iis), min(jjs), max(jjs))

    def filled_bounds(self):
        """Chart bounding box ``(lam1_lo, lam1_hi, lam2_lo, lam2_hi)`` of the
        filled niches, or None if empty."""
        b = self.index_bounds()
        if b is None:
            return None
        i0, i1, j0, j1 = b
        return (self.origin + i0 * self.cell, self.origin + (i1 + 1) * self.cell,
                self.origin + j0 * self.cell, self.origin + (j1 + 1) * self.cell)

    def elevation_grid(self) -> np.ma.MaskedArray:
        """Elite log-likelihood over the filled bounding box, a masked array."""
        b = self.index_bounds()
        if b is None:
            return np.ma.masked_all((1, 1))
        i0, i1, j0, j1 = b
        g = np.full((i1 - i0 + 1, j1 - j0 + 1), np.nan)
        for (i, j), e in self.elites.items():
            g[i - i0, j - j0] = e.logL
        return np.ma.masked_invalid(g)

    # -- accessors for downstream analysis and plotting -------------------
    # hifuku computes; the caller plots.  These return plain arrays and
    # dataframes in barycentric or Cartesian coordinates.

    def points(self, coords: str = "barycentric", anchors=None):
        """Scattered elite points as ``(x, y, logL, z)`` arrays.

        ``coords="barycentric"`` returns the niche centers ``(lam1, lam2)``;
        ``coords="cartesian"`` maps them through the anchor triangle into the
        Euclidean CBS plane.  ``anchors`` defaults to the archive's stored
        ``anchors``; a cartesian request with neither is an error.
        """
        from hifuku.utils import barycentric_to_cartesian
        if anchors is None:
            anchors = self.anchors
        keys = list(self.elites)
        lam1 = np.array([self.origin + (k[0] + 0.5) * self.cell for k in keys])
        lam2 = np.array([self.origin + (k[1] + 0.5) * self.cell for k in keys])
        logL = np.array([self.elites[k].logL for k in keys])
        z = np.array([self.elites[k].z for k in keys])
        if coords == "barycentric":
            return lam1, lam2, logL, z
        if coords == "cartesian":
            if anchors is None:
                raise ValueError("cartesian coords require anchors")
            x, y = barycentric_to_cartesian(lam1, lam2, anchors)
            return x, y, logL, z
        raise ValueError(f"coords must be 'barycentric' or 'cartesian', got {coords!r}")

    def records(self, coords: str = "barycentric", anchors=None) -> np.ndarray:
        """Scattered elevation records as an ``(N, 3)`` array.

        Each row is one elite ``(coord1, coord2, logL)``, in barycentric
        ``(lam1, lam2, logL)`` or Cartesian ``(x, y, logL)`` coordinates per
        ``coords``.  Unlike :meth:`surface`, this does no interpolation; it hands
        the raw scattered records to a plotting utility that triangulates them,
        for example ``ax.tricontourf(*archive.records(coords="cartesian",
        anchors=tri).T)``.
        """
        c1, c2, logL, _z = self.points(coords=coords, anchors=anchors)
        return np.column_stack([c1, c2, logL])

    def locate(self, tree, coords: str = "barycentric"):
        """Return the chart coordinates of a tree as ``(x, y, z)``.

        This method places ``tree`` in the same frame as the map.  It uses the
        archive's own anchors, taxon table, and metric.  A feature tree, such as
        the maximum-likelihood tree or a ufboot replicate, lands where it belongs
        on the map.  ``coords`` is "barycentric" or "cartesian".  ``z`` is the
        out-of-plane residual.
        """
        if self.anchors is None or self.taxon_table is None:
            raise ValueError(
                "archive lacks anchors or taxon_table; build it with "
                "run_archive or run_archive_gpu"
            )
        return locate_tree(tree, self.taxon_table, self.anchors, coords)

    def frame(self, anchors=None, backend: str = "pandas"):
        """Tidy dataframe of the elites (niche indices, coordinates, logL, z).

        Includes Cartesian ``x``, ``y`` columns when ``anchors`` is given or the
        archive has stored ``anchors``.
        """
        from hifuku.utils import elite_frame
        if anchors is None:
            anchors = self.anchors
        return elite_frame(self, anchors=anchors, backend=backend)

    def surface(self, n: int = 200, coords: str = "barycentric",
                anchors=None, radius=None, z_scale=None):
        """Interpolated elevation surface as ``(X, Y, Z)`` meshgrids.

        Shepard-interpolates the elite log-likelihoods over a regular ``n`` by
        ``n`` grid spanning the filled region, in barycentric or Cartesian
        coordinates.  ``Z`` is masked beyond the support radius.  The result is a
        plain gridded field: it drops into ``ax.contourf(X, Y, Z)``,
        ``ax.pcolormesh(X, Y, Z)``, ``ax.plot_surface(X, Y, Z)``, and the like.
        """
        from hifuku.utils import shepard_grid
        if self.n_filled == 0:
            raise ValueError("empty archive")
        px, py, logL, z = self.points(coords=coords, anchors=anchors)
        xs = np.linspace(px.min(), px.max(), n)
        ys = np.linspace(py.min(), py.max(), n)
        X, Y = np.meshgrid(xs, ys)
        Z = shepard_grid(np.column_stack([px, py]), logL, X, Y,
                         radius=radius, z=(z if z_scale else None), z_scale=z_scale)
        return X, Y, Z

tree_at

tree_at(key)

Return the elite tree at niche key, parsing its stored newick on first access. For a live archive the tree is already present; for a loaded archive it is materialized from tree_source and cached.

Source code in src/hifuku/archive.py
def tree_at(self, key):
    """Return the elite tree at niche ``key``, parsing its stored newick on
    first access.  For a live archive the tree is already present; for a
    loaded archive it is materialized from ``tree_source`` and cached."""
    elite = self.elites[key]
    if elite.tree is not None:
        return elite.tree
    if self.tree_source is None or key not in self.tree_source:
        raise KeyError(f"no tree available for niche {key}")
    from hifuku.tree_io import read_tree
    t = read_tree(self.tree_source[key], self.taxon_table, from_string=True)
    _set_global_leaf_indices(t, self.taxon_table)
    elite.tree = t
    return t

trees

trees()

Iterate ((i, j), HifukuTree) over all elites, parsing lazily.

Source code in src/hifuku/archive.py
def trees(self):
    """Iterate ``((i, j), HifukuTree)`` over all elites, parsing lazily."""
    for key in self.keys:
        yield key, self.tree_at(key)

niche_of

niche_of(lam1: float, lam2: float)

Return the (i, j) niche of a chart point. Unbounded: never None.

Source code in src/hifuku/archive.py
def niche_of(self, lam1: float, lam2: float):
    """Return the ``(i, j)`` niche of a chart point.  Unbounded: never None."""
    i = int(np.floor((lam1 - self.origin) / self.cell))
    j = int(np.floor((lam2 - self.origin) / self.cell))
    return (i, j)

niche_center

niche_center(key)

Chart coordinates (lam1, lam2) of a niche's center.

Source code in src/hifuku/archive.py
def niche_center(self, key):
    """Chart coordinates ``(lam1, lam2)`` of a niche's center."""
    i, j = key
    return (self.origin + (i + 0.5) * self.cell,
            self.origin + (j + 0.5) * self.cell)

place

place(key, elite: Elite) -> bool

Store the elite when its niche is empty or the elite is better.

Source code in src/hifuku/archive.py
def place(self, key, elite: Elite) -> bool:
    """Store the elite when its niche is empty or the elite is better."""
    cur = self.elites.get(key)
    if cur is None:
        self.elites[key] = elite
        self.keys.append(key)
        return True
    if elite.logL > cur.logL:
        self.elites[key] = elite
        return True
    return False

index_bounds

index_bounds()

(i_min, i_max, j_min, j_max) over filled niches, or None if empty.

Source code in src/hifuku/archive.py
def index_bounds(self):
    """``(i_min, i_max, j_min, j_max)`` over filled niches, or None if empty."""
    if not self.elites:
        return None
    iis = [k[0] for k in self.elites]
    jjs = [k[1] for k in self.elites]
    return (min(iis), max(iis), min(jjs), max(jjs))

filled_bounds

filled_bounds()

Chart bounding box (lam1_lo, lam1_hi, lam2_lo, lam2_hi) of the filled niches, or None if empty.

Source code in src/hifuku/archive.py
def filled_bounds(self):
    """Chart bounding box ``(lam1_lo, lam1_hi, lam2_lo, lam2_hi)`` of the
    filled niches, or None if empty."""
    b = self.index_bounds()
    if b is None:
        return None
    i0, i1, j0, j1 = b
    return (self.origin + i0 * self.cell, self.origin + (i1 + 1) * self.cell,
            self.origin + j0 * self.cell, self.origin + (j1 + 1) * self.cell)

elevation_grid

elevation_grid() -> np.ma.MaskedArray

Elite log-likelihood over the filled bounding box, a masked array.

Source code in src/hifuku/archive.py
def elevation_grid(self) -> np.ma.MaskedArray:
    """Elite log-likelihood over the filled bounding box, a masked array."""
    b = self.index_bounds()
    if b is None:
        return np.ma.masked_all((1, 1))
    i0, i1, j0, j1 = b
    g = np.full((i1 - i0 + 1, j1 - j0 + 1), np.nan)
    for (i, j), e in self.elites.items():
        g[i - i0, j - j0] = e.logL
    return np.ma.masked_invalid(g)

points

points(coords: str = 'barycentric', anchors=None)

Scattered elite points as (x, y, logL, z) arrays.

coords="barycentric" returns the niche centers (lam1, lam2); coords="cartesian" maps them through the anchor triangle into the Euclidean CBS plane. anchors defaults to the archive's stored anchors; a cartesian request with neither is an error.

Source code in src/hifuku/archive.py
def points(self, coords: str = "barycentric", anchors=None):
    """Scattered elite points as ``(x, y, logL, z)`` arrays.

    ``coords="barycentric"`` returns the niche centers ``(lam1, lam2)``;
    ``coords="cartesian"`` maps them through the anchor triangle into the
    Euclidean CBS plane.  ``anchors`` defaults to the archive's stored
    ``anchors``; a cartesian request with neither is an error.
    """
    from hifuku.utils import barycentric_to_cartesian
    if anchors is None:
        anchors = self.anchors
    keys = list(self.elites)
    lam1 = np.array([self.origin + (k[0] + 0.5) * self.cell for k in keys])
    lam2 = np.array([self.origin + (k[1] + 0.5) * self.cell for k in keys])
    logL = np.array([self.elites[k].logL for k in keys])
    z = np.array([self.elites[k].z for k in keys])
    if coords == "barycentric":
        return lam1, lam2, logL, z
    if coords == "cartesian":
        if anchors is None:
            raise ValueError("cartesian coords require anchors")
        x, y = barycentric_to_cartesian(lam1, lam2, anchors)
        return x, y, logL, z
    raise ValueError(f"coords must be 'barycentric' or 'cartesian', got {coords!r}")

records

records(coords: str = 'barycentric', anchors=None) -> np.ndarray

Scattered elevation records as an (N, 3) array.

Each row is one elite (coord1, coord2, logL), in barycentric (lam1, lam2, logL) or Cartesian (x, y, logL) coordinates per coords. Unlike :meth:surface, this does no interpolation; it hands the raw scattered records to a plotting utility that triangulates them, for example ax.tricontourf(*archive.records(coords="cartesian", anchors=tri).T).

Source code in src/hifuku/archive.py
def records(self, coords: str = "barycentric", anchors=None) -> np.ndarray:
    """Scattered elevation records as an ``(N, 3)`` array.

    Each row is one elite ``(coord1, coord2, logL)``, in barycentric
    ``(lam1, lam2, logL)`` or Cartesian ``(x, y, logL)`` coordinates per
    ``coords``.  Unlike :meth:`surface`, this does no interpolation; it hands
    the raw scattered records to a plotting utility that triangulates them,
    for example ``ax.tricontourf(*archive.records(coords="cartesian",
    anchors=tri).T)``.
    """
    c1, c2, logL, _z = self.points(coords=coords, anchors=anchors)
    return np.column_stack([c1, c2, logL])

locate

locate(tree, coords: str = 'barycentric')

Return the chart coordinates of a tree as (x, y, z).

This method places tree in the same frame as the map. It uses the archive's own anchors, taxon table, and metric. A feature tree, such as the maximum-likelihood tree or a ufboot replicate, lands where it belongs on the map. coords is "barycentric" or "cartesian". z is the out-of-plane residual.

Source code in src/hifuku/archive.py
def locate(self, tree, coords: str = "barycentric"):
    """Return the chart coordinates of a tree as ``(x, y, z)``.

    This method places ``tree`` in the same frame as the map.  It uses the
    archive's own anchors, taxon table, and metric.  A feature tree, such as
    the maximum-likelihood tree or a ufboot replicate, lands where it belongs
    on the map.  ``coords`` is "barycentric" or "cartesian".  ``z`` is the
    out-of-plane residual.
    """
    if self.anchors is None or self.taxon_table is None:
        raise ValueError(
            "archive lacks anchors or taxon_table; build it with "
            "run_archive or run_archive_gpu"
        )
    return locate_tree(tree, self.taxon_table, self.anchors, coords)

frame

frame(anchors=None, backend: str = 'pandas')

Tidy dataframe of the elites (niche indices, coordinates, logL, z).

Includes Cartesian x, y columns when anchors is given or the archive has stored anchors.

Source code in src/hifuku/archive.py
def frame(self, anchors=None, backend: str = "pandas"):
    """Tidy dataframe of the elites (niche indices, coordinates, logL, z).

    Includes Cartesian ``x``, ``y`` columns when ``anchors`` is given or the
    archive has stored ``anchors``.
    """
    from hifuku.utils import elite_frame
    if anchors is None:
        anchors = self.anchors
    return elite_frame(self, anchors=anchors, backend=backend)

surface

surface(n: int = 200, coords: str = 'barycentric', anchors=None, radius=None, z_scale=None)

Interpolated elevation surface as (X, Y, Z) meshgrids.

Shepard-interpolates the elite log-likelihoods over a regular n by n grid spanning the filled region, in barycentric or Cartesian coordinates. Z is masked beyond the support radius. The result is a plain gridded field: it drops into ax.contourf(X, Y, Z), ax.pcolormesh(X, Y, Z), ax.plot_surface(X, Y, Z), and the like.

Source code in src/hifuku/archive.py
def surface(self, n: int = 200, coords: str = "barycentric",
            anchors=None, radius=None, z_scale=None):
    """Interpolated elevation surface as ``(X, Y, Z)`` meshgrids.

    Shepard-interpolates the elite log-likelihoods over a regular ``n`` by
    ``n`` grid spanning the filled region, in barycentric or Cartesian
    coordinates.  ``Z`` is masked beyond the support radius.  The result is a
    plain gridded field: it drops into ``ax.contourf(X, Y, Z)``,
    ``ax.pcolormesh(X, Y, Z)``, ``ax.plot_surface(X, Y, Z)``, and the like.
    """
    from hifuku.utils import shepard_grid
    if self.n_filled == 0:
        raise ValueError("empty archive")
    px, py, logL, z = self.points(coords=coords, anchors=anchors)
    xs = np.linspace(px.min(), px.max(), n)
    ys = np.linspace(py.min(), py.max(), n)
    X, Y = np.meshgrid(xs, ys)
    Z = shepard_grid(np.column_stack([px, py]), logL, X, Y,
                     radius=radius, z=(z if z_scale else None), z_scale=z_scale)
    return X, Y, Z

locate_tree

locate_tree(tree, taxon_table, triangle, coords='barycentric')

Return the chart coordinates of a tree as (x, y, z).

This function aligns the leaves of the tree to taxon_table. It measures the distance from the tree to each anchor of triangle with the chart's own metric, then solves for the chart position.

coords="barycentric" returns (lambda1, lambda2, z). coords="cartesian" returns the point in the Euclidean metric plane. z is the out-of-plane residual. It measures how far the tree sits off the anchor plane.

triangle provides the anchor trees, their embedded geometry, and the metric. The chart owns its metric so a tree cannot be placed under a metric inconsistent with the chart it lands on.

Source code in src/hifuku/archive.py
def locate_tree(tree, taxon_table, triangle, coords="barycentric"):
    """Return the chart coordinates of a tree as ``(x, y, z)``.

    This function aligns the leaves of the tree to ``taxon_table``.  It measures
    the distance from the tree to each anchor of ``triangle`` with the chart's
    own metric, then solves for the chart position.

    ``coords="barycentric"`` returns ``(lambda1, lambda2, z)``.
    ``coords="cartesian"`` returns the point in the Euclidean metric plane.
    ``z`` is the out-of-plane residual.  It measures how far the tree sits off
    the anchor plane.

    ``triangle`` provides the anchor trees, their embedded geometry, and the
    metric.  The chart owns its metric so a tree cannot be placed under a metric
    inconsistent with the chart it lands on.
    """
    from hifuku.utils import barycentric_to_cartesian
    metric = triangle.tree_metric
    anchors = (triangle.r1, triangle.r2, triangle.r3)
    _set_global_leaf_indices(tree, taxon_table)
    for a in anchors:
        _set_global_leaf_indices(a, taxon_table)
    s = [float(metric(tree, a)[0]) for a in anchors]
    lam, z = barycentric(s[0], s[1], s[2], triangle)
    lam1, lam2 = float(lam[0]), float(lam[1])
    if coords == "barycentric":
        return lam1, lam2, float(z)
    if coords == "cartesian":
        x, y = barycentric_to_cartesian(lam1, lam2, triangle)
        return float(x), float(y), float(z)
    raise ValueError(f"coords must be 'barycentric' or 'cartesian', got {coords!r}")