Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion cell2fire/Cell2FireC/Cell2Fire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ Cell2Fire::Cell2Fire(arguments _args) : CSVWeather(_args.InFolder + "Weather.csv
this->nonBurnableCells.clear();
this->burningCells.clear();
this->burntCells.clear();
this->ignitionCells.clear();
this->harvestCells.clear();
for (i=0; i < this->statusCells.size(); i++){
if(this->statusCells[i] < 3) this->availCells.insert (i+1);
Expand Down Expand Up @@ -535,6 +536,7 @@ void Cell2Fire::reset(int rnumber, double rnumber2){
this->nonBurnableCells.clear();
this->burningCells.clear();
this->burntCells.clear();
this->ignitionCells.clear();
this->harvestCells.clear();

// Harvest Cells
Expand Down Expand Up @@ -697,6 +699,7 @@ bool Cell2Fire::RunIgnition(std::default_random_engine generator){
this->nIgnitions++;
this->burningCells.insert(newId);
this->burntCells.insert(newId);
this->ignitionCells.insert(newId); // forest root: excluded as a message destination
this->availCells.erase(newId);

// Print sets information
Expand Down Expand Up @@ -1049,6 +1052,63 @@ void Cell2Fire::GetMessages(std::unordered_map<int, std::vector<int>> sendMessag
}


/*
* Reduce the raw FSCell event buffer to the fire-propagation forest written to MessagesFileNN.csv.
*
* FSCell is an append-only stream of (source, destination, period, ROS) reach-to-center events.
* A destination may appear several times (several sources reaching it in one period, or repeated
* reaches across periods) and may even name a cell that never actually burned. The MessagesFile is
* meant to be a spanning forest rooted at the ignition cells, with exactly one parent edge per
* burned, non-ignition cell, so we select a single representative edge per burned destination.
*
* Selection rule (per destination): earliest fire period; ties broken by highest realized ROS, then
* by lowest source id. Output rows are sorted by destination id so the file is deterministic.
*/
std::vector<double> Cell2Fire::buildMessageTree(){
// destination -> winning edge {source, destination, period, ros}
std::unordered_map<int, std::vector<double>> best;

for (size_t i = 0; i + 3 < this->FSCell.size(); i += 4){
double src = this->FSCell[i];
int dst = (int) this->FSCell[i + 1];
double period = this->FSCell[i + 2];
double ros = this->FSCell[i + 3];

// Keep only edges into cells that actually burned, and never into an ignition root.
if (this->burntCells.find(dst) == this->burntCells.end()) continue;
if (this->ignitionCells.find(dst) != this->ignitionCells.end()) continue;

auto it = best.find(dst);
if (it == best.end()){
best[dst] = {src, (double) dst, period, ros};
continue;
}

double bSrc = it->second[0], bPeriod = it->second[2], bRos = it->second[3];
bool better = (period < bPeriod)
|| (period == bPeriod && ros > bRos)
|| (period == bPeriod && ros == bRos && src < bSrc);
if (better){
it->second = {src, (double) dst, period, ros};
}
}

// Emit rows ordered by destination id for a reproducible file.
std::vector<int> dsts;
dsts.reserve(best.size());
for (auto & kv : best) dsts.push_back(kv.first);
std::sort(dsts.begin(), dsts.end());

std::vector<double> tree;
tree.reserve(dsts.size() * 4);
for (int d : dsts){
std::vector<double> & e = best[d];
tree.insert(tree.end(), e.begin(), e.end());
}
return tree;
}


// Display results
void Cell2Fire::Results(){
/*****************************************************************************
Expand Down Expand Up @@ -1151,7 +1211,9 @@ void Cell2Fire::Results(){
std::cout << "We are generating the network messages to a csv file " << messagesName << std::endl;
}
CSVWriter CSVPloter(messagesName, ",");
CSVPloter.printCSVDouble_V2(this->burntCells.size() - this->nIgnitions, 4, this->FSCell);
// Serialize one canonical parent edge per burned, non-ignition cell (see buildMessageTree).
std::vector<double> messageTree = this->buildMessageTree();
CSVPloter.printCSVDouble_V2(messageTree.size() / 4, 4, messageTree);
}


Expand Down
2 changes: 2 additions & 0 deletions cell2fire/Cell2FireC/Cell2Fire.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class Cell2Fire {
std::unordered_set<int> nonBurnableCells;
std::unordered_set<int> burningCells;
std::unordered_set<int> burntCells;
std::unordered_set<int> ignitionCells; // roots of the propagation forest (no parent edge)
std::unordered_set<int> harvestCells;

// Cells Dictionary
Expand All @@ -102,6 +103,7 @@ class Cell2Fire {
bool RunIgnition(std::default_random_engine generator);
std::unordered_map<int, std::vector<int>> SendMessages();
void GetMessages(std::unordered_map<int, std::vector<int>> sendMessageList);
std::vector<double> buildMessageTree();
void Results();
void outputGrid();
void updateWeather();
Expand Down
6 changes: 4 additions & 2 deletions cell2fire/utils/Heuristics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1530,7 +1530,8 @@ def Global_FPVPlot(self, normalized=False, xticks=50, yticks=50):
plt.title(self.Alg + " Heatmap $|R| = 100$", y=1.02)

# Modify existing map to have white values
cmap = cm.get_cmap('RdBu_r')
# matplotlib.cm.get_cmap was removed in matplotlib 3.9; use the registry.
cmap = matplotlib.colormaps['RdBu_r']
lower = plt.cm.seismic(np.ones(2)*0.50) # Original is ones
range = cmap(np.linspace(0.5, np.max((self._FPVMatrix / np.max(self._FPVMatrix))), 100))
colors = np.vstack((lower,range))
Expand Down Expand Up @@ -1616,7 +1617,8 @@ def Ind_FPVPlot(self, nSim, FPVMatrix, normalized=False):
plt.title("FPV Heatmap")

# Modify existing map to have white values
cmap = cm.get_cmap('RdBu_r')
# matplotlib.cm.get_cmap was removed in matplotlib 3.9; use the registry.
cmap = matplotlib.colormaps['RdBu_r']
lower = plt.cm.seismic(np.ones(100)*0.50)
upper = cmap(np.linspace(1-0.5, 1, 90))
colors = np.vstack((lower, upper))
Expand Down
3 changes: 2 additions & 1 deletion cell2fire/utils/Stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,8 @@ def ROSHeatmap(self, ROSM, Path=None, nscen=1, sq=True, namePlot="ROS_HeatMap",
plt.title(Title)

# Modify existing map to have white values
cmap = cm.get_cmap('RdBu_r')
# matplotlib.cm.get_cmap was removed in matplotlib 3.9; use the registry.
cmap = matplotlib.colormaps['RdBu_r']
lower = plt.cm.seismic(np.ones(1)*0.50) # Original is ones
upper = cmap(np.linspace(0.5, 1, 100))
colors = np.vstack((lower,upper))
Expand Down
13 changes: 9 additions & 4 deletions tests/test_ptree.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,15 @@ def test_readme_cmd(self):

csv_path = os.path.join(data_path, "..", "results", "Sub40x40", "Messages", "MessagesFile01.csv")
df = pd.read_csv(csv_path, names = ["node1", "node2", "time", "ros"])
self.assertAlmostEqual(df['node1'][10],935,0), "TEST ERROR"
self.assertAlmostEqual(df['node2'][10],896,0), "TEST ERROR"
self.assertAlmostEqual(df['time'][10], 139, 0), "TEST ERROR"
self.assertAlmostEqual(df['node2'][10], 14.1367, 2), "TEST ERROR"
# MessagesFile is the fire-propagation forest: exactly one parent edge per burned,
# non-ignition cell, with rows sorted by destination (node2) so the file is
# deterministic. See Cell2Fire::buildMessageTree in Cell2Fire.cpp.
self.assertEqual(df['node2'].nunique(), len(df)) # one parent per destination
self.assertEqual(list(df['node2']), sorted(df['node2'])) # deterministic ordering
self.assertAlmostEqual(df['node1'][10], 456, 0), "TEST ERROR"
self.assertAlmostEqual(df['node2'][10], 417, 0), "TEST ERROR"
self.assertAlmostEqual(df['time'][10], 402, 0), "TEST ERROR"
self.assertAlmostEqual(df['ros'][10], 27.2931, 2), "TEST ERROR"

if __name__ == "__main__":

Expand Down
Loading