interview_kernel.py 12.4 KB
Newer Older
1
2
3
from sys import executable
from os.path import join
#from pathlib import Path
4

5
from metakernel import MetaKernel
6
7
from IPython.display import HTML, Javascript
from metakernel import IPythonKernel
8
import ipywidgets as widgets
9

10
11
12
# http://mattoc.com/python-yes-no-prompt-cli.html
# https://github.com/phfaist/pylatexenc for directly converting Latex commands to unicode
from pylatexenc.latex2text import LatexNodes2Text
13
import getpass
14
from bokeh.io import output_notebook
15

16
from pde_state_machine import *
17
from string_handling import build_url, get_recursively
18
from distutils.util import strtobool
19

20

21
22
"""This is a Jupyter kernel derived from MetaKernel. To use it, install it with the install.py script and run 
"jupyter notebook --debug --NotebookApp.token='' " from terminal. """
23
class Interview(MetaKernel):
24
    implementation = 'Interview'
25
    implementation_version = '0.1'
Theresa Pollinger's avatar
Theresa Pollinger committed
26
    language = 'text'
27
28
    language_version = '0.1'
    language_info = {
Theresa Pollinger's avatar
Theresa Pollinger committed
29
        'name': 'text',
30
31
        'mimetype': 'text/plain',
        'file_extension': '.txt',
Theresa Pollinger's avatar
Theresa Pollinger committed
32
        'help_links': MetaKernel.help_links,
33
    }
Theresa Pollinger's avatar
Theresa Pollinger committed
34

35
36
37
38
39
40
41
    kernel_json = {
        "argv": [
            executable, "-m", "interview_kernel", "-f", "{connection_file}"],
        "display_name": "TheInterview",
        "language": "text",
        "name": "interview_kernel"
    }
42

43
44
45
46
47
48
    banner = \
"""**Hello, """ + getpass.getuser() + """! I am TheInterview, your partial differential equations and simulations expert.**
Let's set up a model and simulation together.

To get explanations, enter `explain <optional keyword>`. 
To see a recap of what we know so far, enter `recap <optional keyword>`. 
49
To interactively visualize the current theory graph, enter `tgwiev` or `tgview mpd`. 
50
51
52
53
Otherwise, you can always answer with \LaTeX-type input.


"""
54
    #You can inspect the currently loaded MMT theories under http://localhost:43397  #TODO
55

56
    def __init__(self, **kwargs):
57

58
        self.state_machine = PDE_States(self.poutput, self.update_prompt, self.please_prompt, self.display_html)
59

60
        # call superclass constructor
61
62
        super(Interview, self).__init__(**kwargs)

63
64
65
        # To make custom magics happen, cf. https://github.com/Calysto/metakernel
        # from IPython import get_ipython
        # from metakernel import register_ipython_magics
66
        # register_ipython_magics()
67
68
69

        self.update_prompt()
        self.poutstring = ""# to collect string output to send
70
        self.outstream_name = 'stdout'
71
72
73
74
75

        # already send some input to state machine, to capture initial output and have it displayed via kernel.js
        self.state_machine.handle_state_dependent_input("anything")   # TODO compatibility with not-notebook?
        self.my_markdown_greeting = Interview.banner + self.poutstring
        self.poutstring = ""
76
77
78

        # bokeh notebook setup
        output_notebook()
79

80
    def poutput(self, text, outstream_name='stdout'):
81
        """Accumulate the output here"""
82
        self.poutstring += str(text) + "\n"
83
        self.outstream_name = outstream_name
84
85

    ############# input processing if not explain or undo
86
87
    # def do_execute(self, code, silent=False, store_history=True, user_expressions=None,
    #                allow_stdin=False):
88
    def do_execute_direct(self, code, silent=False, allow_stdin=True):
89
        """This is where the user input enters our code"""
90

91
92
        arg = LatexNodes2Text().latex_to_text(code)

93
94
        if not self.keyword_handling(arg):
            if not self.prompt_input_handling(arg):
95
                self.state_machine.handle_state_dependent_input(arg)
96
97

        if not silent:
98
99
100
101
            if self.outstream_name == "stderr": #TODO make errors markdown but red
                # string output
                stream_content = {'name': self.outstream_name, 'text': self.poutstring}
                self.send_response(self.iopub_socket, 'stream', stream_content)
102
103
104
105
106
107
            #    data_content = {
            #                        "ename": "InterviewError",
            #                        "evalue": self.poutstring,
            #                        "traceback": [self.poutstring],
            #                    }
            #    self.send_response(self.iopub_socket, 'error', data_content)
108
109
110
111
112
113
114
115
            else:
                # for other mime types, cf. http://ipython.org/ipython-doc/stable/notebook/nbformat.html
                data_content = {"data": {
                                            "text/markdown": self.poutstring,
                                        },
                                "metadata": {}
                                }
                self.send_response(self.iopub_socket, 'display_data', data_content)
116

117
        self.poutstring = ""
118
        self.outstream_name = 'stdout'
119

Theresa Pollinger's avatar
Theresa Pollinger committed
120
        return  # stream_content['text']
121

122
    def please_prompt(self, query, if_yes, if_no=None, pass_other=False):
123
        self.poutput(str(query)) # + " [y/n]? ")
124
125
126
        self.state_machine.prompted = True
        self.state_machine.if_yes = if_yes
        self.state_machine.if_no = if_no
127
        self.state_machine.pass_other = pass_other
128
        self.display_widget()
129

130
    def prompt_input_handling(self, arg):  # TODO make this widget-ed
131
132
        """ 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."""
133
        if self.state_machine.prompted:
134
135
136
137
            if arg == "":
                ret = True
            else:
                try:
138
                    ret = strtobool(str(arg).strip().lower())
139
                except ValueError:
140
141
                    if self.state_machine.pass_other:
                        return False
142
                    # or use as input to callback an input processing fcn..?
143
                    self.poutput("Please answer with y/n")
144
                    return True
145
            self.state_machine.prompted = False
146
            if ret:
147
148
                if self.state_machine.if_yes is not None:
                    self.state_machine.if_yes()
149
150
            elif self.state_machine.if_no is not None:
                self.state_machine.if_no()
151
152
153
            return True
        return False

154
155
156
157
158
159
160
161
162
    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
163
164
        if arg.startswith("tgview"):
            self.display_tgview(arg)
165
            return True
166
167
        if arg.startswith("undo"):
            self.do_undo(arg)
168
            return True
169
170
171
        if arg.startswith("widget"):
            self.display_widget()
            return True
172
173
        return False

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
    # called when user types 'explain [expression]'
    def do_explain(self, expression):
        "Explain an expression or the theoretical background to what we are currently looking for"
        if expression:
            explanation = "hello, " + expression  # TODO query flexiformal content through mmt
        else:
            explanation = 'hello'
        self.poutput(explanation)

    def help_explain(self):
        self.poutput('\n'.join(['explain [expression]',
                                'explain the expression given or the theory currently used',
                                ]))

    # called when user types 'undo'
    def do_undo(self, expression):
        "Go back to the last question"
191
        self.state_machine.trigger('last_state')
192
193
194
195
196
197
198

    def help_undo(self):
        self.poutput('\n'.join(['undo',
                                'Go back to the last question',
                                ]))

    def update_prompt(self):
199
        self.prompt = "(" + self.state_machine.state + ")" #TODO
200
201

    # tab completion for empty lines
202
    def do_complete(self, code, cursor_pos):
203
        """Override of cmd2 method which completes command names both for command completion and help."""
204
205
206
207
208
209
210
211
212
213
214
215
216
        # define the "default" input for the different states we can be in
        state_dependent_default_input = {
            'greeting': 'hi',
            'dimensions': '1',
            'domain': ['Ω = [ 0 ; 1 ]'],
            'unknowns': ['u : Ω → ℝ'],
            'parameters': ['f :  ℝ → ℝ = [x: ℝ] x '],  # ['f : Ω → ℝ = [x:Ω] x ⋅ x'],
            'pdes': ['∆u = f(x)'],
            'bcs': ['u = 0'],  # ,'u (1) = x_1**2'],
            'sim': ['FD'],
        }
        if not code or state_dependent_default_input[self.state_machine.state].startswith(code):
            return state_dependent_default_input[self.state_machine.state]
217
        else:
218
219
220
            # Call super class method.
            super(Interview, self).do_complete(code, cursor_pos)
            return
221

222
223
224
    def display_html(self, code=None):

        # highlight some of the code entered and show line numbers (just to play around)
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
        #self.Display(HTML("""
        #<style type="text/css">
        #      .styled-background { background-color: #ff7; }
        #</style>
        #<script>
        #if (typeof markedText !== 'undefined') {
        #        markedText.clear();
        #}
        #IPython.notebook.select_prev()
        #var cell = IPython.notebook.get_selected_cell();
        #markedText = cell.code_mirror.markText({line: %s, col: %s},
        #                                       {line: %s, col: %s},
        #                                       {className: "styled-background"});
        #cell.show_line_numbers(1)
        #IPython.notebook.select_next()
        #</script>
        #                    """ % (1, 0, 3, 0)))
242

243
        output_notebook()
244
245
246
247
248
        if code:
            self.Display(HTML(code))

    def display_tgview(self, args=''):
        """displays the theory graph viewer as html, cf. https://github.com/UniFormal/TGView/wiki/"""
249

250
        args = args.replace("tgview", '', 1).strip()
251

252
        server_url = str(self.state_machine.mmtinterface.mmt_base_url)
253
254

        if args == '':
255
256
            url_args_dict = dict(type="pgraph",
                                 graphdata=self.state_machine.mmtinterface.namespace)
257
258
259
260
            # if applicable, highlight the ephemeral parts https://github.com/UniFormal/TGView/issues/25
            thynames = get_recursively(self.state_machine.simdata, "theoryname")
            # if thynames:
            #    url_args_dict["highlight"] = ",".join(thynames)
Theresa Pollinger's avatar
Theresa Pollinger committed
261
262
            # for now, highlight the "persistent ephemeral" theories, cf https://github.com/UniFormal/MMT/issues/326
            url_args_dict["highlight"] = "actual*,ephemeral*,u,q,α,SHE"
263
        else:
264
            model_name = self.state_machine.generate_mpd_theories()
Theresa Pollinger's avatar
Theresa Pollinger committed
265
            if model_name is None:
266
267
                model_name = "Model"
            url_args_dict = dict(type="mpd",
Theresa Pollinger's avatar
Theresa Pollinger committed
268
269
                                 graphdata=self.state_machine.mmtinterface.namespace + "?" + model_name,
                                 highlight="MPD_pde*")
270

271
        # have the side bars go away
272
        url_args_dict["viewOnlyMode"] = "true"
273

274
        tgview_url = build_url(server_url, "graphs/tgview.html", args_dict=url_args_dict)
275
        #print(tgview_url)
276
277

        code = """
278
            <iframe 
279
                src="{}" 
280
281
                style="width: 100%; height: 510px; border: none"
            >
282
            </iframe>
283
284
285
        """.format(tgview_url)

        self.display_html(code)
286

287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
    def display_widget(self):
        # needs jupyter nbextension enable --py widgetsnbextension
        from IPython.display import display
        from IPython.core.formatters import IPythonDisplayFormatter
        w = widgets.ToggleButton(
            value=False,
            description='Click me',
            disabled=False,
            button_style='', # 'success', 'info', 'warning', 'danger' or ''
            tooltip='Description',
            icon='check'
        )
        f = IPythonDisplayFormatter()
        # these should all do it, but all return the same string
        #f(w) # = "ToggleButton(value=False, description='Click me', icon='check', tooltip='Description')"
        #self._ipy_formatter(w)  # = "
        #display(w) # = "
        # self.Display(w)  # = "
        widgets.ToggleButton(
            value=False,
            description='Click me',
            disabled=False,
            button_style='',  # 'success', 'info', 'warning', 'danger' or ''
            tooltip='Description',
            icon='check'
        )

314
315

if __name__ == '__main__':
316
317
318
    # from ipykernel.kernelapp import IPKernelApp
    # IPKernelApp.launch_instance(kernel_class=Interview)
    Interview.run_as_main()