text: import netket as nk
  
  from netket.operator import local_values
  
  from netket.stats import (
      statistics as _statistics,
      mean as _mean,
      sum_inplace as _sum_inplace
  )
  
  from netket.vmc_common import info, tree_map
  
  from mpi4py import MPI
  
  from netket.utils import (
      MPI_comm as _MPI_comm,
      n_nodes as _n_nodes,
      node_number as _rank
  )
  
  from netket.abstract_variational_driver import AbstractVariationalDriver
  
  import numpy as np
  
  
  class ExactOpt(AbstractVariationalDriver):
      def __init__(self, hamiltonian, basis, machine, optimizer, sr=None, weightings=None):
          self._ham = hamiltonian
          super().__init__(machine, optimizer)
          self._sr = sr
          if self._sr is not None:
              self._sr.setup(machine)
          self._dp = None
          self._npar = self._machine.n_par
          self.basis = basis
          self.weightings = weightings
          self.total_basis_size = _MPI_comm.allreduce(len(self.basis))
          self._grads = None
          self._jac = None
  
      def _get_mc_stats(self, op):
          squared_amps = np.exp(2 * self.machine.log_val(self.basis).real)
          if self.weightings is not None:
              squared_amps *= self.weightings
          norm = _MPI_comm.allreduce(np.sum(squared_amps))
          self.prefactors = self.total_basis_size * (squared_amps/norm)
          loc = self.prefactors * local_values(op, self._machine, self.basis)
          return self.prefactors, loc, _statistics(loc)
  
      def _estimate_stats(self, observable):
          return self._get_mc_stats(observable)[1]
  
      def reset(self):
          return super().reset()
  
      def info(self):
          return "Exact variational method."
  
      @property
      def energy(self):
          return self._loss_stats
  
      def _forward_and_backward(self):
          self.prefactors, eloc, self._loss_stats = self._get_mc_stats(self._ham)
          E0 = _mean(eloc)
          eloc = (eloc - E0 *self.prefactors)/self.total_basis_size
          if self._sr:
              self._grads, self._jac = self._machine.vector_jacobian_prod(self.basis, eloc, self._grads, return_jacobian=True)
  
              self._grads = tree_map(_sum_inplace, self._grads)
  
              self._dp = self._sr.compute_update(self._jac, self._grads, self._dp, self.prefactors)
          else:
              self._grads = self._machine.vector_jacobian_prod(self.basis, eloc, self._grads)
  
              self._grads = tree_map(_sum_inplace, self._grads)
  
              if not self._machine.has_complex_parameters:
                  self._dp = tree_map(lambda x: x.real, self._grads)
              else:
                  self._dp = self._grads
  
          return self._dp