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
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()
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
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
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)
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)
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)
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
"""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")