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 : }
|