/// <summary> /// This function will "flux" the wood from path to path using connection points and list of sites in the paths until it meets an exit path. /// </summary> /// <param name="connectionToThisSite">The site from which we will beguin to flux on this current path. Has to be in the current path.</param> /// <param name="woodFlux">The amount of wood to be fluxed along the paths to an exit point</param> public void FluxPathFromSite(Site connectionToThisPath, double woodFlux) { FluxPath currentPath = this; Site currentConnectionPoint = connectionToThisPath; int connectionIndex; List <Site> sitesTofFlux; while (!currentPath.isAnEnd) { // Case of micro-paths (1 site), in order to avoid errors. if (currentPath.sitesInPath.Count == 1) { SiteVars.RoadsInLandscape[currentPath.sitesInPath[0]].timestepWoodFlux += woodFlux; } else { connectionIndex = currentPath.sitesInPath.IndexOf(currentConnectionPoint); if (connectionIndex == -1) { throw new Exception("FOREST ROADS SIMULATION ERROR : During the computing of the flux of wood, site " + currentConnectionPoint + " was not found in the corresponding path that contained " + currentPath.sitesInPath.Count + " sites. This is not normal." + PlugIn.errorToGithub); } sitesTofFlux = currentPath.sitesInPath.GetRange(connectionIndex, (currentPath.sitesInPath.Count - connectionIndex)); foreach (Site siteToFlux in sitesTofFlux) { SiteVars.RoadsInLandscape[siteToFlux].timestepWoodFlux += woodFlux; } } currentConnectionPoint = currentPath.connectionToNext; currentPath = currentPath.nextPath; } // Ending when we reached a currentPath that is an end // Case of micro-paths (1 site), in order to avoid errors. if (currentPath.sitesInPath.Count == 1) { SiteVars.RoadsInLandscape[currentPath.sitesInPath[0]].timestepWoodFlux += woodFlux; } else { connectionIndex = currentPath.sitesInPath.IndexOf(currentConnectionPoint); if (connectionIndex == -1) { throw new Exception("FOREST ROADS SIMULATION ERROR : During the computing of the flux of wood, site " + currentConnectionPoint + " was not found in the corresponding path that contained " + currentPath.sitesInPath.Count + " sites. This is not normal." + PlugIn.errorToGithub); } sitesTofFlux = currentPath.sitesInPath.GetRange(connectionIndex, (currentPath.sitesInPath.Count - connectionIndex)); foreach (Site siteToFlux in sitesTofFlux) { SiteVars.RoadsInLandscape[siteToFlux].timestepWoodFlux += woodFlux; } } }
// Constructor for the end of the dijkstra search, when we have found a connection to another path public FluxPath(List <Site> sitesInPath, Site connectionToNext) { if (sitesInPath.Count == 0) { throw new Exception("FOREST ROADS SIMULATION ERROR : Tried to create a path with no sites in it." + PlugIn.errorToGithub); } // We create this path properly this.sitesInPath = sitesInPath; this.connectionToNext = connectionToNext; this.nextPath = RoadNetwork.fluxPathDictionary[connectionToNext]; this.isAnEnd = false; RoadNetwork.fluxPathCatalogue.Add(this); foreach (Site site in sitesInPath) { RoadNetwork.fluxPathDictionary[site] = this; } }
/// <summary> /// Finds the least cost path from roads to roads to a exit point for the wood in the landscape, and add the given wood flux to every road visited. /// Warning : The starting site must not be inside an existing fluxpath. /// </summary> /// /// <param name="ModelCore"> /// The model's core framework. /// </param> /// /// <param name="startingSite"> /// The starting site of the search. /// </param> /// /// <param name="Woodflux"> /// The flux of wood that is going to flow to the exit point. /// </param> public static void DijkstraWoodFlux(ICore ModelCore, Site startingSite, double woodFlux) { // We initialize the frontier and everything else Priority_Queue.SimplePriorityQueue <Site> frontier = new Priority_Queue.SimplePriorityQueue <Site>(); Dictionary <Site, Site> predecessors = new Dictionary <Site, Site>(); Dictionary <Site, double> costSoFar = new Dictionary <Site, Double>(); HashSet <Site> isClosed = new HashSet <Site>(); bool haveWeFoundARoadToConnectTo = false; costSoFar[startingSite] = 0; frontier.Enqueue(startingSite, 0); Site siteToClose; double newDistanceToStart; // Useless assignement to please the gods of C# Site arrivalSite = startingSite; // First, we got to check the possibility that the starting site IS the arrival site. This is possible in very rare situations, // as the given starting site can be an exit point...That is not surounded by roads. If that happens, the dijkstra will not be able to // open any neighbors, and will fail. Again, very rare, but still important. if (RoadNetwork.fluxPathDictionary.ContainsKey(startingSite) || SiteVars.RoadsInLandscape[startingSite].IsAPlaceForTheWoodToGo) { haveWeFoundARoadToConnectTo = true; goto End; } // We loop until the list is empty while (frontier.Count > 0) { // We take the site with the lowest distance to start. siteToClose = frontier.Dequeue(); // We look at each of its neighbours, but only those with roads on them. foreach (Site neighbourToOpen in MapManager.GetNeighbouringSitesWithRoads(siteToClose)) { // We don't consider the neighbour if it is closed if (!isClosed.Contains(neighbourToOpen)) { // We get the value of the distance to start by using the current node to close, which is just an addition of the distance to the start // from the node to close + the cost between it and the neighbor. newDistanceToStart = costSoFar[siteToClose] + MapManager.CostOfTransition(siteToClose, neighbourToOpen); // If the node isn't opened yet, or if it is opened and going to start throught the current node to close is closer; then, // this node to close will become its predecessor, and its distance to start will become this one. if (!costSoFar.ContainsKey(neighbourToOpen) || newDistanceToStart < costSoFar[neighbourToOpen]) { costSoFar[neighbourToOpen] = newDistanceToStart; predecessors[neighbourToOpen] = siteToClose; // Case of the node not being opened if (!frontier.Contains(neighbourToOpen)) { frontier.Enqueue(neighbourToOpen, (float)costSoFar[neighbourToOpen]); } // Case of the node being already open else { frontier.UpdatePriority(neighbourToOpen, (float)costSoFar[neighbourToOpen]); } } // We check if the neighbour is a exit node for the wood. If that's the case, search is other. if (RoadNetwork.fluxPathDictionary.ContainsKey(neighbourToOpen) || SiteVars.RoadsInLandscape[neighbourToOpen].IsAPlaceForTheWoodToGo) { arrivalSite = neighbourToOpen; haveWeFoundARoadToConnectTo = true; goto End; } } } // Now that we have checked all of its neighbours, we can close the current node. isClosed.Add(siteToClose); } End: // ModelCore.UI.WriteLine("Dijkstra search for wood flux is over."); // If we're out of the loop, that means that the search is over. If it was successfull before we ran out of neighbours to check, // We can now retrieve the list of the sites that // are the least cost path, and make sure that all of these site now have a road constructed on them, and that it is // indicated as connected to a place where we can make wood go. if (haveWeFoundARoadToConnectTo) { // If we found a fluxPath to connect to, we create a new one starting from the starting site, and stopping just before the arrival (which is our connection site to the fluxpath, and thus already belongs to a fluxpath) if (RoadNetwork.fluxPathDictionary.ContainsKey(arrivalSite)) { List <Site> listOfSitesInLeastCostPath = MapManager.FindPathToStart(startingSite, arrivalSite, predecessors); // We have to reverse the list, because we want to go from the harvested zones to the connection point, and not the opposite. listOfSitesInLeastCostPath.Reverse(); // We don't put the arrival site in the fluxpath, because it is part of another fluxpath. listOfSitesInLeastCostPath.Remove(arrivalSite); FluxPath newFluxPath = new FluxPath(listOfSitesInLeastCostPath, arrivalSite); // Now that this new path is created, we flux the wood. newFluxPath.FluxPathFromSite(startingSite, woodFlux); } // Else, if we didn't found a fluxPath to connect to but an exit point for the wood, we create a new "isAnEnd" path. else { List <Site> listOfSitesInLeastCostPath = MapManager.FindPathToStart(startingSite, arrivalSite, predecessors); // We have to reverse the list, because we want to go from the harvested zones to the exit point, and not the opposite. listOfSitesInLeastCostPath.Reverse(); FluxPath newFluxPathIsAnEnd = new FluxPath(listOfSitesInLeastCostPath); // Then, we flux the wood down this path. newFluxPathIsAnEnd.FluxPathFromSite(startingSite, woodFlux); } } else { throw new Exception("FOREST ROADS SIMULATION ERROR : A Dijkstra search wasn't able to flux the wood from site " + startingSite.Location + " to any exit point. This isn't supposed to happen. Please check the output raster containg the road network for the current timestep (" + ModelCore.CurrentTime + " years) and see if a road is interrupted somewhere." + PlugIn.errorToGithub); } }