1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.ui;
39
40 import ffx.algorithms.AlgorithmFunctions;
41 import ffx.algorithms.AlgorithmListener;
42 import ffx.algorithms.Terminatable;
43 import ffx.algorithms.cli.AlgorithmsScript;
44 import ffx.algorithms.dynamics.MolecularDynamics;
45 import ffx.algorithms.dynamics.integrators.IntegratorEnum;
46 import ffx.algorithms.dynamics.thermostats.ThermostatEnum;
47 import ffx.algorithms.optimize.Minimize;
48 import ffx.numerics.Potential;
49 import ffx.potential.ForceFieldEnergy;
50 import ffx.potential.MolecularAssembly;
51 import ffx.potential.bonded.MSNode;
52 import ffx.potential.bonded.RendererCache.ColorModel;
53 import ffx.potential.bonded.RendererCache.ViewModel;
54 import ffx.potential.cli.PotentialScript;
55 import ffx.potential.utils.PotentialsFunctions;
56 import ffx.utilities.Console;
57 import ffx.utilities.GroovyFileFilter;
58 import groovy.lang.Binding;
59 import groovy.lang.Script;
60
61 import java.awt.Color;
62 import java.awt.Component;
63 import java.awt.Dimension;
64 import java.awt.EventQueue;
65 import java.awt.Menu;
66 import java.awt.MenuBar;
67 import java.awt.Point;
68 import java.awt.Rectangle;
69 import java.io.File;
70 import java.net.URL;
71 import java.nio.file.Path;
72 import java.nio.file.Paths;
73 import java.util.ArrayList;
74 import java.util.EventObject;
75 import java.util.List;
76 import java.util.Map;
77 import java.util.logging.Level;
78 import java.util.logging.Logger;
79 import javax.swing.ImageIcon;
80 import javax.swing.JFrame;
81 import javax.swing.JOptionPane;
82 import javax.swing.JScrollPane;
83 import javax.swing.JSplitPane;
84 import javax.swing.JTextPane;
85 import javax.swing.JViewport;
86 import javax.swing.SwingUtilities;
87 import javax.swing.text.Style;
88 import javax.swing.text.StyleConstants;
89 import javax.swing.text.StyleContext;
90 import javax.swing.text.StyledDocument;
91
92 import org.codehaus.groovy.runtime.MethodClosure;
93
94 import org.graalvm.polyglot.Context;
95 import org.graalvm.polyglot.Engine;
96 import org.graalvm.polyglot.Source;
97 import org.graalvm.polyglot.Value;
98 import org.graalvm.polyglot.Language;
99 import org.graalvm.polyglot.proxy.ProxyArray;
100
101
102
103
104
105
106
107
108 public class ModelingShell extends Console implements AlgorithmListener {
109
110
111
112
113 private static final Logger logger = Logger.getLogger(ModelingShell.class.getName());
114 private static final double toSeconds = 1.0e-9;
115
116
117
118
119 private final MainPanel mainPanel;
120
121
122
123 private final boolean headless;
124
125
126
127 private boolean interrupted;
128
129
130
131
132 private Terminatable terminatableAlgorithm = null;
133
134
135
136 private boolean scriptRunning;
137
138
139
140 private long time;
141
142 private long subTime;
143 private List<String> args;
144
145
146
147
148
149
150 public ModelingShell(MainPanel mainPanel) {
151 super();
152 this.mainPanel = mainPanel;
153 headless = java.awt.GraphicsEnvironment.isHeadless();
154 initContext(getShell().getContext());
155 }
156
157
158
159
160 public void after() {
161 time = System.nanoTime() - time;
162 scriptRunning = false;
163 if (!interrupted) {
164 appendOutput(String.format("\n Script wall clock time: %6.3f (sec)", time * toSeconds),
165 getPromptStyle());
166 } else {
167 appendOutput(String.format("\n Script interrupted after: %6.3f (sec)", time * toSeconds),
168 getPromptStyle());
169 }
170 mainPanel.getModelingPanel().enableLaunch(true);
171 }
172
173
174
175
176 @Override
177 public boolean algorithmUpdate(MolecularAssembly active) {
178 if (interrupted) {
179 return false;
180 }
181
182 GraphicsCanvas graphics = mainPanel.getGraphics3D();
183 if (graphics != null) {
184
185
186
187
188
189 graphics.updateSceneWait(active, true, false, null, false, null);
190
191 }
192
193
194 return !interrupted;
195 }
196
197
198
199
200
201
202 @Override
203 public Object askToInterruptScript() {
204 if (!scriptRunning) {
205 return true;
206 }
207 int rc = JOptionPane.showConfirmDialog(getScrollArea(),
208 "Script executing. Press 'OK' to attempt to interrupt it before exiting.",
209 "Force Field X Shell", JOptionPane.OK_CANCEL_OPTION);
210 if (rc == JOptionPane.OK_OPTION) {
211 doInterrupt();
212 return true;
213 } else {
214 return false;
215 }
216 }
217
218
219
220
221
222
223 @Override
224 public boolean askToSaveFile() {
225 File file = (File) getScriptFile();
226 if (file == null || !getDirty()) {
227 return true;
228 }
229 return switch (JOptionPane.showConfirmDialog((Component) getFrame(),
230 "Save changes to " + file.getName() + "?", "Force Field X Shell",
231 JOptionPane.YES_NO_CANCEL_OPTION)) {
232 case JOptionPane.YES_OPTION -> fileSave();
233 case JOptionPane.NO_OPTION -> true;
234 default -> false;
235 };
236 }
237
238
239
240
241 public void before() {
242 interrupted = false;
243 terminatableAlgorithm = null;
244 time = System.nanoTime();
245 subTime = time;
246 mainPanel.getModelingPanel().enableLaunch(false);
247 scriptRunning = true;
248 }
249
250 @Override
251 public void clearContext() {
252 super.clearContext();
253 initContext(getShell().getContext());
254 }
255
256 @Override
257 public void clearContext(EventObject evt) {
258 super.clearContext(evt);
259 initContext(getShell().getContext());
260 }
261
262
263
264
265
266
267 @Override
268 public void clearOutput() {
269 if (!java.awt.GraphicsEnvironment.isHeadless()) {
270 JTextPane output = getOutputArea();
271 output.setText("");
272 appendOutput(
273 MainPanel.border + "\n" + MainPanel.title + MainPanel.aboutString + "\n" + MainPanel.border
274 + "\n", getCommandStyle());
275 }
276 }
277
278 @Override
279 public void clearOutput(EventObject evt) {
280 clearOutput();
281 }
282
283
284
285
286
287
288 public ForceFieldEnergy energy() {
289 if (interrupted) {
290 logger.info(" Algorithm interrupted - skipping energy.");
291 return null;
292 }
293 if (terminatableAlgorithm != null) {
294 logger.info(" Algorithm already running - skipping energy.");
295 return null;
296 }
297
298 MolecularAssembly active = mainPanel.getHierarchy().getActive();
299 if (active != null) {
300 ForceFieldEnergy energy = active.getPotentialEnergy();
301 if (energy == null) {
302 energy = ForceFieldEnergy.energyFactory(active);
303 active.setPotential(energy);
304 }
305 energy.energy(false, true);
306 return energy;
307 }
308 return null;
309 }
310
311 @Override
312 public void fileNewWindow() {
313 mainPanel.resetShell();
314 }
315
316 @Override
317 public void fileNewWindow(EventObject evt) {
318 fileNewWindow();
319 }
320
321 public AlgorithmFunctions getUIAlgorithmUtils() {
322 return new UIUtils(this, mainPanel);
323 }
324
325 public PotentialsFunctions getUIPotentialsUtils() {
326 return new UIUtils(this, mainPanel);
327 }
328
329
330
331
332
333
334
335
336
337
338
339
340 public void md(int nStep, double timeStep, double printInterval, double saveInterval,
341 double temperature, boolean initVelocities, File dyn) {
342 if (interrupted || terminatableAlgorithm != null) {
343 return;
344 }
345 FFXSystem active = mainPanel.getHierarchy().getActive();
346 if (active != null) {
347 MolecularDynamics molecularDynamics = new MolecularDynamics(active,
348 active.getPotentialEnergy(), this, ThermostatEnum.BUSSI, IntegratorEnum.BEEMAN);
349 terminatableAlgorithm = molecularDynamics;
350 molecularDynamics.dynamic(nStep, timeStep, printInterval, saveInterval, temperature,
351 initVelocities, dyn);
352 terminatableAlgorithm = null;
353 }
354 }
355
356
357
358
359 @Override
360 public void run() {
361 if (!headless) {
362 if (SwingUtilities.isEventDispatchThread()) {
363 init();
364 } else {
365 try {
366 SwingUtilities.invokeAndWait(this::init);
367 } catch (Exception e) {
368
369 }
370 }
371 }
372 }
373
374
375
376
377
378
379 public void select(MSNode node) {
380 if (node != null) {
381 mainPanel.getHierarchy().onlySelection(node);
382 sync();
383 logger.info(String.format(" Selected: %s.", node));
384 }
385 }
386
387
388
389
390
391
392 public void setArgList(List<String> argList) {
393 args = new ArrayList<>(argList);
394 setVariable("args", argList);
395 }
396
397 @Override
398 public void showAbout() {
399 mainPanel.about();
400 }
401
402 @Override
403 public void showAbout(EventObject evt) {
404 showAbout();
405 }
406
407
408
409
410
411
412 public Double time() {
413 long current = System.nanoTime();
414 double timer = (current - subTime) * toSeconds;
415 subTime = current;
416 appendOutput(String.format("\n Intermediate time: %8.3f (sec)\n", timer), getPromptStyle());
417 return timer;
418 }
419
420
421
422
423 @Override
424 public String toString() {
425 return "Force Field X Shell";
426 }
427
428 @Override
429 public void updateTitle() {
430 JFrame frame = (JFrame) this.getFrame();
431 File file = (File) getScriptFile();
432 if (file != null) {
433 String name = file.getName();
434 frame.setTitle(name + " - Force Field X Shell");
435 } else {
436 frame.setTitle("Force Field X Shell");
437 }
438 }
439
440
441
442
443 private void initContext(Binding binding) {
444 binding.setVariable("dat", mainPanel.getHierarchy());
445 binding.setVariable("cmd", mainPanel);
446 binding.setVariable("vis", mainPanel.getGraphics3D());
447 binding.setVariable("sh", this);
448 binding.setVariable("active", mainPanel.getHierarchy().getActive());
449 binding.setVariable("logger", logger);
450
451
452 binding.setVariable("time", new MethodClosure(this, "time"));
453
454
455 binding.setVariable("open", new MethodClosure(mainPanel, "openWait"));
456 binding.setVariable("convertWait", new MethodClosure(mainPanel, "convertWait"));
457 binding.setVariable("save", new MethodClosure(mainPanel, "saveAsXYZ"));
458 binding.setVariable("saveAsXYZ", new MethodClosure(mainPanel, "saveAsXYZ"));
459 binding.setVariable("saveAsP1", new MethodClosure(mainPanel, "saveAsP1"));
460 binding.setVariable("saveAsPDB", new MethodClosure(mainPanel, "saveAsPDB"));
461 binding.setVariable("close", new MethodClosure(mainPanel, "closeWait"));
462 binding.setVariable("closeAll", new MethodClosure(mainPanel, "closeAll"));
463
464
465 binding.setVariable("select", new MethodClosure(this, "select"));
466
467
468 if (!headless) {
469 GraphicsCanvas graphics = mainPanel.getGraphics3D();
470
471
472 int index = 0;
473 for (ViewModel view : ViewModel.values()) {
474 binding.setVariable(view.name(), view);
475 index++;
476 if (index > 8) {
477 break;
478 }
479 }
480 binding.setVariable("view", new MethodClosure(graphics, "viewWait"));
481
482
483 index = 0;
484 for (ColorModel color : ColorModel.values()) {
485 binding.setVariable(color.name(), color);
486 index++;
487 if (index > 6) {
488 break;
489 }
490 }
491 binding.setVariable("color", new MethodClosure(graphics, "colorWait"));
492 }
493
494
495 binding.setVariable("returnEnergy", new MethodClosure(this, "returnEnergy"));
496 binding.setVariable("energy", new MethodClosure(this, "energy"));
497 binding.setVariable("analyze", new MethodClosure(this, "analyze"));
498 binding.setVariable("minimize", new MethodClosure(this, "minimize"));
499 binding.setVariable("minimize_2", new MethodClosure(this, "minimize_2"));
500 binding.setVariable("md", new MethodClosure(this, "md"));
501 binding.setVariable("potential", new MethodClosure(this, "potential"));
502 binding.setVariable("poledit", new MethodClosure(this, "poledit"));
503 binding.setVariable("superpose", new MethodClosure(this, "superpose"));
504
505
506 binding.setVariable("functions", new UIUtils(this, mainPanel));
507
508
509 binding.setVariable("listener", this);
510 }
511
512
513
514
515 private void initMenus() {
516 JFrame frame = (JFrame) this.getFrame();
517 MenuBar menuBar = frame.getMenuBar();
518
519 Menu menu = menuBar.getMenu(2);
520 menu.remove(5);
521 menu.remove(5);
522 menu.remove(9);
523
524
525 menu = menuBar.getMenu(4);
526 menu.remove(4);
527 menu.remove(4);
528 menu.remove(4);
529 menu.remove(5);
530 menu.remove(7);
531 }
532
533
534
535
536 private void init() {
537 try {
538 super.run();
539
540 JTextPane output = getOutputArea();
541 output.setBackground(Color.BLACK);
542 output.setForeground(Color.WHITE);
543
544 JTextPane input = getInputArea();
545 input.setBackground(Color.WHITE);
546 input.setForeground(Color.BLACK);
547
548 StyledDocument doc = output.getStyledDocument();
549 Style defStyle = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
550 Style regular = doc.addStyle("regular", defStyle);
551 Style prompt = doc.addStyle("prompt", regular);
552 Style command = doc.addStyle("command", regular);
553 Style result = doc.addStyle("result", regular);
554 StyleConstants.setFontFamily(regular, "Monospaced");
555 setPromptStyle(prompt);
556 setCommandStyle(command);
557 setResultStyle(result);
558 StyleConstants.setForeground(prompt, Color.ORANGE);
559 StyleConstants.setForeground(command, Color.GREEN);
560 StyleConstants.setForeground(result, Color.GREEN);
561 StyleConstants.setBackground(result, Color.BLACK);
562 clearOutput();
563
564
565
566 getStatusLabel().setText("Welcome to the Force Field X Shell.");
567 JFrame frame = (JFrame) this.getFrame();
568 frame.setTitle("Force Field X Shell");
569 URL iconURL = getClass().getClassLoader().getResource("ffx/ui/icons/icon64.png");
570 ImageIcon icon = new ImageIcon(iconURL);
571 frame.setIconImage(icon.getImage());
572 frame.setSize(600, 600);
573 } catch (Exception e) {
574 logger.warning(" Exception starting up the FFX console.\n" + e);
575 }
576 }
577
578 List<String> getArgs() {
579 return new ArrayList<>(args);
580 }
581
582
583
584
585
586
587
588
589 public Script runFFXScript(File file, List<String> argList) {
590 GroovyFileFilter groovyFileFilter = new GroovyFileFilter();
591
592 if (!groovyFileFilter.accept(file)) {
593
594 return runNonGroovyScript(file, argList);
595 }
596
597 logger.info(" Executing external Groovy script: " + file.getAbsolutePath() + "\n");
598 try {
599 before();
600 Script script = null;
601 try {
602
603 Object o = getShell().run(file, argList);
604
605 if (o instanceof Script) {
606 script = (Script) o;
607 }
608
609
610 if (headless) {
611 if (o instanceof PotentialScript) {
612 ((PotentialScript) o).destroyPotentials();
613 } else if (o instanceof AlgorithmsScript) {
614 ((AlgorithmsScript) o).destroyPotentials();
615 }
616 }
617 } catch (Exception ex) {
618 logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
619 }
620 after();
621 return script;
622 } catch (Exception e) {
623
624
625 String message = "Error evaluating script.";
626 logger.log(Level.WARNING, message, e);
627 }
628
629 return null;
630 }
631
632
633
634
635
636
637
638
639 public Script runNonGroovyScript(File file, List<String> argList) {
640 logger.info(" Attempting to execute Polyglot script:\n " + file.getAbsolutePath() + "\n");
641
642 if (logger.isLoggable(Level.FINE)) {
643 logger.fine(" Available languages: ");
644 try (Engine engine = Engine.create()) {
645 Map<String, Language> map = engine.getLanguages();
646 for (Map.Entry<String, Language> entry : map.entrySet()) {
647 logger.fine(" " + entry.getKey());
648 }
649 } catch (Exception ex) {
650 logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
651 }
652 }
653
654 try {
655 before();
656 Script script = null;
657 String language = Source.findLanguage(file);
658 logger.info(" Detected script language: " + language);
659 Source source = Source.newBuilder(language, file).build();
660 try (Context context = getContext(language)) {
661
662 Value bindings = context.getBindings(language);
663
664 List<Object> objList = new ArrayList<>(argList);
665 ProxyArray argArray = ProxyArray.fromList(objList);
666 bindings.putMember("args", argArray);
667
668 Value result = context.eval(source);
669 logger.info(" Execution of Polyglot script completed.");
670 } catch (Exception ex) {
671 logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
672 }
673 after();
674 return script;
675 } catch (Exception e) {
676
677 String message = "Error evaluating script.";
678 logger.log(Level.WARNING, message, e);
679 }
680 return null;
681 }
682
683 private Context getContext(String language) {
684 if (language.equalsIgnoreCase("python")) {
685
686
687
688 String FFX_HOME = System.getProperty("basedir");
689 Path graalpy = Paths.get(FFX_HOME, "ffx_venv", "bin", "graalpy");
690
691
692 String graalpyString = System.getProperty("graalpy", graalpy.toString());
693 graalpy = Paths.get(graalpyString);
694 if (graalpy.toFile().exists()) {
695 logger.fine(" graalpy (-Dgraalpy=path.to.graalpy): " + graalpy);
696 return Context.newBuilder(language).allowAllAccess(true).option("python.Executable", graalpyString).build();
697 }
698 }
699
700 return Context.newBuilder(language).allowAllAccess(true).build();
701 }
702
703
704
705
706
707
708
709
710 public Script runFFXScript(Class<? extends Script> script, List<String> argList) {
711 logger.info(" Executing internal script: " + script.getCanonicalName() + "\n");
712 try {
713 before();
714 Script groovyScript = null;
715 try {
716
717 Binding binding = new Binding();
718 binding.setVariable("args", argList);
719 initContext(binding);
720
721
722 groovyScript = script.getDeclaredConstructor().newInstance();
723 groovyScript.setBinding(binding);
724 groovyScript.run();
725
726
727 if (headless) {
728 if (groovyScript instanceof PotentialScript) {
729 ((PotentialScript) groovyScript).destroyPotentials();
730 } else if (groovyScript instanceof AlgorithmsScript) {
731 ((AlgorithmsScript) groovyScript).destroyPotentials();
732 }
733 }
734 } catch (Exception ex) {
735 logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
736 }
737 after();
738
739 return groovyScript;
740 } catch (Exception e) {
741
742
743 String message = "Error evaluating script.";
744 logger.log(Level.WARNING, message, e);
745 }
746 return null;
747 }
748
749
750
751
752
753
754 double returnEnergy() {
755 if (interrupted) {
756 logger.info(" Algorithm interrupted - skipping energy.");
757 return 0.0;
758 }
759 if (terminatableAlgorithm != null) {
760 logger.info(" Algorithm already running - skipping energy.");
761 return 0.0;
762 }
763
764 MolecularAssembly active = mainPanel.getHierarchy().getActive();
765 if (active != null) {
766 ForceFieldEnergy energy = active.getPotentialEnergy();
767 if (energy == null) {
768 energy = ForceFieldEnergy.energyFactory(active);
769 active.setPotential(energy);
770 }
771 return energy.energy(false, true);
772 }
773 logger.warning(" Energy could not be calculated");
774 return 0.0;
775 }
776
777
778
779
780
781
782
783 Potential minimize(double eps) {
784 if (interrupted) {
785 logger.info(" Algorithm interrupted - skipping minimization.");
786 return null;
787 }
788 if (terminatableAlgorithm != null) {
789 logger.info(" Algorithm already running - skipping minimization.");
790 return null;
791 }
792 MolecularAssembly active = mainPanel.getHierarchy().getActive();
793 if (active != null) {
794 Minimize minimize = new Minimize(active, this);
795 terminatableAlgorithm = minimize;
796 Potential potential = minimize.minimize(eps);
797 terminatableAlgorithm = null;
798 return potential;
799 } else {
800 logger.info(" No active system to minimize.");
801 }
802 return null;
803 }
804
805
806
807
808
809
810
811 void appendOutputNl(String string, Style style) {
812 if (interrupted) {
813 return;
814 }
815 if (headless) {
816 logger.info(string);
817 }
818 if (string.equals("Result: ")) {
819 string = " Script result: \n";
820 } else if (string.equals("groovy> ")) {
821 string = " ffx> ";
822 }
823
824 super.appendOutputNl(string, style);
825
826 if (EventQueue.isDispatchThread()) {
827 scroll();
828 } else {
829 SwingUtilities.invokeLater(this::scroll);
830 }
831 }
832
833
834
835
836 private void scroll() {
837 JTextPane output = getOutputArea();
838 JSplitPane splitPane = getSplitPane();
839 JScrollPane scrollPane = (JScrollPane) splitPane.getBottomComponent();
840 JViewport viewport = scrollPane.getViewport();
841 Rectangle visibleSize = viewport.getVisibleRect();
842 Dimension totalSize = output.getSize();
843 Point point = new Point(0, totalSize.height - visibleSize.height);
844 viewport.setViewPosition(point);
845 }
846
847
848
849
850
851
852
853 private void appendOutput(String string, Style style) {
854 if (interrupted) {
855 return;
856 }
857
858 if (headless) {
859 logger.info(string);
860 return;
861 }
862
863 super.appendOutput(string, style);
864 if (EventQueue.isDispatchThread()) {
865 scroll();
866 } else {
867 SwingUtilities.invokeLater(this::scroll);
868 }
869 }
870
871
872
873
874
875
876
877 void setMeasurement(String measurement, double d) {
878 try {
879 appendOutput(measurement, getOutputStyle());
880 } catch (Exception e) {
881 String message = "Exception appending measurement to Shell.\n";
882 logger.log(Level.WARNING, message, e);
883 }
884 }
885
886
887
888
889 void sync() {
890 try {
891 setVariable("active", mainPanel.getHierarchy().getActive());
892 } catch (Exception e) {
893 String message = " Exception syncing shell variables.\n";
894 logger.log(Level.WARNING, message, e);
895 }
896 }
897 }