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/ActionWithVector.h"
33 : #include <cstdio>
34 : #include <string>
35 : #include <iostream>
36 :
37 : namespace PLMD {
38 : namespace cltools {
39 :
40 : //+PLUMEDOC TOOLS show_graph
41 : /*
42 : show_graph is a tool that takes a plumed input and generates a flowchart showing how
43 : data flows through the action set involved.
44 :
45 : For example, if we have the following plumed input:
46 :
47 : ```plumed
48 : d1: DISTANCE ATOMS=1,2
49 : a1: ANGLE ATOMS=1,2,3
50 : t1: TORSION ATOMS=1,2,3,4
51 : r: RESTRAINT ARG=d1,a1 AT=1.0,pi/2 KAPPA=100,100
52 : PRINT ARG=d1,a1,t1,r.* FILE=colvar
53 : ```
54 :
55 : We can use the command:
56 :
57 : ```plumed
58 : plumed show_graph --plumed plumed.dat
59 : ```
60 :
61 : To generate the following flowchart showing how data passes through these actions during the PLUMED calculation.
62 :
63 : ```plumed
64 : #MERMAID=value
65 : d1: DISTANCE ATOMS=1,2
66 : a1: ANGLE ATOMS=1,2,3
67 : t1: TORSION ATOMS=1,2,3,4
68 : r: RESTRAINT ARG=d1,a1 AT=1.0,pi/2 KAPPA=100,100
69 : PRINT ARG=d1,a1,t1,r.* FILE=colvar
70 : ```
71 :
72 : Furthermore, if we want to understand how forces on the atoms are calculated from these actions by using the chain rule
73 : we can use the following command:
74 :
75 : ```plumed
76 : plumed show_graph --plumed plumed.dat --force
77 : ```
78 :
79 : To generate the following flowchart:
80 :
81 : ```plumed
82 : #MERMAID=force
83 : d1: DISTANCE ATOMS=1,2
84 : a1: ANGLE ATOMS=1,2,3
85 : t1: TORSION ATOMS=1,2,3,4
86 : r: RESTRAINT ARG=d1,a1 AT=1.0,pi/2 KAPPA=100,100
87 : PRINT ARG=d1,a1,t1,r.* FILE=colvar
88 : ```
89 :
90 : These flowcharts are output in a file called `graph.md` unless you use the `--out` option as shown below:
91 :
92 : ```plumed
93 : plumed show_graph --plumed plumed.dat --out mygraph.md
94 : ```
95 :
96 : In this case the flowchart is output to a file called `mygraph.md`. This file contains the instructions for constructing the
97 : flowchart in [mermaid flowchart syntax](https://mermaid.js.org/syntax/flowchart.html). To construct images similar to those
98 : above you can copy and paste the contents of the output `graph.md` file into [this online tool for
99 : rendering mermaid diagrams](https://mermaid.live).
100 :
101 : If you are writing documentation for PLUMED or tutorials for the [plumed tutorials](www.plumed-tutorials.org) site you can add
102 : these diagrams by adding the instruction `#MERMAID=value` or `#MERMAID=force` into example inputs. When these options are given
103 : inputs are displayed as mermaid diagrams in the final output html.
104 :
105 : */
106 : //+ENDPLUMEDOC
107 :
108 : class ShowGraph :
109 : public CLTool {
110 : public:
111 : static void registerKeywords( Keywords& keys );
112 : explicit ShowGraph(const CLToolOptions& co );
113 : int main(FILE* in, FILE*out,Communicator& pc);
114 5 : std::string description()const {
115 5 : return "generate a graph showing how data flows through a PLUMED action set";
116 : }
117 : std::string getLabel(const Action* a, const bool& amp=false);
118 : std::string getLabel(const std::string& s, const bool& amp=false );
119 : void printStyle( const unsigned& linkcount, const Value* v, OFile& ofile );
120 : void printArgumentConnections( const ActionWithArguments* a, unsigned& linkcount, const bool& force, OFile& ofile );
121 : void printAtomConnections( const ActionAtomistic* a, unsigned& linkcount, const bool& force, OFile& ofile );
122 : void drawActionWithVectorNode( OFile& ofile, PlumedMain& p, Action* ag, const std::vector<std::string>& mychain, std::vector<bool>& printed );
123 : };
124 :
125 16267 : PLUMED_REGISTER_CLTOOL(ShowGraph,"show_graph")
126 :
127 5418 : void ShowGraph::registerKeywords( Keywords& keys ) {
128 5418 : CLTool::registerKeywords( keys );
129 5418 : keys.add("compulsory","--plumed","plumed.dat","the plumed input that we are generating the graph for");
130 5418 : keys.add("compulsory","--out","graph.md","the dot file containing the graph that has been generated");
131 5418 : keys.addFlag("--force",false,"print a graph that shows how forces are passed through the actions");
132 5418 : }
133 :
134 13 : ShowGraph::ShowGraph(const CLToolOptions& co ):
135 13 : CLTool(co) {
136 13 : inputdata=commandline;
137 13 : }
138 :
139 377 : std::string ShowGraph::getLabel(const Action* a, const bool& amp) {
140 377 : return getLabel( a->getLabel(), amp );
141 : }
142 :
143 453 : std::string ShowGraph::getLabel( const std::string& s, const bool& amp ) {
144 453 : if( s.find("@")==std::string::npos ) {
145 405 : return s;
146 : }
147 48 : std::size_t p=s.find_first_of("@");
148 48 : if( amp ) {
149 30 : return "#64;" + s.substr(p+1);
150 : }
151 33 : return s.substr(p+1);
152 : }
153 :
154 85 : void ShowGraph::printStyle( const unsigned& linkcount, const Value* v, OFile& ofile ) {
155 85 : if( v->getRank()>0 && v->hasDerivatives() ) {
156 0 : ofile.printf("linkStyle %d stroke:green,color:green;\n", linkcount);
157 85 : } else if( v->getRank()==1 ) {
158 33 : ofile.printf("linkStyle %d stroke:blue,color:blue;\n", linkcount);
159 52 : } else if ( v->getRank()==2 ) {
160 30 : ofile.printf("linkStyle %d stroke:red,color:red;\n", linkcount);
161 : }
162 85 : }
163 :
164 63 : void ShowGraph::printArgumentConnections( const ActionWithArguments* a, unsigned& linkcount, const bool& force, OFile& ofile ) {
165 63 : if( !a ) {
166 : return;
167 : }
168 101 : for(const auto & v : a->getArguments() ) {
169 55 : if( force && v->forcesWereAdded() ) {
170 28 : ofile.printf("%s -- %s --> %s\n", getLabel(a).c_str(), v->getName().c_str(), getLabel(v->getPntrToAction()).c_str() );
171 14 : printStyle( linkcount, v, ofile );
172 14 : linkcount++;
173 41 : } else if( !force ) {
174 66 : ofile.printf("%s -- %s --> %s\n", getLabel(v->getPntrToAction()).c_str(),v->getName().c_str(),getLabel(a).c_str() );
175 33 : printStyle( linkcount, v, ofile );
176 33 : linkcount++;
177 : }
178 : }
179 : }
180 :
181 55 : void ShowGraph::printAtomConnections( const ActionAtomistic* a, unsigned& linkcount, const bool& force, OFile& ofile ) {
182 55 : if( !a ) {
183 : return;
184 : }
185 179 : for(const auto & d : a->getDependencies() ) {
186 138 : ActionToPutData* dp=dynamic_cast<ActionToPutData*>(d);
187 138 : if( dp && dp->getLabel()=="posx" ) {
188 18 : if( force && (dp->copyOutput(0))->forcesWereAdded() ) {
189 8 : ofile.printf("%s --> MD\n", getLabel(a).c_str() );
190 8 : ofile.printf("linkStyle %d stroke:violet,color:violet;\n", linkcount);
191 8 : linkcount++;
192 : } else {
193 10 : ofile.printf("MD --> %s\n", getLabel(a).c_str() );
194 10 : ofile.printf("linkStyle %d stroke:violet,color:violet;\n", linkcount);
195 10 : linkcount++;
196 : }
197 120 : } else if( dp && dp->getLabel()!="posy" && dp->getLabel()!="posz" && dp->getLabel()!="Masses" && dp->getLabel()!="Charges" ) {
198 21 : if( force && (dp->copyOutput(0))->forcesWereAdded() ) {
199 18 : ofile.printf("%s -- %s --> %s\n",getLabel(a).c_str(), getLabel(d).c_str(), getLabel(d).c_str() );
200 9 : printStyle( linkcount, dp->copyOutput(0), ofile );
201 9 : linkcount++;
202 : } else {
203 24 : ofile.printf("%s -- %s --> %s\n", getLabel(d).c_str(),getLabel(d).c_str(),getLabel(a).c_str() );
204 12 : printStyle( linkcount, dp->copyOutput(0), ofile );
205 12 : linkcount++;
206 : }
207 21 : continue;
208 : }
209 117 : ActionWithVirtualAtom* dv=dynamic_cast<ActionWithVirtualAtom*>(d);
210 117 : if( dv ) {
211 4 : if( force && (dv->copyOutput(0))->forcesWereAdded() ) {
212 2 : ofile.printf("%s -- %s --> %s\n", getLabel(a).c_str(),getLabel(d).c_str(),getLabel(d).c_str() );
213 1 : ofile.printf("linkStyle %d stroke:violet,color:violet;\n", linkcount);
214 1 : linkcount++;
215 : } else {
216 6 : ofile.printf("%s -- %s --> %s\n", getLabel(d).c_str(),getLabel(d).c_str(),getLabel(a).c_str() );
217 3 : ofile.printf("linkStyle %d stroke:violet,color:violet;\n", linkcount);
218 3 : linkcount++;
219 : }
220 : }
221 : }
222 : }
223 :
224 30 : void ShowGraph::drawActionWithVectorNode( OFile& ofile, PlumedMain& p, Action* ag, const std::vector<std::string>& mychain, std::vector<bool>& printed ) {
225 30 : ActionWithVector* agg=dynamic_cast<ActionWithVector*>(ag);
226 : std::vector<std::string> matchain;
227 30 : agg->getAllActionLabelsInMatrixChain( matchain );
228 30 : if( matchain.size()>0 ) {
229 16 : ofile.printf("subgraph sub%s_mat [%s]\n",getLabel(agg).c_str(), getLabel(agg).c_str());
230 24 : for(unsigned j=0; j<matchain.size(); ++j ) {
231 16 : Action* agm=p.getActionSet().selectWithLabel<Action*>(matchain[j]);
232 60 : for(unsigned k=0; k<mychain.size(); ++k ) {
233 60 : if( mychain[k]==matchain[j] ) {
234 : printed[k]=true;
235 16 : break;
236 : }
237 : }
238 32 : ofile.printf("%s([\"label=%s \n %s \n\"])\n", getLabel(matchain[j]).c_str(), getLabel(matchain[j],true).c_str(), agm->writeInGraph().c_str() );
239 : }
240 8 : ofile.printf("end\n");
241 16 : ofile.printf("style sub%s_mat fill:lightblue\n",getLabel(ag).c_str());
242 : } else {
243 44 : ofile.printf("%s([\"label=%s \n %s \n\"])\n", getLabel(ag->getLabel()).c_str(), getLabel(ag->getLabel(),true).c_str(), ag->writeInGraph().c_str() );
244 : }
245 30 : }
246 :
247 8 : int ShowGraph::main(FILE* in, FILE*out,Communicator& pc) {
248 :
249 : std::string inpt;
250 16 : parse("--plumed",inpt);
251 : std::string outp;
252 8 : parse("--out",outp);
253 : bool forces;
254 8 : parseFlag("--force",forces);
255 :
256 : // Create a plumed main object and initilize
257 8 : PlumedMain p;
258 8 : int rr=sizeof(double);
259 8 : p.cmd("setRealPrecision",&rr);
260 8 : double lunit=1.0;
261 8 : p.cmd("setMDLengthUnits",&lunit);
262 8 : double cunit=1.0;
263 8 : p.cmd("setMDChargeUnits",&cunit);
264 8 : double munit=1.0;
265 8 : p.cmd("setMDMassUnits",&munit);
266 8 : p.cmd("setPlumedDat",inpt.c_str());
267 8 : p.cmd("setLog",out);
268 8 : int natoms=1000000;
269 8 : p.cmd("setNatoms",&natoms);
270 8 : p.cmd("init");
271 :
272 8 : unsigned linkcount=0;
273 8 : OFile ofile;
274 8 : ofile.open(outp);
275 8 : if( forces ) {
276 : unsigned step=1;
277 4 : p.cmd("setStep",step);
278 4 : p.cmd("prepareCalc");
279 4 : ofile.printf("flowchart BT \n");
280 : std::vector<std::string> drawn_nodes;
281 : std::set<std::string> atom_force_set;
282 103 : for(auto pp=p.getActionSet().rbegin(); pp!=p.getActionSet().rend(); ++pp) {
283 : const auto & a(pp->get());
284 534 : if( a->getName()=="DOMAIN_DECOMPOSITION" || a->getLabel()=="posx" || a->getLabel()=="posy" || a->getLabel()=="posz" || a->getLabel()=="Masses" || a->getLabel()=="Charges" ) {
285 24 : continue;
286 : }
287 :
288 75 : if(a->isActive()) {
289 44 : ActionToPutData* ap=dynamic_cast<ActionToPutData*>(a);
290 44 : if( ap ) {
291 8 : ofile.printf("%s(\"label=%s \n %s \n\")\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
292 4 : continue;
293 : }
294 40 : ActionWithValue* av=dynamic_cast<ActionWithValue*>(a);
295 40 : if( !av ) {
296 5 : continue ;
297 : }
298 : // Now apply the force if there is one
299 35 : a->apply();
300 : bool hasforce=false;
301 67 : for(int i=0; i<av->getNumberOfComponents(); ++i) {
302 42 : if( (av->copyOutput(i))->forcesWereAdded() ) {
303 : hasforce=true;
304 : break;
305 : }
306 : }
307 : //Check if there are forces here
308 35 : ActionWithArguments* aaa=dynamic_cast<ActionWithArguments*>(a);
309 35 : if( aaa ) {
310 46 : for(const auto & v : aaa->getArguments() ) {
311 30 : if( v->forcesWereAdded() ) {
312 : hasforce=true;
313 : break;
314 : }
315 : }
316 : }
317 35 : if( !hasforce ) {
318 14 : continue;
319 : }
320 21 : ActionWithVector* avec=dynamic_cast<ActionWithVector*>(a);
321 21 : if( avec ) {
322 8 : ActionWithVector* head=avec->getFirstActionInChain();
323 : std::vector<std::string> mychain;
324 8 : head->getAllActionLabelsInChain( mychain );
325 8 : std::vector<bool> printed(mychain.size(),false);
326 16 : ofile.printf("subgraph sub%s [%s]\n",getLabel(head).c_str(),getLabel(head).c_str());
327 70 : for(unsigned i=0; i<mychain.size(); ++i) {
328 : bool drawn=false;
329 314 : for(unsigned j=0; j<drawn_nodes.size(); ++j ) {
330 294 : if( drawn_nodes[j]==mychain[i] ) {
331 : drawn=true;
332 : break;
333 : }
334 : }
335 62 : if( drawn ) {
336 42 : continue;
337 : }
338 20 : ActionWithVector* ag=p.getActionSet().selectWithLabel<ActionWithVector*>(mychain[i]);
339 20 : plumed_assert( ag );
340 20 : drawn_nodes.push_back( mychain[i] );
341 20 : if( !printed[i] ) {
342 16 : drawActionWithVectorNode( ofile, p, ag, mychain, printed );
343 : printed[i]=true;
344 : }
345 41 : for(const auto & v : ag->getArguments() ) {
346 : bool chain_conn=false;
347 109 : for(unsigned j=0; j<mychain.size(); ++j) {
348 105 : if( (v->getPntrToAction())->getLabel()==mychain[j] ) {
349 : chain_conn=true;
350 : break;
351 : }
352 : }
353 21 : if( !chain_conn ) {
354 4 : continue;
355 : }
356 34 : ofile.printf("%s -. %s .-> %s\n", getLabel(v->getPntrToAction()).c_str(),v->getName().c_str(),getLabel(ag).c_str() );
357 17 : printStyle( linkcount, v, ofile );
358 17 : linkcount++;
359 : }
360 : }
361 8 : ofile.printf("end\n");
362 8 : if( avec!=head ) {
363 70 : for(unsigned i=0; i<mychain.size(); ++i) {
364 62 : ActionWithVector* c = p.getActionSet().selectWithLabel<ActionWithVector*>( mychain[i] );
365 62 : plumed_assert(c);
366 62 : if( c->getNumberOfAtoms()>0 || c->hasStoredArguments() ) {
367 60 : for(unsigned j=0; j<avec->getNumberOfComponents(); ++j ) {
368 30 : if( avec->copyOutput(j)->getRank()>0 ) {
369 20 : continue;
370 : }
371 20 : ofile.printf("%s == %s ==> %s\n", getLabel(avec).c_str(), avec->copyOutput(j)->getName().c_str(), getLabel(c).c_str() );
372 10 : linkcount++;
373 : }
374 30 : if( c->getNumberOfAtoms()>0 ) {
375 16 : atom_force_set.insert( c->getLabel() );
376 : }
377 : }
378 : }
379 : }
380 8 : } else {
381 : // Print out the node if we have force on it
382 26 : ofile.printf("%s([\"label=%s \n %s \n\"])\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
383 : }
384 : // Check where this force is being added
385 21 : printArgumentConnections( aaa, linkcount, true, ofile );
386 : }
387 : }
388 : // Now draw connections from action atomistic to relevant actions
389 4 : std::vector<ActionAtomistic*> all_atoms = p.getActionSet().select<ActionAtomistic*>();
390 33 : for(const auto & at : all_atoms ) {
391 29 : ActionWithValue* av=dynamic_cast<ActionWithValue*>(at);
392 : bool hasforce=false;
393 29 : if( av ) {
394 44 : for(unsigned i=0; i<av->getNumberOfComponents(); ++i ) {
395 26 : if( av->copyOutput(i)->forcesWereAdded() ) {
396 8 : printAtomConnections( at, linkcount, true, ofile );
397 8 : atom_force_set.erase( av->getLabel() );
398 : break;
399 : }
400 : }
401 : }
402 : }
403 9 : for(const auto & l : atom_force_set ) {
404 5 : ActionAtomistic* at = p.getActionSet().selectWithLabel<ActionAtomistic*>(l);
405 5 : plumed_assert(at);
406 5 : printAtomConnections( at, linkcount, true, ofile );
407 : }
408 4 : ofile.printf("MD(positions from MD)\n");
409 : return 0;
410 4 : }
411 :
412 4 : ofile.printf("flowchart TB \n");
413 4 : ofile.printf("MD(positions from MD)\n");
414 98 : for(const auto & aa : p.getActionSet() ) {
415 : Action* a(aa.get());
416 504 : if( a->getName()=="DOMAIN_DECOMPOSITION" || a->getLabel()=="posx" || a->getLabel()=="posy" || a->getLabel()=="posz" || a->getLabel()=="Masses" || a->getLabel()=="Charges" ) {
417 24 : continue;
418 : }
419 70 : ActionToPutData* ap=dynamic_cast<ActionToPutData*>(a);
420 70 : if( ap ) {
421 8 : ofile.printf("%s(\"label=%s \n %s \n\")\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
422 4 : continue;
423 : }
424 66 : ActionShortcut* as=dynamic_cast<ActionShortcut*>(a);
425 66 : if( as ) {
426 24 : continue ;
427 : }
428 42 : ActionWithValue* av=dynamic_cast<ActionWithValue*>(a);
429 42 : ActionWithArguments* aaa=dynamic_cast<ActionWithArguments*>(a);
430 42 : ActionAtomistic* at=dynamic_cast<ActionAtomistic*>(a);
431 42 : ActionWithVector* avec=dynamic_cast<ActionWithVector*>(a);
432 : // Print out the connections between nodes
433 42 : printAtomConnections( at, linkcount, false, ofile );
434 42 : printArgumentConnections( aaa, linkcount, false, ofile );
435 : // Print out the nodes
436 42 : if( avec && !avec->actionInChain() ) {
437 6 : ofile.printf("subgraph sub%s [%s]\n",getLabel(a).c_str(),getLabel(a).c_str());
438 : std::vector<std::string> mychain;
439 3 : avec->getAllActionLabelsInChain( mychain );
440 3 : std::vector<bool> printed(mychain.size(),false);
441 21 : for(unsigned i=0; i<mychain.size(); ++i) {
442 18 : Action* ag=p.getActionSet().selectWithLabel<Action*>(mychain[i]);
443 18 : if( !printed[i] ) {
444 14 : drawActionWithVectorNode( ofile, p, ag, mychain, printed );
445 : printed[i]=true;
446 : }
447 : }
448 3 : ofile.printf("end\n");
449 42 : } else if( !av ) {
450 22 : ofile.printf("%s(\"label=%s \n %s \n\")\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
451 28 : } else if( !avec ) {
452 26 : ofile.printf("%s([\"label=%s \n %s \n\"])\n", getLabel(a).c_str(), getLabel(a,true).c_str(), a->writeInGraph().c_str() );
453 : }
454 : }
455 4 : ofile.close();
456 :
457 : return 0;
458 8 : }
459 :
460 : } // End of namespace
461 : }
|