@@ -52,6 +52,7 @@ class StructureValidationArtifacts:
5252 designed_coordinates : Dict [str , Tuple [float , float , float ]]
5353 target_file : Path
5454 nerdss_files : Dict [str , Path ]
55+ preflight_warning_message : Optional [str ] = None
5556
5657
5758@dataclass (frozen = True )
@@ -292,6 +293,82 @@ def get_structure_validation_counts(system: System) -> Dict[str, int]:
292293 return dict (sorted (counts .items ()))
293294
294295
296+ def _get_designed_connected_components (system : System ) -> list [tuple [str , ...]]:
297+ """Return connected components of the designed assembly using molecule instance names."""
298+ adjacency : Dict [str , set [str ]] = defaultdict (set )
299+ for molecule_instance in system .molecule_instances :
300+ adjacency .setdefault (molecule_instance .name , set ())
301+ for _interface_instance , partner_instance in molecule_instance .interfaces_neighbors_map .items ():
302+ if partner_instance is None :
303+ continue
304+ adjacency [molecule_instance .name ].add (partner_instance .name )
305+ adjacency [partner_instance .name ].add (molecule_instance .name )
306+
307+ components : list [tuple [str , ...]] = []
308+ visited : set [str ] = set ()
309+ for instance_name in sorted (adjacency ):
310+ if instance_name in visited :
311+ continue
312+
313+ stack = [instance_name ]
314+ visited .add (instance_name )
315+ component : list [str ] = []
316+ while stack :
317+ current = stack .pop ()
318+ component .append (current )
319+ for neighbor in sorted (adjacency [current ]):
320+ if neighbor not in visited :
321+ visited .add (neighbor )
322+ stack .append (neighbor )
323+
324+ components .append (tuple (sorted (component )))
325+
326+ return components
327+
328+
329+ def _format_disconnected_design_warning (components : Sequence [Sequence [str ]]) -> Optional [str ]:
330+ """Describe disconnected designed subunit groups for early validation feedback."""
331+ if len (components ) <= 1 :
332+ return None
333+ formatted_components = [
334+ ", " .join (component )
335+ for component in sorted ((tuple (component ) for component in components ), key = lambda comp : comp )
336+ ]
337+ if len (formatted_components ) == 2 :
338+ return (
339+ "Validation preflight warning: the designed assembly graph is disconnected, so it cannot form a "
340+ f"single N-mer. Subunits { formatted_components [0 ]} are disconnected from subunits "
341+ f"{ formatted_components [1 ]} ."
342+ )
343+
344+ return (
345+ "Validation preflight warning: the designed assembly graph is disconnected, so it cannot form a "
346+ f"single N-mer. Connected subunit groups: { '; ' .join (formatted_components )} ."
347+ )
348+
349+
350+ def get_disconnected_design_message (system : System , * , prefix : str ) -> Optional [str ]:
351+ """Return a formatted disconnected-assembly message for the given system."""
352+ components = _get_designed_connected_components (system )
353+ if len (components ) <= 1 :
354+ return None
355+
356+ formatted_components = [
357+ ", " .join (component )
358+ for component in sorted ((tuple (component ) for component in components ), key = lambda comp : comp )
359+ ]
360+ if len (formatted_components ) == 2 :
361+ return (
362+ f"{ prefix } : the designed assembly graph is disconnected, so it cannot form a single N-mer. "
363+ f"Subunits { formatted_components [0 ]} are disconnected from subunits { formatted_components [1 ]} ."
364+ )
365+
366+ return (
367+ f"{ prefix } : the designed assembly graph is disconnected, so it cannot form a single N-mer. "
368+ f"Connected subunit groups: { '; ' .join (formatted_components )} ."
369+ )
370+
371+
295372def build_validation_molecule_counts (system : System , initial_molecule_count : int = 1 ) -> Dict [str , int ]:
296373 """Return validation counts with a configurable initial copy number per molecule type."""
297374 target_counts = get_structure_validation_counts (system )
@@ -403,6 +480,9 @@ def prepare_structure_validation(
403480 initial_molecule_count = config .initial_molecule_count ,
404481 )
405482 target_counts = get_structure_validation_counts (system )
483+ preflight_warning_message = _format_disconnected_design_warning (
484+ _get_designed_connected_components (system )
485+ )
406486
407487 final_designed_coordinates = {
408488 key : tuple (float (value ) for value in coords )
@@ -442,12 +522,16 @@ def prepare_structure_validation(
442522 shutil .copyfile (parms_path , titration_parms_path )
443523 nerdss_files ["parms_titrate" ] = titration_parms_path
444524
525+ if preflight_warning_message :
526+ warnings .warn (preflight_warning_message , RuntimeWarning )
527+
445528 return StructureValidationArtifacts (
446529 molecule_counts = molecule_counts ,
447530 target_counts = target_counts ,
448531 designed_coordinates = final_designed_coordinates ,
449532 target_file = target_file ,
450533 nerdss_files = nerdss_files ,
534+ preflight_warning_message = preflight_warning_message ,
451535 )
452536
453537
0 commit comments