Repository: arviz-devs/arviz

Issue:
Error with PyMC3 model that contains Potential
**Describe the bug**
For PyMC3 model that contains Potential, io_pymc3 is attempting to call `eval()` without graph dependence.

**To Reproduce**
```python
with pm.Model() as m:
    x = pm.Normal('x', 0., 1.)
    pm.Potential('z', pm.Normal.dist(x, 1.).logp(np.random.randn(10)))
    trace = pm.sample()
```
returns:
```python
---------------------------------------------------------------------------
MissingInputError                         Traceback (most recent call last)
<ipython-input-45-c2e72dd27111> in <module>
      2     x = pm.Normal('x', 0., 1.)
      3     pm.Potential('z', pm.Normal.dist(x, 1.).logp(np.random.randn(10)))
----> 4     trace = pm.sample()

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/pymc3-3.8-py3.8.egg/pymc3/sampling.py in sample(draws, step, init, n_init, start, trace, chain_idx, chains, cores, tune, progressbar, model, random_seed, discard_tuned_samples, compute_convergence_checks, callback, **kwargs)
    539             warnings.warn("The number of samples is too small to check convergence reliably.")
    540         else:
--> 541             trace.report._run_convergence_checks(trace, model)
    542 
    543     trace.report._log_summary()

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/pymc3-3.8-py3.8.egg/pymc3/backends/report.py in _run_convergence_checks(self, trace, model)
     96                 varnames.append(rv_name)
     97 
---> 98         self._ess = ess = ess(trace, var_names=varnames)
     99         self._rhat = rhat = rhat(trace, var_names=varnames)
    100 

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/pymc3-3.8-py3.8.egg/pymc3/stats/__init__.py in wrapped(*args, **kwargs)
     36                 )
     37                 kwargs[new] = kwargs.pop(old)
---> 38             return func(*args, **kwargs)
     39 
     40     return wrapped

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/arviz-0.6.1-py3.8.egg/arviz/stats/diagnostics.py in ess(data, var_names, method, relative, prob)
    187             raise TypeError(msg)
    188 
--> 189     dataset = convert_to_dataset(data, group="posterior")
    190     var_names = _var_names(var_names, dataset)
    191 

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/arviz-0.6.1-py3.8.egg/arviz/data/converters.py in convert_to_dataset(obj, group, coords, dims)
    166     xarray.Dataset
    167     """
--> 168     inference_data = convert_to_inference_data(obj, group=group, coords=coords, dims=dims)
    169     dataset = getattr(inference_data, group, None)
    170     if dataset is None:

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/arviz-0.6.1-py3.8.egg/arviz/data/converters.py in convert_to_inference_data(obj, group, coords, dims, **kwargs)
     87             return from_pystan(**kwargs)
     88     elif obj.__class__.__name__ == "MultiTrace":  # ugly, but doesn't make PyMC3 a requirement
---> 89         return from_pymc3(trace=kwargs.pop(group), **kwargs)
     90     elif obj.__class__.__name__ == "EnsembleSampler":  # ugly, but doesn't make emcee a requirement
     91         return from_emcee(sampler=kwargs.pop(group), **kwargs)

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/arviz-0.6.1-py3.8.egg/arviz/data/io_pymc3.py in from_pymc3(trace, prior, posterior_predictive, coords, dims, model)
    350 ):
    351     """Convert pymc3 data into an InferenceData object."""
--> 352     return PyMC3Converter(
    353         trace=trace,
    354         prior=prior,

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/arviz-0.6.1-py3.8.egg/arviz/data/io_pymc3.py in to_inference_data(self)
    342             id_dict["predictions_constant_data"] = self.constant_data_to_xarray()
    343         else:
--> 344             id_dict["constant_data"] = self.constant_data_to_xarray()
    345         return InferenceData(**id_dict)
    346 

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/arviz-0.6.1-py3.8.egg/arviz/data/base.py in wrapped(cls, *args, **kwargs)
     34                 if all([getattr(cls, prop_i) is None for prop_i in prop]):
     35                     return None
---> 36             return func(cls, *args, **kwargs)
     37 
     38         return wrapped

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/arviz-0.6.1-py3.8.egg/arviz/data/base.py in wrapped(cls, *args, **kwargs)
     34                 if all([getattr(cls, prop_i) is None for prop_i in prop]):
     35                     return None
---> 36             return func(cls, *args, **kwargs)
     37 
     38         return wrapped

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/arviz-0.6.1-py3.8.egg/arviz/data/io_pymc3.py in constant_data_to_xarray(self)
    309             # this might be a Deterministic, and must be evaluated
    310             elif hasattr(self.model[name], "eval"):
--> 311                 vals = self.model[name].eval()
    312             vals = np.atleast_1d(vals)
    313             val_dims = dims.get(name)

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/gof/graph.py in eval(self, inputs_to_values)
    520         inputs = tuple(sorted(inputs_to_values.keys(), key=id))
    521         if inputs not in self._fn_cache:
--> 522             self._fn_cache[inputs] = theano.function(inputs, self)
    523         args = [inputs_to_values[param] for param in inputs]
    524 

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/compile/function.py in function(inputs, outputs, mode, updates, givens, no_default_updates, accept_inplace, name, rebuild_strict, allow_input_downcast, profile, on_unused_input)
    304         # note: pfunc will also call orig_function -- orig_function is
    305         #      a choke point that all compilation must pass through
--> 306         fn = pfunc(params=inputs,
    307                    outputs=outputs,
    308                    mode=mode,

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/compile/pfunc.py in pfunc(params, outputs, mode, updates, givens, no_default_updates, accept_inplace, name, rebuild_strict, allow_input_downcast, profile, on_unused_input, output_keys)
    481         inputs.append(si)
    482 
--> 483     return orig_function(inputs, cloned_outputs, mode,
    484                          accept_inplace=accept_inplace, name=name,
    485                          profile=profile, on_unused_input=on_unused_input,

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/compile/function_module.py in orig_function(inputs, outputs, mode, accept_inplace, name, profile, on_unused_input, output_keys)
   1830     try:
   1831         Maker = getattr(mode, 'function_maker', FunctionMaker)
-> 1832         m = Maker(inputs,
   1833                   outputs,
   1834                   mode,

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/compile/function_module.py in __init__(self, inputs, outputs, mode, accept_inplace, function_builder, profile, on_unused_input, fgraph, output_keys, name)
   1484             # make the fgraph (copies the graph, creates NEW INPUT AND
   1485             # OUTPUT VARIABLES)
-> 1486             fgraph, additional_outputs = std_fgraph(inputs, outputs,
   1487                                                     accept_inplace)
   1488             fgraph.profile = profile

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/compile/function_module.py in std_fgraph(input_specs, output_specs, accept_inplace)
    178     orig_outputs = [spec.variable for spec in output_specs] + updates
    179 
--> 180     fgraph = gof.fg.FunctionGraph(orig_inputs, orig_outputs,
    181                                   update_mapping=update_mapping)
    182 

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/gof/fg.py in __init__(self, inputs, outputs, features, clone, update_mapping)
    173 
    174         for output in outputs:
--> 175             self.__import_r__(output, reason="init")
    176         for i, output in enumerate(outputs):
    177             output.clients.append(('output', i))

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/gof/fg.py in __import_r__(self, variable, reason)
    344         # Imports the owners of the variables
    345         if variable.owner and variable.owner not in self.apply_nodes:
--> 346                 self.__import__(variable.owner, reason=reason)
    347         elif (variable.owner is None and
    348                 not isinstance(variable, graph.Constant) and

~/anaconda3/envs/pymc3/lib/python3.8/site-packages/Theano-1.0.4-py3.8.egg/theano/gof/fg.py in __import__(self, apply_node, check, reason)
    389                                      "for more information on this error."
    390                                      % (node.inputs.index(r), str(node)))
--> 391                         raise MissingInputError(error_msg, variable=r)
    392 
    393         for node in new_nodes:

MissingInputError: Input 0 of the graph (indices start from 0), used to compute InplaceDimShuffle{x}(x), was not provided and not given a value. Use the Theano flag exception_verbosity='high', for more information on this error.
```



Discussion:
FWIW, `pm.Potential` are factors added to the log_prob, we should not consider it in the trace and it is not observed. It is safe to exclude these.

Resolving patch:
diff --git a/arviz/data/io_pymc3.py b/arviz/data/io_pymc3.py
--- a/arviz/data/io_pymc3.py
+++ b/arviz/data/io_pymc3.py
@@ -287,6 +287,7 @@ def is_data(name, var) -> bool:
                 var not in self.model.deterministics
                 and var not in self.model.observed_RVs
                 and var not in self.model.free_RVs
+                and var not in self.model.potentials
                 and (self.observations is None or name not in self.observations)
             )
 


Test patch:
diff --git a/arviz/tests/test_data_pymc.py b/arviz/tests/test_data_pymc.py
--- a/arviz/tests/test_data_pymc.py
+++ b/arviz/tests/test_data_pymc.py
@@ -251,6 +251,15 @@ def test_single_observation(self):
         inference_data = from_pymc3(trace=trace)
         assert inference_data
 
+    def test_potential(self):
+        with pm.Model():
+            x = pm.Normal("x", 0.0, 1.0)
+            pm.Potential("z", pm.Normal.dist(x, 1.0).logp(np.random.randn(10)))
+            trace = pm.sample(100, chains=2)
+
+        inference_data = from_pymc3(trace=trace)
+        assert inference_data
+
     def test_constant_data(self):
         with pm.Model():
             x = pm.Data("x", [1.0, 2.0, 3.0])