Beispiel #1
0
 def setup_impurity_solver(self,
                           mesh_param={},
                           sp={},
                           nrgp={},
                           verbose_nrg=False):
     self.cp.update(
         mesh_param
     )  # dictionary cp is set up by model-specific constructor of the derived class, but we need to add mesh_parameters
     self.S = Solver(**self.cp)
     self.S.set_verbosity(verbose_nrg)
     self.sp.update(sp)  # add remaining solve parameters
     self.nrgp.update(nrgp)  # optionally add low-level NRG parameters
     self.S.set_nrg_params(**self.nrgp)
     # Now we initialize the physical quantities
     Sigma, mu = self.initial_Sigma_mu(
     )  # Get initial self-energy and chemical potential
     Gloc, Delta = self_consistency(
         Sigma, mu, self.ht
     )  # Initialize local GF Gloc and hybridization function Delta
     Gloc, Delta, mu = self.adjust_mu(
         Delta, Sigma, mu
     )  # Adjust mu toward satisfying occupancy goal (and return updated Glocal and Delta as well)
     self.Gloc = Gloc.copy()  # Initial approximation for local lattice GF
     self.Delta = Delta.copy(
     )  # Initial approximation for hybridization function
     self.set_mu(mu)  # Sets mu and updates the impurity level
class test_2orb(unittest.TestCase):

    # Construct Parameters
    cp = {}
    cp["model"] = "2orb-UJ"
    cp["symtype"] = "QS"
    cp["mesh_max"] = 1.0
    cp["mesh_min"] = 1e-1
    cp["mesh_ratio"] = 1.1

    # Set up the Solver
    S = Solver(**cp)

    # Solve Parameters
    sp = {}
    sp["T"] = 1e-1
    sp["Lambda"] = 4.0
    sp["Nz"] = 1
    sp["Tmin"] = 0.5
    sp["keep"] = 2000
    sp["keepenergy"] = 10.0

    # Model Parameters
    mp = {}
    mp["U1"] = 1.0
    mp["U2"] = 0.9
    mp["eps1"] = -0.5
    mp["eps2"] = -0.4
    mp["U12"] = 0.1
    mp["J12"] = 0.05
    sp["model_parameters"] = mp

    # Low-level NRG Parameters
    np = {}
    np["bins"] = 50
    S.set_nrg_params(**np)

    # # Initialize hybridization function
    S.Delta_w['imp'][0,0] << 0.5 * SemiCircular(1.0)
    S.Delta_w['imp'][1,1] << 0.4 * SemiCircular(1.0)
    # Out-of-diagonal Delta is zero

    # Solve the impurity model
    S.solve(**sp)

    # # Store the Result
    with HDFArchive("3_2orb-UJ_QS.out.h5", 'w') as arch:
        arch["A_w"] = S.A_w
        arch["G_w"] = S.G_w
        arch["F_w"] = S.F_w
        arch["Sigma_w"] = S.Sigma_w

    # Compare against reference result
    h5diff("3_2orb-UJ_QS.out.h5", "3_2orb-UJ_QS.ref.h5")
Beispiel #3
0
class test_SIAM(unittest.TestCase):

    # Construct Parameters
    cp = {}
    cp["model"] = "SIAM"
    cp["symtype"] = "QS"
    cp["mesh_max"] = 1.0
    cp["mesh_min"] = 1e-3
    cp["mesh_ratio"] = 1.1

    # Set up the Solver
    S = Solver(**cp)

    # Solve Parameters
    sp = {}
    sp["T"] = 1e-3
    sp["Lambda"] = 4.0
    sp["Nz"] = 4
    sp["Tmin"] = 1e-4
    sp["keep"] = 50
    sp["keepenergy"] = 6.0

    # Model Parameters
    mp = {}
    mp["U1"] = 0.5
    mp["eps1"] = -0.24
    sp["model_parameters"] = mp

    # Low-level NRG Parameters
    np = {}
    np["bins"] = 50
    S.set_nrg_params(**np)

    # # Initialize hybridization function
    S.Delta_w['imp'] << 0.1 * SemiCircular(1.0)

    # Solve the impurity model
    S.solve(**sp)

    if mpi.is_master_node():
      # Store the Result
      fnout = "MPI_SIAM_QS_np%i.out.h5" % mpi.size
      with HDFArchive(fnout, 'w') as arch:
        arch["A_w"] = S.A_w
        arch["G_w"] = S.G_w
        arch["F_w"] = S.F_w
        arch["Sigma_w"] = S.Sigma_w

      # Compare against reference result
      fnref = "MPI_SIAM_QS.ref.h5" # the same for all cases!
      h5diff(fnout, fnref)
Beispiel #4
0
class test_SIAM(unittest.TestCase):

    # Construct Parameters
    cp = {}
    cp["model"] = "SIAM"
    cp["symtype"] = "QSZ"
    cp["mesh_max"] = 1.0
    cp["mesh_min"] = 1e-3
    cp["mesh_ratio"] = 1.1

    # Set up the Solver
    S = Solver(**cp)

    # Solve Parameters
    sp = {}
    sp["T"] = 1e-3
    sp["Lambda"] = 4.0
    sp["Nz"] = 2
    sp["Tmin"] = 1e-4
    sp["keep"] = 50
    sp["keepenergy"] = 6.0

    # Model Parameters
    mp = {}
    mp["U1"] = 0.5
    mp["eps1"] = -0.24
    mp["B1"] = -0.1
    sp["model_parameters"] = mp

    # Low-level NRG Parameters
    np = {}
    np["bins"] = 50
    S.set_nrg_params(**np)

    # # Initialize hybridization function
    S.Delta_w['up'] << 0.05 * SemiCircular(1.0)
    S.Delta_w['dn'] << 0.1 * SemiCircular(1.0)

    # Solve the impurity model
    S.solve(**sp)

    # # Store the Result
    with HDFArchive("2_SIAM_QSZ.out.h5", 'w') as arch:
        arch["A_w"] = S.A_w
        arch["G_w"] = S.G_w
        arch["F_w"] = S.F_w
        arch["Sigma_w"] = S.Sigma_w

    # Compare against reference result
    h5diff("2_SIAM_QSZ.out.h5", "2_SIAM_QSZ.ref.h5")
class test_SIAM(unittest.TestCase):

    # Construct Parameters
    cp = {}
    cp["model"] = "SIAM"
    cp["symtype"] = "QS"
    cp["mesh_max"] = 1.0
    cp["mesh_min"] = 1e-3
    cp["mesh_ratio"] = 1.1

    # Set up the Solver
    S = Solver(**cp)

    # Solve Parameters
    sp = {}
    sp["T"] = 1e-3
    sp["Lambda"] = 4.0
    sp["Nz"] = 2
    sp["Tmin"] = 1e-4
    sp["keep"] = 50
    sp["keepenergy"] = 6.0

    # Model Parameters
    mp = {}
    mp["U1"] = 0.5
    mp["eps1"] = -0.24
    sp["model_parameters"] = mp

    # Low-level NRG Parameters
    np = {}
    np["bins"] = 50
    S.set_nrg_params(**np)

    # # Initialize hybridization function
    S.Delta_w['imp'] << 0.1 * SemiCircular(1.0)

    # Solve the impurity model
    S.solve(**sp)

    if mpi.is_master_node(): HDFArchive('Solver.h5', 'w')['S'] = S

    # Rerun the Solver and Compare
    S_old = HDFArchive('Solver.h5', 'r')['S']
    S_old.solve(**S_old.last_solve_params)
    assert_block_gfs_are_close(S_old.G_w, S.G_w, 1e-12)
class test_SIAM(unittest.TestCase):

    # Construct Parameters
    cp = {}
    cp["model"] = "SIAM"
    cp["symtype"] = "QS"
    cp["mesh_max"] = 1.0
    cp["mesh_min"] = 1e-3
    cp["mesh_ratio"] = 1.1

    # Set up the Solver
    S = Solver(**cp)

    # Solve Parameters
    sp = {}
    sp["T"] = 1e-3
    sp["Lambda"] = 4.0
    sp["Nz"] = 2
    sp["Tmin"] = 1e-4
    sp["keep"] = 50
    sp["keepenergy"] = 6.0

    # Model Parameters
    mp = {}
    mp["U1"] = 0.5
    mp["eps1"] = -0.24
    sp["model_parameters"] = mp

    # Low-level NRG Parameters
    np = {}
    np["bins"] = 50
    S.set_nrg_params(**np)

    # # Initialize hybridization function
    S.Delta_w['imp'] << 0.1 * SemiCircular(1.0)

    # Solve the impurity model
    S.solve(**sp)
    G_w_1 = S.G_w.copy()

    S.solve(**sp)
    G_w_2 = S.G_w.copy()

    assert_block_gfs_are_close(G_w_1, G_w_2, 1e-12)
Beispiel #7
0
from nrgljubljana_interface import Solver, Flat
from h5 import *

# Parameters
D, V, U = 1.0, 0.25, 1.0
e_f = -U / 2.0  # particle-hole symmetric case
T = 1e-5

# Set up the Solver
S = Solver(model="SIAM",
           symtype="QS",
           mesh_max=2.0,
           mesh_min=1e-5,
           mesh_ratio=1.01)

# Solve Parameters
sp = {
    "T": T,
    "Lambda": 2.0,
    "Nz": 4,
    "Tmin": 1e-6,
    "keep": 2000,
    "keepenergy": 10.0,
    "bandrescale": 1.0
}

# Model Parameters
mp = {"U1": U, "eps1": e_f}
sp["model_parameters"] = mp

# Initialize hybridization function
Beispiel #8
0
from pytriqs.archive import *
import math

# Parameters
D = 1.0  # bandwidth
U = 1.0
Gamma = 0.1
e_f = -0.4
U12 = 1.0
J12 = 0.0
T = 1e-3

# Set up the Solver
S = Solver(model="2orb-UJ",
           symtype="QS",
           mesh_max=2.00676,
           mesh_min=1e-5,
           mesh_ratio=1.01)

# Solve Parameters
sp = {
    "T": T,
    "Lambda": 4.0,
    "Nz": 4,
    "Tmin": 1e-5,
    "keep": 4000,
    "keepenergy": 10.0,
    "bandrescale": 1.0
}

# Model Parameters
Beispiel #9
0
from nrgljubljana_interface import Solver, Flat
from pytriqs.archive import *

# Parameters
D, V, U = 1.0, 0.2, 0  # Zero e-e interaction
e_f = -U / 2.0  # particle-hole symmetric case
T = 1e-5
omega = 0.2  # phonon frequency
g1 = 0.2  # electron-phonon coupling strength
n1 = 1  # n1 in g1*(a+a^dag)*(n-n1)

# Set up the Solver
S = Solver(model="Holstein/Nph=10",
           symtype="QS",
           mesh_max=2.0,
           mesh_min=1e-5,
           mesh_ratio=1.01)

# Solve Parameters
sp = {
    "T": T,
    "Lambda": 2.0,
    "Nz": 4,
    "Tmin": 1e-6,
    "keep": 1000,
    "keepenergy": 10.0,
    "bandrescale": 1.0
}

# Model Parameters
mp = {"U1": U, "eps1": e_f, "omega": omega, "g1": g1, "n1": n1}
Beispiel #10
0
class DMFT_solver(object):
    itern = 0  # iteration counter
    okn = 0  # number of iterations in which convergent criteria were satisfied
    solution_filename = "solution.h5"  # converged solution (all convergence criteria satisfied)
    checkpoint_filename = "checkpoint.h5"  # current solution (for checkpoint/restart functionality)
    stats_filename = "stats.dat"  # some statistics about the DMFT iteration
    stop_flag_file = "STOP"  # the calculation can be aborted by creating this file
    converged_flag_file = "CONVERGED"  # the script touches this file upon reaching convergence

    # Provides the initial Sigma and mu
    def initial_Sigma_mu(self):
        if os.path.isfile(self.checkpoint_filename
                          ):  # continue DMFT iteration after interruption
            return restart_calculation(self.checkpoint_filename)
        elif os.path.isfile(
                self.solution_filename
        ):  # continue DMFT iteration after failed convergence
            return restart_calculation(self.solution_filename)
        else:  # start from scratch
            return self.new_calculation()

    def __init__(self, param, dmft_param):
        self.param = param
        self.dmft_param = dmft_param
        self.occupancy_goal = param["occupancy"]
        self.T = param["T"]
        self.observables = []  # List of expectation values to compute
        self.cp = {}  # Dictinary with creator parameters
        self.sp = {
            "T": self.T,
            "model_parameters": {}
        }  # Dictionary with solver parameters
        self.mp = {}  # Dictionary with model parameters
        self.nrgp = {
        }  # Dictionary with low-level NRG Parameters (optional tweaks)
        # Open file with basic information (convergence criteria, occupancy, expectation values of operators) for monitoring the iteration process
        if mpi.is_master_node():
            self.stats_file = open(self.stats_filename, "w",
                                   buffering=1)  # line buffered
        # Initialize a function ht0 for calculating the Hilbert transform of the DOS
        if (param["dos"] == "Bethe"):
            self.ht1 = lambda z: 2 * (
                z - 1j * np.sign(z.imag) * np.sqrt(1 - z**2)
            )  # Analytical expression for Hilbert transform of Bethe lattice DOS
            self.ht0 = lambda z: self.ht1(z / param["Bethe_unit"])
        else:
            table = np.loadtxt(param["dos"])
            self.dosA = Gf(mesh=MeshReFreqPts(table[:, 0]), target_shape=[])
            for i, w in enumerate(self.dosA.mesh):
                self.dosA[w] = np.array([[table[i, 1]]])
            self.ht0 = lambda z: hilbert_transform_refreq(self.dosA, z)

    # Performs the Hilbert transform with argument z, ensuring that the imaginary part is positive (to produce retarded GFs)
    def ht(self, z, EPS=1e-20):
        return self.ht0(z.real + 1j * (z.imag if z.imag > 0.0 else EPS)
                        )  # Fix problems due to causality violation

    # Model specific changes to the impurity model parameters need to be defined in derived classes
    def set_mu(self, mu):
        self.mu = mu

    # This may be called after all constructors (base and all derived classes) have done their job
    def setup_impurity_solver(self,
                              mesh_param={},
                              sp={},
                              nrgp={},
                              verbose_nrg=False):
        self.cp.update(
            mesh_param
        )  # dictionary cp is set up by model-specific constructor of the derived class, but we need to add mesh_parameters
        self.S = Solver(**self.cp)
        self.S.set_verbosity(verbose_nrg)
        self.sp.update(sp)  # add remaining solve parameters
        self.nrgp.update(nrgp)  # optionally add low-level NRG parameters
        self.S.set_nrg_params(**self.nrgp)
        # Now we initialize the physical quantities
        Sigma, mu = self.initial_Sigma_mu(
        )  # Get initial self-energy and chemical potential
        Gloc, Delta = self_consistency(
            Sigma, mu, self.ht
        )  # Initialize local GF Gloc and hybridization function Delta
        Gloc, Delta, mu = self.adjust_mu(
            Delta, Sigma, mu
        )  # Adjust mu toward satisfying occupancy goal (and return updated Glocal and Delta as well)
        self.Gloc = Gloc.copy()  # Initial approximation for local lattice GF
        self.Delta = Delta.copy(
        )  # Initial approximation for hybridization function
        self.set_mu(mu)  # Sets mu and updates the impurity level

    # Iteratively adjust mu, taking into account the self-consistency equation.
    # Returns an improved estimate for the hybridisation function.
    def adjust_mu(self, Delta_in, Sigma, old_mu):
        Delta = Delta_in.copy()
        mu = old_mu
        max_mu_adjust = self.dmft_param.get(
            "max_mu_adjust", 10
        )  #  number of cycles for adjusting the value of the chemical potential
        for _ in range(max_mu_adjust):
            mu = update_mu(Delta, Sigma, self.occupancy_goal, self.T, mu)
            Gloc, Delta = self_consistency(Sigma, mu, self.ht)
        new_mu = mu
        max_mu_diff = self.dmft_param.get(
            "max_mu_diff", None)  # maximum change in mu that is allowed
        if max_mu_diff is not None:
            new_mu = clamp(old_mu - max_mu_diff, new_mu, old_mu + max_mu_diff)
        mu_alpha = self.dmft_param.get(
            "mu_alpha",
            1.0)  # mixing parameter for the chemical potential adjustment
        new_mu = mu_alpha * new_mu + (1.0 - mu_alpha) * old_mu
        if verbose():
            print("Adjusted mu from %.10f to %.10f" % (old_mu, new_mu))
        return Gloc, Delta, new_mu

    def set_model_parameters(self, mp):
        for k, v in mp.items():
            if v is None:
                del mp[k]  # Remove undefined parameters from the dictionary
        self.sp["model_parameters"].update(mp)  # Update, not an assignment!

    # Store the complete set of results
    def store_result(self, fn):
        with HDFArchive(fn, 'w') as arch:
            arch["S"] = self.S
            arch["Gloc"] = self.Gloc
            arch["Delta"] = self.Delta
            arch["mu"] = self.mu
            arch["Gself"] = self.Gself
            arch["occupancy"] = self.occupancy

    # Perform a DMFT step. Input is the hybridization function for solving the effective impurity model,
    # output is a new hybridization function resulting from the application of the DMFT self-consistency equation.
    def dmft_step(self, Delta_in):
        self.itern += 1
        if verbose():
            print("\nIteration %i min_iter=%i max_iter=%i" %
                  (self.itern, self.dmft_param["min_iter"],
                   self.dmft_param["max_iter"]))
        Delta_in_fixed = fix_hyb_function(Delta_in,
                                          self.dmft_param["Delta_min"])
        self.S.Delta_w << Delta_in_fixed

        t0 = timeit.default_timer()
        self.S.solve(**self.sp)  # Solve the impurity model
        t1 = timeit.default_timer()
        dt = int(t1 - t0)
        solver_time = '{:02}:{:02}:{:02}'.format(dt // 3600, dt % 3600 // 60,
                                                 dt % 60)  # hh:mm:ss format

        self.Gself = calc_G(
            Delta_in_fixed, self.S.Sigma_w,
            self.mu)  # impurity GF ("self-energy-trick" improved)
        Gloc_prev = self.Gloc.copy(
        )  # store copy of previous Gloc for checking convergence
        self.Gloc, self.Delta = self_consistency(
            self.S.Sigma_w, self.mu,
            self.ht)  # apply the DMFT self-consistency equation
        self.occupancy = calc_occupancy(
            self.Delta, self.S.Sigma_w, self.mu,
            self.T)  # occupancy calculated from the local lattice GF

        diff_loc_imp = gf_diff(
            self.Gself,
            self.Gloc)  # difference between impurity and local lattice GF
        diff_prev = gf_diff(
            self.Gloc, Gloc_prev
        )  # difference between two consecutively computed local latice GFs
        diff_occupancy = abs(
            self.occupancy - self.occupancy_goal
        )  # this difference is used as the measure of deviation

        stats = OrderedDict([("itern", self.itern), ("time", solver_time),
                             ("mu", self.mu), ("diff_loc_imp", diff_loc_imp),
                             ("diff_prev", diff_prev),
                             ("diff_occupancy", diff_occupancy),
                             ("occupancy", self.occupancy)])
        for i in self.observables:
            stats[i] = self.S.expv[i]
        header_string = fmt_str_header(
            len(stats)).format(*[i for i in stats.keys()])
        stats_string = fmt_str(len(stats)).format(*[i for i in stats.values()])
        if mpi.is_master_node():
            if self.itern == 1:
                print(header_string, file=self.stats_file)
            print(stats_string, file=self.stats_file)
        if verbose():
            print(header_string)
            print(stats_string)

        if self.dmft_param["store_steps"] and mpi.is_master_node():
            os.mkdir(str(self.itern))  # one subdirectory per iteration
            save_BlockGf(str(self.itern) + "/Delta", Delta_in_fixed)
            save_BlockGf(str(self.itern) + "/Sigma",
                         self.S.Sigma_w)  # self-energy
            save_BlockGf(str(self.itern) + "/G",
                         self.Gloc)  # local lattice Green's function
            save_BlockA(str(self.itern) + "/A",
                        self.Gloc)  # spectral function of local lattice GF
            self.store_result(str(self.itern) + "/" + self.solution_filename)

        if mpi.is_master_node():
            self.store_result(self.checkpoint_filename
                              )  # for checkpoint/restart functionality

        # Check for convergence
        if (diff_loc_imp < self.dmft_param["eps_loc_imp"]
                and diff_prev < self.dmft_param["eps_prev"]
                and diff_occupancy < self.dmft_param["eps_occupancy"]):
            self.okn += 1
        else:
            self.okn = 0

        # The only way to exit the DMFT loop is by generating exceptions.
        if (self.okn >= self.dmft_param.get("conv_iter", 1)
                and self.itern >= self.dmft_param["min_iter"]):
            if mpi.is_master_node():
                self.store_result(self.solution_filename
                                  )  # full converged results as an HDF5 file
                try:
                    os.remove(self.checkpoint_filename
                              )  # checkpoint file is no longer needed
                except OSError:
                    pass
                if self.converged_flag_file:
                    touch(self.converged_flag_file
                          )  # signal convergence through a file
            raise Converged("%s\n%s" % (header_string, stats_string))
        if (self.itern == self.dmft_param["max_iter"]):
            raise FailedToConverge("%s\n%s" % (header_string, stats_string))
        if self.stop_flag_file and os.path.isfile(self.stop_flag_file):
            raise ForcedStop()

        if self.dmft_param["occup_method"] == "adjust":
            self.Gloc, self.Delta, new_mu = self.adjust_mu(
                self.Delta, self.S.Sigma_w,
                self.mu)  # here we update mu to get closer to target occupancy
            self.set_mu(new_mu)

        return self.Delta

    # DMFT driver routine with linear mixing
    def solve_with_linear_mixing(self, alpha, mixer_param):
        Delta_in = self.Delta.copy()
        while True:
            Delta_out = self.dmft_step(Delta_in)
            newDelta = alpha * Delta_out + (1 - alpha) * Delta_in
            Delta_in << newDelta

    # DMFT driver routine with Broyden mixing: the goal is to find a root of the function F(Delta)=dmft_step(Delta)-Delta.
    def solve_with_broyden_mixing(self, alpha, broyden_param):
        if self.dmft_param["occup_method"] == "broyden":

            def F(Delta_in, mu):
                if verbose():
                    print("(Broyden) Setting mu to %.18f" % mu)
                self.set_mu(mu)
                Delta_out = self.dmft_step(Delta_in)
                occup_factor = self.dmft_param.get("occup_factor", 1.0)
                return Delta_out - Delta_in, occup_factor * (
                    self.occupancy_goal - self.occupancy)

            npF = lambda x: wrap(*F(*unwrap(x, self.S, scalar=True))
                                 )  # unpack the tuples
            xin = wrap(self.Delta, self.mu)
        else:

            def F(Delta):
                return self.dmft_step(Delta) - Delta

            npF = lambda x: bgf_to_nparray(F(nparray_to_bgf(x, self.S)))
            xin = bgf_to_nparray(self.Delta)
        bp = {
            "reduction_method": "svd",
            "max_rank": 20,
            "f_tol": 1e-99
        }  # Loop forever (small f_tol!)
        bp.update(broyden_param)
        optimize.broyden1(npF, xin, alpha=alpha, verbose=verbose(), **bp)
        raise Converged("broyden1() converged")

    def solve(self):
        alpha = self.dmft_param["alpha"]
        if (self.dmft_param["mixing_method"] == "linear"):
            self.solve_with_linear_mixing(alpha)
        if (self.dmft_param["mixing_method"] == "broyden"):
            self.solve_with_broyden_mixing(
                alpha, self.dmft_param.get("broyden_param", {}))
Beispiel #11
0
# Additional quantities of interest
observables = ["n_d", "n_d^2"]
if B is not None:
  observables.extend(["SZd"])
if "Holstein" in model:
  observables.extend(["nph", "displ", "displ^2"])

# Run-time global variables
itern = 0                                          # iteration counter
verbose = verbose and mpi.is_master_node()         # output is only produced by the master node
store_steps = store_steps and mpi.is_master_node() # output is only produced by the master node
symtype = ("QS" if B is None else "QSZ")

# Set up the Solver
S = Solver(model=model, symtype=symtype, mesh_max=mesh_max, mesh_min=mesh_min, mesh_ratio=mesh_ratio)
S.set_verbosity(verbose_nrg)

newG = lambda : S.G_w.copy()                             # Creates a BlockGf object of appropriate structure
nr_blocks = lambda bgf : len([bl for bl in bgf.indices]) # Returns the number of blocks in a BlockGf object
block_size = lambda bl : len(S.G_w[bl].indices[0])       # Matrix size of Green's functions in block 'bl'
identity = lambda bl : np.identity(block_size(bl))       # Returns the identity matrix in block 'bl'

# Solve Parameters
sp = { "T": T, "Lambda": 2.0, "Nz": 4, "Tmin": 1e-5, "keep": 10000, "keepenergy": 10.0 }

# Model Parameters
mp = { "U1": U, "B1": B, "omega": omega, "g1": g1, "n1": n1 }
for k,v in mp.items():
  if v is None: del mp[k] # Remove undefined parameters from the dictionary
sp["model_parameters"] = mp