server.py 12.5 KB
Newer Older
marius's avatar
marius committed
1
2
import tornado.ioloop
import grond
marius's avatar
wip    
marius committed
3
import os
marius's avatar
marius committed
4
import os.path as op
Marius Isken's avatar
Marius Isken committed
5
import logging
6
import numpy as num
Marius Isken's avatar
wip    
Marius Isken committed
7
import socket
marius's avatar
marius committed
8
9
10

from collections import OrderedDict

Marius Isken's avatar
wip    
Marius Isken committed
11
from pyrocko.guts import Object, Bool, String, Int, List
12

Marius Isken's avatar
Marius Isken committed
13
from tornado.web import RequestHandler, StaticFileHandler
marius's avatar
marius committed
14
15
16
from tornado import gen

from bokeh.embed import autoload_server
Marius Isken's avatar
Marius Isken committed
17
18
19
from bokeh.application import Application
from bokeh.server.server import Server as BokehServer
from bokeh.application.handlers import Handler as BokehHandler
marius's avatar
marius committed
20

Marius Isken's avatar
Marius Isken committed
21
22
from bokeh.models import ColumnDataSource
from bokeh import layouts
marius's avatar
marius committed
23
24
from bokeh.plotting import figure

Marius Isken's avatar
wip    
Marius Isken committed
25
26
27
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('grond.baraddur')

marius's avatar
marius committed
28

Marius Isken's avatar
Marius Isken committed
29
30
31
32
33
34
35
36
37
def makeColorGradient(misfits, fr=1., fg=.5, fb=1.,
                      pr=0, pg=2.5, pb=4):
    misfits /= misfits.max()
    r = num.sin(fr * misfits + pr) * 127 + 128
    g = num.sin(fg * misfits + pg) * 127 + 128
    b = num.sin(fb * misfits + pb) * 127 + 128
    return ['#%02x%02x%02x' % (r[i], g[i], b[i]) for i in xrange(misfits.size)]


38
39
40
41
class BaraddurRequestHandler(RequestHandler):
    def initialize(self, config):
        self.config = config

Marius Isken's avatar
Marius Isken committed
42

43
44
45
46
47
48
class BaraddurBokehHandler(BokehHandler):
    def __init__(self, config, *args, **kwargs):
        BokehHandler.__init__(self, *args, **kwargs)
        self.config = config


marius's avatar
wip    
marius committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
class GrondBokehModel(object):
    def __init__(self, config):
        self.config = config
        self.set_rundir(self.config.rundir)

    def set_rundir(self, rundir):
        logger.debug('Loading problem from %s' % rundir)
        self.rundir = rundir
        self.problem = grond.core.load_problem_info(self.rundir)
        self.parameters = self.problem.parameters
        self.nparameters = self.problem.nparameters
        self.ntargets = self.problem.ntargets

    def get_models(self, skip_nmodels=0):
        fn = op.join(self.rundir, 'models')
        with open(fn, 'r') as f:
            nmodels = os.fstat(f.fileno()).st_size / (self.nparameters * 8)
            nmodels -= skip_nmodels
            f.seek(skip_nmodels * self.nparameters * 8)
            data = num.fromfile(
                f, dtype='<f8', count=nmodels * self.nparameters)\
                .astype(num.float)

        nmodels = data.size/self.nparameters - skip_nmodels
        models = data.reshape((nmodels, self.nparameters))

        mods_dict = {}
        for ip, par in enumerate(self.parameters):
            mods_dict[par.name] = models[:, ip]
        mods_dict['niter'] = num.arange(nmodels, dtype=num.int) + (nmodels+1)
        return nmodels, mods_dict

    def get_misfits(self, skip_nmodels=0):
        fn = op.join(self.rundir, 'misfits')

        with open(fn, 'r') as f:
            nmodels = os.fstat(f.fileno()).st_size / (self.nparameters * 8)
            nmodels -= skip_nmodels
            f.seek(skip_nmodels * self.ntargets * 2 * 8)
            data = num.fromfile(
                f, dtype='<f8', count=nmodels*self.ntargets*2)\
                .astype(num.float)

        data = data.reshape((nmodels, self.ntargets*2))

        combi = num.empty_like(data)
        combi[:, 0::2] = data[:, :self.ntargets]
        combi[:, 1::2] = data[:, self.ntargets:]

        assert(data.size/self.nparameters - skip_nmodels == nmodels)
        misfits = combi.reshape((nmodels, self.ntargets, 2))

        mf_dict = {}
        for it in xrange(self.ntargets):
            mf_dict['target_%03d' % it] = misfits[:, it, 0]
        mf_dict['target_mean'] = num.mean(misfits[:, :, 0])

        mf_dict['niter'] = num.arange(nmodels, dtype=num.int) + (nmodels+1)
        return nmodels, misfits


110
111
112
class Status(BaraddurRequestHandler):

    class MisfitsPlot(BaraddurBokehHandler):
Marius Isken's avatar
Marius Isken committed
113
114

        def modify_document(self, doc):
115
            self.nmodels = 0
Marius Isken's avatar
Marius Isken committed
116
            self.source = ColumnDataSource(
marius's avatar
wip    
marius committed
117
118
                data={'n': num.ndarray(0),
                      'gm': num.ndarray(0)})
Marius Isken's avatar
Marius Isken committed
119
120
121
122
123
124
125
126
127
128
129
130
131
            self.update_misfits()

            plot = figure(webgl=True,
                          x_axis_label='Iteration #',
                          y_axis_label='Misfit')
            plot.scatter('n', 'gm',
                         source=self.source, alpha=.4)

            doc.add_root(plot)
            doc.add_periodic_callback(self.update_misfits, 1e3)

        @gen.coroutine
        def update_misfits(self):
132
            mx, misfits = grond.core.load_problem_data(
133
134
                self.config.rundir, self.config.problem,
                skip_models=self.nmodels)
135
136
            new_nmodels = mx.shape[0]

Marius Isken's avatar
Marius Isken committed
137
            fits = num.mean(misfits, axis=1)
138
139
140
141
142
            self.source.stream(dict(gm=fits[:, 0],
                                    n=num.arange(new_nmodels,
                                                 dtype=num.int) +
                                    self.nmodels + 1))
            self.nmodels += new_nmodels
Marius Isken's avatar
Marius Isken committed
143
144
145
146
147
148
149
150

    bokeh_handlers = {'misfit_plot': MisfitsPlot}

    @gen.coroutine
    def get(self):
        self.render('status.html',
                    pages=pages,
                    misfit_plot=autoload_server(None, url='/misfit_plot'),
151
                    problem=self.config.problem)
Marius Isken's avatar
Marius Isken committed
152
153


154
class Parameters(BaraddurRequestHandler):
Marius Isken's avatar
Marius Isken committed
155

156
    class ParameterPlots(BaraddurBokehHandler):
Marius Isken's avatar
Marius Isken committed
157
158
159
160

        ncols = 4

        def modify_document(self, doc):
161
            self.nmodels = 0
162
163
            problem = self.config.problem

164
165
            self.source = ColumnDataSource()
            for p in ['n'] + [p.name for p in problem.parameters]:
marius's avatar
wip    
marius committed
166
                self.source.add(num.ndarray(0), p)
Marius Isken's avatar
Marius Isken committed
167
168
169
170
171
172
173
174
            self.update_parameters()

            plots = []
            for par in problem.parameters:
                fig = figure(webgl=True,
                             x_axis_label='Iteration #',
                             y_axis_label='%s [%s]' % (par.label, par.unit))
                fig.scatter('n', par.name,
175
                            source=self.source, alpha=.4)
Marius Isken's avatar
Marius Isken committed
176
                plots.append(fig)
Marius Isken's avatar
wip    
Marius Isken committed
177
178
            plots += [None] * (self.ncols - (len(plots) % self.ncols))
            print plots
Marius Isken's avatar
Marius Isken committed
179
180
181
182
183
184
185

            grid = layouts.gridplot(
                plots,
                responsive=True,
                ncols=self.ncols)

            doc.add_root(grid)
186
            doc.add_periodic_callback(self.update_parameters, 2.5*1e3)
Marius Isken's avatar
Marius Isken committed
187
188
189

        @gen.coroutine
        def update_parameters(self):
190
191
192
193
194
195
196
197
            problem = self.config.problem

            try:
                mx, misfits = grond.core.load_problem_data(
                    self.config.rundir, problem, skip_models=self.nmodels)
            except IOError:
                return

198
            new_nmodels = mx.shape[0]
Marius Isken's avatar
Marius Isken committed
199

200
201
202
203
204
            new_data = {}
            for ip, par in enumerate(problem.parameters):
                new_data[par.name] = mx[:, ip]
            new_data['n'] = num.arange(new_nmodels, dtype=num.int) +\
                self.nmodels + 1
Marius Isken's avatar
Marius Isken committed
205

206
207
            self.source.stream(new_data)
            self.nmodels += new_nmodels
Marius Isken's avatar
Marius Isken committed
208
209

    bokeh_handlers = {'parameter_plot': ParameterPlots}
marius's avatar
marius committed
210
211
212
213

    @gen.coroutine
    def get(self):

Marius Isken's avatar
Marius Isken committed
214
215
216
217
218
        self.render('parameter_plots.html',
                    pages=pages,
                    parameter_plot=autoload_server(
                        None,
                        url='/parameter_plot'),
219
                    problem=self.config.problem)
Marius Isken's avatar
Marius Isken committed
220
221


222
class Summary(BaraddurRequestHandler):
Marius Isken's avatar
Marius Isken committed
223
224
225

    @gen.coroutine
    def get(self):
marius's avatar
marius committed
226
227
        self.render('summary.html',
                    pages=pages,
228
                    problem=self.config.problem)
marius's avatar
marius committed
229
230


Marius Isken's avatar
wip    
Marius Isken committed
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
class Targets(BaraddurRequestHandler):

    class TargetContributionPlot(BaraddurBokehHandler):

        def modify_document(self, doc):
            self.nmodels = 0
            self.source = ColumnDataSource()
            self.update_contributions()

            plot = figure(webgl=True,
                          x_axis_label='Iteration #',
                          y_axis_label='Misfit')

            doc.add_root(plot)
            doc.add_periodic_callback(self.update_contributions, 1e3)

        @gen.coroutine
        def update_contributions(self):
            mx, misfits = grond.core.load_problem_data(
                self.config.rundir, self.config.problem,
                skip_models=self.nmodels)

            print misfits
            new_nmodels = mx.shape[0]

            # self.source.stream(dict(m=misfits[:, :, 0],
            #                         n=num.arange(new_nmodels,
            #                                      dtype=num.int) +
            #                         self.nmodels + 1))
            self.nmodels += new_nmodels

    bokeh_handlers = {'contribution_plot': TargetContributionPlot}
Marius Isken's avatar
Marius Isken committed
263

marius's avatar
marius committed
264
265
    @gen.coroutine
    def get(self):
Marius Isken's avatar
wip    
Marius Isken committed
266
267
268
269
270
271
        self.render('targets.html',
                    contribution_plot=autoload_server(
                        None,
                        url='/contribution_plot'),
                    pages=pages,
                    problem=self.config.problem)
marius's avatar
marius committed
272
273
274


pages = OrderedDict([
Marius Isken's avatar
wip    
Marius Isken committed
275
    ('Summary', Summary),
Marius Isken's avatar
Marius Isken committed
276
277
    ('Status', Status),
    ('Parameters', Parameters),
Marius Isken's avatar
wip    
Marius Isken committed
278
    ('Targets', Targets),
marius's avatar
marius committed
279
280
281
])


282
class BaraddurConfig(Object):
Marius Isken's avatar
wip    
Marius Isken committed
283
284
    rundir = String.T(
        help='Grond rundir.')
285
    debug = Bool.T(
286
        default=False,
287
        optional=True)
Marius Isken's avatar
wip    
Marius Isken committed
288
289
290
291
    hosts = List.T(
        String.T(),
        default=['*'],
        optional=True,
marius's avatar
wip    
marius committed
292
        help='List of allowed hosts, default is all \'*\'.')
marius's avatar
wip    
marius committed
293
294
    port = Int.T(
        default=8080,
Marius Isken's avatar
wip    
Marius Isken committed
295
296
        optional=True,
        help='Port to listen on.')
297
298
299
300
301
302
303
304
305

    @property
    def problem(self):
        return grond.core.load_problem_info(self.rundir)


class Baraddur(BokehServer):
    def __init__(self, rundir=None, *args, **kwargs):
        self.config = BaraddurConfig(rundir=rundir)
306
307
        print self.config
        self.ioloop = tornado.ioloop.IOLoop.current()
Marius Isken's avatar
wip    
Marius Isken committed
308
        port_offset = 0
309

Marius Isken's avatar
wip    
Marius Isken committed
310
311
312
313
314
        while True:
            try:
                BokehServer.__init__(
                    self,
                    self.get_bokeh_apps(),
315
                    io_loop=self.ioloop,
Marius Isken's avatar
wip    
Marius Isken committed
316
317
318
319
320
321
322
323
324
325
326
327
                    extra_patterns=self.get_tornado_handlers(),
                    port=self.config.port + port_offset,
                    host=self.config.hosts)
                break
            except socket.error as se:
                if se.errno == 98 and port_offset < 50:  # Port in use
                    port_offset += 1
                    logger.info('Port %d in use, bailing to %d'
                                % (self.config.port + port_offset - 1,
                                   self.config.port + port_offset))
                else:
                    raise se
328
        tornado_app = self._tornado
329
330
        tornado_app.settings['template_path'] = op.join(
            op.dirname(op.abspath(__file__)), 'templates')
331
332
333
334
335
336
337
338
339
340

        if self.config.debug:
            tornado_app.settings.setdefault('autoreload', True)
            tornado_app.settings.setdefault('compiled_template_cache', False)
            tornado_app.settings.setdefault('static_hash_cache', False)
            tornado_app.settings.setdefault('serve_traceback', True)
            # Automatically reload modified modules
            from tornado import autoreload
            autoreload.start()

Marius Isken's avatar
wip    
Marius Isken committed
341
            logging.getLogger('').setLevel(logging.DEBUG)
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356

    def get_bokeh_apps(self):
        bokeh_apps = {}
        for tornado_handler in pages.itervalues():
            handler_docs = getattr(tornado_handler, 'bokeh_handlers',
                                   None)
            if handler_docs is None:
                continue

            for url, bokeh_handler in handler_docs.iteritems():
                bokeh_apps['/%s' % url] = Application(bokeh_handler(
                    self.config))
        return bokeh_apps

    def get_tornado_handlers(self):
Marius Isken's avatar
Marius Isken committed
357
358
        return [(r'/', pages.values()[0],
                 {'config': self.config})] +\
359
360
               [(r'/%s' % title, handler,
                 {'config': self.config})
Marius Isken's avatar
Marius Isken committed
361
362
363
364
                for title, handler in pages.iteritems()] +\
               [(r'/css/(.*)', StaticFileHandler,
                {'path': op.join(op.dirname(__file__), 'css')})]

365
366
367
368
369
370
371
372
373
374
    def start(self, signal=None):
        logger.info('Starting Baraddur server on http://localhost:%d'
                    % (self.port))

        if signal is not None:
            def shutdown():
                if not signal.empty():
                    self.stop()
            tornado.ioloop.PeriodicCallback(shutdown, 2000).start()

375
        BokehServer.start(self)
376
377
378
379
380
381
382
        self.ioloop.start()

    def stop(self, *args, **kwargs):
        print args, kwargs
        logger.info('Stopping Baraddur server...')
        BokehServer.stop(self)
        self.ioloop.stop()
383
384


385
if __name__ == '_123_main__':
386
387
388
    baraddur = Baraddur(
        rundir='/home/marius/Development/testing/grond/rundir')
    baraddur.start()
389
    print 'here!'