1111
1212from mathics .builtin .base import Builtin , AtomBuiltin
1313from mathics .builtin .box .graphics import GraphicsBox
14+ from mathics .builtin .box .inout import _BoxedString
1415from mathics .builtin .patterns import Matcher
1516from mathics .core .atoms import Integer , Integer0 , Integer1 , Real
1617from mathics .core .convert .expression import ListExpression
2728 SymbolRGBColor ,
2829 SymbolRule ,
2930)
30-
3131from inspect import isgenerator
3232
3333WL_MARKER_TO_NETWORKX = {
@@ -416,24 +416,39 @@ def _normalize_edges(edges):
416416
417417
418418class Graph (Atom ):
419+ class_head_name = "Pymathics`Graph"
419420
420421 options = DEFAULT_GRAPH_OPTIONS
421422
422423 def __init__ (self , G , ** kwargs ):
423424 super (Graph , self ).__init__ ()
424425 self .G = G
425426
427+ def __hash__ (self ):
428+ return hash (("Pymathics`Graph" , self .G ))
429+
430+ def __str__ (self ):
431+ return "-Graph-"
432+
433+ def atom_to_boxes (self , f , evaluation ) -> _BoxedString :
434+ return _BoxedString ("-Graph-" )
435+
436+ def default_format (self , evaluation , form ):
437+ return "-Graph-"
438+
439+ def do_format (self , evaluation , form ):
440+ return self
441+
426442 @property
427443 def edges (self ):
428444 return self .G .edges
429445
430- @property
431- def vertices (self ):
432- return self .G .nodes
433-
434446 def empty (self ):
435447 return len (self .G ) == 0
436448
449+ def is_loop_free (self ):
450+ return not any (True for _ in nx .nodes_with_selfloops (self .G ))
451+
437452 # networkx graphs can't be for mixed
438453 def is_mixed_graph (self ):
439454 return False
@@ -442,100 +457,85 @@ def is_mixed_graph(self):
442457 def is_multigraph (self ):
443458 return isinstance (self .G , (nx .MultiDiGraph , nx .MultiGraph ))
444459
445- def is_loop_free (self ):
446- return not any (True for _ in nx .nodes_with_selfloops (self .G ))
447-
448- def __str__ (self ):
449- return "-Graph-"
450-
451- def do_copy (self ):
452- return Graph (self .G )
453-
454460 def get_sort_key (self , pattern_sort = False ):
455461 if pattern_sort :
456462 return super (Graph , self ).get_sort_key (True )
457463 else :
458464 return hash (self )
459465
460- def default_format (self , evaluation , form ):
461- return "-Graph-"
466+ @property
467+ def vertices (self ):
468+ return self .G .nodes
462469
463- def same (self , other ):
464- return isinstance (other , Graph ) and self .G == other .G
465- # FIXME
466- # self.properties == other.properties
467- # self.options == other.options
468- # self.highlights == other.highlights
469470
470- def to_python (self , * args , ** kwargs ):
471- return self .G
471+ class _Collection (object ):
472+ def __init__ (self , expressions , properties = None , index = None ):
473+ self .expressions = expressions
474+ self .properties = properties if properties else None
475+ self .index = index
472476
473- def __hash__ (self ):
474- return hash (("Graph" , self .G )) # FIXME self.properties, ...
477+ def clone (self ):
478+ properties = self .properties
479+ return _Collection (
480+ self .expressions [:], properties [:] if properties else None , None
481+ )
475482
476- def atom_to_boxes (self , form , evaluation ):
477- return Expression (SymbolGraphBox , self , form )
483+ def filter (self , expressions ):
484+ index = self .get_index ()
485+ return [expr for expr in expressions if expr in index ]
478486
479- def boxes_to_xml (self , ** options ):
480- # Figure out what to do here.
481- return "-Graph-XML-"
487+ def extend (self , expressions , properties ):
488+ if properties :
489+ if self .properties is None :
490+ self .properties = [None ] * len (self .expressions )
491+ self .properties .extend (properties )
492+ self .expressions .extend (expressions )
493+ self .index = None
494+ return expressions
482495
483- def get_property (self , element , name ):
484- if element .get_head_name () in ("System`DirectedEdge" , "System`UndirectedEdge" ):
485- x = self .edges .get_property (element , name )
486- if x is None :
487- x = self .vertices .get_property (element , name )
488- return x
496+ def delete (self , expressions ):
497+ index = self .get_index ()
498+ trash = set (index [x ] for x in expressions )
499+ deleted = [self .expressions [i ] for i in trash ]
500+ self .expressions = [x for i , x in enumerate (self .expressions ) if i not in trash ]
501+ self .properties = [x for i , x in enumerate (self .properties ) if i not in trash ]
502+ self .index = None
503+ return deleted
489504
490- def delete_edges (self , edges_to_delete ):
491- G = self .G .copy ()
492- directed = G .is_directed ()
493-
494- edges_to_delete = list (_normalize_edges (edges_to_delete ))
495- # FIXME: edges_to_delete is needs to be a tuple. tuples
496- # are edges in networkx
497- edges_to_delete = [edge for edge in self .edges if edge in edges_to_delete ]
498-
499- for edge in edges_to_delete :
500- if edge .has_form ("DirectedEdge" , 2 ):
501- if directed :
502- u , v = edge .elements
503- G .remove_edge (u , v )
504- elif edge .has_form ("UndirectedEdge" , 2 ):
505- u , v = edge .elements
506- if directed :
507- G .remove_edge (u , v )
508- G .remove_edge (v , u )
509- else :
510- G .remove_edge (u , v )
511-
512- edges = self .edges .clone ()
513- edges .delete (edges_to_delete )
514-
515- return Graph (G )
516-
517- def update_weights (self , evaluation ):
518- weights = None
519- G = self .G
520-
521- if self .is_multigraph ():
522- for u , v , k , w in G .edges .data (
523- "System`EdgeWeight" , default = None , keys = True
524- ):
525- data = G .get_edge_data (u , v , key = k )
526- w = data .get ()
527- if w is not None :
528- w = w .evaluate (evaluation ).to_mpmath ()
529- G [u ][v ][k ]["WEIGHT" ] = w
530- weights = "WEIGHT"
505+ def data (self ):
506+ return self .expressions , list (self .get_properties ())
507+
508+ def get_index (self ):
509+ index = self .index
510+ if index is None :
511+ index = dict ((v , i ) for i , v in enumerate (self .expressions ))
512+ self .index = index
513+ return index
514+
515+ def get_properties (self ):
516+ if self .properties :
517+ for p in self .properties :
518+ yield p
531519 else :
532- for u , v , w in G .edges .data ("System`EdgeWeight" , default = None ):
533- if w is not None :
534- w = w .evaluate (evaluation ).to_mpmath ()
535- G [u ][v ]["WEIGHT" ] = w
536- weights = "WEIGHT"
520+ for _ in range (len (self .expressions )):
521+ yield None
537522
538- return weights
523+ def get_sorted (self ):
524+ index = self .get_index ()
525+ return lambda c : sorted (c , key = lambda v : index [v ])
526+
527+ def get_property (self , element , name ):
528+ properties = self .properties
529+ if properties is None :
530+ return None
531+ index = self .get_index ()
532+ i = index .get (element )
533+ if i is None :
534+ return None
535+ p = properties [i ]
536+ if p is None :
537+ return None
538+ return p .get (name )
539539
540540
541541def _is_connected (G ):
@@ -1754,9 +1754,7 @@ def apply(self, graph, evaluation, options):
17541754
17551755 def degrees (graph ):
17561756 degrees = dict (list (graph .G .degree (graph .vertices )))
1757- return ListExpression (
1758- * [Integer (degrees .get (v , 0 )) for v in graph .vertices ]
1759- )
1757+ return ListExpression (* [Integer (degrees .get (v , 0 )) for v in graph .vertices ])
17601758
17611759 return self ._evaluate_atom (graph , options , degrees )
17621760
0 commit comments