77
88from atomrdf .datamodels .structure import AtomicScaleSample
99from atomrdf .datamodels .workflow .workflow import Simulation
10+ from atomrdf .datamodels .workflow .operations import (
11+ DeleteAtom ,
12+ SubstituteAtom ,
13+ AddAtom ,
14+ Rotate ,
15+ Translate ,
16+ Shear ,
17+ )
1018from atomrdf import KnowledgeGraph
1119from rdflib import URIRef , Literal , Namespace , XSD
1220
1321DCAT = Namespace ("http://www.w3.org/ns/dcat#" )
1422
23+ # Mapping of operation method names to their classes
24+ OPERATION_MAP = {
25+ "DeleteAtom" : DeleteAtom ,
26+ "SubstituteAtom" : SubstituteAtom ,
27+ "AddAtom" : AddAtom ,
28+ "Rotate" : Rotate ,
29+ "Rotation" : Rotate , # Alias
30+ "Translate" : Translate ,
31+ "Translation" : Translate , # Alias
32+ "Shear" : Shear ,
33+ }
34+
1535
1636class WorkflowParser :
1737 """
@@ -20,7 +40,8 @@ class WorkflowParser:
2040 Handles parsing of:
2141 - Computational samples (with deduplication via hashing)
2242 - Workflows/Simulations
23- - Activities (transformations between samples)
43+ - Operations (transformations between samples: DeleteAtom, SubstituteAtom,
44+ AddAtom, Rotate, Translate, Shear)
2445
2546 Attributes
2647 ----------
@@ -477,32 +498,92 @@ def parse_workflows(self, workflow_data_list: List[Dict[str, Any]]) -> List[str]
477498
478499 return workflow_uris
479500
480- def parse_activities (self , activity_data_list : List [Dict [str , Any ]]) -> List [str ]:
501+ def parse_operations (self , operation_data_list : List [Dict [str , Any ]]) -> List [str ]:
481502 """
482- Parse activity data (transformations between samples).
503+ Parse operation data (transformations between samples).
504+
505+ Operations include: DeleteAtom, SubstituteAtom, AddAtom, Rotate,
506+ Translate, and Shear.
483507
484508 Parameters
485509 ----------
486- activity_data_list : list of dict
487- List of activity dictionaries
510+ operation_data_list : list of dict
511+ List of operation dictionaries. Each must have:
512+ - 'method': The operation type (e.g., 'DeleteAtom', 'Rotate')
513+ - 'input_sample': Sample ID or list of sample IDs
514+ - 'output_sample': Sample ID or list of sample IDs
515+ - Additional method-specific parameters (e.g., rotation_matrix for Rotate)
488516
489517 Returns
490518 -------
491519 list of str
492- List of activity URIs created
520+ List of operation URIs created
493521
494- Notes
495- -----
496- This method is not yet fully implemented.
522+ Raises
523+ ------
524+ ValueError
525+ If operation method is not recognized
497526 """
498- # TODO: Implement activity parsing
499- # This will be similar to workflow parsing but for Activity objects
500- activity_uris = []
501- if self .debug :
502- print (
503- f"Activity parsing not yet implemented. Found { len (activity_data_list )} activities."
504- )
505- return activity_uris
527+ operation_uris = []
528+
529+ for i , operation_data in enumerate (operation_data_list ):
530+ method = operation_data .get ("method" )
531+ if not method :
532+ if self .debug :
533+ print (f"Skipping operation { i + 1 } : no method specified" )
534+ continue
535+
536+ # Get the operation class
537+ operation_class = OPERATION_MAP .get (method )
538+ if not operation_class :
539+ raise ValueError (
540+ f"Unknown operation method: { method } . "
541+ f"Available methods: { list (OPERATION_MAP .keys ())} "
542+ )
543+
544+ # Resolve input sample references
545+ if "input_sample" in operation_data :
546+ input_sample = operation_data ["input_sample" ]
547+ if isinstance (input_sample , list ):
548+ operation_data ["input_sample" ] = [
549+ self .sample_map .get (s , s ) for s in input_sample
550+ ]
551+ else :
552+ operation_data ["input_sample" ] = self .sample_map .get (
553+ input_sample , input_sample
554+ )
555+
556+ # Resolve output sample references
557+ if "output_sample" in operation_data :
558+ output_sample = operation_data ["output_sample" ]
559+ if isinstance (output_sample , list ):
560+ operation_data ["output_sample" ] = [
561+ self .sample_map .get (s , s ) for s in output_sample
562+ ]
563+ else :
564+ operation_data ["output_sample" ] = self .sample_map .get (
565+ output_sample , output_sample
566+ )
567+
568+ # Remove 'method' from data as it's not part of the model
569+ operation_data_copy = operation_data .copy ()
570+ operation_data_copy .pop ("method" , None )
571+
572+ # Create the operation object
573+ operation = operation_class (** operation_data_copy )
574+
575+ # Add to knowledge graph
576+ operation .to_graph (self .kg )
577+ operation_uris .append (operation .id )
578+
579+ if self .debug :
580+ print (
581+ f"Added operation { i + 1 } ({ method } ): "
582+ f"{ operation_data .get ('input_sample' )} -> "
583+ f"{ operation_data .get ('output_sample' )} "
584+ )
585+
586+ return operation_uris
506587
507588 def parse (self , data : Union [str , Path , Dict [str , Any ]]) -> Dict [str , Any ]:
508589 """
@@ -521,7 +602,7 @@ def parse(self, data: Union[str, Path, Dict[str, Any]]) -> Dict[str, Any]:
521602
522603 - 'sample_map' : dict mapping original IDs to URIs
523604 - 'workflow_uris' : list of created workflow URIs
524- - 'activity_uris ' : list of created activity URIs
605+ - 'operation_uris ' : list of created operation URIs
525606
526607 Raises
527608 ------
@@ -556,19 +637,22 @@ def parse(self, data: Union[str, Path, Dict[str, Any]]) -> Dict[str, Any]:
556637 f"Unsupported data type: { type (data )} . Expected str, Path, or dict."
557638 )
558639
559- result = {"sample_map" : {}, "workflow_uris" : [], "activity_uris " : []}
640+ result = {"sample_map" : {}, "workflow_uris" : [], "operation_uris " : []}
560641
561- # Parse samples first (they may be referenced by workflows/activities )
642+ # Parse samples first (they may be referenced by workflows/operations )
562643 if "computational_sample" in data :
563644 result ["sample_map" ] = self .parse_samples (data ["computational_sample" ])
564645
565646 # Parse workflows
566647 if "workflow" in data :
567648 result ["workflow_uris" ] = self .parse_workflows (data ["workflow" ])
568649
569- # Parse activities
570- if "activity" in data :
571- result ["activity_uris" ] = self .parse_activities (data ["activity" ])
650+ # Parse operations (formerly activities)
651+ if "operation" in data :
652+ result ["operation_uris" ] = self .parse_operations (data ["operation" ])
653+ # Backwards compatibility: also check for 'activity' key
654+ elif "activity" in data :
655+ result ["operation_uris" ] = self .parse_operations (data ["activity" ])
572656
573657 return result
574658
@@ -705,26 +789,26 @@ def parse_workflow(
705789 return uris [0 ] if uris else None
706790
707791
708- def parse_activity (
709- activity_data : Dict [str , Any ], graph : Optional [KnowledgeGraph ] = None
792+ def parse_operation (
793+ operation_data : Dict [str , Any ], graph : Optional [KnowledgeGraph ] = None
710794) -> str :
711795 """
712- Parse a single activity .
796+ Parse a single operation .
713797
714798 Parameters
715799 ----------
716- activity_data : dict
717- Activity dictionary
800+ operation_data : dict
801+ Operation dictionary with 'method' field specifying the operation type
718802 graph : KnowledgeGraph, optional
719803 Knowledge graph instance. If None, creates a new one.
720804
721805 Returns
722806 -------
723807 str or None
724- URI of the created activity
808+ URI of the created operation
725809 """
726810 parser = WorkflowParser (kg = graph )
727- uris = parser .parse_activities ([ activity_data ])
811+ uris = parser .parse_operations ([ operation_data ])
728812 return uris [0 ] if uris else None
729813
730814
0 commit comments