dwavesystems / dimod

A shared API for QUBO/Ising samplers.
https://docs.ocean.dwavesys.com/en/stable/docs_dimod/
Apache License 2.0
122 stars 81 forks source link

_to_networkx_graph() creates unserializable vartype obj #735

Open JoelPasvolsky opened 3 years ago

JoelPasvolsky commented 3 years ago

Description Try to convert a BQM (either default or AdjDictBQM) to a networkx graph, then use bokeh to plot, and bokeh gives a TypeError: Object of type 'Vartype' is not JSON serializable error.

Steps To Reproduce

import dimod
import networkx as nx
from bokeh.io import show
from bokeh.plotting import figure, from_networkx

bqm = dimod.BQM.from_ising({}, {('ab'): 1})
# bqm = dimod.AdjDictBQM.from_ising({}, {('ab'): 1})

G = dimod.to_networkx_graph(bqm)

plot = figure(title="Networkx Integration Demonstration", x_range=(-1.1,1.1), y_range=(-1.1,1.1),
              tools="", toolbar_location=None)

graph = from_networkx(G, nx.spring_layout, scale=2, center=(0,0))
plot.renderers.append(graph)

show(plot)

Expected Behavior Graph output should be serializable

Environment

Additional Context Workaround: convert without the vartype.

G = nx.Graph()
G.add_nodes_from(bqm.variables)
G.add_edges_from(bqm.quadratic)

Below is the full error message:

TypeError                                 Traceback (most recent call last)
<ipython-input-18-c8982eef3cb2> in <module>()
     18 plot.renderers.append(graph)
     19 
---> 20 show(plot)

/opt/conda/lib/python3.6/site-packages/bokeh/io/showing.py in show(obj, browser, new, notebook_handle, notebook_url, **kw)
    137         return run_notebook_hook(state.notebook_type, 'app', obj, state, notebook_url, **kw)
    138 
--> 139     return _show_with_state(obj, state, browser, new, notebook_handle=notebook_handle)
    140 
    141 #-----------------------------------------------------------------------------

/opt/conda/lib/python3.6/site-packages/bokeh/io/showing.py in _show_with_state(obj, state, browser, new, notebook_handle)
    171 
    172     if state.notebook:
--> 173         comms_handle = run_notebook_hook(state.notebook_type, 'doc', obj, state, notebook_handle)
    174         shown = True
    175 

/opt/conda/lib/python3.6/site-packages/bokeh/io/notebook.py in run_notebook_hook(notebook_type, action, *args, **kw)
    296     if _HOOKS[notebook_type][action] is None:
    297         raise RuntimeError("notebook hook for %r did not install %r action" % notebook_type, action)
--> 298     return _HOOKS[notebook_type][action](*args, **kw)
    299 
    300 #-----------------------------------------------------------------------------

/opt/conda/lib/python3.6/site-packages/bokeh/io/notebook.py in show_doc(obj, state, notebook_handle)
    506     from ..embed.notebook import notebook_content
    507     comms_target = make_id() if notebook_handle else None
--> 508     (script, div, cell_doc) = notebook_content(obj, comms_target)
    509 
    510     publish_display_data({HTML_MIME_TYPE: div})

/opt/conda/lib/python3.6/site-packages/bokeh/embed/notebook.py in notebook_content(model, notebook_comms_target, theme)
     83     # has models with the same IDs as they were started with
     84     with OutputDocumentFor([model], apply_theme=theme, always_new=True) as new_doc:
---> 85         (docs_json, [render_item]) = standalone_docs_json_and_render_items([model])
     86 
     87     div = div_for_render_item(render_item)

/opt/conda/lib/python3.6/site-packages/bokeh/embed/util.py in standalone_docs_json_and_render_items(models, suppress_callback_warning)
    292     docs_json = {}
    293     for doc, (docid, _) in docs.items():
--> 294         docs_json[docid] = doc.to_json()
    295 
    296     render_items = []

/opt/conda/lib/python3.6/site-packages/bokeh/document/document.py in to_json(self)
    899         # this is a total hack to go via a string, needed because
    900         # our BokehJSONEncoder goes straight to a string.
--> 901         doc_json = self.to_json_string()
    902         return loads(doc_json)
    903 

/opt/conda/lib/python3.6/site-packages/bokeh/document/document.py in to_json_string(self, indent)
    928         }
    929 
--> 930         return serialize_json(json, indent=indent)
    931 
    932     def validate(self):

/opt/conda/lib/python3.6/site-packages/bokeh/core/json_encoder.py in serialize_json(obj, pretty, indent, **kwargs)
    164         indent = 2
    165 
--> 166     return json.dumps(obj, cls=BokehJSONEncoder, allow_nan=False, indent=indent, separators=separators, sort_keys=True, **kwargs)
    167 
    168 

/opt/conda/lib/python3.6/json/__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    236         check_circular=check_circular, allow_nan=allow_nan, indent=indent,
    237         separators=separators, default=default, sort_keys=sort_keys,
--> 238         **kw).encode(obj)
    239 
    240 

/opt/conda/lib/python3.6/json/encoder.py in encode(self, o)
    197         # exceptions aren't as detailed.  The list call should be roughly
    198         # equivalent to the PySequence_Fast that ''.join() would do.
--> 199         chunks = self.iterencode(o, _one_shot=True)
    200         if not isinstance(chunks, (list, tuple)):
    201             chunks = list(chunks)

/opt/conda/lib/python3.6/json/encoder.py in iterencode(self, o, _one_shot)
    255                 self.key_separator, self.item_separator, self.sort_keys,
    256                 self.skipkeys, _one_shot)
--> 257         return _iterencode(o, 0)
    258 
    259 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,

/opt/conda/lib/python3.6/site-packages/bokeh/core/json_encoder.py in default(self, obj)
    259 
    260         else:
--> 261             return self.transform_python_types(obj)
    262 
    263 #-----------------------------------------------------------------------------

/opt/conda/lib/python3.6/site-packages/bokeh/core/json_encoder.py in transform_python_types(self, obj)
    226 
    227         else:
--> 228             return super().default(obj)
    229 
    230     def default(self, obj):

/opt/conda/lib/python3.6/json/encoder.py in default(self, o)
    178         """
    179         raise TypeError("Object of type '%s' is not JSON serializable" %
--> 180                         o.__class__.__name__)
    181 
    182     def encode(self, o):

TypeError: Object of type 'Vartype' is not JSON serializable
arcondello commented 3 years ago

Worth noting that networkx.Graph is not itself JSON-serializable, Bokeh has implemented a custom encoder BokehJSONEncoder.

So I think the request is really "Graph output should be serializable by Bokeh's custom encoder"

arcondello commented 3 years ago

IMO this is not an easy fix. Simplest would be del G.vartype.

JoelPasvolsky commented 3 years ago

Error persists (TypeError: Object of type 'Vartype' is not JSON serializable) even after deleting it.

G = dimod.to_networkx_graph(bqm)
print(G.vartype)
del G.vartype
print(G.vartype)

Vartype.SPIN

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-29-4497731d6b2b> in <module>()
      2 print(G.vartype)
      3 del G.vartype
----> 4 print(G.vartype)

AttributeError: 'Graph' object has no attribute 'vartype'

try plotting again:

show(plot)
TypeError                                 Traceback (most recent call last)
...
TypeError: Object of type 'Vartype' is not JSON serializable
arcondello commented 3 years ago

Ah, got it, comes from https://github.com/dwavesystems/dimod/blob/126a9056c4226d6955395620efb94ebee07581a5/dimod/converters.py#L49

Ok. I think your workaround it best for now.