/// <summary> /// This is the timestepping method /// </summary> /// <param name="TimeStep"></param> public void Update(DateTime NewTime) { TimeSpan TimeStep = NewTime.Subtract(CurrentTime); //Log input of water. Could be zero Output.Inflow.AddSiValue(CurrentTime, NewTime, _waterReceivedSinceLastUpdate / TimeStep.TotalSeconds); _waterReceivedSinceLastUpdate = 0; double vol = CurrentStoredWater.Volume; //loop the sources foreach (ISource IWS in Sources) { CurrentStoredWater.Add(IWS.GetSourceWater(CurrentTime, TimeStep)); } //Update output Output.Sources.AddSiValue(CurrentTime, NewTime, (CurrentStoredWater.Volume - vol) / TimeStep.TotalSeconds); vol = CurrentStoredWater.Volume; //Loop the groundwater sources foreach (var gwb in GroundwaterBoundaries.Where(var => var.IsSource(CurrentTime))) { CurrentStoredWater.Add(gwb.GetSourceWater(CurrentTime, TimeStep)); } //Update output Output.GroundwaterInflow.AddSiValue(CurrentTime, NewTime, (CurrentStoredWater.Volume - vol) / TimeStep.TotalSeconds); vol = CurrentStoredWater.Volume; //Loop the precipitation foreach (ISource IWS in Precipitation) { CurrentStoredWater.Add(IWS.GetSourceWater(CurrentTime, TimeStep)); } //Update output Output.Precipitation.AddSiValue(CurrentTime, NewTime, (CurrentStoredWater.Volume - vol) / TimeStep.TotalSeconds); vol = CurrentStoredWater.Volume; //Loop the Evaporation boundaries foreach (var IEB in _evapoBoundaries) { CurrentStoredWater.Evaporate(IEB.GetSinkVolume(CurrentTime, TimeStep)); } //Update output Output.Evaporation.AddSiValue(CurrentTime, NewTime, -(CurrentStoredWater.Volume - vol) / TimeStep.TotalSeconds); vol = CurrentStoredWater.Volume; //loop the sinks if (CurrentStoredWater.Volume > 0) { foreach (var IWS in Sinks) { double sinkvolume = IWS.GetSinkVolume(CurrentTime, TimeStep); IWS.ReceiveSinkWater(CurrentTime, TimeStep, CurrentStoredWater.Substract(sinkvolume)); } } //Update output Output.Sinks.AddSiValue(CurrentTime, NewTime, (vol - CurrentStoredWater.Volume) / TimeStep.TotalSeconds); vol = CurrentStoredWater.Volume; //Loop the groundwater sinks if (CurrentStoredWater.Volume > 0) { foreach (var gwb in GroundwaterBoundaries.Where(var => !var.IsSource(CurrentTime))) { double sinkvolume = gwb.GetSinkVolume(CurrentTime, TimeStep); gwb.ReceiveSinkWater(CurrentTime, TimeStep, CurrentStoredWater.Substract(sinkvolume)); } } //Update output Output.GroundwaterOutflow.AddSiValue(CurrentTime, NewTime, (vol - CurrentStoredWater.Volume) / TimeStep.TotalSeconds); vol = CurrentStoredWater.Volume; IWaterPacket WaterToRoute; //Now substract the water that is to be routed if (CurrentStoredWater.Volume > Volume) //Only go here if there is a surplus of water. { WaterToRoute = CurrentStoredWater.Substract(CurrentStoredWater.Volume - Volume); //Write routed water. The value is the average value for the timestep Output.Outflow.AddSiValue(CurrentTime, NewTime, WaterToRoute.Volume / TimeStep.TotalSeconds); SendWaterDownstream(WaterToRoute, CurrentTime, NewTime); } else { Output.Outflow.AddSiValue(CurrentTime, NewTime, 0); } //Write current volume to output. The calculated volume is at the end of the timestep Output.StoredVolume.AddSiValue(NewTime, CurrentStoredWater.Volume); //Move the water in time. Consider if this the right place to do it or it should be at the beginning of the time step CurrentStoredWater.MoveInTime(TimeStep, ChemicalFactory.Instance.LakeReactions, Area); //Log Output Output.Log(CurrentStoredWater, CurrentTime, NewTime); CurrentTime = NewTime; }
/// <summary> /// This is the timestepping procedure /// </summary> /// <param name="TimeStep"></param> public void Update(DateTime NewTime) { CurrentTimeStep = NewTime.Subtract(CurrentTime); #region Sum of Sinks and sources //Sum the sources var GWFlow = GroundwaterBoundaries.Where(var => var.IsSource(CurrentTime)).Select(var => var.GetSourceWater(CurrentTime, CurrentTimeStep)); var SourceFlow = Sources.Select(var => var.GetSourceWater(CurrentTime, CurrentTimeStep)); IWaterPacket InFlow = WaterMixer.Mix(GWFlow.Concat(SourceFlow)); double InflowVolume = 0; if (InFlow != null) { InflowVolume = InFlow.Volume; } //Sum the Evaporation boundaries double EvapoVolume = _evapoBoundaries.Sum(var => var.GetSinkVolume(CurrentTime, CurrentTimeStep)); //Sum the sinks double SinkVolume = Sinks.Sum(var => var.GetSinkVolume(CurrentTime, CurrentTimeStep)); //Add the sinking groundwater boundaries SinkVolume += GroundwaterBoundaries.Where(var => !var.IsSource(CurrentTime)).Sum(var => var.GetSinkVolume(CurrentTime, CurrentTimeStep)); double sumSinkSources = InflowVolume - EvapoVolume - SinkVolume; //If we have no water from upstream but Inflow, remove water from inflow to fill stream if (sumSinkSources / Volume > 5) { AddWaterPacket(CurrentTime, NewTime, InFlow.Substract(sumSinkSources - Volume * 5)); InflowVolume = InFlow.Volume; sumSinkSources = InflowVolume - EvapoVolume - SinkVolume; } //Sort the incoming water an put in to queue PrePareIncomingWater(); //Calculate the surplus WaterToRoute = _waterInStream.Sum(var => var.Volume) + InflowVolume - EvapoVolume - SinkVolume - Volume + _incomingWater.Sum(var => var.Volume); //If the loss is bigger than available water, reduce Evaporation and Sinks if (WaterToRoute + Volume < 0) { double reductionfactor = 1 + (WaterToRoute + Volume) / (EvapoVolume + SinkVolume); EvapoVolume *= reductionfactor; SinkVolume *= reductionfactor; WaterToRoute = 0; } //Convert to rates double qu = sumSinkSources / CurrentTimeStep.TotalSeconds / Volume; double qop = _incomingWater.Sum(var => var.Volume) / CurrentTimeStep.TotalSeconds; double qout = qu * Volume + qop; //Create a mixer class Mixer M = new Mixer(InFlow, EvapoVolume, SinkVolume); #endregion #region Stored water //Send stored water out if (WaterToRoute > 0) { double OutflowTime = 0; //The volume that needs to flow out to meet the watertotroute double VToSend = WaterToRoute; if (qu != 0) { VToSend = qout / qu * (1 - 1 / (Math.Exp(qu * CurrentTimeStep.TotalSeconds))); } //There is water in the stream that should be routed while (VToSend > 0 & _waterInStream.Count > 0) { IWaterPacket IW; //Mixing during flow towards end of stream double dv = _waterInStream.Peek().Volume *(Math.Exp(qu * OutflowTime) - 1); //Check if the entire water packet should flow out or it should be split if (_waterInStream.Peek().Volume + dv < VToSend) { IW = _waterInStream.Dequeue(); } else { IW = _waterInStream.Peek().Substract(VToSend); } //Update how mush water is yet to be routed VToSend -= IW.Volume; //Now do the mix M.Mix(dv, IW); //Calculate outflow time double dt; if (qu == 0) { dt = IW.Volume / qop; } else { dt = Math.Log(qout / (qout - qu * IW.Volume)) / qu; } //Mixing during outflow M.Mix(qout * dt - IW.Volume, IW); IW.MoveInTime(TimeSpan.FromSeconds(OutflowTime + dt / 2), IW.Volume / Depth); SendWaterDownstream(IW); OutflowTime += dt; } } //Now move the remaining packets to their final destination and time foreach (IWaterPacket IWP in _waterInStream) { if (qu != 0) { M.Mix(IWP.Volume * (Math.Exp(qu * CurrentTimeStep.TotalSeconds) - 1), IWP); } IWP.MoveInTime(CurrentTimeStep, IWP.Volume / Depth); } #endregion #region Water packets traveling right through double inflowtime = 0; //No water in stream and incoming water. Just pass through if (_waterInStream.Count == 0 & _incomingWater.Count > 0) { //Calculate how much incoming water is required to fill stream volume; double VToStore = Volume; if (qu != 0) { VToStore = qop / qu * Math.Log(Volume * qu / qop + 1); } //Now send water through double incomingVolume = _incomingWater.Sum(var => var.Volume); //Send packets through until the remaining will just fill the stream while (incomingVolume > VToStore + 1e-12 & _incomingWater.Count != 0) { IWaterPacket WP; if (incomingVolume - _incomingWater.Peek().Volume > VToStore) { WP = _incomingWater.Dequeue(); } else { WP = _incomingWater.Peek().Substract(incomingVolume - VToStore); } incomingVolume -= WP.Volume; //Keep track of inflow time. inflowtime += WP.Volume / qop; if (qu != 0) { double dvr = WP.Volume * qu * Volume / qop; M.Mix(dvr, WP); } //Calculate the time a water package uses to travel through the stream TimeSpan CurrentTravelTime; if (qu != 0) { CurrentTravelTime = TimeSpan.FromSeconds(1 / qu * Math.Log(Volume * qu / qop + 1)); } else { CurrentTravelTime = TimeSpan.FromSeconds(Volume / qout); } //Moves right through WP.MoveInTime(CurrentTravelTime, WP.Volume / Depth); SendWaterDownstream(WP); } } #endregion #region Fill the stream //The remaining incoming water is moved forward and stays in the stream. while (_incomingWater.Count > 0) { IWaterPacket WP = _incomingWater.Dequeue(); double dt = WP.Volume / qop; inflowtime += dt; double dt2 = CurrentTimeStep.TotalSeconds - inflowtime; //How much of the timestep is left when this packet has moved in. if (qu != 0) { //Volume change during inflow double Vnew = qop * (Math.Exp(qu * dt) - 1) / qu; //Volume change while in stream Vnew *= Math.Exp(qu * dt2); M.Mix(Vnew - WP.Volume, WP); } //The time is half the inflowtime + the time in stream WP.MoveInTime(TimeSpan.FromSeconds(dt2 + dt / 2), WP.Volume / Depth); _waterInStream.Enqueue(WP); } #endregion Output.Outflow.AddSiValue(CurrentTime, NewTime, WaterToRoute / CurrentTimeStep.TotalSeconds); CurrentTime = NewTime; StartofFlowperiod = CurrentTime; }