Chart Selection¶
A chart is three anchor trees embedded in a plane, and a survey runs genes on it. A gene charted on a poor triangle is measured through a badly conditioned coordinate frame, so the choice of charts matters.
Measurement is separated from policy. chart_table measures the whole candidate
space once: the pairwise anchor graph, the quality of every triple, and how flat
each gene sits on each triple. ChartTable.select is one policy over that
table, and a caller who wants another may filter Q, read a fit row, or hand
a chosen set straight to assign_genes.
Measurement needs no survey. Locating a gene's own tree on a candidate chart and reading its out-of-plane residual says how flat that chart is for that gene, and that is a distance calculation.
reconstruction_error scores a candidate set by re-embedding the pairs its
triangles cover and measuring the result against every pair, including the
ones no chart covers. Scoring only the covered pairs would reward a thin set for
having fewer terms.
The selection thresholds default to the values AnchorTriangle.from_anchors
enforces, so a chart this policy chooses is one the survey will accept.
Leeuw & Mair (2009), verified in
docs/theory/algebra/deleeuw2009smacof-majorization.py.
chart_table
¶
Measure every chart a pool of anchors admits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trees
|
sequence of HifukuTree
|
The anchor pool, on one namespace with |
required |
labels
|
sequence of str
|
One label per anchor. |
required |
metric
|
TreeMetric
|
The metric the charts will use. |
required |
genes
|
sequence of str
|
Gene labels to fit. Defaults to the anchor labels, which is the usual case: each alignment supplies both an anchor and a gene to survey. |
None
|
gene_trees
|
sequence of HifukuTree
|
The tree of each gene. Defaults to the anchor trees. |
None
|
Returns:
| Type | Description |
|---|---|
ChartTable
|
|
Source code in src/hifuku/charts.py
ChartTable
dataclass
¶
Every candidate chart a pool of anchors admits, measured.
Attributes:
| Name | Type | Description |
|---|---|---|
labels |
list[str]
|
The |
D |
ndarray
|
|
B |
ndarray
|
|
triples |
ndarray
|
|
Q |
ndarray
|
|
genes |
list[str]
|
The |
fit |
ndarray
|
|
Notes
The triples are canonical rather than a dense (N, N, N) tensor. The
dense form is symmetric under permutation of its three axes and undefined on
the diagonal, so it is six times redundant. That is irrelevant at N = 10
and decides feasibility later: at N = M = 100 the fit array is
800 MB dense against 130 MB canonical. :meth:as_tensor builds the dense
view when N is small enough to slice interactively.
Source code in src/hifuku/charts.py
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | |
as_tensor
¶
The dense (N, N, N) view of Q, for interactive slicing.
Symmetric in its three axes and NaN wherever two indices repeat. Build
this only for a small pool: it holds N ** 3 floats where the
canonical form holds T.
Source code in src/hifuku/charts.py
containing
¶
Indices of the triples that use one anchor, by label or index.
reconstruction_error
¶
How much of the anchor geometry a candidate set of charts keeps.
A set of triangles covers some pairs of anchors and leaves others uncovered. This re-embeds the anchors from the covered pairs alone, then scores that configuration against every pairwise distance, including the ones no chart covers.
Scoring over every pair is the point. Stress sums only over pairs with
a positive weight, so dropping a pair deletes a non-negative term and a
thinner candidate set scores lower for free. That decrease measures the
edge count, not the embedding. The check is in
docs/theory/algebra/deleeuw2009smacof-majorization.py.
This is a set function, not a table column. What a triangle adds
depends on which triangles are already chosen, so it cannot live in
Q or fit.
The score is not monotone in the candidate set. Adding a triangle
usually lowers it and sometimes raises it, by a small amount: the
re-embedding minimizes stress over the retained pairs, while the score
measures stress over every pair, so a new pair changes the function
being minimized and can move the result to a different local minimum.
Measured on a pool of seven anchors, a rise appeared in four of six
random orders, the largest being 4e-3 against scores near 0.1.
:meth:select therefore takes the best candidate each round and stops
on the size of the improvement, rather than assuming each step helps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
triples
|
array-like of int
|
Row indices into :attr: |
required |
Returns:
| Type | Description |
|---|---|
float
|
Normalized error over all pairs, zero when the retained set reproduces the whole geometry. |
Source code in src/hifuku/charts.py
select
¶
Choose a covering set of charts, then add while it pays.
The policy has two stages. Cover every anchor, so that each one can
host a chart, taking the triangle that lowers the reconstruction error
most at each step. Then keep adding triangles while each removes more
than tol of the error.
This is one policy over an inspectable table. A caller who wants
another may filter :attr:Q, read a :attr:fit row, or pass a chosen
set straight to :func:assign_genes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tol
|
float
|
The least error a further chart must remove to earn its place. |
0.01
|
q_min
|
float
|
Discard a triple whose triangle quality falls below this. A triple with no quality at all is always discarded. Defaults to the threshold the survey itself enforces. |
None
|
b_frac_min
|
float
|
Discard a triple with a pair whose length overlap falls below this. Defaults to the threshold the survey itself enforces. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Row indices into :attr: |
Notes
The two defaults are the thresholds AnchorTriangle.from_anchors
applies, so a chart this policy chooses is one the survey will accept.
A looser setting here emits a plan the survey then refuses.
Source code in src/hifuku/charts.py
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | |
assign_genes
¶
Put every gene on the chart it sits flattest on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
ChartTable
|
|
required |
triples
|
array-like of int
|
The chosen charts, as row indices into |
required |
Returns:
| Type | Description |
|---|---|
list[ChartSpec]
|
Ready for :class: |
Source code in src/hifuku/charts.py
smacof
¶
smacof(D, W, dim: int = 2, *, seed: int = 0, iters: int = _SMACOF_ITERS, tol: float = _SMACOF_TOL, history: bool = False)
Weighted multidimensional scaling by majorization.
Minimizes sum_{i<j} w_ij (delta_ij - d_ij(X))^2. A weight of zero drops
a pair, which is the missing-value structure the paper describes and the way
chart selection expresses a candidate set: a pair of anchors that no chosen
triangle covers is simply not fitted.
Weighted stress has no closed form, so each step builds a quadratic
surrogate that sits above stress and touches it at the current
configuration, then jumps to the surrogate's minimum. That minimum is the
Guttman transform X = V^+ B(Y) Y. The sandwich inequality makes every
step non-increasing in stress.
Leeuw & Mair (2009), equations 4 to 14, verified in
docs/theory/algebra/deleeuw2009smacof-majorization.py.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
D
|
ndarray
|
|
required |
W
|
ndarray
|
|
required |
dim
|
int
|
Dimensions of the configuration. |
2
|
seed
|
int
|
Seed for the starting configuration, used when classical scaling cannot supply one. |
0
|
iters
|
int
|
Maximum majorization steps. |
_SMACOF_ITERS
|
tol
|
float
|
Stop when a step lowers stress by less than this. |
_SMACOF_TOL
|
history
|
bool
|
Also return the stress after each step. |
False
|
Returns:
| Type | Description |
|---|---|
ndarray or tuple
|
|
Source code in src/hifuku/charts.py
|