LCOV - code coverage report
Current view: top level - cltools - ShowGraph.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 173 177 97.7 %
Date: 2026-11-05 10:19:04 Functions: 12 12 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2012-2023 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include "CLTool.h"
      23             : #include "core/CLToolRegister.h"
      24             : #include "tools/Tools.h"
      25             : #include "config/Config.h"
      26             : #include "core/PlumedMain.h"
      27             : #include "core/ActionSet.h"
      28             : #include "core/ActionRegister.h"
      29             : #include "core/ActionShortcut.h"
      30             : #include "core/ActionToPutData.h"
      31             : #include "core/ActionWithVirtualAtom.h"
      32             : #include "core/ActionWithValue.h"
      33             : #include "core/ActionWithVector.h"
      34             : #include "core/ActionWithArguments.h"
      35             : #include <cstdio>
      36             : #include <string>
      37             : 
      38             : namespace PLMD {
      39             : namespace cltools {
      40             : 
      41             : //+PLUMEDOC TOOLS show_graph
      42             : /*
      43             : show_graph is a tool that takes a plumed input and generates a flowchart showing how
      44             : data flows through the action set involved.
      45             : 
      46             : For example, if we have the following plumed input:
      47             : 
      48             : ```plumed
      49             : d1: DISTANCE ATOMS=1,2
      50             : a1: ANGLE ATOMS=1,2,3
      51             : t1: TORSION ATOMS=1,2,3,4
      52             : r: RESTRAINT ARG=d1,a1 AT=1.0,pi/2 KAPPA=100,100
      53             : PRINT ARG=d1,a1,t1,r.* FILE=colvar
      54             : ```
      55             : 
      56             : We can use the command:
      57             : 
      58             : ```plumed
      59             : plumed show_graph --plumed plumed.dat
      60             : ```
      61             : 
      62             : To generate the following flowchart showing how data passes through these actions during the PLUMED calculation.
      63             : 
      64             : ```plumed
      65             : #MERMAID=value
      66             : d1: DISTANCE ATOMS=1,2
      67             : a1: ANGLE ATOMS=1,2,3
      68             : t1: TORSION ATOMS=1,2,3,4
      69             : r: RESTRAINT ARG=d1,a1 AT=1.0,pi/2 KAPPA=100,100
      70             : PRINT ARG=d1,a1,t1,r.* FILE=colvar
      71             : ```
      72             : 
      73             : Furthermore, if we want to understand how forces on the atoms are calculated from these actions by using the chain rule
      74             : we can use the following command:
      75             : 
      76             : ```plumed
      77             : plumed show_graph --plumed plumed.dat --force
      78             : ```
      79             : 
      80             : To generate the following flowchart:
      81             : 
      82             : ```plumed
      83             : #MERMAID=force
      84             : d1: DISTANCE ATOMS=1,2
      85             : a1: ANGLE ATOMS=1,2,3
      86             : t1: TORSION ATOMS=1,2,3,4
      87             : r: RESTRAINT ARG=d1,a1 AT=1.0,pi/2 KAPPA=100,100
      88             : PRINT ARG=d1,a1,t1,r.* FILE=colvar
      89             : ```
      90             : 
      91             : These flowcharts are output in a file called `graph.md` unless you use the `--out` option as shown below:
      92             : 
      93             : ```plumed
      94             : plumed show_graph --plumed plumed.dat --out mygraph.md
      95             : ```
      96             : 
      97             : In this case the flowchart is output to a file called `mygraph.md`.  This file contains the instructions for constructing the
      98             : flowchart in [mermaid flowchart syntax](https://mermaid.js.org/syntax/flowchart.html).  To construct images similar to those
      99             : above you can copy and paste the contents of the output `graph.md` file into [this online tool for
     100             : rendering mermaid diagrams](https://mermaid.live).
     101             : 
     102             : If you are writing documentation for PLUMED or tutorials for the [plumed tutorials](https://www.plumed-tutorials.org) site you can add
     103             : these diagrams by adding the instruction `#MERMAID=value` or `#MERMAID=force` into example inputs.  When these options are given
     104             : inputs are displayed as mermaid diagrams in the final output html.
     105             : 
     106             : */
     107             : //+ENDPLUMEDOC
     108             : 
     109             : class ShowGraph :
     110             :   public CLTool {
     111             : public:
     112             :   static void registerKeywords( Keywords& keys );
     113             :   explicit ShowGraph(const CLToolOptions& co );
     114             :   int main(FILE* in, FILE*out,Communicator& pc);
     115           7 :   std::string description()const {
     116           7 :     return "generate a graph showing how data flows through a PLUMED action set";
     117             :   }
     118             :   std::string getLabel(const Action* a, const bool& amp=false);
     119             :   std::string getLabel(const std::string& s, const bool& amp=false );
     120             :   void printStyle( const unsigned& linkcount, const Value* v, OFile& ofile );
     121             :   void printArgumentConnections( const ActionWithArguments* a, unsigned& linkcount, const bool& force, OFile& ofile );
     122             :   void printAtomConnections( const ActionAtomistic* a, unsigned& linkcount, const bool& force, OFile& ofile );
     123             : };
     124             : 
     125       21980 : PLUMED_REGISTER_CLTOOL(ShowGraph,"show_graph")
     126             : 
     127        7321 : void ShowGraph::registerKeywords( Keywords& keys ) {
     128        7321 :   CLTool::registerKeywords( keys );
     129        7321 :   keys.add("compulsory","--plumed","plumed.dat","the plumed input that we are generating the graph for");
     130        7321 :   keys.add("compulsory","--out","graph.md","the file containing the graph that has been generated, in mermaid flowchart syntax");
     131        7321 :   keys.add("compulsory","--natoms","1000000","the number of atoms in the system (should be larger than the largest atom index in the input)");
     132        7321 :   keys.addFlag("--force",false,"print a graph that shows how forces are passed through the actions");
     133        7321 : }
     134             : 
     135          17 : ShowGraph::ShowGraph(const CLToolOptions& co ):
     136          17 :   CLTool(co) {
     137          17 :   inputdata=inputType::commandline;
     138          17 : }
     139             : 
     140         481 : std::string ShowGraph::getLabel(const Action* a, const bool& amp) {
     141         481 :   return getLabel( a->getLabel(), amp );
     142             : }
     143             : 
     144         481 : std::string ShowGraph::getLabel( const std::string& s, const bool& amp ) {
     145         481 :   if( s.find("@")==std::string::npos ) {
     146         427 :     return s;
     147             :   }
     148          54 :   std::size_t p=s.find_first_of("@");
     149          54 :   if( amp ) {
     150          34 :     return "#64;" + s.substr(p+1);
     151             :   }
     152          37 :   return s.substr(p+1);
     153             : }
     154             : 
     155         104 : void ShowGraph::printStyle( const unsigned& linkcount, const Value* v, OFile& ofile ) {
     156         104 :   if( v->getRank()>0 && v->hasDerivatives() ) {
     157           0 :     ofile.printf("linkStyle %d stroke:green,color:green;\n", linkcount);
     158         104 :   } else if( v->getRank()==1 ) {
     159          40 :     ofile.printf("linkStyle %d stroke:blue,color:blue;\n", linkcount);
     160          64 :   } else if ( v->getRank()==2 ) {
     161          36 :     ofile.printf("linkStyle %d stroke:red,color:red;\n", linkcount);
     162             :   }
     163         104 : }
     164             : 
     165          94 : void ShowGraph::printArgumentConnections( const ActionWithArguments* a, unsigned& linkcount, const bool& force, OFile& ofile ) {
     166          94 :   if( !a ) {
     167             :     return;
     168             :   }
     169          74 :   unsigned kargs = 0, nargs = a->getNumberOfArguments();
     170          74 :   const ActionWithVector* av=dynamic_cast<const ActionWithVector*>( a );
     171          74 :   if( av && av->getNumberOfMasks()>0 ) {
     172           2 :     nargs = nargs - av->getNumberOfMasks();
     173             :   }
     174         158 :   for(const auto & v : a->getArguments() ) {
     175          84 :     kargs++;
     176          84 :     if( force && v->forcesWereAdded() ) {
     177          41 :       if( !v->isConstant() ) {
     178          36 :         if( kargs>nargs ) {
     179           2 :           ofile.printf("%s -. %s .-> %s\n", getLabel(v->getPntrToAction()).c_str(),v->getName().c_str(),getLabel(a).c_str() );
     180             :         } else {
     181          70 :           ofile.printf("%s -- %s --> %s\n", getLabel(a).c_str(), v->getName().c_str(), getLabel(v->getPntrToAction()).c_str() );
     182             :         }
     183          36 :         printStyle( linkcount, v, ofile );
     184          36 :         linkcount++;
     185             :       }
     186          43 :     } else if( !force ) {
     187          43 :       if( kargs>nargs ) {
     188           2 :         ofile.printf("%s -. %s .-> %s\n", getLabel(v->getPntrToAction()).c_str(),v->getName().c_str(),getLabel(a).c_str() );
     189             :       } else {
     190          84 :         ofile.printf("%s -- %s --> %s\n", getLabel(v->getPntrToAction()).c_str(),v->getName().c_str(),getLabel(a).c_str() );
     191             :       }
     192          43 :       printStyle( linkcount, v, ofile );
     193          43 :       linkcount++;
     194             :     }
     195             :   }
     196             : }
     197             : 
     198          77 : void ShowGraph::printAtomConnections( const ActionAtomistic* a, unsigned& linkcount, const bool& force, OFile& ofile ) {
     199          77 :   if( !a ) {
     200             :     return;
     201             :   }
     202         239 :   for(const auto & d : a->getDependencies() ) {
     203         184 :     ActionToPutData* dp=dynamic_cast<ActionToPutData*>(d);
     204         184 :     if( dp && dp->getLabel()=="posx" ) {
     205          22 :       if( force && (dp->copyOutput(0))->forcesWereAdded() ) {
     206          10 :         ofile.printf("%s --> MD\n", getLabel(a).c_str() );
     207          10 :         ofile.printf("linkStyle %d stroke:violet,color:violet;\n", linkcount);
     208          10 :         linkcount++;
     209             :       } else {
     210          12 :         ofile.printf("MD --> %s\n", getLabel(a).c_str() );
     211          12 :         ofile.printf("linkStyle %d stroke:violet,color:violet;\n", linkcount);
     212          12 :         linkcount++;
     213             :       }
     214         162 :     } else if( dp && dp->getLabel()!="posy" && dp->getLabel()!="posz" && dp->getLabel()!="Masses" && dp->getLabel()!="Charges" ) {
     215          25 :       if( force && (dp->copyOutput(0))->forcesWereAdded() ) {
     216          22 :         ofile.printf("%s -- %s --> %s\n",getLabel(a).c_str(), getLabel(d).c_str(), getLabel(d).c_str() );
     217          11 :         printStyle( linkcount, dp->copyOutput(0), ofile );
     218          11 :         linkcount++;
     219             :       } else {
     220          28 :         ofile.printf("%s -- %s --> %s\n", getLabel(d).c_str(),getLabel(d).c_str(),getLabel(a).c_str() );
     221          14 :         printStyle( linkcount, dp->copyOutput(0), ofile );
     222          14 :         linkcount++;
     223             :       }
     224          25 :       continue;
     225             :     }
     226         159 :     ActionWithVirtualAtom* dv=dynamic_cast<ActionWithVirtualAtom*>(d);
     227         159 :     if( dv ) {
     228           6 :       if( force && (dv->copyOutput(0))->forcesWereAdded() ) {
     229           4 :         ofile.printf("%s -- %s --> %s\n", getLabel(a).c_str(),getLabel(d).c_str(),getLabel(d).c_str() );
     230           2 :         ofile.printf("linkStyle %d stroke:violet,color:violet;\n", linkcount);
     231           2 :         linkcount++;
     232             :       } else {
     233           8 :         ofile.printf("%s -- %s --> %s\n", getLabel(d).c_str(),getLabel(d).c_str(),getLabel(a).c_str() );
     234           4 :         ofile.printf("linkStyle %d stroke:violet,color:violet;\n", linkcount);
     235           4 :         linkcount++;
     236             :       }
     237             :     }
     238             :   }
     239             : }
     240             : 
     241          10 : int ShowGraph::main(FILE* in, FILE*out,Communicator& pc) {
     242             : 
     243             :   std::string inpt;
     244          20 :   parse("--plumed",inpt);
     245             :   std::string outp;
     246          10 :   parse("--out",outp);
     247             :   bool forces;
     248          10 :   parseFlag("--force",forces);
     249             :   int natoms;
     250          10 :   parse("--natoms",natoms);
     251             : 
     252             :   // Create a plumed main object and initilize
     253          10 :   PlumedMain p;
     254          10 :   int rr=sizeof(double);
     255          10 :   p.cmd("setRealPrecision",&rr);
     256          10 :   double lunit=1.0;
     257          10 :   p.cmd("setMDLengthUnits",&lunit);
     258          10 :   double cunit=1.0;
     259          10 :   p.cmd("setMDChargeUnits",&cunit);
     260          10 :   double munit=1.0;
     261          10 :   p.cmd("setMDMassUnits",&munit);
     262          10 :   p.cmd("setPlumedDat",inpt.c_str());
     263          10 :   p.cmd("setLog",out);
     264          10 :   p.cmd("setNatoms",&natoms);
     265          10 :   p.cmd("init");
     266             : 
     267          10 :   unsigned linkcount=0;
     268          10 :   OFile ofile;
     269          10 :   ofile.open(outp);
     270          10 :   if( forces ) {
     271             :     unsigned step=1;
     272           5 :     p.cmd("setStep",step);
     273           5 :     p.cmd("prepareCalc");
     274           5 :     ofile.printf("flowchart BT \n");
     275           5 :     std::vector<std::string> drawn_nodes;
     276             :     std::set<std::string> atom_force_set;
     277         127 :     for(auto pp=p.getActionSet().rbegin(); pp!=p.getActionSet().rend(); ++pp) {
     278             :       const auto & a(pp->get());
     279         122 :       if( a->getName()=="DOMAIN_DECOMPOSITION" || a->getLabel()=="posx" || a->getLabel()=="posy" || a->getLabel()=="posz" || a->getLabel()=="Masses" || a->getLabel()=="Charges" ) {
     280          30 :         continue;
     281             :       }
     282             : 
     283          92 :       if(a->isActive()) {
     284          54 :         ActionToPutData* ap=dynamic_cast<ActionToPutData*>(a);
     285          54 :         if( ap ) {
     286          10 :           ofile.printf("%s(\"label=%s \n %s \n\")\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
     287           5 :           continue;
     288             :         }
     289          49 :         ActionWithValue* av=dynamic_cast<ActionWithValue*>(a);
     290          49 :         if( !av ) {
     291           5 :           continue ;
     292             :         }
     293             :         // Now apply the force if there is one
     294          44 :         ActionWithVector* avv=dynamic_cast<ActionWithVector*>(a);
     295          44 :         ActionWithArguments* aaa=dynamic_cast<ActionWithArguments*>(a);
     296          44 :         if( avv ) {
     297          42 :           for(const auto & v : aaa->getArguments() ) {
     298          22 :             v->addForce();
     299             :           }
     300          85 :           for(const auto & d : a->getDependencies() ) {
     301          65 :             ActionToPutData* dp=dynamic_cast<ActionToPutData*>( d );
     302          65 :             if( dp && (dp->getLabel()=="posx" || dp->getLabel()=="Box") ) {
     303          14 :               (dp->copyOutput(0))->addForce();
     304             :             }
     305          65 :             ActionWithVirtualAtom* ava=dynamic_cast<ActionWithVirtualAtom*>( d );
     306          65 :             if( ava ) {
     307           1 :               (ava->copyOutput(0))->addForce();
     308             :             }
     309             :           }
     310             :         } else {
     311          24 :           a->apply();
     312             :         }
     313             :         bool hasforce=false;
     314          62 :         for(unsigned i=0; i<av->getNumberOfComponents(); ++i) {
     315          52 :           if( (av->copyOutput(i))->forcesWereAdded() ) {
     316             :             hasforce=true;
     317             :             break;
     318             :           }
     319             :         }
     320          44 :         if( aaa ) {
     321          37 :           for(const auto & v : aaa->getArguments() ) {
     322          31 :             if( v->forcesWereAdded() ) {
     323             :               hasforce=true;
     324             :               break;
     325             :             }
     326             :           }
     327             :         }
     328          44 :         if( !hasforce ) {
     329           2 :           continue;
     330             :         }
     331             :         // Print out the node if we have force on it
     332          84 :         ofile.printf("%s([\"label=%s \n %s \n\"])\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
     333             :         // Check where this force is being added
     334          42 :         printArgumentConnections( aaa, linkcount, true, ofile );
     335             :       }
     336             :     }
     337             :     // Now draw connections from action atomistic to relevant actions
     338           5 :     std::vector<ActionAtomistic*> all_atoms = p.getActionSet().select<ActionAtomistic*>();
     339          35 :     for(const auto & at : all_atoms ) {
     340          30 :       ActionWithValue* av=dynamic_cast<ActionWithValue*>(at);
     341          30 :       if( av ) {
     342          29 :         for(unsigned i=0; i<av->getNumberOfComponents(); ++i ) {
     343          27 :           if( av->copyOutput(i)->forcesWereAdded() ) {
     344          25 :             printAtomConnections( at, linkcount, true, ofile );
     345          25 :             atom_force_set.erase( av->getLabel() );
     346             :             break;
     347             :           }
     348             :         }
     349             :       }
     350             :     }
     351           5 :     for(const auto & l : atom_force_set ) {
     352           0 :       ActionAtomistic* at = p.getActionSet().selectWithLabel<ActionAtomistic*>(l);
     353           0 :       plumed_assert(at);
     354           0 :       printAtomConnections( at, linkcount, true, ofile );
     355             :     }
     356           5 :     ofile.printf("MD(positions from MD)\n");
     357             :     return 0;
     358           5 :   }
     359             : 
     360           5 :   ofile.printf("flowchart TB \n");
     361           5 :   ofile.printf("MD(positions from MD)\n");
     362         123 :   for(const auto & aa : p.getActionSet() ) {
     363             :     Action* a(aa.get());
     364         118 :     if( a->getName()=="DOMAIN_DECOMPOSITION" || a->getLabel()=="posx" || a->getLabel()=="posy" || a->getLabel()=="posz" || a->getLabel()=="Masses" || a->getLabel()=="Charges" ) {
     365          30 :       continue;
     366             :     }
     367          88 :     ActionToPutData* ap=dynamic_cast<ActionToPutData*>(a);
     368          88 :     if( ap ) {
     369          10 :       ofile.printf("%s(\"label=%s \n %s \n\")\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
     370           5 :       continue;
     371             :     }
     372          83 :     ActionShortcut* as=dynamic_cast<ActionShortcut*>(a);
     373          83 :     if( as ) {
     374          31 :       continue ;
     375             :     }
     376          52 :     ActionWithValue* av=dynamic_cast<ActionWithValue*>(a);
     377          52 :     ActionWithArguments* aaa=dynamic_cast<ActionWithArguments*>(a);
     378          52 :     ActionAtomistic* at=dynamic_cast<ActionAtomistic*>(a);
     379             :     // Print out the connections between nodes
     380          52 :     printAtomConnections( at, linkcount, false, ofile );
     381          52 :     printArgumentConnections( aaa, linkcount, false, ofile );
     382             :     // Print out the nodes
     383          52 :     if( !av ) {
     384          22 :       ofile.printf("%s(\"label=%s \n %s \n\")\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
     385             :     } else {
     386          82 :       ofile.printf("%s([\"label=%s \n %s \n\"])\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
     387             :     }
     388             :   }
     389           5 :   ofile.close();
     390             : 
     391             :   return 0;
     392          10 : }
     393             : 
     394             : } // End of namespace
     395             : }

Generated by: LCOV version 1.16