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
7 changes: 4 additions & 3 deletions rmgpy/data/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1066,9 +1066,10 @@ def descend_tree(self, structure, atoms, root=None, strict=False):
else:
return root
else:
# logging.warning('For {0}, a node {1} with overlapping children {2} was encountered '
# 'in tree with top level nodes {3}. Assuming the first match is the '
# 'better one.'.format(structure, root, next, self.top))
# Multiple children match - pick the first in tree order.
# The tree is constructed deterministically, so this is deterministic.
# (Sorting by label can pick the wrong node when siblings overlap,
# e.g. Nitrites sorts before Nitro but Nitro is the intended match.)
return self.descend_tree(structure, atoms, next_node[0], strict)

def are_siblings(self, node, node_other):
Expand Down
2 changes: 1 addition & 1 deletion rmgpy/data/thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def combine_cycles(cycle1, cycle2):
"""
set1 = set(cycle1)
set2 = set(cycle2)
return list(set1.union(set2))
return sorted(set1.union(set2))


def is_aromatic_ring(submol):
Expand Down
21 changes: 19 additions & 2 deletions rmgpy/molecule/graph.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,10 @@ cdef class Graph(object):
cpdef sort_vertices(self, bint save_order=False):
"""
Sort the vertices in the graph. This can make certain operations, e.g.
the isomorphism functions, much more efficient.
the isomorphism functions, much more efficient. Vertices that have a
``sorting_key`` attribute (e.g. :class:`Atom`) will use it as a chemical
tiebreaker for vertices with identical connectivity values, ensuring
deterministic ordering.
"""
cdef Vertex vertex
cdef int index
Expand All @@ -485,7 +488,21 @@ cdef class Graph(object):
# If we need to sort then let's also update the connecitivities so
# we're sure they are right, since the sorting labels depend on them
self.update_connectivity_values()
self.vertices.sort(key=get_vertex_connectivity_value)

# Build sort keys with optional chemical tiebreaker (sorting_key attribute)
# to ensure deterministic ordering when connectivity values are identical.
# Index is included as a final tiebreaker to avoid comparing vertex
# objects directly (GroupAtom has no __lt__).
# Sort (key, index) pairs, then use the resulting index order to reorder.
paired = []
for index, vertex in enumerate(self.vertices):
conn = get_vertex_connectivity_value(vertex)
sort_key = getattr(vertex, 'sorting_key', None)
paired.append(((conn, sort_key if sort_key is not None else ()), index))

paired.sort()
ordered = [self.vertices[idx] for _, idx in paired]
self.vertices = ordered
for index, vertex in enumerate(self.vertices):
vertex.sorting_label = index

Expand Down
10 changes: 5 additions & 5 deletions rmgpy/molecule/molecule.py
Original file line number Diff line number Diff line change
Expand Up @@ -2838,8 +2838,8 @@ def get_polycycles(self):
if vertex in cycle:
polycyclic_cycle.update(cycle)

# convert each set to a list
continuous_cycles = [list(cycle) for cycle in continuous_cycles]
# convert each set to a list (sorted for deterministic atom ordering)
continuous_cycles = [sorted(cycle) for cycle in continuous_cycles]
return continuous_cycles

def get_monocycles(self):
Expand Down Expand Up @@ -2888,9 +2888,9 @@ def get_disparate_cycles(self):
# Merge connected cycles
monocyclic_cycles, polycyclic_cycles = self._merge_cycles(cycle_sets)

# Convert cycles back to lists
monocyclic_cycles = [list(cycle_set) for cycle_set in monocyclic_cycles]
polycyclic_cycles = [list(cycle_set) for cycle_set in polycyclic_cycles]
# Convert cycles back to lists (sorted for deterministic atom ordering)
monocyclic_cycles = [sorted(cycle_set) for cycle_set in monocyclic_cycles]
polycyclic_cycles = [sorted(cycle_set) for cycle_set in polycyclic_cycles]

return monocyclic_cycles, polycyclic_cycles

Expand Down
7 changes: 6 additions & 1 deletion rmgpy/reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,12 @@ def is_isomorphic(self, other, either_direction=True, check_identical=False, che
save_order=save_order)

# Compare specific_collider to specific_collider
collider_match = (self.specific_collider == other.specific_collider)
if self.specific_collider is None:
collider_match = other.specific_collider is None
elif other.specific_collider is None:
collider_match = False
else:
collider_match = self.specific_collider.is_isomorphic(other.specific_collider)

# Return now, if we can
if forward_reactants_match and forward_products_match and collider_match:
Expand Down
10 changes: 8 additions & 2 deletions rmgpy/rmg/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,13 @@ def execute(self, initialize=True, **kwargs):
# These should be Species or Network objects
logging.info("")

objects_to_enlarge = list(set(objects_to_enlarge))
# objects_to_enlarge can contain Species, Network, or tuples (PDepNetwork, Species)
# Sort deterministically: by label for objects with .label, by first element for tuples
def _sort_key(obj):
if isinstance(obj, tuple):
return (1, getattr(obj[0], 'label', str(obj[0])))
return (0, getattr(obj, 'label', str(obj)))
objects_to_enlarge = sorted(set(objects_to_enlarge), key=_sort_key)

# Add objects to enlarge to the core first
for objectToEnlarge in objects_to_enlarge:
Expand Down Expand Up @@ -1927,7 +1933,7 @@ def process_to_species_networks(self, obj):
rspcs = self.process_reactions_to_species([k for k in obj if isinstance(k, Reaction)])
spcs = {k for k in obj if isinstance(k, Species)} | rspcs
nworks, pspcs = self.process_pdep_networks([k for k in obj if isinstance(k, PDepNetwork)])
spcs = list(spcs - pspcs) # avoid duplicate species
spcs = sorted(spcs - pspcs, key=lambda s: s.label) # avoid duplicate species, sort deterministically
return spcs + nworks
else:
raise TypeError("improper call, obj input was incorrect")
Expand Down
4 changes: 2 additions & 2 deletions test/regression/RMS_CSTR_liquid_oxidation/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@
toleranceMoveToCore=0.01,
toleranceKeepInEdge=0.001,
toleranceInterruptSimulation=1e8,
maximumEdgeSpecies=10000,
maximumEdgeSpecies=300,
minCoreSizeForPrune=10,
minSpeciesExistIterationsForPrune=2,
maxNumObjsPerIter=3,
filterReactions=True,
maxNumSpecies=35,
maxNumSpecies=15,
)

options(
Expand Down
Loading