Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Fix non-deterministic layout order on macOS
Add ext_idx tie-breaking to vertex comparators and component sorting
in AttachmentLayout to ensure consistent layout results across platforms.
Also add energy tie-breaking by ascending permutation order.
  • Loading branch information
even1024 committed Apr 30, 2026
commit 9a757d0d798be6b9451a3dd9aa9b2a05842d2965
61 changes: 59 additions & 2 deletions core/indigo-core/layout/src/attachment_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
***************************************************************************/

#include "layout/attachment_layout.h"
#include <climits>

using namespace indigo;

Expand Down Expand Up @@ -77,6 +78,38 @@ AttachmentLayout::AttachmentLayout(const BiconnectedDecomposer& bc_decom, const
}
}

// Sort undrawn components by non-source ext_idx for deterministic cross-platform order
int n_undrawn = _attached_bc.size() - 1;
if (n_undrawn > 1)
{
for (i = 0; i < n_undrawn - 1; i++)
{
for (int j = i + 1; j < n_undrawn; j++)
{

auto min_ext_idx = [&](int comp_slot) -> int {
const MoleculeLayoutGraph& comp = *bc_components[_attached_bc[comp_slot]];
int min_idx = INT_MAX;
for (int k = comp.vertexBegin(); k < comp.vertexEnd(); k = comp.vertexNext(k))
{
int ext = comp.getVertexExtIdx(k);
if (ext != _src_vertex && ext < min_idx)
min_idx = ext;
}
return min_idx;
};

if (min_ext_idx(j) < min_ext_idx(i))
{
_src_vertex_map.swap(i, j);
_attached_bc.swap(i, j);
_bc_angles.swap(i, j);
_vertices_l.swap(i, j);
}
}
}
}

int n_new_vert = 0;

for (i = 0; i < _attached_bc.size() - 1; i++)
Expand Down Expand Up @@ -254,8 +287,32 @@ void LayoutChooser::_perform(int level)
// Draw new components on vertex
_makeLayout();

// Check if new layout is better
if (_layout.calculateEnergy() < _cur_energy - EPSILON)
float energy = _layout.calculateEnergy();

// Check if new layout is better; on equal energy prefer ascending permutation
// for deterministic cross-platform results (exact == is intentional here).
bool adopt = false;
if (energy < _cur_energy - EPSILON)
{
adopt = true;
}
else if (energy == _cur_energy)
{
// Tie-break: prefer ascending _comp_permutation
bool cur_is_ascending = true;
for (int ni = 0; ni + 1 < _n_components; ni++)
{
if (_comp_permutation[ni] > _comp_permutation[ni + 1])
{
cur_is_ascending = false;
break;
}
}
if (cur_is_ascending)
adopt = true;
}

if (adopt)
{
_layout.applyLayout();
_cur_energy = _layout._energy;
Expand Down
11 changes: 9 additions & 2 deletions core/indigo-core/layout/src/molecule_layout_graph_assign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ static int _vertex_cmp(int& n1, int& n2, void* context)
return -1;
}

return v1.morgan_code - v2.morgan_code;
if (v1.morgan_code != v2.morgan_code)
return v1.morgan_code - v2.morgan_code;

// Tie-break by ext_idx for deterministic order
return v1.ext_idx - v2.ext_idx;
}

// Specialized BiconnectedDecomposer for molecule layout
Expand Down Expand Up @@ -397,7 +401,10 @@ void MoleculeLayoutGraph::_assignAbsoluteCoordinates(float bond_length)
const LayoutVertex& v2 = getLayoutVertex(n2);
if (v1.is_cyclic != v2.is_cyclic)
return v2.is_cyclic;
return v1.morgan_code < v2.morgan_code;
if (v1.morgan_code != v2.morgan_code)
return v1.morgan_code < v2.morgan_code;
// Tie-break by ext_idx for deterministic order
return v1.ext_idx < v2.ext_idx;
};
std::stable_sort(adjacent_list.begin(), adjacent_list.end(), vertex_cmp_less);
_attachDandlingVertices(k, adjacent_list);
Expand Down
Loading