graintrace.grain_graph_matching#
Match grains across two load steps via graph message passing and neighbor selection.
- class graintrace.grain_graph_matching.GraphGrainMatcher(graph_a, graph_b, output_dir='grain_matching_results', output_prefix='out_')[source]#
Bases:
objectMatch grains between two grain graphs (A and B) and write correspondence results.
- match_grains(message_passing_function=None, neighbor_selection_cost_function=None, message_passing_iter=6, neighbor_selection_param=None, angle_convention='bunge', angle_type='degrees', symmetry='432')[source]#
Run message passing on both graphs, select matches, write and return results.
angle_convention/angle_type/symmetrydescribe the graph node Euler features and are threaded into the default message-passing scheme. Defaults matchVoronoiMeshBuilder.build_graphoutput (Bunge, degrees).
- message_passing(graph, message_passing_function, message_passing_iter=6, use_default=False)[source]#
Propagate node features over graph edges for a fixed number of iterations.
- neighbor_selection(graph_a_features, graph_b_features, neighbor_selection_cost_function, neighbor_selection_param=None)[source]#
Assign each grain in A to a grain in B by iterative cost-minimizing selection.
Each A grain’s
topknearest B grains in feature space are fixed once up front as its candidate set; the iteration only re-scores those. Each round every A grain picks its lowest-cost candidate, and where several A grains claim the same B grain the cheapest claim wins and the others go unmatched that round. The assignment is therefore one-to-one in B but not onto: an A grain can finish unmatched (a_to_b[i] == -1).- Parameters:
graph_a_features –
(Na, d)node-feature tensor for step A.graph_b_features –
(Nb, d)node-feature tensor for step B.neighbor_selection_cost_function –
f(Fa, Fb, neigh_a, neigh_b, a_to_b, i, j, lam) -> float; seedefault_neighbor_selection_cost_function().neighbor_selection_param – Dict with keys
"lambda"(weight of the consistency term),"iterations","topk"(candidates per A grain) and"chunk"(rows percdistblock). A"tolerance"key is accepted and echoed back in the returnedparams, but see the warning below.
- Returns:
Dict with
matches(M, 2),costs(M,),a_to_b(Na,),mean_cost_historyand theparamsactually used.
Warning
"tolerance"is never read as a convergence criterion. The loop always runs the fulliterationscount; there is no check onmean_cost_history, so lowering or raising the tolerance changes nothing but the echoed value. The “Converged before max iterations.” message prints only when a round produces no assignment at all and breaks out, which is a degenerate case rather than convergence. Chooseiterationsby inspectingmean_cost_historyafterwards.
- write_results(result)[source]#
Write matches CSV, per-node A->B mapping, embeddings (.pt), and meta JSON.
Every file name starts with the constructor’s
output_prefix, so two runs sharing anoutput_dirkeep their results apart as long as their prefixes differ. The prefix is used verbatim – the default"out_"carries its own separator – and may be""for bare names.Writes
<prefix>matches.csv,<prefix>a_to_b.csv,<prefix>Fa.pt,<prefix>Fb.ptand<prefix>meta.jsonintooutput_dir.
- static default_message_passing_function(angle_convention='bunge', angle_type='degrees', symmetry='432')[source]#
Return (spec, phi_operator) for the default misorientation message-passing scheme.
The packed node feature is
[X, Y, Z, M]: the three raw centroid coordinates plus one scalar message channelMinitialised to zero. On iterationk = 0the message along an edge is the symmetry-reduced misorientation between the two grains’ orientations; on later iterations it is the Euclidean distance between the two endpoints’ current feature vectors, soMaccumulates a neighbourhood-orientation summary.- Parameters:
angle_convention – Euler convention of the graph’s
Eul0/1/2features.angle_type –
"degrees"or"radians"for those features. The emitted misorientation is in the same unit.symmetry – Point group used to reduce the misorientation.
Warning
The X, Y and Z message channels are hard zeros.
phi_operatorwrites literal zero tensors into the first three columns on every iteration, so positions never propagate along edges – onlyMdoes. TheX, Y, Zentries of a node’s feature vector stay at their initial centroid values for the whole run. Since candidate generation is a Euclideancdistover the full feature vector, and the three position channels are raw micrometres whileMis an angle in degrees, candidate selection is dominated by centroid proximity whenever the grains have moved less than a few degrees. Scale or drop the position channels with a custommessage_passing_functionif you need orientation to lead.
- static default_neighbor_selection_cost_function(Fa, Fb, neigh_a, neigh_b, a_to_b, i, j, lam)[source]#
Default matching cost: feature distance plus a neighbor-consistency term.
For a candidate pair \((i, j)\) the cost is the squared feature distance \(\lVert F^a_i - F^b_j \rVert^2\) plus \(\lambda\) times the mean of \(\psi(F^a_p, F^b_q)\) over the neighbours \(p\) of \(i\) whose current match \(q\) is a neighbour of \(j\), where \(\psi(u, v) = -\lVert u - v \rVert^2\). Lower cost wins. Neighbours whose match is unassigned, or falls outside \(j\)’s neighbourhood, are skipped and do not enter the mean.
Warning
The consistency term carries the opposite sign from the base term. The base term uses \(-\psi\), the consistency term uses \(+\psi\), so the bonus grows with how badly the consistent neighbour pairs match in feature space: a candidate whose matched neighbours are far apart scores better than one whose matched neighbours coincide, and better than one with no consistent neighbours at all. Because the term is also averaged rather than summed, agreeing on many neighbours is worth no more than agreeing on one. This is the shipped behaviour and the results in the repository were produced with it; it is documented here rather than changed. Pass your own
neighbor_selection_cost_functionto override it.