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