Automatisches Erzeugen eines Abhängigkeitsgraphen einer beliebigen Mathematica-Funktion?

  • Hat jemand eine Funktion geschrieben, um die Funktionsabhängigkeiten einer Funktion zu ermitteln? Das heißt, es wäre schön, eine Funktion zu haben, die eine Liste von Funktionsabhängigkeiten als Regelwerk zurückgibt und mit eingebauten Funktionen endet, die dann direkt an GraphPlot oder LayeredGraphPlot übergeben werden könnten. Ich bin irgendwie überrascht, dass eine solche Abhängigkeitsfunktion nicht bereits eingebaut ist.

    Bearbeiten: Okay, um einen Beitrag zu leisten Ich möchte ein wenig Wert auf die Diskussion legen und möchte die Funktionen von Szabolcs modifizieren:

     SetAttributes[functionQ, HoldAll]
    functionQ[
      sym_Symbol] := (DownValues[sym] =!= {}) && (OwnValues[sym] === {})
    
    (*My addition:*)
    SetAttributes[terminalQ, HoldAll]
    terminalQ[sym_Symbol] := MemberQ[Attributes[sym], Protected]
    
    (*added terminalQ to the Select:*)
    SetAttributes[dependencies, HoldAll]
    dependencies[sym_Symbol] := 
     List @@ Select[
       Union@Level[(Hold @@ DownValues[sym])[[All, 2]], {-1}, Hold, 
         Heads -> True], functionQ[#] || terminalQ[#] &]
    
    (*adds hyperlinks to Help:*)
    SetAttributes[dependencyGraphB, HoldAll]
    dependencyGraphB[sym_Symbol] := 
     Module[{vertices, edges}, 
      vertices = 
       FixedPoint[Union@Flatten@Join[#, dependencies /@ #] &, {sym}];
      edges = Flatten[Thread[Rule[#, dependencies[#]]] & /@ vertices];
      GraphPlot[edges, 
       VertexRenderingFunction -> (If[MemberQ[Attributes[#2], Protected], 
           Text[Hyperlink[
             StyleForm[Framed[#2, FrameMargins -> 1, Background -> Pink], 
              FontSize -> 7], "paclet:ref/" <> ToString[#2]], #1], 
           Text[Framed[Style[DisplayForm[#2], Black, 8], 
             Background -> LightBlue, FrameStyle -> Gray, 
             FrameMargins -> 3], #1]] &)]]
     

    Automatisches Erzeugen eines Abhängigkeitsgraphen einer beliebigen Mathematica-Funktion?

    Nun, da ich darüber nachdenke, sollte genau diese Abhängigkeitsfunktion in allen Parallel -Funktionen eingebaut sein, damit MMA weiß, welche Definitionen an die Kernel gesendet werden sollen . Leider denke ich, dass sie diese elegantere Methode vermeiden und einfach jedes verdammte Ding senden, das in Context steht, was wahrscheinlich übertrieben ist.

    17 April 2012
    Jon Galloway
1 answer
  • Präambel

    Das Problem ist nicht so unbedeutend, wie es auf den ersten Blick erscheinen mag. Das Hauptproblem besteht darin, dass viele Symbole durch (lexikalische) Scoping-Konstrukte lokalisiert werden und nicht gezählt werden sollten. Um dies vollständig zu lösen, benötigen wir einen Parser für Mathematica-Code, der den Umfang berücksichtigt.

    Eine der umfassendsten Behandlungen dieses Problems wurde von David Wagner in seinem Artikel aus dem Mathematica-Journal und teilweise in seinem Buch repliziert. Ich werde seinen Ideen folgen, aber meine eigene Umsetzung zeigen. Ich werde eine Art simples Recusrive-Abstiegs-Parser implementieren, das das Scoping berücksichtigt. Dies ist keine vollständige Sache, aber es werden bestimmte Feinheiten veranschaulicht (insbesondere sollten wir eine vorzeitige Bewertung von Code während der Analyse verhindern. Daher ist dies eine gute Übung für das Arbeiten mit festgehaltenen / nicht ausgewerteten Ausdrücken.)

    Implementierung (nur zur Veranschaulichung gibt nicht vor, dass sie abgeschlossen ist)

    Hier ist der Code:

     ClearAll[getDeclaredSymbols, getDependenciesInDeclarations, $OneStepDependencies,
      getSymbolDependencies, getPatternSymbols,inSymbolDependencies, $inDepends];
    
    SetAttributes[{getDeclaredSymbols, getDependenciesInDeclarations, 
       getSymbolDependencies, getPatternSymbols,inSymbolDependencies}, HoldAll];
    
    $OneStepDependencies = False;
    
    inSymbolDependencies[_] = False;
    
    globalProperties[] =
        {DownValues, UpValues, OwnValues, SubValues, FormatValues, NValues, 
         Options, DefaultValues};
    
    
    getDeclaredSymbols[{decs___}] :=
        Thread@Replace[HoldComplete[{decs}], HoldPattern[a_ = rhs_] :> a, {2}];
    
    getDependenciesInDeclarations[{decs___}, dependsF_] :=
      Flatten@Cases[Unevaluated[{decs}], 
          HoldPattern[Set[a_, rhs_]] :> dependsF[rhs]];
    
    getPatternSymbols[expr_] :=
      Cases[ 
         Unevaluated[expr], 
         Verbatim[Pattern][ss_, _] :> HoldComplete[ss], 
         {0, Infinity},  Heads -> True];
    
    getSymbolDependencies[s_Symbol, dependsF_] :=
      Module[{result},
        inSymbolDependencies[s] = True;
         result = 
           Append[
             Replace[
                Flatten[Function[prop, prop[s]] /@ globalProperties[]],
                {
                  (HoldPattern[lhs_] :> rhs_) :>
                    With[{excl = getPatternSymbols[lhs]},
                     Complement[
                       Join[
                          withExcludedSymbols[dependsF[rhs], excl],
                          Module[{res},
                             (* To avoid infinite recursion *)
                             depends[s] = {HoldComplete[s]};
                             res = withExcludedSymbols[dependsF[lhs], excl];
                             depends[s] =.;
                             res
                          ]
                       ],
                       excl]
                    ],
                  x_ :> dependsF[x]
                },
                {1}
             ],
             HoldComplete[s]
           ];
        inSymbolDependencies[s] =.;
        result] /; ! TrueQ[inSymbolDependencies[s]];
    
    getSymbolDependencies[s_Symbol, dependsF_] := {};
    
    
    (* This function prevents leaking symbols on which global symbols colliding with 
    ** the pattern names (symbols) may depend 
    *)
    ClearAll[withExcludedSymbols];
    SetAttributes[withExcludedSymbols, HoldFirst];
    withExcludedSymbols[code_, syms : {___HoldComplete}] :=
       Module[{result, alreadyDisabled },
         SetAttributes[alreadyDisabled, HoldAllComplete];
         alreadyDisabled[_] = False;
         Replace[syms,
           HoldComplete[s_] :>
             If[! inSymbolDependencies[s],
                inSymbolDependencies[s] = True,
                (* else *)
                alreadyDisabled[s] = True
             ],
           {1}];
         result = code;
         Replace[syms, 
            HoldComplete[s_] :> 
               If[! alreadyDisabled[s], inSymbolDependencies[s] =.], 
            {1}
         ];
         ClearAll[alreadyDisabled];
         result
     ];
    
    
    (* The main function *)
    ClearAll[depends];
    SetAttributes[depends, HoldAll];
    depends[(RuleDelayed | SetDelayed)[lhs_, rhs_]] :=
       With[{pts = getPatternSymbols[lhs]},
          Complement[
            Join[
              withExcludedSymbols[depends[lhs], pts], 
              withExcludedSymbols[depends[rhs], pts]
            ],
            pts]
       ];
    depends[Function[Null, body_, atts_]] := depends[body];
    depends[Function[body_]] := depends[body];
    depends[Function[var_, body_]] := depends[Function[{var}, body]];
    depends[Function[{vars__}, body_]] := 
       Complement[depends[body], Thread[HoldComplete[{vars}]]];
    depends[(With | Module)[decs_, body_]] :=
      Complement[
        Join[
          depends[body],
          getDependenciesInDeclarations[decs, depends]
        ],
        getDeclaredSymbols[decs]
      ];
    depends[f_[elems___]] :=
      Union[depends[Unevaluated[f]], 
        Sequence @@ Map[depends, Unevaluated[{elems}]]];
    depends[s_Symbol /; Context[s] === "System`"] := {};
    depends[s_Symbol] /; ! $OneStepDependencies || ! TrueQ[$inDepends] :=  
       Block[{$inDepends = True},
          Union@Flatten@getSymbolDependencies[s, depends ]
       ];
    depends[s_Symbol] := {HoldComplete[s]};
    depends[a_ /; AtomQ[Unevaluated[a]]] := {};
     

    Illustration

    Zunächst einige einfache Beispiele:

     In[100]:= depends[Function[{a,b,c},a+b+c+d]]
    Out[100]= {HoldComplete[d]}
    
    In[101]:= depends[With[{d=e},Function[{a,b,c},a+b+c+d]]]
    Out[101]= {HoldComplete[e]}
    
    In[102]:= depends[p:{a_Integer,b_Integer}:>Total[p]]
    Out[102]= {}
    
    In[103]:= depends[p:{a_Integer,b_Integer}:>Total[p]*(a+b)^c]
    Out[103]= {HoldComplete[c]}
     

    Nun ein Beispiel:

     In[223]:= depends[depends]
    Out[223]= 
    {HoldComplete[depends],HoldComplete[getDeclaredSymbols],
     HoldComplete[getDependenciesInDeclarations],HoldComplete[getPatternSymbols],
     HoldComplete[getSymbolDependencies],HoldComplete[globalProperties],
     HoldComplete[inSymbolDependencies],HoldComplete[withExcludedSymbols],
     HoldComplete[$inDepends],HoldComplete[$OneStepDependencies]}
     

    Wie Sie sehen, kann mein Code rekursive Funktionen verarbeiten. Der Code von depends enthält viele weitere Symbole, aber wir haben nur solche gefunden, die global sind (nicht in den Scoping-Konstrukten lokalisiert).

    Beachten Sie, dass standardmäßig alle abhängigen Symbole auf allen Ebenen enthalten sind. Um nur die "First-Level" -Funktionen / -Symbole zu erhalten, von denen ein bestimmtes Symbol abhängt, muss die Variable $OneStepDependencies auf True gesetzt werden:

     In[224]:= 
    $OneStepDependencies =True;
    depends[depends]
    
    Out[225]= {HoldComplete[depends],HoldComplete[getDeclaredSymbols],
    HoldComplete[getDependenciesInDeclarations],HoldComplete[getPatternSymbols],
    HoldComplete[getSymbolDependencies],HoldComplete[withExcludedSymbols],
    HoldComplete[$inDepends],HoldComplete[$OneStepDependencies]}
     

    Dieses letzte Regime kann verwendet werden, um den Abhängigkeitsbaum zu rekonstruieren, wie dies beispielsweise von @Szabolcs in der Antwort vorgeschlagen wird.

    Anwendbarkeit < / h3>

    Diese Antwort ist erheblich komplexer als die von @Szabolcs und wahrscheinlich auch (beträchtlich) langsamer, zumindest in einigen FällenFälle. Wann sollte man es benutzen? Die Antwort hängt meiner Meinung nach davon ab, wie wichtig es ist, alle Abhängigkeiten zu finden. Wenn Sie nur ein grobes visuelles Bild für die Abhängigkeiten benötigen, sollte der Vorschlag von @ Szabolcs in den meisten Fällen gut funktionieren. Die aktuelle Antwort kann Vorteile haben, wenn:

    • Sie möchten Abhängigkeiten in einem beliebigen Code analysieren, nicht notwendigerweise in einer Funktion platziert (diese Funktion wird in @ Szabolcs 'Ansatz leicht umgangen, wenn sie nicht besonders bequem umgangen wird, indem zuerst eine Dummy-Zero-Argument-Funktion mit dem Code erstellt und anschließend analysiert wird).

    • Es ist wichtig, dass Sie alle Abhängigkeiten finden.

    Dinge wie

     $functionDoingSomething = Function[var,If[test[var],f[var],g[var]]]
    myFunction[x_,y_]:= x+ $functionDoingSomething [y]
     

    entkommt den Abhängigkeiten, die der @ Szabolcs-Code gefunden hat (wie er selbst in den Kommentaren erwähnte) und kann daher ganze Abhängigkeitszweige (für f, g und test hier wegschneiden ). Es gibt andere Fälle, zum Beispiel im Zusammenhang mit UpValues, Abhängigkeiten durch Options und Defaults und möglicherweise auch andere Möglichkeiten.

    Es kann mehrere Situationen geben, in denen das Finden aller Abhängigkeiten kritisch ist. Zum einen, wenn Sie Introspection programmgesteuert als eines der Meta-Programmiertools verwenden. In diesem Fall müssen Sie sicher sein, dass alles korrekt ist, da Sie auf dieser Funktionalität aufbauen. Um zu verallgemeinern, müssen Sie möglicherweise etwas wie das, was ich vorgeschlagen habe, verwenden (wenn auch ohne Fehler :)), wenn der Endbenutzer dieser Funktion eine andere Person (oder etwas anderes als eine andere Funktion) als Sie selbst ist.

    Es kann auch sein, dass Sie das genaue Abhängigkeitsbild für sich selbst benötigen, auch wenn Sie es nicht programmgesteuert weiter verwenden möchten.

    In vielen Fällen ist dies alles nicht sehr kritisch, und der Vorschlag von @Szabolcs kann eine bessere und einfachere Alternative darstellen. Die Frage ist im Wesentlichen: Möchten Sie Tools auf Benutzerebene oder auf Systemebene erstellen?

    15 January 2012
    MattH