We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
make test: no attribute 'optim' error in 'examples/contrib/oed/ab_test.py' 
### Issue Description
On the latest dev branch, `make test` gives the following error:

_
examples/contrib/oed/ab_test.py:12: in <module>
    from gp_bayes_opt import GPBayesOptimizer
examples/contrib/oed/gp_bayes_opt.py:11: in <module>
    class GPBayesOptimizer(pyro.optim.multi.MultiOptimizer):
E   AttributeError: module 'pyro' has no attribute 'optim'

### Environment
For any bugs, please provide the following:
 - OS and python version: CentOS Linux 7 (Core); Python 3.7.1
 - PyTorch version, or if relevant, output of `pip freeze`: PyTorch 1.0.0
 - Pyro version: output of `python -c 'import pyro; print pyro.__version__'`: pyro 0.3.0+9adbdb7

### Code Snippet

```
make install
make format
make test
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `examples/contrib/oed/gp_bayes_opt.py`
Content:
```
1 import torch
2 import torch.autograd as autograd
3 import torch.optim as optim
4 from torch.distributions import transform_to
5 
6 import pyro
7 import pyro.contrib.gp as gp
8 from pyro.infer import TraceEnum_ELBO
9 
10 
11 class GPBayesOptimizer(pyro.optim.multi.MultiOptimizer):
12     """Performs Bayesian Optimization using a Gaussian Process as an
13     emulator for the unknown function.
14     """
15 
16     def __init__(self, constraints, gpmodel, num_acquisitions, acquisition_func=None):
17         """
18         :param torch.constraint constraints: constraints defining the domain of `f`
19         :param gp.models.GPRegression gpmodel: a (possibly initialized) GP
20             regression model. The kernel, etc is specified via `gpmodel`.
21         :param int num_acquisitions: number of points to acquire at each step
22         :param function acquisition_func: a function to generate acquisitions.
23             It should return a torch.Tensor of new points to query.
24         """
25         if acquisition_func is None:
26             acquisition_func = self.acquire_thompson
27 
28         self.constraints = constraints
29         self.gpmodel = gpmodel
30         self.num_acquisitions = num_acquisitions
31         self.acquisition_func = acquisition_func
32 
33     def update_posterior(self, X, y):
34         X = torch.cat([self.gpmodel.X, X])
35         y = torch.cat([self.gpmodel.y, y])
36         self.gpmodel.set_data(X, y)
37         optimizer = torch.optim.Adam(self.gpmodel.parameters(), lr=0.001)
38         gp.util.train(self.gpmodel, optimizer,
39                       loss_fn=TraceEnum_ELBO(strict_enumeration_warning=False).differentiable_loss,
40                       retain_graph=True)
41 
42     def find_a_candidate(self, differentiable, x_init):
43         """Given a starting point, `x_init`, takes one LBFGS step
44         to optimize the differentiable function.
45 
46         :param function differentiable: a function amenable to torch
47             autograd
48         :param torch.Tensor x_init: the initial point
49 
50         """
51         # transform x to an unconstrained domain
52         unconstrained_x_init = transform_to(self.constraints).inv(x_init)
53         unconstrained_x = unconstrained_x_init.detach().clone().requires_grad_(True)
54         # TODO: Use LBFGS with line search by pytorch #8824 merged
55         minimizer = optim.LBFGS([unconstrained_x], max_eval=20)
56 
57         def closure():
58             minimizer.zero_grad()
59             if (torch.log(torch.abs(unconstrained_x)) > 25.).any():
60                 return torch.tensor(float('inf'))
61             x = transform_to(self.constraints)(unconstrained_x)
62             y = differentiable(x)
63             autograd.backward(unconstrained_x,
64                               autograd.grad(y, unconstrained_x, retain_graph=True))
65             return y
66 
67         minimizer.step(closure)
68         # after finding a candidate in the unconstrained domain,
69         # convert it back to original domain.
70         x = transform_to(self.constraints)(unconstrained_x)
71         opt_y = differentiable(x)
72         return x.detach(), opt_y.detach()
73 
74     def opt_differentiable(self, differentiable, num_candidates=5):
75         """Optimizes a differentiable function by choosing `num_candidates`
76         initial points at random and calling :func:`find_a_candidate` on
77         each. The best candidate is returned with its function value.
78 
79         :param function differentiable: a function amenable to torch autograd
80         :param int num_candidates: the number of random starting points to
81             use
82         :return: the minimiser and its function value
83         :rtype: tuple
84         """
85 
86         candidates = []
87         values = []
88         for j in range(num_candidates):
89             x_init = self.gpmodel.X.new_empty(1).uniform_(
90                 self.constraints.lower_bound, self.constraints.upper_bound)
91             x, y = self.find_a_candidate(differentiable, x_init)
92             if torch.isnan(y):
93                 continue
94             candidates.append(x)
95             values.append(y)
96 
97         mvalue, argmin = torch.min(torch.cat(values), dim=0)
98         return candidates[argmin.item()], mvalue
99 
100     def acquire_thompson(self, num_acquisitions=1, **opt_params):
101         """Selects `num_acquisitions` query points at which to query the
102         original function by Thompson sampling.
103 
104         :param int num_acquisitions: the number of points to generate
105         :param dict opt_params: additional parameters for optimization
106             routines
107         :return: a tensor of points to evaluate `loss` at
108         :rtype: torch.Tensor
109         """
110 
111         # Initialize the return tensor
112         X = self.gpmodel.X.new_empty(num_acquisitions, *self.gpmodel.X.shape[1:])
113 
114         for i in range(num_acquisitions):
115             sampler = self.gpmodel.iter_sample(noiseless=False)
116             x, _ = self.opt_differentiable(sampler, **opt_params)
117             X[i, ...] = x
118 
119         return X
120 
121     def get_step(self, loss, params, verbose=False):
122         X = self.acquisition_func(num_acquisitions=self.num_acquisitions)
123         y = loss(X)
124         if verbose:
125             print("Acquire at: X")
126             print(X)
127             print("y")
128             print(y)
129         self.update_posterior(X, y)
130         return self.opt_differentiable(lambda x: self.gpmodel(x)[0])
131
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/examples/contrib/oed/gp_bayes_opt.py b/examples/contrib/oed/gp_bayes_opt.py
--- a/examples/contrib/oed/gp_bayes_opt.py
+++ b/examples/contrib/oed/gp_bayes_opt.py
@@ -3,9 +3,9 @@
 import torch.optim as optim
 from torch.distributions import transform_to
 
-import pyro
 import pyro.contrib.gp as gp
 from pyro.infer import TraceEnum_ELBO
+import pyro.optim
 
 
 class GPBayesOptimizer(pyro.optim.multi.MultiOptimizer):

{"golden_diff": "diff --git a/examples/contrib/oed/gp_bayes_opt.py b/examples/contrib/oed/gp_bayes_opt.py\n--- a/examples/contrib/oed/gp_bayes_opt.py\n+++ b/examples/contrib/oed/gp_bayes_opt.py\n@@ -3,9 +3,9 @@\n import torch.optim as optim\n from torch.distributions import transform_to\n \n-import pyro\n import pyro.contrib.gp as gp\n from pyro.infer import TraceEnum_ELBO\n+import pyro.optim\n \n \n class GPBayesOptimizer(pyro.optim.multi.MultiOptimizer):\n", "issue": "make test: no attribute 'optim' error in 'examples/contrib/oed/ab_test.py' \n### Issue Description\r\nOn the latest dev branch, `make test` gives the following error:\r\n\r\n_\r\nexamples/contrib/oed/ab_test.py:12: in <module>\r\n    from gp_bayes_opt import GPBayesOptimizer\r\nexamples/contrib/oed/gp_bayes_opt.py:11: in <module>\r\n    class GPBayesOptimizer(pyro.optim.multi.MultiOptimizer):\r\nE   AttributeError: module 'pyro' has no attribute 'optim'\r\n\r\n### Environment\r\nFor any bugs, please provide the following:\r\n - OS and python version: CentOS Linux 7 (Core); Python 3.7.1\r\n - PyTorch version, or if relevant, output of `pip freeze`: PyTorch 1.0.0\r\n - Pyro version: output of `python -c 'import pyro; print pyro.__version__'`: pyro 0.3.0+9adbdb7\r\n\r\n### Code Snippet\r\n\r\n```\r\nmake install\r\nmake format\r\nmake test\r\n```\r\n\n", "before_files": [{"content": "import torch\nimport torch.autograd as autograd\nimport torch.optim as optim\nfrom torch.distributions import transform_to\n\nimport pyro\nimport pyro.contrib.gp as gp\nfrom pyro.infer import TraceEnum_ELBO\n\n\nclass GPBayesOptimizer(pyro.optim.multi.MultiOptimizer):\n    \"\"\"Performs Bayesian Optimization using a Gaussian Process as an\n    emulator for the unknown function.\n    \"\"\"\n\n    def __init__(self, constraints, gpmodel, num_acquisitions, acquisition_func=None):\n        \"\"\"\n        :param torch.constraint constraints: constraints defining the domain of `f`\n        :param gp.models.GPRegression gpmodel: a (possibly initialized) GP\n            regression model. The kernel, etc is specified via `gpmodel`.\n        :param int num_acquisitions: number of points to acquire at each step\n        :param function acquisition_func: a function to generate acquisitions.\n            It should return a torch.Tensor of new points to query.\n        \"\"\"\n        if acquisition_func is None:\n            acquisition_func = self.acquire_thompson\n\n        self.constraints = constraints\n        self.gpmodel = gpmodel\n        self.num_acquisitions = num_acquisitions\n        self.acquisition_func = acquisition_func\n\n    def update_posterior(self, X, y):\n        X = torch.cat([self.gpmodel.X, X])\n        y = torch.cat([self.gpmodel.y, y])\n        self.gpmodel.set_data(X, y)\n        optimizer = torch.optim.Adam(self.gpmodel.parameters(), lr=0.001)\n        gp.util.train(self.gpmodel, optimizer,\n                      loss_fn=TraceEnum_ELBO(strict_enumeration_warning=False).differentiable_loss,\n                      retain_graph=True)\n\n    def find_a_candidate(self, differentiable, x_init):\n        \"\"\"Given a starting point, `x_init`, takes one LBFGS step\n        to optimize the differentiable function.\n\n        :param function differentiable: a function amenable to torch\n            autograd\n        :param torch.Tensor x_init: the initial point\n\n        \"\"\"\n        # transform x to an unconstrained domain\n        unconstrained_x_init = transform_to(self.constraints).inv(x_init)\n        unconstrained_x = unconstrained_x_init.detach().clone().requires_grad_(True)\n        # TODO: Use LBFGS with line search by pytorch #8824 merged\n        minimizer = optim.LBFGS([unconstrained_x], max_eval=20)\n\n        def closure():\n            minimizer.zero_grad()\n            if (torch.log(torch.abs(unconstrained_x)) > 25.).any():\n                return torch.tensor(float('inf'))\n            x = transform_to(self.constraints)(unconstrained_x)\n            y = differentiable(x)\n            autograd.backward(unconstrained_x,\n                              autograd.grad(y, unconstrained_x, retain_graph=True))\n            return y\n\n        minimizer.step(closure)\n        # after finding a candidate in the unconstrained domain,\n        # convert it back to original domain.\n        x = transform_to(self.constraints)(unconstrained_x)\n        opt_y = differentiable(x)\n        return x.detach(), opt_y.detach()\n\n    def opt_differentiable(self, differentiable, num_candidates=5):\n        \"\"\"Optimizes a differentiable function by choosing `num_candidates`\n        initial points at random and calling :func:`find_a_candidate` on\n        each. The best candidate is returned with its function value.\n\n        :param function differentiable: a function amenable to torch autograd\n        :param int num_candidates: the number of random starting points to\n            use\n        :return: the minimiser and its function value\n        :rtype: tuple\n        \"\"\"\n\n        candidates = []\n        values = []\n        for j in range(num_candidates):\n            x_init = self.gpmodel.X.new_empty(1).uniform_(\n                self.constraints.lower_bound, self.constraints.upper_bound)\n            x, y = self.find_a_candidate(differentiable, x_init)\n            if torch.isnan(y):\n                continue\n            candidates.append(x)\n            values.append(y)\n\n        mvalue, argmin = torch.min(torch.cat(values), dim=0)\n        return candidates[argmin.item()], mvalue\n\n    def acquire_thompson(self, num_acquisitions=1, **opt_params):\n        \"\"\"Selects `num_acquisitions` query points at which to query the\n        original function by Thompson sampling.\n\n        :param int num_acquisitions: the number of points to generate\n        :param dict opt_params: additional parameters for optimization\n            routines\n        :return: a tensor of points to evaluate `loss` at\n        :rtype: torch.Tensor\n        \"\"\"\n\n        # Initialize the return tensor\n        X = self.gpmodel.X.new_empty(num_acquisitions, *self.gpmodel.X.shape[1:])\n\n        for i in range(num_acquisitions):\n            sampler = self.gpmodel.iter_sample(noiseless=False)\n            x, _ = self.opt_differentiable(sampler, **opt_params)\n            X[i, ...] = x\n\n        return X\n\n    def get_step(self, loss, params, verbose=False):\n        X = self.acquisition_func(num_acquisitions=self.num_acquisitions)\n        y = loss(X)\n        if verbose:\n            print(\"Acquire at: X\")\n            print(X)\n            print(\"y\")\n            print(y)\n        self.update_posterior(X, y)\n        return self.opt_differentiable(lambda x: self.gpmodel(x)[0])\n", "path": "examples/contrib/oed/gp_bayes_opt.py"}], "after_files": [{"content": "import torch\nimport torch.autograd as autograd\nimport torch.optim as optim\nfrom torch.distributions import transform_to\n\nimport pyro.contrib.gp as gp\nfrom pyro.infer import TraceEnum_ELBO\nimport pyro.optim\n\n\nclass GPBayesOptimizer(pyro.optim.multi.MultiOptimizer):\n    \"\"\"Performs Bayesian Optimization using a Gaussian Process as an\n    emulator for the unknown function.\n    \"\"\"\n\n    def __init__(self, constraints, gpmodel, num_acquisitions, acquisition_func=None):\n        \"\"\"\n        :param torch.constraint constraints: constraints defining the domain of `f`\n        :param gp.models.GPRegression gpmodel: a (possibly initialized) GP\n            regression model. The kernel, etc is specified via `gpmodel`.\n        :param int num_acquisitions: number of points to acquire at each step\n        :param function acquisition_func: a function to generate acquisitions.\n            It should return a torch.Tensor of new points to query.\n        \"\"\"\n        if acquisition_func is None:\n            acquisition_func = self.acquire_thompson\n\n        self.constraints = constraints\n        self.gpmodel = gpmodel\n        self.num_acquisitions = num_acquisitions\n        self.acquisition_func = acquisition_func\n\n    def update_posterior(self, X, y):\n        X = torch.cat([self.gpmodel.X, X])\n        y = torch.cat([self.gpmodel.y, y])\n        self.gpmodel.set_data(X, y)\n        optimizer = torch.optim.Adam(self.gpmodel.parameters(), lr=0.001)\n        gp.util.train(self.gpmodel, optimizer,\n                      loss_fn=TraceEnum_ELBO(strict_enumeration_warning=False).differentiable_loss,\n                      retain_graph=True)\n\n    def find_a_candidate(self, differentiable, x_init):\n        \"\"\"Given a starting point, `x_init`, takes one LBFGS step\n        to optimize the differentiable function.\n\n        :param function differentiable: a function amenable to torch\n            autograd\n        :param torch.Tensor x_init: the initial point\n\n        \"\"\"\n        # transform x to an unconstrained domain\n        unconstrained_x_init = transform_to(self.constraints).inv(x_init)\n        unconstrained_x = unconstrained_x_init.detach().clone().requires_grad_(True)\n        # TODO: Use LBFGS with line search by pytorch #8824 merged\n        minimizer = optim.LBFGS([unconstrained_x], max_eval=20)\n\n        def closure():\n            minimizer.zero_grad()\n            if (torch.log(torch.abs(unconstrained_x)) > 25.).any():\n                return torch.tensor(float('inf'))\n            x = transform_to(self.constraints)(unconstrained_x)\n            y = differentiable(x)\n            autograd.backward(unconstrained_x,\n                              autograd.grad(y, unconstrained_x, retain_graph=True))\n            return y\n\n        minimizer.step(closure)\n        # after finding a candidate in the unconstrained domain,\n        # convert it back to original domain.\n        x = transform_to(self.constraints)(unconstrained_x)\n        opt_y = differentiable(x)\n        return x.detach(), opt_y.detach()\n\n    def opt_differentiable(self, differentiable, num_candidates=5):\n        \"\"\"Optimizes a differentiable function by choosing `num_candidates`\n        initial points at random and calling :func:`find_a_candidate` on\n        each. The best candidate is returned with its function value.\n\n        :param function differentiable: a function amenable to torch autograd\n        :param int num_candidates: the number of random starting points to\n            use\n        :return: the minimiser and its function value\n        :rtype: tuple\n        \"\"\"\n\n        candidates = []\n        values = []\n        for j in range(num_candidates):\n            x_init = self.gpmodel.X.new_empty(1).uniform_(\n                self.constraints.lower_bound, self.constraints.upper_bound)\n            x, y = self.find_a_candidate(differentiable, x_init)\n            if torch.isnan(y):\n                continue\n            candidates.append(x)\n            values.append(y)\n\n        mvalue, argmin = torch.min(torch.cat(values), dim=0)\n        return candidates[argmin.item()], mvalue\n\n    def acquire_thompson(self, num_acquisitions=1, **opt_params):\n        \"\"\"Selects `num_acquisitions` query points at which to query the\n        original function by Thompson sampling.\n\n        :param int num_acquisitions: the number of points to generate\n        :param dict opt_params: additional parameters for optimization\n            routines\n        :return: a tensor of points to evaluate `loss` at\n        :rtype: torch.Tensor\n        \"\"\"\n\n        # Initialize the return tensor\n        X = self.gpmodel.X.new_empty(num_acquisitions, *self.gpmodel.X.shape[1:])\n\n        for i in range(num_acquisitions):\n            sampler = self.gpmodel.iter_sample(noiseless=False)\n            x, _ = self.opt_differentiable(sampler, **opt_params)\n            X[i, ...] = x\n\n        return X\n\n    def get_step(self, loss, params, verbose=False):\n        X = self.acquisition_func(num_acquisitions=self.num_acquisitions)\n        y = loss(X)\n        if verbose:\n            print(\"Acquire at: X\")\n            print(X)\n            print(\"y\")\n            print(y)\n        self.update_posterior(X, y)\n        return self.opt_differentiable(lambda x: self.gpmodel(x)[0])\n", "path": "examples/contrib/oed/gp_bayes_opt.py"}]}