Beispiel #1
0
    def DeclareEquations(self):
        dae.daeModel.DeclareEquations(self)
        ndD = self.ndD
        N = ndD["N"]  # number of grid points in particle
        T = self.ndD_s["T"]  # nondimensional temperature
        r_vec, volfrac_vec = geo.get_unit_solid_discr(ndD['shape'], N)

        # Prepare the Ideal Solution log ratio terms
        self.ISfuncs = None
        if ndD["logPad"]:
            self.ISfuncs = np.array([
                extern_funcs.LogRatio("LR", self, dae.unit(), self.c(k))
                for k in range(N)
            ])

        # Prepare noise
        self.noise = None
        if ndD["noise"]:
            numnoise = ndD["numnoise"]
            noise_prefac = ndD["noise_prefac"]
            tvec = np.linspace(0., 1.05 * self.ndD_s["tend"], numnoise)
            noise_data = noise_prefac * np.random.randn(numnoise, N)
            # Previous_output is common for all external functions
            previous_output = []
            self.noise = [
                extern_funcs.InterpTimeVector("noise", self, dae.unit(),
                                              dae.Time(), tvec, noise_data,
                                              previous_output, _position_)
                for _position_ in range(N)
            ]

        # Figure out mu_O, mu of the oxidized state
        mu_O, act_lyte = calc_mu_O(self.c_lyte, self.phi_lyte, self.phi_m, T,
                                   self.ndD_s["elyteModelType"])

        # Define average filling fraction in particle
        eq = self.CreateEquation("cbar")
        eq.Residual = self.cbar()
        for k in range(N):
            eq.Residual -= self.c(k) * volfrac_vec[k]

        # Define average rate of filling of particle
        eq = self.CreateEquation("dcbardt")
        eq.Residual = self.dcbardt()
        for k in range(N):
            eq.Residual -= self.c.dt(k) * volfrac_vec[k]

        c = np.empty(N, dtype=object)
        c[:] = [self.c(k) for k in range(N)]
        if ndD["type"] in ["ACR", "diffn", "CHR"]:
            # Equations for 1D particles of 1 field varible
            self.sld_dynamics_1D1var(c, mu_O, act_lyte, self.ISfuncs,
                                     self.noise)
        elif ndD["type"] in ["homog", "homog_sdn"]:
            # Equations for 0D particles of 1 field variables
            self.sld_dynamics_0D1var(c, mu_O, act_lyte, self.ISfuncs,
                                     self.noise)

        for eq in self.Equations:
            eq.CheckUnitsConsistency = False
Beispiel #2
0
    def __init__(self,
                 Name,
                 Parent=None,
                 Description="",
                 ndD=None,
                 ndD_s=None):
        dae.daeModel.__init__(self, Name, Parent, Description)

        if (ndD is None) or (ndD_s is None):
            raise Exception("Need input parameter dictionary")
        self.ndD = ndD
        self.ndD_s = ndD_s

        # Domain
        self.Dmn = dae.daeDomain("discretizationDomain", self, dae.unit(),
                                 "discretization domain")

        # Define some variable types
        mole_frac_t = dae.daeVariableType(name="mole_frac_t",
                                          units=dae.unit(),
                                          lowerBound=0,
                                          upperBound=1,
                                          initialGuess=0.25,
                                          absTolerance=ndD_s["absTol"])
        # Variables
        self.c = dae.daeVariable("c", mole_frac_t, self,
                                 "Concentration in active particle",
                                 [self.Dmn])
        self.cbar = dae.daeVariable(
            "cbar", mole_frac_t, self,
            "Average concentration in active particle")
        self.dcbardt = dae.daeVariable("dcbardt", dae.no_t, self,
                                       "Rate of particle filling")
        if ndD["type"] not in ["ACR"]:
            self.Rxn = dae.daeVariable("Rxn", dae.no_t, self,
                                       "Rate of reaction")
        else:
            self.Rxn = dae.daeVariable("Rxn", dae.no_t, self,
                                       "Rate of reaction", [self.Dmn])

        # Ports
        self.portInLyte = ports.portFromElyte("portInLyte", dae.eInletPort,
                                              self,
                                              "Inlet port from electrolyte")
        self.portInBulk = ports.portFromBulk(
            "portInBulk", dae.eInletPort, self,
            "Inlet port from e- conducting phase")
        self.phi_lyte = self.portInLyte.phi_lyte()
        self.c_lyte = self.portInLyte.c_lyte()
        self.phi_m = self.portInBulk.phi_m()
Beispiel #3
0
    def __init__(self,
                 Name,
                 Parent=None,
                 Description="",
                 ndD=None,
                 ndD_s=None):
        dae.daeModel.__init__(self, Name, Parent, Description)
        if (ndD is None) or (ndD_s is None):
            raise Exception("Need input parameter dictionary")
        self.ndD = ndD
        self.ndD_s = ndD_s

        # Domain
        self.Dmn = dae.daeDomain("discretizationDomain", self, dae.unit(),
                                 "discretization domain")

        # Variables
        self.c1 = dae.daeVariable(
            "c1", mole_frac_t, self,
            "Concentration in 'layer' 1 of active particle", [self.Dmn])
        self.c2 = dae.daeVariable(
            "c2", mole_frac_t, self,
            "Concentration in 'layer' 2 of active particle", [self.Dmn])
        self.cbar = dae.daeVariable(
            "cbar", mole_frac_t, self,
            "Average concentration in active particle")
        self.c1bar = dae.daeVariable(
            "c1bar", mole_frac_t, self,
            "Average concentration in 'layer' 1 of active particle")
        self.c2bar = dae.daeVariable(
            "c2bar", mole_frac_t, self,
            "Average concentration in 'layer' 2 of active particle")
        self.dcbardt = dae.daeVariable("dcbardt", dae.no_t, self,
                                       "Rate of particle filling")
        if ndD["type"] not in ["ACR2"]:
            self.Rxn1 = dae.daeVariable("Rxn1", dae.no_t, self,
                                        "Rate of reaction 1")
            self.Rxn2 = dae.daeVariable("Rxn2", dae.no_t, self,
                                        "Rate of reaction 2")
        else:
            self.Rxn1 = dae.daeVariable("Rxn1", dae.no_t, self,
                                        "Rate of reaction 1", [self.Dmn])
            self.Rxn2 = dae.daeVariable("Rxn2", dae.no_t, self,
                                        "Rate of reaction 2", [self.Dmn])

        #Get reaction rate function from dictionary name
        self.calc_rxn_rate = getattr(reactions, ndD["rxnType"])

        # Ports
        self.portInLyte = ports.portFromElyte("portInLyte", dae.eInletPort,
                                              self,
                                              "Inlet port from electrolyte")
        self.portInBulk = ports.portFromBulk(
            "portInBulk", dae.eInletPort, self,
            "Inlet port from e- conducting phase")
        self.phi_lyte = self.portInLyte.phi_lyte
        self.c_lyte = self.portInLyte.c_lyte
        self.phi_m = self.portInBulk.phi_m
Beispiel #4
0
    def DeclareEquations(self):
        dae.daeModel.DeclareEquations(self)
        ndD = self.ndD
        N = ndD["N"]  # number of grid points in particle
        T = self.ndD_s["T"]  # nondimensional temperature
        r_vec, volfrac_vec = geo.get_unit_solid_discr(ndD['shape'], N)

        # Prepare the Ideal Solution log ratio terms
        self.ISfuncs1 = self.ISfuncs2 = None
        if ndD["logPad"]:
            self.ISfuncs1 = np.array([
                extern_funcs.LogRatio("LR1", self, dae.unit(), self.c1(k))
                for k in range(N)
            ])
            self.ISfuncs2 = np.array([
                extern_funcs.LogRatio("LR2", self, dae.unit(), self.c2(k))
                for k in range(N)
            ])
        ISfuncs = (self.ISfuncs1, self.ISfuncs2)

        # Prepare noise
        self.noise1 = self.noise2 = None
        if ndD["noise"]:
            numnoise = ndD["numnoise"]
            noise_prefac = ndD["noise_prefac"]
            tvec = np.linspace(0., 1.05 * self.ndD_s["tend"], numnoise)
            noise_data1 = noise_prefac * np.random.randn(numnoise, N)
            noise_data2 = noise_prefac * np.random.randn(numnoise, N)
            # Previous_output is common for all external functions
            previous_output1 = []
            previous_output2 = []
            self.noise1 = [
                extern_funcs.InterpTimeVector("noise1", self, dae.unit(),
                                              dae.Time(), tvec, noise_data1,
                                              previous_output1, _position_)
                for _position_ in range(N)
            ]
            self.noise2 = [
                extern_funcs.InterpTimeVector("noise2", self, dae.unit(),
                                              dae.Time(), tvec, noise_data2,
                                              previous_output2, _position_)
                for _position_ in range(N)
            ]
        noises = (self.noise1, self.noise2)

        # Figure out mu_O, mu of the oxidized state
        mu_O, act_lyte = calc_mu_O(self.c_lyte(), self.phi_lyte(),
                                   self.phi_m(), T,
                                   self.ndD_s["elyteModelType"])

        # Define average filling fractions in particle
        eq1 = self.CreateEquation("c1bar")
        eq2 = self.CreateEquation("c2bar")
        eq1.Residual = self.c1bar()
        eq2.Residual = self.c2bar()
        for k in range(N):
            eq1.Residual -= self.c1(k) * volfrac_vec[k]
            eq2.Residual -= self.c2(k) * volfrac_vec[k]
        eq = self.CreateEquation("cbar")
        eq.Residual = self.cbar() - utils.mean_linear(self.c1bar(),
                                                      self.c2bar())

        # Define average rate of filling of particle
        eq = self.CreateEquation("dcbardt")
        eq.Residual = self.dcbardt()
        for k in range(N):
            eq.Residual -= utils.mean_linear(self.c1.dt(k),
                                             self.c2.dt(k)) * volfrac_vec[k]

        c1 = np.empty(N, dtype=object)
        c2 = np.empty(N, dtype=object)
        c1[:] = [self.c1(k) for k in range(N)]
        c2[:] = [self.c2(k) for k in range(N)]
        if ndD["type"] in ["diffn2", "CHR2"]:
            # Equations for 1D particles of 1 field varible
            self.sld_dynamics_1D2var(c1, c2, mu_O, act_lyte, ISfuncs, noises)
        elif ndD["type"] in ["homog2", "homog2_sdn"]:
            # Equations for 0D particles of 1 field variables
            self.sld_dynamics_0D2var(c1, c2, mu_O, act_lyte, ISfuncs, noises)

        for eq in self.Equations:
            eq.CheckUnitsConsistency = False
Beispiel #5
0
    def __init__(self,
                 Name,
                 Parent=None,
                 Description="",
                 ndD_s=None,
                 ndD_e=None):
        dae.daeModel.__init__(self, Name, Parent, Description)

        if (ndD_s is None) or (ndD_e is None):
            raise Exception("Need input parameter dictionaries")
        self.ndD = ndD_s
        self.profileType = ndD_s['profileType']
        Nvol = ndD_s["Nvol"]
        Npart = ndD_s["Npart"]
        self.trodes = trodes = ndD_s["trodes"]

        # Domains where variables are distributed
        self.DmnCell = {}  # domains over full cell dimensions
        self.DmnPart = {}  # domains over particles in each cell volume
        if Nvol["s"] >= 1:  # If we have a separator
            self.DmnCell["s"] = dae.daeDomain(
                "DmnCell_s", self, dae.unit(),
                "Simulated volumes in the separator")
        for trode in trodes:
            self.DmnCell[trode] = dae.daeDomain(
                "DmnCell_{trode}".format(trode=trode), self, dae.unit(),
                "Simulated volumes in electrode {trode}".format(trode=trode))
            self.DmnPart[trode] = dae.daeDomain(
                "Npart_{trode}".format(trode=trode), self, dae.unit(),
                "Particles sampled in each control " +
                "volume in electrode {trode}".format(trode=trode))

        # Variables
        self.c_lyte = {}
        self.phi_lyte = {}
        self.phi_bulk = {}
        self.phi_part = {}
        self.R_Vp = {}
        self.ffrac = {}
        for trode in trodes:
            # Concentration/potential in electrode regions of elyte
            self.c_lyte[trode] = dae.daeVariable(
                "c_lyte_{trode}".format(trode=trode), conc_t, self,
                "Concentration in the elyte in electrode {trode}".format(
                    trode=trode), [self.DmnCell[trode]])
            self.phi_lyte[trode] = dae.daeVariable(
                "phi_lyte_{trode}".format(trode=trode), elec_pot_t, self,
                "Electric potential in elyte in electrode {trode}".format(
                    trode=trode), [self.DmnCell[trode]])
            self.phi_bulk[trode] = dae.daeVariable(
                "phi_bulk_{trode}".format(trode=trode), elec_pot_t, self,
                "Electrostatic potential in the bulk solid",
                [self.DmnCell[trode]])
            self.phi_part[trode] = dae.daeVariable(
                "phi_part_{trode}".format(trode=trode), elec_pot_t, self,
                "Electrostatic potential at each particle",
                [self.DmnCell[trode], self.DmnPart[trode]])
            self.R_Vp[trode] = dae.daeVariable(
                "R_Vp_{trode}".format(trode=trode), dae.no_t, self,
                "Rate of reaction of positives per electrode volume",
                [self.DmnCell[trode]])
            self.ffrac[trode] = dae.daeVariable(
                "ffrac_{trode}".format(trode=trode), mole_frac_t, self,
                "Overall filling fraction of solids in electrodes")
        if Nvol["s"] >= 1:  # If we have a separator
            self.c_lyte["s"] = dae.daeVariable(
                "c_lyte_s", conc_t, self,
                "Concentration in the electrolyte in the separator",
                [self.DmnCell["s"]])
            self.phi_lyte["s"] = dae.daeVariable(
                "phi_lyte_s", elec_pot_t, self,
                "Electrostatic potential in electrolyte in separator",
                [self.DmnCell["s"]])
        # Note if we're doing a single electrode volume simulation
        # It will be in a perfect bath of electrolyte at the applied
        # potential.
        if Nvol["a"] == 0 and Nvol["s"] == 0 and Nvol["c"] == 1:
            self.SVsim = True
        else:
            self.SVsim = False
        if not self.SVsim:
            # Ghost points (GP) to aid in boundary condition (BC) implemenation
            self.c_lyteGP_L = dae.daeVariable("c_lyteGP_L", conc_t, self,
                                              "c_lyte left BC GP")
            self.phi_lyteGP_L = dae.daeVariable("phi_lyteGP_L", elec_pot_t,
                                                self, "phi_lyte left BC GP")
        self.phi_applied = dae.daeVariable(
            "phi_applied", elec_pot_t, self,
            "Overall battery voltage (at anode current collector)")
        self.phi_cell = dae.daeVariable(
            "phi_cell", elec_pot_t, self,
            "Voltage between electrodes (phi_applied less series resistance)")
        self.current = dae.daeVariable("current", dae.no_t, self,
                                       "Total current of the cell")
        self.endCondition = dae.daeVariable(
            "endCondition", dae.no_t, self,
            "A nonzero value halts the simulation")

        # Create models for representative particles within electrode
        # volumes and ports with which to talk to them.
        self.portsOutLyte = {}
        self.portsOutBulk = {}
        self.particles = {}
        for trode in trodes:
            Nv = Nvol[trode]
            Np = Npart[trode]
            self.portsOutLyte[trode] = np.empty(Nv, dtype=object)
            self.portsOutBulk[trode] = np.empty((Nv, Np), dtype=object)
            self.particles[trode] = np.empty((Nv, Np), dtype=object)
            for vInd in range(Nv):
                self.portsOutLyte[trode][vInd] = ports.portFromElyte(
                    "portTrode{trode}vol{vInd}".format(trode=trode, vInd=vInd),
                    dae.eOutletPort, self, "Electrolyte port to particles")
                for pInd in range(Np):
                    self.portsOutBulk[trode][vInd, pInd] = ports.portFromBulk(
                        "portTrode{trode}vol{vInd}part{pInd}".format(
                            trode=trode, vInd=vInd, pInd=pInd),
                        dae.eOutletPort, self,
                        "Bulk electrode port to particles")
                    solidType = ndD_e[trode]["indvPart"][vInd, pInd]['type']
                    if solidType in ndD_s["2varTypes"]:
                        pMod = mod_electrodes.Mod2var
                    elif solidType in ndD_s["1varTypes"]:
                        pMod = mod_electrodes.Mod1var
                    else:
                        raise NotImplementedError("unknown solid type")
                    self.particles[trode][vInd, pInd] = pMod(
                        "partTrode{trode}vol{vInd}part{pInd}".format(
                            trode=trode, vInd=vInd, pInd=pInd),
                        self,
                        ndD=ndD_e[trode]["indvPart"][vInd, pInd],
                        ndD_s=ndD_s)
                    self.ConnectPorts(
                        self.portsOutLyte[trode][vInd],
                        self.particles[trode][vInd, pInd].portInLyte)
                    self.ConnectPorts(
                        self.portsOutBulk[trode][vInd, pInd],
                        self.particles[trode][vInd, pInd].portInBulk)
Beispiel #6
0
    def DeclareEquations(self):
        dae.daeModel.DeclareEquations(self)

        # Some values of domain lengths
        trodes = self.trodes
        ndD = self.ndD
        Nvol = ndD["Nvol"]
        Npart = ndD["Npart"]
        Nlyte = np.sum(list(Nvol.values()))

        # Define the overall filling fraction in the electrodes
        for trode in trodes:
            eq = self.CreateEquation("ffrac_{trode}".format(trode=trode))
            eq.Residual = self.ffrac[trode]()
            dx = 1. / Nvol[trode]
            # Make a float of Vtot, total particle volume in electrode
            # Note: for some reason, even when "factored out", it's a bit
            # slower to use Sum(self.psd_vol_ac[l].array([], [])
            tmp = 0
            for vInd in range(Nvol[trode]):
                for pInd in range(Npart[trode]):
                    Vj = ndD["psd_vol_FracVol"][trode][vInd, pInd]
                    tmp += self.particles[trode][vInd, pInd].cbar() * Vj * dx
            eq.Residual -= tmp

        # Define dimensionless R_Vp for each electrode volume
        for trode in trodes:
            for vInd in range(Nvol[trode]):
                eq = self.CreateEquation("R_Vp_trode{trode}vol{vInd}".format(
                    vInd=vInd, trode=trode))
                # Start with no reaction, then add reactions for each
                # particle in the volume.
                RHS = 0
                # sum over particle volumes in given electrode volume
                for pInd in range(Npart[trode]):
                    # The volume of this particular particle
                    Vj = ndD["psd_vol_FracVol"][trode][vInd, pInd]
                    RHS += -(ndD["beta"][trode] *
                             (1 - ndD["poros"][trode]) * ndD["P_L"][trode] *
                             Vj * self.particles[trode][vInd, pInd].dcbardt())
                eq.Residual = self.R_Vp[trode](vInd) - RHS

        # Define output port variables
        for trode in trodes:
            for vInd in range(Nvol[trode]):
                eq = self.CreateEquation(
                    "portout_c_trode{trode}vol{vInd}".format(vInd=vInd,
                                                             trode=trode))
                eq.Residual = (self.c_lyte[trode](vInd) -
                               self.portsOutLyte[trode][vInd].c_lyte())
                eq = self.CreateEquation(
                    "portout_p_trode{trode}vol{vInd}".format(vInd=vInd,
                                                             trode=trode))
                phi_lyte = self.phi_lyte[trode](vInd)
                eq.Residual = (phi_lyte -
                               self.portsOutLyte[trode][vInd].phi_lyte())
                for pInd in range(Npart[trode]):
                    eq = self.CreateEquation(
                        "portout_pm_trode{trode}v{vInd}p{pInd}".format(
                            vInd=vInd, pInd=pInd, trode=trode))
                    eq.Residual = (
                        self.phi_part[trode](vInd, pInd) -
                        self.portsOutBulk[trode][vInd, pInd].phi_m())

            # Simulate the potential drop along the bulk electrode
            # solid phase
            simBulkCond = ndD['simBulkCond'][trode]
            if simBulkCond:
                # Calculate the RHS for electrode conductivity
                phi_tmp = utils.add_gp_to_vec(
                    utils.get_var_vec(self.phi_bulk[trode], Nvol[trode]))
                porosvec = utils.pad_vec(
                    utils.get_const_vec(
                        (1 -
                         self.ndD["poros"][trode])**(1 -
                                                     ndD["BruggExp"][trode]),
                        Nvol[trode]))
                poros_walls = utils.mean_harmonic(porosvec)
                if trode == "a":  # anode
                    # Potential at the current collector is from
                    # simulation
                    phi_tmp[0] = self.phi_cell()
                    # No current passes into the electrolyte
                    phi_tmp[-1] = phi_tmp[-2]
                else:  # cathode
                    phi_tmp[0] = phi_tmp[1]
                    # Potential at current at current collector is
                    # reference (set)
                    phi_tmp[-1] = ndD["phi_cathode"]
                dx = ndD["L"][trode] / Nvol[trode]
                dvg_curr_dens = np.diff(-poros_walls * ndD["sigma_s"][trode] *
                                        np.diff(phi_tmp) / dx) / dx
            # Actually set up the equations for bulk solid phi
            for vInd in range(Nvol[trode]):
                eq = self.CreateEquation("phi_ac_trode{trode}vol{vInd}".format(
                    vInd=vInd, trode=trode))
                if simBulkCond:
                    eq.Residual = -dvg_curr_dens[vInd] - self.R_Vp[trode](vInd)
                else:
                    if trode == "a":  # anode
                        eq.Residual = self.phi_bulk[trode](
                            vInd) - self.phi_cell()
                    else:  # cathode
                        eq.Residual = self.phi_bulk[trode](
                            vInd) - ndD["phi_cathode"]

            # Simulate the potential drop along the connected
            # particles
            simPartCond = ndD['simPartCond'][trode]
            for vInd in range(Nvol[trode]):
                phi_bulk = self.phi_bulk[trode](vInd)
                for pInd in range(Npart[trode]):
                    G_l = ndD["G"][trode][vInd, pInd]
                    phi_n = self.phi_part[trode](vInd, pInd)
                    if pInd == 0:  # reference bulk phi
                        phi_l = phi_bulk
                    else:
                        phi_l = self.phi_part[trode](vInd, pInd - 1)
                    if pInd == (Npart[trode] -
                                1):  # No particle at end of "chain"
                        G_r = 0
                        phi_r = phi_n
                    else:
                        G_r = ndD["G"][trode][vInd, pInd + 1]
                        phi_r = self.phi_part[trode](vInd, pInd + 1)
                    # charge conservation equation around this particle
                    eq = self.CreateEquation(
                        "phi_ac_trode{trode}vol{vInd}part{pInd}".format(
                            vInd=vInd, trode=trode, pInd=pInd))
                    if simPartCond:
                        # -dcsbar/dt = I_l - I_r
                        eq.Residual = (
                            self.particles[trode][vInd, pInd].dcbardt() +
                            ((-G_l * (phi_n - phi_l)) - (-G_r *
                                                         (phi_r - phi_n))))
                    else:
                        eq.Residual = self.phi_part[trode](vInd,
                                                           pInd) - phi_bulk

        # If we have a single electrode volume (in a perfect bath),
        # electrolyte equations are simple
        if self.SVsim:
            eq = self.CreateEquation("c_lyte")
            eq.Residual = self.c_lyte["c"].dt(0) - 0
            eq = self.CreateEquation("phi_lyte")
            eq.Residual = self.phi_lyte["c"](0) - self.phi_cell()
        else:
            disc = geom.get_elyte_disc(Nvol, ndD["L"], ndD["poros"],
                                       ndD["BruggExp"])
            cvec = utils.get_asc_vec(self.c_lyte, Nvol)
            dcdtvec = utils.get_asc_vec(self.c_lyte, Nvol, dt=True)
            phivec = utils.get_asc_vec(self.phi_lyte, Nvol)
            Rvvec = utils.get_asc_vec(self.R_Vp, Nvol)
            # Apply concentration and potential boundary conditions
            # Ghost points on the left and no-gradients on the right
            ctmp = np.hstack((self.c_lyteGP_L(), cvec, cvec[-1]))
            phitmp = np.hstack((self.phi_lyteGP_L(), phivec, phivec[-1]))
            # If we don't have a porous anode:
            # 1) the total current flowing into the electrolyte is set
            # 2) assume we have a Li foil with BV kinetics and the specified rate constant
            eqC = self.CreateEquation("GhostPointC_L")
            eqP = self.CreateEquation("GhostPointP_L")
            if Nvol["a"] == 0:
                # Concentration BC from mass flux
                Nm_foil = get_lyte_internal_fluxes(ctmp[0:2], phitmp[0:2],
                                                   disc["dxd1"][0],
                                                   disc["eps_o_tau_edges"][0],
                                                   ndD)[0]
                eqC.Residual = Nm_foil[0]
                # Phi BC from BV at the foil
                # We assume BV kinetics with alpha = 0.5,
                # exchange current density, ecd = k0_foil * c_lyte**(0.5)
                cWall = utils.mean_harmonic(ctmp[0], ctmp[1])
                ecd = ndD["k0_foil"] * cWall**0.5
                # -current = ecd*(exp(-eta/2) - exp(eta/2))
                # note negative current because positive current is
                # oxidation here
                # -current = ecd*(-2*sinh(eta/2))
                # eta = 2*arcsinh(-current/(-2*ecd))
                BVfunc = -self.current() / ecd
                eta_eff = 2 * np.arcsinh(-BVfunc / 2.)
                eta = eta_eff + self.current() * ndD["Rfilm_foil"]
                #                # Infinitely fast anode kinetics
                #                eta = 0.
                # eta = mu_R - mu_O = -mu_O (evaluated at interface)
                # mu_O = [T*ln(c) +] phiWall - phi_cell = -eta
                # phiWall = -eta + phi_cell [- T*ln(c)]
                phiWall = -eta + self.phi_cell()
                if ndD["elyteModelType"] == "dilute":
                    phiWall -= ndD["T"] * np.log(cWall)
                # phiWall = 0.5 * (phitmp[0] + phitmp[1])
                eqP.Residual = phiWall - utils.mean_linear(
                    phitmp[0], phitmp[1])
            # We have a porous anode -- no flux of charge or anions through current collector
            else:
                eqC.Residual = ctmp[0] - ctmp[1]
                eqP.Residual = phitmp[0] - phitmp[1]

            Nm_edges, i_edges = get_lyte_internal_fluxes(
                ctmp, phitmp, disc["dxd1"], disc["eps_o_tau_edges"], ndD)
            dvgNm = np.diff(Nm_edges) / disc["dxd2"]
            dvgi = np.diff(i_edges) / disc["dxd2"]
            for vInd in range(Nlyte):
                # Mass Conservation (done with the anion, although "c" is neutral salt conc)
                eq = self.CreateEquation(
                    "lyte_mass_cons_vol{vInd}".format(vInd=vInd))
                eq.Residual = disc["porosvec"][vInd] * dcdtvec[vInd] + (
                    1. / ndD["num"]) * dvgNm[vInd]
                # Charge Conservation
                eq = self.CreateEquation(
                    "lyte_charge_cons_vol{vInd}".format(vInd=vInd))
                eq.Residual = -dvgi[vInd] + ndD["zp"] * Rvvec[vInd]

        # Define the total current. This must be done at the capacity
        # limiting electrode because currents are specified in
        # C-rates.
        eq = self.CreateEquation("Total_Current")
        eq.Residual = self.current()
        limtrode = ("c" if ndD["z"] < 1 else "a")
        dx = 1. / Nvol[limtrode]
        rxn_scl = ndD["beta"][limtrode] * (
            1 - ndD["poros"][limtrode]) * ndD["P_L"][limtrode]
        for vInd in range(Nvol[limtrode]):
            if limtrode == "a":
                eq.Residual -= dx * self.R_Vp[limtrode](vInd) / rxn_scl
            else:
                eq.Residual += dx * self.R_Vp[limtrode](vInd) / rxn_scl
        # Define the measured voltage, offset by the "applied" voltage
        # by any series resistance.
        # phi_cell = phi_applied - I*R
        eq = self.CreateEquation("Measured_Voltage")
        eq.Residual = self.phi_cell() - (self.phi_applied() -
                                         ndD["Rser"] * self.current())

        if self.profileType == "CC":
            # Total Current Constraint Equation
            eq = self.CreateEquation("Total_Current_Constraint")
            if ndD["tramp"] > 0:
                eq.Residual = self.current() - (
                    ndD["currPrev"] + (ndD["currset"] - ndD["currPrev"]) *
                    (1 - np.exp(-dae.Time() / (ndD["tend"] * ndD["tramp"]))))
            else:
                eq.Residual = self.current() - ndD["currset"]
        elif self.profileType == "CV":
            # Keep applied potential constant
            eq = self.CreateEquation("applied_potential")
            if ndD["tramp"] > 0:
                eq.Residual = self.phi_applied() - (
                    ndD["phiPrev"] + (ndD["Vset"] - ndD["phiPrev"]) *
                    (1 - np.exp(-dae.Time() / (ndD["tend"] * ndD["tramp"]))))
            else:
                eq.Residual = self.phi_applied() - ndD["Vset"]
        elif self.profileType == "CCsegments":
            if ndD["tramp"] > 0:
                ndD["segments_setvec"][0] = ndD["currPrev"]
                self.segSet = extern_funcs.InterpTimeScalar(
                    "segSet", self, dae.unit(), dae.Time(),
                    ndD["segments_tvec"], ndD["segments_setvec"])
                eq = self.CreateEquation("Total_Current_Constraint")
                eq.Residual = self.current() - self.segSet()

            #CCsegments implemented as discontinuous equations
            else:
                #First segment
                time = ndD["segments"][0][1]
                self.IF(dae.Time() < dae.Constant(time * s), 1.e-3)
                eq = self.CreateEquation("Total_Current_Constraint")
                eq.Residual = self.current() - ndD["segments"][0][0]

                #Middle segments
                for i in range(1, len(ndD["segments"]) - 1):
                    time = time + ndD["segments"][i][1]
                    self.ELSE_IF(dae.Time() < dae.Constant(time * s), 1.e-3)
                    eq = self.CreateEquation("Total_Current_Constraint")
                    eq.Residual = self.current() - ndD["segments"][i][0]

                #Last segment
                self.ELSE()
                eq = self.CreateEquation("Total_Current_Constraint")
                eq.Residual = self.current() - ndD["segments"][-1][0]
                self.END_IF()

        elif self.profileType == "CVsegments":
            if ndD["tramp"] > 0:
                ndD["segments_setvec"][0] = ndD["phiPrev"]
                self.segSet = extern_funcs.InterpTimeScalar(
                    "segSet", self, dae.unit(), dae.Time(),
                    ndD["segments_tvec"], ndD["segments_setvec"])
                eq = self.CreateEquation("applied_potential")
                eq.Residual = self.phi_applied() - self.segSet()

            #CVsegments implemented as discontinuous equations
            else:
                #First segment
                time = ndD["segments"][0][1]
                self.IF(dae.Time() < dae.Constant(time * s), 1.e-3)
                eq = self.CreateEquation("applied_potential")
                eq.Residual = self.phi_applied() - ndD["segments"][0][0]

                #Middle segments
                for i in range(1, len(ndD["segments"]) - 1):
                    time = time + ndD["segments"][i][1]
                    self.ELSE_IF(dae.Time() < dae.Constant(time * s), 1.e-3)
                    eq = self.CreateEquation("applied_potential")
                    eq.Residual = self.phi_applied() - ndD["segments"][i][0]

                #Last segment
                self.ELSE()
                eq = self.CreateEquation("applied_potential")
                eq.Residual = self.phi_applied() - ndD["segments"][-1][0]
                self.END_IF()

        for eq in self.Equations:
            eq.CheckUnitsConsistency = False

        #Ending conditions for the simulation
        if self.profileType in ["CC", "CCsegments"]:
            #Vmax reached
            self.ON_CONDITION((self.phi_applied() <= ndD["phimin"])
                              & (self.endCondition() < 1),
                              setVariableValues=[(self.endCondition, 1)])

            #Vmin reached
            self.ON_CONDITION((self.phi_applied() >= ndD["phimax"])
                              & (self.endCondition() < 1),
                              setVariableValues=[(self.endCondition, 2)])
    def __init__(self, Name, ninemlComponent, Parent = None, Description = ""):
        """
        Iterates over *Parameters*, *State variables*, *Aliases*, *Analogue ports*, *Event ports*, 
        *Sub-nodes* and *Port connections* and creates corresponding daetools objects.
        
        :param Name: string
        :param ninemlComponent: AL component object
        :param Parent: daeModel-derived object
        :param Description: string
            
        :raises: RuntimeError
        """
        #start = time()
        
        daet.daeModel.__init__(self, Name, Parent, Description)
        
        #print('    daeModel.__init__({0}) = {1}'.format(Name, time() - start))

        start = time()

        self.ninemlComponent        = ninemlComponent
        self.nineml_parameters      = []
        self.nineml_state_variables = []
        self.nineml_aliases         = []
        self.nineml_analog_ports    = []
        self.nineml_reduce_ports    = []
        self.nineml_event_ports     = []
        self.ninemlSubComponents    = []

        # AL component may be None (useful in certain cases); therefore do not raise an exception
        if not self.ninemlComponent:
            return
        
        # 1) Create parameters
        for param in self.ninemlComponent.parameters:
            self.nineml_parameters.append( daet.daeParameter(param.name, daet.unit(), self, "") )

        # 2) Create state-variables (diff. variables)
        for var in self.ninemlComponent.state_variables:
            self.nineml_state_variables.append( daet.daeVariable(var.name, dae_nineml_t, self, "") )

        # 3) Create alias variables (algebraic)
        for alias in self.ninemlComponent.aliases:
            self.nineml_aliases.append( daet.daeVariable(alias.lhs, dae_nineml_t, self, "") )

        # 4) Create analog-ports and reduce-ports
        for analog_port in self.ninemlComponent.analog_ports:
            if analog_port.mode == 'send':
                self.nineml_analog_ports.append( ninemlAnalogPort(analog_port.name, daet.eOutletPort, self, "") )
            elif analog_port.mode == 'recv':
                self.nineml_analog_ports.append( ninemlAnalogPort(analog_port.name, daet.eInletPort, self, "") )
            elif analog_port.mode == 'reduce':
                self.nineml_reduce_ports.append( ninemlReduceAnalogPort(analog_port.name, self) )
            else:
                raise RuntimeError("")

        # 5) Create event-ports
        for event_port in self.ninemlComponent.event_ports:
            if event_port.mode == 'send':
                self.nineml_event_ports.append( daet.daeEventPort(event_port.name, daet.eOutletPort, self, "") )
            elif event_port.mode == 'recv':
                self.nineml_event_ports.append( daet.daeEventPort(event_port.name, daet.eInletPort, self, "") )
            else:
                raise RuntimeError("")

        # 6) Create sub-nodes
        for name, subcomponent in list(self.ninemlComponent.subnodes.items()):
            self.ninemlSubComponents.append( nineml_daetools_bridge(name, subcomponent, self, '') )

        # 7) Create port connections
        for port_connection in self.ninemlComponent.portconnections:
            #print 'try to connect {0} to {1}'.format(port_connection[0].getstr('.'), port_connection[1].getstr('.'))
            portFrom = getObjectFromNamespaceAddress(self, port_connection[0], look_for_ports = True, look_for_reduceports = True)
            portTo   = getObjectFromNamespaceAddress(self, port_connection[1], look_for_ports = True, look_for_reduceports = True)
            #print '  {0} -> {1}\n'.format(type(portFrom), type(portTo))
            connectPorts(portFrom, portTo, self)
    # Search for the 'name' in the 'root' model (in the types of objects given in **kwargs)
    return findObjectInModel(root, objectName, **kwargs)

def fixObjectName(name):
    """
    Replaces spaces in the 'name' string with underscores and returns the modified string.
    
    :param name: string
        
    :rtype: string
    :raises:
    """
    new_name = name.replace(' ', '_')
    return new_name

dae_nineml_t = daet.daeVariableType("dae_nineml_t", daet.unit(), -1.0e+20, 1.0e+20, 0.0, 1e-12)

class ninemlAnalogPort(daet.daePort):
    """
    Represents NineML analogue ports with a single variable (referred to with the 'value' daet.daeVariable). 
    """
    def __init__(self, Name, PortType, Model, Description = ''):
        """
        :param Name: string
        :param PortType: daetools port type (eInlet, eOutlet, eInletOutlet)
        :param Model: daeModel-derived object
        :param Description: string
            
        :raises: RuntimeError
        """
        daet.daePort.__init__(self, Name, PortType, Model, Description)
Beispiel #9
0
    def __init__(self, Name, ninemlComponent, Parent=None, Description=""):
        """
        Iterates over *Parameters*, *State variables*, *Aliases*, *Analogue ports*, *Event ports*, 
        *Sub-nodes* and *Port connections* and creates corresponding daetools objects.
        
        :param Name: string
        :param ninemlComponent: AL component object
        :param Parent: daeModel-derived object
        :param Description: string
            
        :raises: RuntimeError
        """
        #start = time()

        daet.daeModel.__init__(self, Name, Parent, Description)

        #print('    daeModel.__init__({0}) = {1}'.format(Name, time() - start))

        start = time()

        self.ninemlComponent = ninemlComponent
        self.nineml_parameters = []
        self.nineml_state_variables = []
        self.nineml_aliases = []
        self.nineml_analog_ports = []
        self.nineml_reduce_ports = []
        self.nineml_event_ports = []
        self.ninemlSubComponents = []

        # AL component may be None (useful in certain cases); therefore do not raise an exception
        if not self.ninemlComponent:
            return

        # 1) Create parameters
        for param in self.ninemlComponent.parameters:
            self.nineml_parameters.append(
                daet.daeParameter(param.name, daet.unit(), self, ""))

        # 2) Create state-variables (diff. variables)
        for var in self.ninemlComponent.state_variables:
            self.nineml_state_variables.append(
                daet.daeVariable(var.name, dae_nineml_t, self, ""))

        # 3) Create alias variables (algebraic)
        for alias in self.ninemlComponent.aliases:
            self.nineml_aliases.append(
                daet.daeVariable(alias.lhs, dae_nineml_t, self, ""))

        # 4) Create analog-ports and reduce-ports
        for analog_port in self.ninemlComponent.analog_ports:
            if analog_port.mode == 'send':
                self.nineml_analog_ports.append(
                    ninemlAnalogPort(analog_port.name, daet.eOutletPort, self,
                                     ""))
            elif analog_port.mode == 'recv':
                self.nineml_analog_ports.append(
                    ninemlAnalogPort(analog_port.name, daet.eInletPort, self,
                                     ""))
            elif analog_port.mode == 'reduce':
                self.nineml_reduce_ports.append(
                    ninemlReduceAnalogPort(analog_port.name, self))
            else:
                raise RuntimeError("")

        # 5) Create event-ports
        for event_port in self.ninemlComponent.event_ports:
            if event_port.mode == 'send':
                self.nineml_event_ports.append(
                    daet.daeEventPort(event_port.name, daet.eOutletPort, self,
                                      ""))
            elif event_port.mode == 'recv':
                self.nineml_event_ports.append(
                    daet.daeEventPort(event_port.name, daet.eInletPort, self,
                                      ""))
            else:
                raise RuntimeError("")

        # 6) Create sub-nodes
        for name, subcomponent in list(self.ninemlComponent.subnodes.items()):
            self.ninemlSubComponents.append(
                nineml_daetools_bridge(name, subcomponent, self, ''))

        # 7) Create port connections
        for port_connection in self.ninemlComponent.portconnections:
            #print 'try to connect {0} to {1}'.format(port_connection[0].getstr('.'), port_connection[1].getstr('.'))
            portFrom = getObjectFromNamespaceAddress(self,
                                                     port_connection[0],
                                                     look_for_ports=True,
                                                     look_for_reduceports=True)
            portTo = getObjectFromNamespaceAddress(self,
                                                   port_connection[1],
                                                   look_for_ports=True,
                                                   look_for_reduceports=True)
            #print '  {0} -> {1}\n'.format(type(portFrom), type(portTo))
            connectPorts(portFrom, portTo, self)
Beispiel #10
0

def fixObjectName(name):
    """
    Replaces spaces in the 'name' string with underscores and returns the modified string.
    
    :param name: string
        
    :rtype: string
    :raises:
    """
    new_name = name.replace(' ', '_')
    return new_name


dae_nineml_t = daet.daeVariableType("dae_nineml_t", daet.unit(), -1.0e+20,
                                    1.0e+20, 0.0, 1e-12)


class ninemlAnalogPort(daet.daePort):
    """
    Represents NineML analogue ports with a single variable (referred to with the 'value' daet.daeVariable). 
    """
    def __init__(self, Name, PortType, Model, Description=''):
        """
        :param Name: string
        :param PortType: daetools port type (eInlet, eOutlet, eInletOutlet)
        :param Model: daeModel-derived object
        :param Description: string
            
        :raises: RuntimeError
Beispiel #11
0
"""This defines the ports by which mod_cell interacts with mod_electrodes."""
import daetools.pyDAE as dae

mole_frac_t = dae.daeVariableType(
    name="mole_frac_t", units=dae.unit(), lowerBound=0,
    upperBound=1, initialGuess=0.25, absTolerance=1e-6)
elec_pot_t = dae.daeVariableType(
    name="elec_pot_t", units=dae.unit(), lowerBound=-1e20,
    upperBound=1e20, initialGuess=0, absTolerance=1e-5)


class portFromElyte(dae.daePort):
    def __init__(self, Name, PortType, Model, Description=""):
        dae.daePort.__init__(self, Name, PortType, Model, Description)
        self.c_lyte = dae.daeVariable(
            "c_lyte", mole_frac_t, self,
            "Concentration in the electrolyte")
        self.phi_lyte = dae.daeVariable(
            "phi_lyte", elec_pot_t, self,
            "Electric potential in the electrolyte")


class portFromBulk(dae.daePort):
    def __init__(self, Name, PortType, Model, Description=""):
        dae.daePort.__init__(self, Name, PortType, Model, Description)
        self.phi_m = dae.daeVariable(
            "phi_m", elec_pot_t, self,
            "Electric potential in the e- conducting phase")