interview.py 9.04 KB
Newer Older
Theresa Pollinger's avatar
Theresa Pollinger committed
1
2
#!/usr/bin/env python3

3
# http://cmd2.readthedocs.io
Theresa Pollinger's avatar
Theresa Pollinger committed
4
import cmd2 as cmd
5
# http://mattoc.com/python-yes-no-prompt-cli.html
Theresa Pollinger's avatar
Theresa Pollinger committed
6
from distutils.util import strtobool
7
# https://github.com/phfaist/pylatexenc for directly converting Latex commands to unicode
Theresa Pollinger's avatar
Theresa Pollinger committed
8
9
10
from pylatexenc.latex2text import LatexNodes2Text
import pyparsing as pp

11
from pde_state_machine import *
12

13
14
15
16
17
18
19
20
import matplotlib
matplotlib.use('nbagg')
import matplotlib.pyplot as plt
from bokeh.io import output_notebook, show, export_svgs
from bokeh.plotting import figure
from tempfile import NamedTemporaryFile

# This "main class" is a REPL loop, by subclassing the cmd2 Cmd class
21
class Interview(cmd.Cmd):
Theresa Pollinger's avatar
Theresa Pollinger committed
22
    def __init__(self, *args, **kwargs):
23
24
25

        self.state_machine = PDE_States(self.poutput, self.update_prompt, self.please_prompt)

Theresa Pollinger's avatar
Theresa Pollinger committed
26
27
28
        # initialize legal characters for cmd
        self.legalChars = u'!#$%.:;?@_-<>' + pp.printables + pp.alphas8bit + pp.punc8bit
        # TODO why does "<" not show?
29
30
        # allow all useful unicode characters to be used, and some more
        for i in range(0x20, 0x2E7F):
Theresa Pollinger's avatar
Theresa Pollinger committed
31
32
33
34
35
36
            self.legalChars += chr(i)

        # call cmd constructor
        super(Interview, self).__init__(*args, **kwargs)

        # Initialize cmd member variables
37
        self.myname = 'TheInterview'
Theresa Pollinger's avatar
Theresa Pollinger committed
38
        self.username = 'user'
39
40
41
42
        self.intro = "Hello, " + self.username + "! I am " + self.myname + \
                     ", your partial differential equations and simulations expert. " \
                     "Let's set up a simulation together.\n" \
                     "Please enter anything to start the interview."
43
        # self.greeting()
Theresa Pollinger's avatar
Theresa Pollinger committed
44
45
        self.update_prompt()

46

47
    #### functions for user interaction
Theresa Pollinger's avatar
Theresa Pollinger committed
48
49

    def obviously_stupid_input(self):
50
        self.poutput("Trying to be funny, huh?")
Theresa Pollinger's avatar
Theresa Pollinger committed
51

52
    ############# input processing if not explain or undo
Theresa Pollinger's avatar
Theresa Pollinger committed
53
54
55
    def default(self, line):
        raw = line.parsed['raw']
        arg = LatexNodes2Text().latex_to_text(raw)
56
        # pythonic switch-case, cf. https://bytebaker.com/2008/11/03/switch-case-statement-in-python/
57

58
59
60
        if not self.keyword_handling(arg):
            if not self.prompt_input_handling(arg):
                self.state_input_handling(arg)
61
62
63
64

    def state_input_handling(self, arg):
        """The standard input handling, depending on which state we are in"""
        # pythonic switch-case, cf. https://bytebaker.com/2008/11/03/switch-case-statement-in-python/
Theresa Pollinger's avatar
Theresa Pollinger committed
65
        try:
66
            self.state_machine.stateDependentInputHandling[self.state_machine.state](arg)
67
        except Exception as error:
68
            #self.state_machine.exaout.create_output(self.state_machine.simdata)
Theresa Pollinger's avatar
Theresa Pollinger committed
69
70
            raise

71
    def please_prompt(self, query, if_yes, if_no=None, pass_other=False):
Theresa Pollinger's avatar
Theresa Pollinger committed
72
        self.poutput(str(query) + " [y/n]? ")
73
74
75
        self.state_machine.prompted = True
        self.state_machine.if_yes = if_yes
        self.state_machine.if_no = if_no
76
        self.state_machine.pass_other = pass_other
77
78
79
80

    def prompt_input_handling(self, arg):
        """ If we asked for a yes-no answer, execute what was specified in please_prompt.
        return true if the input was handled here, and false if not."""
81
        if self.state_machine.prompted:
82
83
84
85
86
87
            if arg == "":
                ret = True
            else:
                try:
                    ret = strtobool(str(arg).strip().lower())
                except ValueError:
88
89
                    if self.state_machine.pass_other:
                        return False
90
                    # or use as input to callback an input processing fcn..?
91
                    self.poutput("Please answer with y/n")
92
                    return True
93
            self.state_machine.prompted = False
94
            if ret:
95
96
                if self.state_machine.if_yes is not None:
                    self.state_machine.if_yes()
97
98
            elif self.state_machine.if_no is not None:
                self.state_machine.if_no()
99
100
101
            return True
        return False

102
103
104
105
106
107
108
109
110
111
112
    def keyword_handling(self, arg):
        """ If keywords for special meta-functions are given,
        executes the corresponding functions and returns true if it did."""
        if arg.startswith("explain"):
            self.state_machine.explain(arg)
            return True
        if arg.startswith("recap"):
            self.state_machine.recap(arg)
            return True
        return False

Theresa Pollinger's avatar
Theresa Pollinger committed
113
    def help_explain(self):
114
115
116
        self.poutput('\n'.join(['explain [expression]',
                                'explain the expression given or the theory currently used',
                                ]))
Theresa Pollinger's avatar
Theresa Pollinger committed
117
118
119
120
121
122
123

    # called when user types 'undo'
    def do_undo(self, expression):
        "Go back to the last question"
        self.trigger('last_state')

    def help_undo(self):
124
125
126
        self.poutput('\n'.join(['undo',
                                'Go back to the last question',
                                ]))
Theresa Pollinger's avatar
Theresa Pollinger committed
127
128

    def update_prompt(self):
129
        self.prompt = "(" + self.state_machine.state + ")"
Theresa Pollinger's avatar
Theresa Pollinger committed
130

131
    # tab completion for empty lines
Theresa Pollinger's avatar
Theresa Pollinger committed
132
133
134
135
136
137
    def completenames(self, text, line, begidx, endidx):
        """Override of cmd2 method which completes command names both for command completion and help."""
        command = text
        if self.case_insensitive:
            command = text.lower()
        if not command:
138
            # define the "default" input for the different states we can be in
Theresa Pollinger's avatar
Theresa Pollinger committed
139
140
141
            self.stateDependentDefaultInput = {
                'dimensions': '1',
                'domain': ['Ω = [ 0 ; 1 ]'],
142
                'unknowns': ['u : Ω → ℝ'],
143
                'parameters': ['f :  ℝ → ℝ = [x: ℝ] x '],  # ['f : Ω → ℝ = [x:Ω] x ⋅ x'],
144
145
146
                'pdes': ['∆u = f(x_1)'],
                'bcs': ['u (0) = 0'],  # ,'u (1) = x_1**2'],
                'sim': ['FD'],
Theresa Pollinger's avatar
Theresa Pollinger committed
147
            }
148
            return self.stateDependentDefaultInput[self.state_machine.state]
Theresa Pollinger's avatar
Theresa Pollinger committed
149
150
151
152
153
154
155
156
157
        else:
            # Call super class method.  Need to do it this way for Python 2 and 3 compatibility
            cmd_completion = cmd.Cmd.completenames(self, command)

            # If we are completing the initial command name and get exactly 1 result and are at end of line, add a space
            if begidx == 0 and len(cmd_completion) == 1 and endidx == len(line):
                cmd_completion[0] += ' '
            return cmd_completion

158
159
160
161
    def greeting(self):  # TODO make work in proper order
        self.poutput(
            "Hello, " + self.username + "! I am " + self.myname + ", your partial differential equations and simulations expert. " \
                                                                  "Let's set up a simulation together.\n")
Theresa Pollinger's avatar
Theresa Pollinger committed
162
163
        self.trigger("greeting_over")

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    def do_plt(self, userstring):
        plt.ion()

        plot = plt.plot([3, 8, 2, 5, 1])
        #self.Display(plot)
        plt.show() #TODO find out why there is no comm and interactive shell - and if it should be there

        # cf. http://ipython-books.github.io/16-creating-a-simple-kernel-for-jupyter/
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.plot([3, 8, 2, 5, 1])

        # We create a PNG out of this plot.
        png = _to_png(fig)

        with open('png.png', 'w') as f:
            f.write(png)

        # and send it along as rich content
        self.richcontent = dict()

        # We prepare the response with our rich data (the plot).
        self.richcontent['source'] = 'kernel'

        # This dictionary may contain different MIME representations of the output.
        self.richcontent['data'] = {
                                       'image/png': png  # TODO error: Notebook JSON is invalid: [{'image/png': ...
                                   },
        # We can specify the image size in the metadata field.
        self.richcontent['metadata'] = {
            'image/png': {
                'width': 600,
                'height': 400
            }
        }
        self.poutput("image!")

    # cf. nbviewer.jupyter.org/github/bokeh/bokeh-notebooks/blob/master/tutorial/01 - Basic Plotting.ipynb
    def do_bokeh(self, userstring):
        # create a new plot with default tools, using figure
        p = figure(plot_width=400, plot_height=400)

        # add a circle renderer with a size, color, and alpha
        p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=15, line_color="navy", fill_color="orange", fill_alpha=0.5)
        show(p)
        t = NamedTemporaryFile(suffix="svg")
        p.output_backend = "svg"
        export_svgs(p, filename=t.name)
        svg = open(t.name).buffer

        # and send it along as rich content
        self.richcontent = dict()

        # We prepare the response with our rich data (the plot).
        self.richcontent['source'] = 'kernel'

        # This dictionary may contain different MIME representations of the output.
        self.richcontent['data'] = {
                                       'image/svg': svg
                                   },
        # We can specify the image size in the metadata field.
        self.richcontent['metadata'] = {
            'image/svg': {
                'width': 600,
                'height': 400
            }
        }
231

Theresa Pollinger's avatar
Theresa Pollinger committed
232
233
if __name__ == '__main__':
    Interview().cmdloop()