def test_harmonic_oscillator(self, num_electrons, expected_total_energy,
                                 expected_kinetic_energy):
        grids = np.linspace(-5, 5, 501)
        potential_fn = functools.partial(single_electron.harmonic_oscillator,
                                         k=1)
        solver = single_electron.SparseEigenSolver(grids=grids,
                                                   potential_fn=potential_fn,
                                                   num_electrons=num_electrons)

        solver.solve_ground_state()
        # Integrate the density over the grid points.
        norm = np.sum(solver.density) * solver.dx

        np.testing.assert_allclose(norm, num_electrons)
        # i-th eigen-energy of the quantum harmonic oscillator is
        # 0.5 + (i - 1).
        # The total energy for num_electrons states is
        # (0.5 + 0.5 + num_electrons - 1) / 2 * num_electrons
        # = num_electrons ** 2 / 2
        np.testing.assert_allclose(solver.total_energy,
                                   expected_total_energy,
                                   atol=1e-3)
        # Kinetic energy should equal to half of the total energy.
        # num_electrons ** 2 / 4
        np.testing.assert_allclose(solver.kinetic_energy,
                                   expected_kinetic_energy,
                                   atol=1e-3)
 def test_additional_levels_negative(self):
   with self.assertRaisesRegexp(
       ValueError,
       'additional_levels is expected to be non-negative, but got -1'):
     single_electron.SparseEigenSolver(
         grids=np.linspace(-5, 5, 501),
         potential_fn=functools.partial(
             single_electron.harmonic_oscillator, k=1),
         num_electrons=3,
         additional_levels=-1)
 def test_additional_levels_too_large(self):
     with self.assertRaisesRegexp(
             ValueError,
             r'additional_levels is expected to be smaller than '
             r'num_grids - num_electrons \(498\), but got 499'):
         single_electron.SparseEigenSolver(
             grids=np.linspace(-5, 5, 501),
             potential_fn=functools.partial(
                 single_electron.harmonic_oscillator, k=1),
             num_electrons=3,
             additional_levels=499)
  def test_poschl_teller(
      self, num_electrons, lam, expected_energy, atol):
    # NOTE(leeley): Default additional_levels cannot converge to the correct
    # eigenstate, even for the first eigenstate. I set the additional_levels
    # to 20 so it can converge to the correct eigenstate and reach the same
    # accuracy of the EigenSolver.
    solver = single_electron.SparseEigenSolver(
        grids=np.linspace(-10, 10, 1001),
        potential_fn=functools.partial(single_electron.poschl_teller, lam=lam),
        num_electrons=num_electrons,
        additional_levels=20)

    solver.solve_ground_state()

    self.assertAlmostEqual(np.sum(solver.density) * solver.dx, num_electrons)
    np.testing.assert_allclose(solver.total_energy, expected_energy, atol=atol)
    def test_gaussian_dips(self):
        grids = np.linspace(-5, 5, 501)
        coeff = np.array([1., 1.])
        sigma = np.array([1., 1.])
        mu = np.array([-2., 2.])
        potential_fn = functools.partial(single_electron.gaussian_dips,
                                         coeff=coeff,
                                         sigma=sigma,
                                         mu=mu)
        solver = single_electron.SparseEigenSolver(grids, potential_fn)

        solver.solve_ground_state()
        # Integrate the density over the grid points.
        norm = np.sum(solver.density) * solver.dx
        # Exact kinetic energy from von Weizsacker functional.
        t_vw = single_electron.vw_grid(solver.density, solver.dx)

        np.testing.assert_allclose(norm, 1.)
        # Kinetic energy should equal to the exact solution from
        # von Weizsacker kinetic energy functional.
        np.testing.assert_allclose(solver.kinetic_energy, t_vw, atol=1e-4)