-- Program to solve Two-Fluid equations --[[ Extension of the electron dissipation region in collisionless Hall MHD Reconnection, B. P.Sullivan, Yi-Min Huang, and A. Bhattacharjee, Phys. Plasmas, 16, 102111 (2009). --]] -- decomposition object to use decomp = DecompRegionCalc2D.CartGeneral {} -- global parameters gasGamma = 5./3. elcCharge = -1.0 ionCharge = 1.0 ionMass = 1.0 elcMass = ionMass/1863. lightSpeed = 1.0 epsilon0 = 1.0 mgnErrorSpeedFactor = 1.0 Bx0 = 1/10.0 -- normalization n0 = 1.0 cfl = 0.9 nSpecies = 2 Bx02 = Bx0*Bx0 wci = ionCharge*Bx0/ionMass ionPlasmaFreq = math.sqrt(n0*ionCharge*ionCharge/(epsilon0*ionMass)) ionSkinDepth = lightSpeed/ionPlasmaFreq mu0 = 1/(lightSpeed*lightSpeed*epsilon0) VAx0 = Bx0/math.sqrt(mu0*n0*ionMass) Bnoise = Bx0*0.00001 Vnoise = VAx0*0.00001 L = 12.8*ionSkinDepth Lx = L Ly = L B0 = Bx0*2*Lucee.Pi/12.8 Ttot = Bx0*Bx0/mu0/n0 -- and assume Te = Ti V0 = 0.1*VAx0 NX = 4097 NY = 4097 -- computational domain grid = Grid.RectCart2D { lower = {-Lx/2, -Ly/2}, upper = {Lx/2, Ly/2}, cells = {NX, NY}, decomposition = decomp, periodicDirs = {0,1}, } -- solution q = DataStruct.Field2D { onGrid = grid, numComponents = 18, ghost = {2, 2}, } -- solution after X update qX = DataStruct.Field2D { onGrid = grid, numComponents = 28, ghost = {2, 2}, } qDup = DataStruct.Field2D { onGrid = grid, numComponents = 18, ghost = {2, 2}, } -- updated solution qNew = DataStruct.Field2D { onGrid = grid, numComponents = 18, ghost = {2, 2}, } -- create duplicate copy in case we need to take step again qNewDup = DataStruct.Field2D { onGrid = grid, numComponents = 18, ghost = {2, 2}, } -- create aliases to various sub-system elcFluid = q:alias(0, 5) ionFluid = q:alias(5, 10) emField = q:alias(10, 18) elcFluidX = qX:alias(0, 5) ionFluidX = qX:alias(5, 10) emFieldX = qX:alias(10, 18) elcFluidNew = qNew:alias(0, 5) ionFluidNew = qNew:alias(5, 10) emFieldNew = qNew:alias(10, 18) -- alias for various fields for diagnostics byAlias = qNew:alias(14, 15) ezAlias = qNew:alias(12, 13) neAlias = qNew:alias(0, 1) uzeAlias = qNew:alias(3, 4) uziAlias = qNew:alias(8, 9) -- function to apply initial conditions function init(x,y,z) local me = elcMass local mi = ionMass local qe = elcCharge local qi = ionCharge local gasGamma1 = gasGamma-1 local pi = Lucee.Pi math.randomseed( os.time() ) local Bx = B0*math.cos(2*pi*y/L)*math.sin(2*pi*x/L) + Bnoise*math.random()*math.random(-1,1) local By =-B0*math.cos(2*pi*x/L)*math.sin(2*pi*y/L) + Bnoise*math.random()*math.random(-1,1) local B2 = Bx*Bx+By*By local rhoe = me*n0*(1+(Bx02-B2)/(2*n0*Ttot)) local ere = (rhoe/me)*(0.5*Ttot)/(gasGamma1) local rhoi = mi*n0*(1+(Bx02-B2)/(2*n0*Ttot)) local izmom = (4*pi*B0*mi/(mu0*L*qi))*math.sin(2*pi*x/L)*math.sin(2*pi*y/L) + n0*mi*Vnoise*math.random()*math.random(-1,1) local ixmom = rhoi*V0*math.sin(2*pi*y/L) local iymom = rhoi*V0*math.sin(2*pi*x/L) local eri = (rhoi/mi)*(0.5*Ttot)/(gasGamma1)+ 0.5*(izmom*izmom+ixmom*ixmom+iymom*iymom)/rhoi return rhoe, 0.0, 0.0, 0.0, ere, rhoi, ixmom, iymom, izmom, eri, 0.0, 0.0, 0.0, Bx, By, 0.0, 0.0, 0.0 end -- set initial conditions for fields and fluids q:set(init) -- get ghost cells correct q:sync() -- copy initial conditions over qNew:copy(q) -- define various equations to solve elcEulerEqn = HyperEquation.Euler { -- gas adiabatic constant gasGamma = gasGamma, } ionEulerEqn = HyperEquation.Euler { -- gas adiabatic constant gasGamma = gasGamma, } -- (Lax equations are used to fix negative pressure/density) elcEulerLaxEqn = HyperEquation.Euler { -- gas adiabatic constant gasGamma = gasGamma, -- use Lax fluxes numericalFlux = "lax", } ionEulerLaxEqn = HyperEquation.Euler { -- gas adiabatic constant gasGamma = gasGamma, -- use Lax fluxes numericalFlux = "lax", } maxwellEqn = HyperEquation.PhMaxwell { -- speed of light lightSpeed = lightSpeed, -- correction speeds elcErrorSpeedFactor = 0.0, mgnErrorSpeedFactor = mgnErrorSpeedFactor } -- updater for electron equations elcFluidSlvrDir0 = Updater.WavePropagation2D { onGrid = grid, equation = elcEulerEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "monotonized-centered", cfl = cfl, cflm = 1.1*cfl, updateDirections = {0} -- directions to update } -- set input/output arrays (these do not change so set it once) elcFluidSlvrDir0:setIn( {elcFluid} ) elcFluidSlvrDir0:setOut( {elcFluidX} ) -- updater for ion equations ionFluidSlvrDir0 = Updater.WavePropagation2D { onGrid = grid, equation = ionEulerEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "monotonized-centered", cfl = cfl, cflm = 1.1*cfl, updateDirections = {0} -- directions to update } -- set input/output arrays (these do not change so set it once) ionFluidSlvrDir0:setIn( {ionFluid} ) ionFluidSlvrDir0:setOut( {ionFluidX} ) -- updater for Maxwell equations maxSlvrDir0 = Updater.WavePropagation2D { onGrid = grid, equation = maxwellEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "monotonized-centered", cfl = cfl, cflm = 1.1*cfl, updateDirections = {0} -- directions to update } -- set input/output arrays (these do not change so set it once) maxSlvrDir0:setIn( {emField} ) maxSlvrDir0:setOut( {emFieldX} ) -- updater for electron equations elcFluidSlvrDir1 = Updater.WavePropagation2D { onGrid = grid, equation = elcEulerEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "monotonized-centered", cfl = cfl, cflm = 1.1*cfl, updateDirections = {1} -- directions to update } -- set input/output arrays (these do not change so set it once) elcFluidSlvrDir1:setIn( {elcFluidX} ) elcFluidSlvrDir1:setOut( {elcFluidNew} ) -- updater for ion equations ionFluidSlvrDir1 = Updater.WavePropagation2D { onGrid = grid, equation = ionEulerEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "monotonized-centered", cfl = cfl, cflm = 1.1*cfl, updateDirections = {1} -- directions to update } -- set input/output arrays (these do not change so set it once) ionFluidSlvrDir1:setIn( {ionFluidX} ) ionFluidSlvrDir1:setOut( {ionFluidNew} ) -- updater for Maxwell equations maxSlvrDir1 = Updater.WavePropagation2D { onGrid = grid, equation = maxwellEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "monotonized-centered", cfl = cfl, cflm = 1.1*cfl, updateDirections = {1} -- directions to update } -- set input/output arrays (these do not change so set it once) maxSlvrDir1:setIn( {emFieldX} ) maxSlvrDir1:setOut( {emFieldNew} ) -- (Lax equation solver are used to fix negative pressure/density) -- updater for electron equations elcLaxSlvr = Updater.WavePropagation2D { onGrid = grid, equation = elcEulerLaxEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "zero", cfl = cfl, cflm = 1.1*cfl, } -- set input/output arrays (these do not change so set it once) elcLaxSlvr:setIn( {elcFluid} ) elcLaxSlvr:setOut( {elcFluidNew} ) -- updater for ion equations ionLaxSlvr = Updater.WavePropagation2D { onGrid = grid, equation = ionEulerLaxEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "zero", cfl = cfl, cflm = 1.1*cfl, } -- set input/output arrays (these do not change so set it once) ionLaxSlvr:setIn( {ionFluid} ) ionLaxSlvr:setOut( {ionFluidNew} ) maxSlvr = Updater.WavePropagation2D { onGrid = grid, equation = maxwellEqn, -- one of no-limiter, min-mod, superbee, van-leer, monotonized-centered, beam-warming limiter = "monotonized-centered", cfl = cfl, cflm = 1.1*cfl, } -- set input/output arrays (these do not change so set it once) maxSlvr:setIn( {emField} ) maxSlvr:setOut( {emFieldNew} ) -- updater for two-fluid sources sourceSlvr = Updater.ImplicitFiveMomentSrc2D { -- grid on which to run updater onGrid = grid, -- number of fluids numFluids = 2, -- species charge charge = {elcCharge, ionCharge}, -- species mass mass = {elcMass, ionMass}, -- premittivity of free space epsilon0 = epsilon0, -- linear solver to use: one of partialPivLu or colPivHouseholderQr linearSolver = "partialPivLu", -- has static magnetic field hasStaticField = false, } -- function to apply boundary conditions function applyBc(fld, t) -- periodic fld:sync() end -- apply BCs to initial conditions applyBc(q) applyBc(qNew) function updateSource(inpElc, inpIon, inpEm, tCurr, tEnd) sourceSlvr:setOut( {inpElc, inpIon, inpEm} ) sourceSlvr:setCurrTime(tCurr) sourceSlvr:advance(tEnd) end -- function to update the fluid and field using dimensional splitting function updateFluidsAndField(tCurr, t) local myStatus = true local myDtSuggested = 1e3*math.abs(t-tCurr) -- X-direction updates for i,slvr in ipairs({elcFluidSlvrDir0, ionFluidSlvrDir0, maxSlvrDir0}) do slvr:setCurrTime(tCurr) local status, dtSuggested = slvr:advance(t) myStatus = status and myStatus myDtSuggested = math.min(myDtSuggested, dtSuggested) end -- apply BCs to intermediate update after X sweep applyBc(qX) -- Y-direction updates for i,slvr in ipairs({elcFluidSlvrDir1, ionFluidSlvrDir1, maxSlvrDir1}) do slvr:setCurrTime(tCurr) local status, dtSuggested = slvr:advance(t) myStatus = status and myStatus myDtSuggested = math.min(myDtSuggested, dtSuggested) end return myStatus, myDtSuggested end -- function to take one time-step function solveTwoFluidSystem(tCurr, t) local dthalf = 0.5*(t-tCurr) -- update source term updateSource(elcFluid, ionFluid, emField, tCurr, tCurr+dthalf) applyBc(q) -- update fluids and fields local status, dtSuggested = updateFluidsAndField(tCurr, t) -- update source terms updateSource(elcFluidNew, ionFluidNew, emFieldNew, tCurr, tCurr+dthalf) applyBc(qNew) return status, dtSuggested end -- function to take one time-step function solveTwoFluidLaxSystem(tCurr, t) local dthalf = 0.5*(t-tCurr) -- update source term updateSource(elcFluid, ionFluid, emField, tCurr, tCurr+dthalf) applyBc(q) -- advance electrons elcLaxSlvr:setCurrTime(tCurr) local elcStatus, elcDtSuggested = elcLaxSlvr:advance(t) -- advance ions ionLaxSlvr:setCurrTime(tCurr) local ionStatus, ionDtSuggested = ionLaxSlvr:advance(t) -- advance fields maxSlvr:setCurrTime(tCurr) local maxStatus, maxDtSuggested = maxSlvr:advance(t) -- check if any updater failed local status = false local dtSuggested = math.min(elcDtSuggested, ionDtSuggested, maxDtSuggested) if (elcStatus and ionStatus and maxStatus) then status = true end -- update source terms updateSource(elcFluidNew, ionFluidNew, emFieldNew, tCurr, tCurr+dthalf) applyBc(qNew) return status, dtSuggested end -- dynvector to store integrated flux byFlux = DataStruct.DynVector { numComponents = 1 } byFluxCalc = Updater.IntegrateFieldAlongLine2D { onGrid = grid, -- start cell startCell = {0, NY/2}, -- direction to integrate in dir = 0, -- number of cells to integrate numCells = NX, -- integrand integrand = function (by) return math.abs(by) end, } byFluxCalc:setIn( {byAlias} ) byFluxCalc:setOut( {byFlux} ) -- dynvector to store Ez at X-point xpointEz = DataStruct.DynVector { numComponents = 1 } xpointEzRec = Updater.RecordFieldInCell2D { onGrid = grid, -- index of cell to record cellIndex = {(NX-1)/2, (NY-1)/2}, } xpointEzRec:setIn( {ezAlias} ) xpointEzRec:setOut( {xpointEz} ) -- dynvector to store number density at X-point xpointNe = DataStruct.DynVector { numComponents = 1 } xpointNeRec = Updater.RecordFieldInCell2D { onGrid = grid, -- index of cell to record cellIndex = {(NX-1)/2, (NY-1)/2}, } xpointNeRec:setIn( {neAlias} ) xpointNeRec:setOut( {xpointNe} ) -- dynvector to store electron uz at X-point xpointUze = DataStruct.DynVector { numComponents = 1 } xpointUzeRec = Updater.RecordFieldInCell2D { onGrid = grid, -- index of cell to record cellIndex = {(NX-1)/2, (NY-1)/2}, } xpointUzeRec:setIn( {uzeAlias} ) xpointUzeRec:setOut( {xpointUze} ) -- dynvector to store ion uz at X-point xpointUzi = DataStruct.DynVector { numComponents = 1 } xpointUziRec = Updater.RecordFieldInCell2D { onGrid = grid, -- index of cell to record cellIndex = {(NX-1)/2, (NY-1)/2}, } xpointUziRec:setIn( {uziAlias} ) xpointUziRec:setOut( {xpointUzi} ) -- compute diagnostic function calcDiagnostics(tCurr, t) for i,diag in ipairs({byFluxCalc, xpointEzRec, xpointNeRec, xpointUzeRec, xpointUziRec}) do diag:setCurrTime(tCurr) diag:advance(t) end end -- advance solution from tStart to tEnd, using optimal time-steps. function advanceFrame(tStart, tEnd, initDt) local step = 1 local tCurr = tStart local myDt = initDt local tfStatus, tfDtSuggested local useLaxSolver = false while true do qDup:copy(q) qNewDup:copy(qNew) if (tCurr+myDt > tEnd) then -- hit tEnd exactly myDt = tEnd-tCurr end Lucee.logInfo (string.format(" Taking step %d at time %g with dt %g", step, tCurr, myDt)) if (useLaxSolver) then -- (call Lax solver if positivity violated) tfStatus, tfDtSuggested = solveTwoFluidLaxSystem(tCurr, tCurr+myDt) useLaxSolver = false else tfStatus, tfDtSuggested = solveTwoFluidSystem(tCurr, tCurr+myDt) end if (tfStatus == false) then Lucee.logInfo (string.format(" ** Time step %g too large! Will retake with dt %g", myDt, tfDtSuggested)) myDt = tfDtSuggested qNew:copy(qNewDup) q:copy(qDup) elseif ((elcEulerEqn:checkInvariantDomain(elcFluidNew) == false) or (ionEulerEqn:checkInvariantDomain(ionFluidNew) == false)) then Lucee.logInfo (string.format("** Negative pressure or density at %g! Will retake step with Lax fluxes", tCurr+myDt)) q:copy(qDup) qNew:copy(qNewDup) useLaxSolver = true else if (qNew:hasNan()) then Lucee.logInfo (string.format(" ** Nan occured at %g! Stopping simulation", tCurr)) break end --calcDiagnostics(tCurr, tCurr+myDt) q:copy(qNew) myDt = tfDtSuggested tCurr = tCurr + myDt step = step + 1 if (tCurr >= tEnd) then break end end end return tfDtSuggested end function writeFrame(frame, tCurr) qNew:write(string.format("q_%d.h5", frame), tCurr ) byFlux:write( string.format("byFlux_%d.h5", frame) ) xpointEz:write(string.format("xpointEz_%d.h5", frame) ) xpointNe:write(string.format("xpointNe_%d.h5", frame) ) xpointUze:write(string.format("xpointUze_%d.h5", frame) ) xpointUzi:write(string.format("xpointUzi_%d.h5", frame) ) end -- compute diagnostics and write out initial conditions --calcDiagnostics(0.0, 0.0) writeFrame(0, 0.0) dtSuggested = 1.0 -- initial time-step to use (this will be discarded and adjusted to CFL value) -- parameters to control time-stepping tStart = 0.0 tEnd = 12/wci nFrames = 12 tFrame = (tEnd-tStart)/nFrames -- time between frames tCurr = tStart -- main loop for frame = 1, nFrames do Lucee.logInfo (string.format("-- Advancing solution from %g to %g", tCurr, tCurr+tFrame)) -- advance solution between frames dtSuggested = advanceFrame(tCurr, tCurr+tFrame, dtSuggested) -- write out data writeFrame(frame, tCurr+tFrame) tCurr = tCurr+tFrame Lucee.logInfo ("") end