Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignition points python side done, doesn't work well on cell2fire thou… #2

Merged
merged 1 commit into from
Apr 2, 2022
Merged
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
1 change: 1 addition & 0 deletions cell2fire/Cell2FireC/Cell2Fire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ Cell2Fire::Cell2Fire(arguments _args) : CSVWeather(_args.InFolder + "Weather.csv
}

// Harvested cells
// FIXME: WTF does this do?
if(strcmp(this->args.HarvestPlan.c_str(), EM) != 0){
std::string sep = ",";
CSVReader CSVHPlan(this->args.HarvestPlan, sep);
Expand Down
47 changes: 47 additions & 0 deletions cell2fire/Cell2FireC/Makefile_willshen
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
EIGENDIR = /Users/admin/workspace/eigen-3.4.0/
CC = g++
MPCC = g++
OPENMP = -openmp
CFLAGS = -std=c++11 -O3 -I$(EIGENDIR)
LIBS = -m64 -fPIC -fno-strict-aliasing -fexceptions -DNDEBUG -DIL_STD -lm -lpthread -ldl

TARGETS = Cell2Fire

all: $(TARGETS)

Cell2Fire: Cell2Fire.o CellsFBP.o FBPfunc5_NoDebug.o SpottingFBP.o ReadCSV.o ReadArgs.o Lightning.o WriteCSV.o Ellipse.o
$(CC) -o $@ $(LIBS) -Xclang -fopenmp -lomp Cell2Fire.o CellsFBP.o FBPfunc5_NoDebug.o SpottingFBP.o ReadCSV.o ReadArgs.o Lightning.o WriteCSV.o Ellipse.o

Cell2Fire.o: Cell2Fire.cpp CellsFBP.o FBPfunc5_NoDebug.o SpottingFBP.o ReadCSV.o ReadArgs.o WriteCSV.o
$(CC) -c $(CFLAGS) -Xclang -fopenmp -lomp Cell2Fire.cpp

SpottingFBP.o: SpottingFBP.cpp SpottingFBP.h CellsFBP.h
$(CC) -c $(CFLAGS) SpottingFBP.cpp CellsFBP.h

CellsFBP.o: CellsFBP.cpp CellsFBP.h FBPfunc5_NoDebug.o
$(CC) -c $(CFLAGS) CellsFBP.cpp

FBPfunc5_NoDebug.o: FBPfunc5_NoDebug.c FBP5.0.h
$(CC) -c $(CFLAGS) FBPfunc5_NoDebug.c

ReadCSV.o: ReadCSV.cpp ReadCSV.h FBPfunc5_NoDebug.o
$(CC) -c $(CFLAGS) ReadCSV.cpp

ReadArgs.o: ReadArgs.cpp ReadArgs.h
$(CC) -c $(CFLAGS) ReadArgs.cpp

Lightning.o: Lightning.cpp Lightning.h
$(CC) -c $(CFLAGS) Lightning.cpp

Forest.o: Forest.cpp Forest.h
$(CC) -c $(CFLAGS) Forest.cpp

WriteCSV.o: WriteCSV.cpp WriteCSV.h
$(CC) -c $(CFLAGS) WriteCSV.cpp

Ellipse.o: Ellipse.cpp Ellipse.h
$(CC) -c $(LIBS) $(CFLAGS) Ellipse.cpp


clean:
rm Lightning.o ReadArgs.o ReadCSV.o FBPfunc5_NoDebug.o Cell2Fire.o CellsFBP.o Cell2Fire SpottingFBP.o Forest.o WriteCSV.o Ellipse.o *.gch
74 changes: 66 additions & 8 deletions cell2fire/firehose/models.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import os
from dataclasses import dataclass
from functools import cached_property
from typing import NamedTuple
from typing import NamedTuple, List, ClassVar

import numpy as np

from utils.ReadDataPrometheus import Dictionary


class IgnitionPoint(NamedTuple):
x: int = 0
y: int = 0
radius: int = 0
@dataclass(frozen=True)
class IgnitionPoint:
idx: int
year: int


@dataclass(frozen=True)
class IgnitionPoints:
points: List[IgnitionPoint]

# This is a class variable as it's fixed as input for
# all ignition points. 0 is default used in parser.
RADIUS: ClassVar[int] = 0
CSV_NAME: ClassVar[str] = "Ignitions.csv"

@property
def year(self) -> int:
year = [p.year for p in self.points]
assert len(set(year)) == 1, "All ignition points must have the same year"
return year[0]

def get_csv(self) -> str:
csv_list = ["Year,Ncell"]
for point in self.points:
csv_list.append(f"{point.year},{point.idx}")
return "\n".join(csv_list)

def write_to_csv(self, path: str) -> None:
with open(path, "w") as f:
f.write(self.get_csv())


class ExperimentHelper(NamedTuple):
@dataclass(frozen=True)
class ExperimentHelper:
base_dir: str
map: str

Expand All @@ -33,7 +60,8 @@ def forest_datafile(self) -> str:
def output_folder(self) -> str:
return "{}/../results/{}/".format(self.base_dir, self.map)

def load_forest_image(self) -> np.ndarray:
@cached_property
def forest_image(self) -> np.ndarray:
# Load in the raw forest image
forest_image_data = np.loadtxt(self.forest_datafile, skiprows=6)

Expand All @@ -51,3 +79,33 @@ def load_forest_image(self) -> np.ndarray:
forest_image[x, y] = fb_dict[str(int(forest_image_data[x, y]))][:3]

return forest_image

def generate_random_ignition_points(
self, num_points: int = 1, year: int = 1, radius: int = IgnitionPoints.RADIUS
) -> IgnitionPoints:
"""
Generates random ignition points.

:param num_points: number of ignition points to generate
:param year: the year, we only support one year for now
:param radius: the radius of the ignition point, default to 0
:return: List of ignition points, which are tuples with
(year, index of cell with the ignition point)
"""
height, width = self.forest_image.shape[:2]
available_idxs = np.arange(width * height)

# Set radius class variable
IgnitionPoints.RADIUS = radius

# TODO: check type of vegetation in forest image or do we not care?
# we should probably otherwise there could be no ignition and loop fails
ignition_points = np.random.choice(available_idxs, num_points, replace=False)

ignition_points = IgnitionPoints(
points=[
IgnitionPoint(point, year + idx)
for idx, point in enumerate(ignition_points)
]
)
return ignition_points
45 changes: 39 additions & 6 deletions cell2fire/firehose/process.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,58 @@
import os
import shutil
import subprocess
from typing import Optional
from datetime import datetime
from typing import Optional, TYPE_CHECKING

from firehose.models import ExperimentHelper
from firehose.models import IgnitionPoints

_COMMAND_STR = "{} --input-instance-folder {} --output-folder {} --ignitions --sim-years 1 \
if TYPE_CHECKING:
# Workaround for circular imports as I can't be bothered refactoring
from gym_env import FireEnv

_COMMAND_STR = "{binary} --input-instance-folder {input} --output-folder {output} --ignitions --sim-years {sim_years} \
--nsims 1 --grids --final-grid --Fire-Period-Length 1.0 --output-messages \
--weather rows --nweathers 1 --ROS-CV 0.5 --IgnitionRad 0 --seed 123 --nthreads 1 \
--weather rows --nweathers 1 --ROS-CV 0.5 --IgnitionRad {ignition_radius} --seed 123 --nthreads 1 \
--ROS-Threshold 0.1 --HFI-Threshold 0.1 --HarvestPlan"


class Cell2FireProcess:
# TODO: detect if process throws an error?

def __init__(self, helper: ExperimentHelper):
def __init__(self, env: "FireEnv"):

command_str = _COMMAND_STR.format(
helper.binary_path, helper.data_folder, helper.output_folder
binary=env.helper.binary_path,
input=self.manipulate_input_data_folder(env),
output=env.helper.output_folder,
ignition_radius=IgnitionPoints.RADIUS,
sim_years=1,
)

self._command_str_args = command_str.split(" ")
self.process: Optional[subprocess.Popen] = None

def manipulate_input_data_folder(
self, env: "FireEnv", experiment_dir="/tmp/firehose"
) -> str:
# Copy input data folder to new one we generate
datetime_str = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
tmp_dir = os.path.join(experiment_dir, f"{env.helper.map}_{datetime_str}")
shutil.copytree(env.helper.data_folder, tmp_dir)

# Delete existing ignition points and write our ignition points
ignition_points_csv = os.path.join(tmp_dir, IgnitionPoints.CSV_NAME)
os.remove(ignition_points_csv)
env.ignition_points.write_to_csv(ignition_points_csv)

print(f"Copied modified input data folder to {tmp_dir}")
# This adds a trailing slash if its required
tmp_dir = os.path.join(tmp_dir, "")
return tmp_dir

def spawn(self):
print("Spawning cell2fire process")
print("Command:", " ".join(self._command_str_args))
self.process = subprocess.Popen(
self._command_str_args,
stdout=subprocess.PIPE,
Expand Down
49 changes: 32 additions & 17 deletions cell2fire/gym_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,62 @@
from gym import Env, spaces
from gym.spaces import Discrete, Box

from firehose.models import IgnitionPoint, ExperimentHelper
from firehose.models import IgnitionPoint, ExperimentHelper, IgnitionPoints
from firehose.process import Cell2FireProcess

ENVS = []
_MODULE_DIR = os.path.dirname(os.path.realpath(__file__))


def fire_size_reward(state, forest, scale=10):
idxs = np.where(state > 0)
return -len(idxs[0])/(forest.shape[0]*forest.shape[1])*scale
return -len(idxs[0]) / (forest.shape[0] * forest.shape[1]) * scale


class FireEnv(Env):
def __init__(
self,
fire_map: str = "Sub20x20",
fire_map: str = "Harvest40x40",
max_steps: int = 200,
ignition_points: Optional[List[IgnitionPoint]] = None,
reward_func = fire_size_reward
ignition_points: Optional[IgnitionPoints] = None,
reward_func=fire_size_reward,
num_ignition_points: int = 5, # if ignition_points is specified this is ignored
):
if not ignition_points:
ignition_points = [IgnitionPoint()]
# TODO: Create the process with the input map
self.iter = 0
self.max_steps = max_steps

# Helper code
self.helper = ExperimentHelper(base_dir=_MODULE_DIR, map=fire_map)
self.forest_image = self.helper.load_forest_image()
self.forest_image = self.helper.forest_image

self.action_space = Discrete(self.forest_image.shape[0]*self.forest_image.shape[1])
self.observation_space = spaces.Box(low=0, high=255,
shape=(self.forest_image.shape[0], self.forest_image.shape[1]), dtype=np.uint8)
# Randomly generate ignition points if required
if not ignition_points:
self.ignition_points = self.helper.generate_random_ignition_points(
num_points=num_ignition_points,
)
else:
self.ignition_points = ignition_points

self.action_space = Discrete(
self.forest_image.shape[0] * self.forest_image.shape[1]
)
self.observation_space = spaces.Box(
low=0,
high=255,
shape=(self.forest_image.shape[0], self.forest_image.shape[1]),
dtype=np.uint8,
)
self.state = np.zeros((self.forest_image.shape[0], self.forest_image.shape[1]))
# Cell2Fire Process
self.fire_process = Cell2FireProcess(self.helper)

# TODO: pass these into the binary
self.ignition_points = ignition_points

# Reward function
self.reward_func = reward_func

# NOTE: this should be the last thing that is done after we have set all the
# relevant properties in this instance
# Cell2Fire Process
self.fire_process = Cell2FireProcess(self)

def step(self, action, debug: bool = True):
# if debug:
# print(action, "step")
Expand All @@ -60,7 +75,7 @@ def step(self, action, debug: bool = True):
# print(result)
# assert len(result)>0

value = str(action+1) + "\n"
value = str(action + 1) + "\n"
value = bytes(value, "UTF-8")
self.fire_process.write_action(value)

Expand Down