def evaluate(input_rows, data, aggregate): quantities = [ QuantityFactory.create_quantity( symbol_type=ROW_IDX_TO_SYMBOL_NAME[idx], value=ureg.parse_expression(row['Editable Value'])) for idx, row in enumerate(input_rows) if row['Editable Value'] ] if data and len(data) > 0: quantities += json.loads(data, cls=MontyDecoder).values() if not quantities: raise PreventUpdate material = Material() for quantity in quantities: material.add_quantity(quantity) graph = Graph() output_material = graph.evaluate(material) if aggregate: output_quantities = output_material.get_aggregated_quantities( ).values() else: output_quantities = output_material.get_quantities() output_rows = [{ 'Property': quantity.symbol.display_names[0], 'Value': quantity.pretty_string(sigfigs=3) } for quantity in output_quantities] output_table = dt.DataTable(id='output-table', rows=output_rows, editable=False) # TODO: clean up input_quantity_names = [q.symbol.name for q in quantities] derived_quantity_names = set( [q.symbol.name for q in output_quantities]) - \ set(input_quantity_names) material_graph_data = graph_conversion( graph.get_networkx_graph(), nodes_to_highlight_green=input_quantity_names, nodes_to_highlight_yellow=list(derived_quantity_names)) options = AESTHETICS['global_options'] options['edges']['color'] = '#000000' output_graph = html.Div(GraphComponent(id='material-graph', graph=material_graph_data, options=options), style={ 'width': '100%', 'height': '400px' }) return [output_graph, html.Br(), output_table]
def test_graph_conversion(self): graph = Graph() converted = graph_conversion(graph.get_networkx_graph()) serialized = json.dumps(converted) self.assertIsNotNone(serialized) # Ensure that there are both nodes and proper edges self.assertIn('Band gap', [n['label'] for n in converted['nodes']]) self.assertIn({'from': 'band_gap', "to": "Is Metallic"}, converted['edges'])
def test_graph_conversion(self): graph = Graph() converted = graph_conversion(graph.get_networkx_graph()) serialized = json.dumps(converted) self.assertIsNotNone(serialized) # Ensure that there are both nodes and proper edges self.assertIn('Band gap', [n['data']['label'] for n in converted if n['group'] == 'nodes']) self.assertIn({'source': 'band_gap', "target": "Is Metallic"}, [n['data'] for n in converted if n['group'] == 'edges'])
def explore_layout(app): graph_data = graph_conversion(propnet_nx_graph, hide_unconnected_nodes=False) graph_component = html.Div( id='graph_component', children=[ Cytoscape(id='pn-graph', elements=graph_data, stylesheet=GRAPH_STYLESHEET, layout=GRAPH_LAYOUT_CONFIG, **GRAPH_SETTINGS['full_view']) ], ) graph_layout = html.Div(id='graph_top_level', children=[ dcc.Checklist( id='graph_options', options=[{ 'label': 'Show models', 'value': 'show_models' }, { 'label': 'Show properties', 'value': 'show_properties' }], value=['show_properties'], labelStyle={'display': 'inline-block'}), html.Div(id='graph_explorer', children=[graph_component]) ]) @app.callback(Output('pn-graph', 'elements'), [Input('graph_options', 'value')], [State('pn-graph', 'elements')]) def change_propnet_graph_label_selection(props, elements): show_properties = 'show_properties' in props show_models = 'show_models' in props update_labels(elements, show_models=show_models, show_symbols=show_properties) return elements layout = html.Div([ html.Div([graph_layout], className='row'), html.Div([ html.Div([models_index], className='six columns'), html.Div([symbols_index()], className='six columns'), ], className='row') ]) return layout
def get_graph_component(props): aesthetics = AESTHETICS.copy() show_properties = 'show_properties' in props show_models = 'show_models' in props set_(aesthetics, "node_aesthetics.Symbol.show_labels", show_properties) set_(aesthetics, "node_aesthetics.Model.show_labels", show_models) graph_data = graph_conversion(g, aesthetics=aesthetics) graph_component = html.Div( id=str(uuid4()), children=[ GraphComponent(id=str(uuid4()), graph=graph_data, options=AESTHETICS['global_options']) ], style={ 'width': '100%', 'height': '800px' }) return [graph_component]
def symbol_layout(symbol_name): """Create a Dash layout for a provided symbol. Args: symbol_name (str): a symbol name aesthetics (dict): an aesthetics configuration dictionary Returns: Dash layout """ # list to hold layouts for each section layouts = [] symbol = Registry("symbols")[symbol_name] main_name = symbol.display_names[0] layouts.append(html.H6('Graph')) # TODO: costly, should just construct subgraph directly? subgraph = nx.ego_graph(propnet_nx_graph, symbol, undirected=True, radius=2) subgraph_data = graph_conversion(subgraph, show_model_labels=True, show_symbol_labels=True) if len(subgraph_data) < 50: graph_config = GRAPH_LAYOUT_CONFIG.copy() graph_config['maxSimulationTime'] = 1500 else: graph_config = GRAPH_LAYOUT_CONFIG layouts.append( html.Div( Cytoscape(id="model_graph", elements=subgraph_data, stylesheet=GRAPH_STYLESHEET, layout=graph_config, **GRAPH_SETTINGS['model_symbol_view']))) if len(symbol.display_names) > 1: display_names = ", ".join(symbol.display_names[1:]) other_names = dcc.Markdown("Also known as: {}".format(display_names)) layouts.append(other_names) if len(symbol.display_symbols) > 1: symbols = " ".join(symbol.display_symbols) symbols = dcc.Markdown("Common symbols: {}".format(symbols)) layouts.append(symbols) if symbol.category in ('property', 'condition'): units = dcc.Markdown("Canonical units: **{}**".format( symbol.unit_as_string)) dimension = dcc.Markdown("**{}**".format(symbol.dimension_as_string)) layouts.append(units) layouts.append(dimension) if symbol.comment: layouts.append(dcc.Markdown(symbol.comment)) return html.Div([ main_name, html.Br(), html.Div(layouts), html.Br(), #dcc.Link('< Back to Properties', href='/property'), #html.Br(), dcc.Link('< Back', href='/explore') ])
def symbol_layout(symbol_name, aesthetics=None): """Create a Dash layout for a provided symbol. Args: model: a symbol name Returns: Dash layout """ aesthetics = aesthetics or AESTHETICS.copy() # list to hold layouts for each section layouts = [] symbol = DEFAULT_SYMBOLS[symbol_name] main_name = symbol.display_names[0] layouts.append(html.H6('Graph')) # TODO: costly, should just construct subgraph directly? g = Graph() subgraph = nx.ego_graph(g.graph, symbol, undirected=True, radius=2) options = AESTHETICS['global_options'] if "arrows" in options["edges"]: options["edges"]["arrows"] = "to" set_(aesthetics, "node_options.show_model_labels", True) layouts.append( html.Div(GraphComponent(id="model_graph", graph=graph_conversion(subgraph, aesthetics=AESTHETICS), options=AESTHETICS['global_options']), style={ 'width': '100%', 'height': '300px' })) if len(symbol.display_names) > 1: display_names = ", ".join(symbol.display_names[1:]) other_names = dcc.Markdown("Also known as: {}".format(display_names)) layouts.append(other_names) if len(symbol.display_symbols) > 1: symbols = " ".join(symbol.display_symbols) symbols = dcc.Markdown("Common symbols: {}".format(symbols)) layouts.append(symbols) if symbol.category in ('property', 'condition'): units = dcc.Markdown("Canonical units: **{}**".format( symbol.unit_as_string)) dimension = dcc.Markdown("**{}**".format(symbol.dimension_as_string)) layouts.append(units) layouts.append(dimension) if symbol.comment: layouts.append(dcc.Markdown(symbol.comment)) return html.Div([ main_name, html.Br(), html.Div(layouts), html.Br(), dcc.Link('< Back to Properties', href='/property'), html.Br(), dcc.Link('<< Back to Home', href='/') ])
def model_layout(model_name): """Create a Dash layout for a provided model. Args: model: an instance of an AbstractModel subclass Returns: Dash layout """ # dict to hold layouts for each section layouts = OrderedDict() # instantiate model from name model = DEFAULT_MODEL_DICT[model_name] model_title = html.Div(className='row', children=[ html.H3(model.title), ]) # TODO: costly, should just construct subgraph directly? g = Graph() subgraph = nx.ego_graph(g.graph, model, undirected=True) options = AESTHETICS['global_options'] if "arrows" in options["edges"]: options["edges"]["arrows"] = "to" layouts['Graph'] = html.Div(GraphComponent( id="model_graph", graph=graph_conversion(subgraph), options=AESTHETICS['global_options']), style={ 'width': '100%', 'height': '300px' }) if model.categories: tags = html.Ul(className="tags", children=[ html.Li(tag, className="tag") for tag in model.categories ]) layouts['Tags'] = tags if model.references: references = html.Div([ dcc.Markdown(references_to_markdown(ref)) for ref in model.references ]) layouts['References'] = references symbols = html.Div(children=[ html.Div(className='row', children=[ html.Div(className='two columns', children=[str(symbol)]), html.Div( className='ten columns', children=[ dcc.Link( DEFAULT_SYMBOLS[prop_name].display_names[0], href='/property/{}'.format(prop_name)) ]) ]) for symbol, prop_name in model.symbol_property_map.items() ]) layouts['Symbols'] = symbols layouts['Description'] = dcc.Markdown(model.description) if model.validate_from_preset_test(): sample_data_header = html.Div( className='row', children=[ html.Div(className='five columns', style={'text-align': 'center'}, children=[html.H4('Input(s)')]), html.Div(className='two columns', style={'text-align': 'center'}, children=[html.H4('->')]), html.Div(className='five columns', style={'text-align': 'center'}, children=[html.H4('Output(s)')]) ]) layouts['Sample Code'] = dcc.Markdown('```\n{}```'.format( model.example_code)) sublayouts = [] for title, layout in layouts.items(): sublayouts.append(html.H6(title)) sublayouts.append(layout) return html.Div([ model_title, html.Br(), *sublayouts, html.Br(), dcc.Link('< Back to Models', href='/model'), html.Br(), dcc.Link('<< Back to Home', href='/') ])
app = dash.Dash() server = app.server app.config.supress_callback_exceptions = True # TODO: remove this? app.scripts.config.serve_locally = True app.title = "The Hitchhikers Guide to Materials Science" route = dcc.Location(id='url', refresh=False) cache = Cache(app.server, config={ 'CACHE_TYPE': 'filesystem', 'CACHE_DIR': '.tmp' }) mpr = MPRester() g = Propnet().graph graph_data = graph_conversion(g) graph_component = html.Div(id='graph', children=[ ForceGraphComponent( id='propnet-graph', graphData=graph_data, width=800, height=350 )], className='box') # highlight node for corresponding content @app.callback( Output('propnet-graph', 'selectedNode'), [Input('url', 'pathname')] ) def hightlight_node_for_content(pathname):
def retrieve_material(n_clicks, query, derive_properties): """ Gets the material view from options Args: n_clicks (int): load material click formula (string): formula to find derive_properties ([str]): list of derivation options Returns: Div of graph component with fulfilled options """ if n_clicks is None: return "" log.info("Fetching data from MP for query {}".format(query)) if query.startswith("mp-") or query.startswith("mvc-"): mpid = query else: mpid = mpr.get_mpid_from_formula(query) material = mpr.get_material_for_mpid(mpid) if not material: return "Material not found." log.info("Retrieved material {} for formula {}".format( mpid, material['pretty_formula'])) log.debug("Adding material to graph.") p = Graph() material_quantity_names = [ q.symbol.name for q in material.get_quantities() ] g = p.graph if 'derive' in derive_properties: log.info("Deriving quantities for {}".format(mpid)) material = p.evaluate(material) if 'aggregate' in derive_properties: log.debug("Aggregating quantities for material {}".format(mpid)) # TODO: get aggregated quantities should return a list quantities = material.get_aggregated_quantities().items() else: quantities = [(q.symbol, q) for q in material.get_quantities()] else: quantities = [(q.symbol, q) for q in material.get_quantities()] rows = [] for symbol, quantity in quantities: rows.append({ 'Symbol': symbol.display_names[0], 'Value': quantity.pretty_string(3), # TODO: node.node_value.value? this has to make sense # 'Units': str(node.node_value.symbol.unit_as_string) }) table = dt.DataTable(rows=rows, row_selectable=True, filterable=True, sortable=True, editable=False, selected_row_indices=[], id='datatable') derived_quantity_names = set([symbol.name for symbol, quantity in quantities]) -\ set(material_quantity_names) material_graph_data = graph_conversion( g, nodes_to_highlight_green=material_quantity_names, nodes_to_highlight_yellow=list(derived_quantity_names)) options = AESTHETICS['global_options'] options['edges']['color'] = '#000000' material_graph_component = html.Div(GraphComponent( id='material-graph', graph=material_graph_data, options=options), style={ 'width': '100%', 'height': '400px' }) return html.Div( [html.H3('Graph'), material_graph_component, html.H3('Table'), table])
def evaluate(input_rows, data, aggregate): quantities = [] for idx, row in enumerate(input_rows): if row['Editable Value']: try: value = ureg.parse_expression(row['Editable Value']) units = Registry("units").get(ROW_IDX_TO_SYMBOL_NAME[idx]) value.ito(units) except Exception: # Someone put an invalid value in the table # TODO: Make error known to the user raise PreventUpdate q = QuantityFactory.create_quantity( symbol_type=ROW_IDX_TO_SYMBOL_NAME[idx], value=value) quantities.append(q) if data and len(data) > 0: quantities += json.loads(data, cls=MontyDecoder).values() if not quantities: raise PreventUpdate material = Material() for quantity in quantities: material.add_quantity(quantity) output_material = graph_evaluator.evaluate(material, timeout=5) if aggregate: aggregated_quantities = output_material.get_aggregated_quantities() non_aggregatable_quantities = [ v for v in output_material.get_quantities() if v.symbol not in aggregated_quantities ] output_quantities = list( aggregated_quantities.values()) + non_aggregatable_quantities else: output_quantities = output_material.get_quantities() output_rows = [{ 'Property': quantity.symbol.display_names[0], 'Value': quantity.pretty_string(sigfigs=3) } for quantity in output_quantities] output_table = dt.DataTable(id='output-table', data=output_rows, columns=[{ 'id': val, 'name': val } for val in ('Property', 'Value')], editable=False, **DATA_TABLE_STYLE) # TODO: clean up input_quantity_names = [q.symbol for q in quantities] derived_quantity_names = \ set([q.symbol for q in output_quantities]) - \ set(input_quantity_names) models_evaluated = set( output_q.provenance.model for output_q in output_material.get_quantities()) models_evaluated = [ Registry("models").get(m) for m in models_evaluated if Registry("models").get(m) is not None ] material_graph_data = graph_conversion( propnet_nx_graph, derivation_pathway={ 'inputs': input_quantity_names, 'outputs': list(derived_quantity_names), 'models': models_evaluated }) output_graph = html.Div(children=[ dcc.Checklist(id='material-graph-options', options=[{ 'label': 'Show models', 'value': 'show_models' }, { 'label': 'Show properties', 'value': 'show_properties' }], value=['show_properties'], labelStyle={'display': 'inline-block'}), Cytoscape(id='material-graph', elements=material_graph_data, stylesheet=GRAPH_STYLESHEET, layout=GRAPH_LAYOUT_CONFIG, **GRAPH_SETTINGS['full_view']) ]) return [output_graph, html.Br(), output_table]
def explore_layout(app): g = Graph().get_networkx_graph() graph_data = graph_conversion(g) graph_component = html.Div(id='graph_component', children=[ GraphComponent( id='propnet-graph', graph=graph_data, options=AESTHETICS['global_options']) ], style={ 'width': '100%', 'height': '800px' }) graph_layout = html.Div(id='graph_top_level', children=[ dcc.Checklist( id='graph_options', options=[{ 'label': 'Show models', 'value': 'show_models' }, { 'label': 'Show properties', 'value': 'show_properties' }], values=['show_properties'], labelStyle={'display': 'inline-block'}), html.Div(id='graph_explorer', children=[graph_component]) ]) # Define default graph component # TODO: this looks bad, re-evaluate @app.callback(Output('graph_explorer', 'children'), [Input('graph_options', 'values')]) def get_graph_component(props): aesthetics = AESTHETICS.copy() show_properties = 'show_properties' in props show_models = 'show_models' in props set_(aesthetics, "node_aesthetics.Symbol.show_labels", show_properties) set_(aesthetics, "node_aesthetics.Model.show_labels", show_models) graph_data = graph_conversion(g, aesthetics=aesthetics) graph_component = html.Div( id=str(uuid4()), children=[ GraphComponent(id=str(uuid4()), graph=graph_data, options=AESTHETICS['global_options']) ], style={ 'width': '100%', 'height': '800px' }) return [graph_component] layout = html.Div([ html.Div([graph_layout], className='row'), html.Div([ html.Div([models_index], className='six columns'), html.Div([symbols_index()], className='six columns'), ], className='row') ]) return layout