From 2168e5f7c560285d14b72a70c1c7e742d864601c Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 10:44:31 -0400 Subject: [PATCH 01/38] add **/sims to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 106a839c3..6d402f00e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ **/dist **/logs **/private +**/sims **/output* **/.venv **/src/cpp/version.cpp From de9440db94f08ad9f37194855883b0e64bfdbf5a Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 08:43:17 -0400 Subject: [PATCH 02/38] move python to ./src/py --- .docker/Dockerfile | 8 ++++---- .dockerignore | 1 + .gitmodules | 2 +- docker-compose.yml | 9 +++++++++ {firestarr/src => src}/py/cffdrs-ng | 0 {firestarr/src => src}/py/firestarr/azurebatch.py | 0 .../src => src}/py/firestarr/azurebatch_helpers.py | 0 {firestarr/src => src}/py/firestarr/check_and_publish.py | 0 {firestarr/src => src}/py/firestarr/common.py | 0 {firestarr/src => src}/py/firestarr/datasources/cwfif.py | 0 {firestarr/src => src}/py/firestarr/datasources/cwfis.py | 0 .../src => src}/py/firestarr/datasources/datatypes.py | 0 .../src => src}/py/firestarr/datasources/default.py | 0 .../py/firestarr/datasources/public/agency_on.py | 0 .../src => src}/py/firestarr/datasources/spotwx.py | 0 {firestarr/src => src}/py/firestarr/fires.py | 0 {firestarr/src => src}/py/firestarr/gis.py | 0 {firestarr/src => src}/py/firestarr/log.py | 0 {firestarr/src => src}/py/firestarr/main.py | 0 {firestarr/src => src}/py/firestarr/make_bounds.py | 0 {firestarr/src => src}/py/firestarr/model_data.py | 0 {firestarr/src => src}/py/firestarr/net.py | 0 {firestarr/src => src}/py/firestarr/publish.py | 0 {firestarr/src => src}/py/firestarr/publish_azure.py | 0 {firestarr/src => src}/py/firestarr/publish_geoserver.py | 0 {firestarr/src => src}/py/firestarr/redundancy.py | 0 {firestarr/src => src}/py/firestarr/run.py | 0 {firestarr/src => src}/py/firestarr/sim_wrapper.py | 0 {firestarr/src => src}/py/firestarr/simulation.py | 0 {firestarr/src => src}/py/firestarr/tqdm_util.py | 0 30 files changed, 15 insertions(+), 5 deletions(-) rename {firestarr/src => src}/py/cffdrs-ng (100%) rename {firestarr/src => src}/py/firestarr/azurebatch.py (100%) rename {firestarr/src => src}/py/firestarr/azurebatch_helpers.py (100%) rename {firestarr/src => src}/py/firestarr/check_and_publish.py (100%) rename {firestarr/src => src}/py/firestarr/common.py (100%) rename {firestarr/src => src}/py/firestarr/datasources/cwfif.py (100%) rename {firestarr/src => src}/py/firestarr/datasources/cwfis.py (100%) rename {firestarr/src => src}/py/firestarr/datasources/datatypes.py (100%) rename {firestarr/src => src}/py/firestarr/datasources/default.py (100%) rename {firestarr/src => src}/py/firestarr/datasources/public/agency_on.py (100%) rename {firestarr/src => src}/py/firestarr/datasources/spotwx.py (100%) rename {firestarr/src => src}/py/firestarr/fires.py (100%) rename {firestarr/src => src}/py/firestarr/gis.py (100%) rename {firestarr/src => src}/py/firestarr/log.py (100%) rename {firestarr/src => src}/py/firestarr/main.py (100%) rename {firestarr/src => src}/py/firestarr/make_bounds.py (100%) rename {firestarr/src => src}/py/firestarr/model_data.py (100%) rename {firestarr/src => src}/py/firestarr/net.py (100%) rename {firestarr/src => src}/py/firestarr/publish.py (100%) rename {firestarr/src => src}/py/firestarr/publish_azure.py (100%) rename {firestarr/src => src}/py/firestarr/publish_geoserver.py (100%) rename {firestarr/src => src}/py/firestarr/redundancy.py (100%) rename {firestarr/src => src}/py/firestarr/run.py (100%) rename {firestarr/src => src}/py/firestarr/sim_wrapper.py (100%) rename {firestarr/src => src}/py/firestarr/simulation.py (100%) rename {firestarr/src => src}/py/firestarr/tqdm_util.py (100%) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 456ba141a..a4f288d19 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -166,11 +166,11 @@ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/bounds.geojson /appl/firestarr/ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/fuel.lut /appl/firestarr/ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/settings.ini /appl/firestarr/ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/scripts /appl/firestarr/scripts/ -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/src/py/firestarr /appl/firestarr/src/py/firestarr/ +COPY --chown=${USERNAME}:${USERNAME} ./src/py/firestarr /appl/firestarr/src/py/firestarr/ WORKDIR /appl/firestarr/src/py/cffdrs-ng -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/src/py/cffdrs-ng/NG_FWI.py . -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/src/py/cffdrs-ng/old_cffdrs.py . -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/src/py/cffdrs-ng/util.py . +COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/NG_FWI.py . +COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/old_cffdrs.py . +COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/util.py . USER ${USERNAME} # doesn't delete intermediaries, but maybe smaller between versions? diff --git a/.dockerignore b/.dockerignore index ec377e956..294dd6746 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,5 +11,6 @@ !firestarr/scripts/*.sh !firestarr/settings.ini !./config +!src **/__pycache__ diff --git a/.gitmodules b/.gitmodules index dde1bae96..c49b7da42 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "cffdrs-ng"] - path = firestarr/src/py/cffdrs-ng + path = src/py/cffdrs-ng url = https://github.com/jordan-evens/cffdrs-ng.git branch = main diff --git a/docker-compose.yml b/docker-compose.yml index cde0caad4..f91eaf0be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,7 @@ services: target: /appl/config - data:/appl/data - sims:/appl/data/sims + - py:/appl/firestarr/src/py - type: bind source: ./gis target: /appl/gis @@ -44,6 +45,7 @@ services: target: /appl/config - data:/appl/data - sims:/appl/data/sims + - py:/appl/firestarr/src/py - type: bind source: ./firestarr target: /appl/firestarr @@ -75,6 +77,7 @@ services: target: /appl/config - data:/appl/data - sims:/appl/data/sims + - py:/appl/firestarr/src/py - type: bind source: ./firestarr target: /appl/firestarr @@ -160,3 +163,9 @@ volumes: type: none o: bind device: ./sims + py: + driver: local + driver_opts: + type: none + o: bind + device: ./src/py diff --git a/firestarr/src/py/cffdrs-ng b/src/py/cffdrs-ng similarity index 100% rename from firestarr/src/py/cffdrs-ng rename to src/py/cffdrs-ng diff --git a/firestarr/src/py/firestarr/azurebatch.py b/src/py/firestarr/azurebatch.py similarity index 100% rename from firestarr/src/py/firestarr/azurebatch.py rename to src/py/firestarr/azurebatch.py diff --git a/firestarr/src/py/firestarr/azurebatch_helpers.py b/src/py/firestarr/azurebatch_helpers.py similarity index 100% rename from firestarr/src/py/firestarr/azurebatch_helpers.py rename to src/py/firestarr/azurebatch_helpers.py diff --git a/firestarr/src/py/firestarr/check_and_publish.py b/src/py/firestarr/check_and_publish.py similarity index 100% rename from firestarr/src/py/firestarr/check_and_publish.py rename to src/py/firestarr/check_and_publish.py diff --git a/firestarr/src/py/firestarr/common.py b/src/py/firestarr/common.py similarity index 100% rename from firestarr/src/py/firestarr/common.py rename to src/py/firestarr/common.py diff --git a/firestarr/src/py/firestarr/datasources/cwfif.py b/src/py/firestarr/datasources/cwfif.py similarity index 100% rename from firestarr/src/py/firestarr/datasources/cwfif.py rename to src/py/firestarr/datasources/cwfif.py diff --git a/firestarr/src/py/firestarr/datasources/cwfis.py b/src/py/firestarr/datasources/cwfis.py similarity index 100% rename from firestarr/src/py/firestarr/datasources/cwfis.py rename to src/py/firestarr/datasources/cwfis.py diff --git a/firestarr/src/py/firestarr/datasources/datatypes.py b/src/py/firestarr/datasources/datatypes.py similarity index 100% rename from firestarr/src/py/firestarr/datasources/datatypes.py rename to src/py/firestarr/datasources/datatypes.py diff --git a/firestarr/src/py/firestarr/datasources/default.py b/src/py/firestarr/datasources/default.py similarity index 100% rename from firestarr/src/py/firestarr/datasources/default.py rename to src/py/firestarr/datasources/default.py diff --git a/firestarr/src/py/firestarr/datasources/public/agency_on.py b/src/py/firestarr/datasources/public/agency_on.py similarity index 100% rename from firestarr/src/py/firestarr/datasources/public/agency_on.py rename to src/py/firestarr/datasources/public/agency_on.py diff --git a/firestarr/src/py/firestarr/datasources/spotwx.py b/src/py/firestarr/datasources/spotwx.py similarity index 100% rename from firestarr/src/py/firestarr/datasources/spotwx.py rename to src/py/firestarr/datasources/spotwx.py diff --git a/firestarr/src/py/firestarr/fires.py b/src/py/firestarr/fires.py similarity index 100% rename from firestarr/src/py/firestarr/fires.py rename to src/py/firestarr/fires.py diff --git a/firestarr/src/py/firestarr/gis.py b/src/py/firestarr/gis.py similarity index 100% rename from firestarr/src/py/firestarr/gis.py rename to src/py/firestarr/gis.py diff --git a/firestarr/src/py/firestarr/log.py b/src/py/firestarr/log.py similarity index 100% rename from firestarr/src/py/firestarr/log.py rename to src/py/firestarr/log.py diff --git a/firestarr/src/py/firestarr/main.py b/src/py/firestarr/main.py similarity index 100% rename from firestarr/src/py/firestarr/main.py rename to src/py/firestarr/main.py diff --git a/firestarr/src/py/firestarr/make_bounds.py b/src/py/firestarr/make_bounds.py similarity index 100% rename from firestarr/src/py/firestarr/make_bounds.py rename to src/py/firestarr/make_bounds.py diff --git a/firestarr/src/py/firestarr/model_data.py b/src/py/firestarr/model_data.py similarity index 100% rename from firestarr/src/py/firestarr/model_data.py rename to src/py/firestarr/model_data.py diff --git a/firestarr/src/py/firestarr/net.py b/src/py/firestarr/net.py similarity index 100% rename from firestarr/src/py/firestarr/net.py rename to src/py/firestarr/net.py diff --git a/firestarr/src/py/firestarr/publish.py b/src/py/firestarr/publish.py similarity index 100% rename from firestarr/src/py/firestarr/publish.py rename to src/py/firestarr/publish.py diff --git a/firestarr/src/py/firestarr/publish_azure.py b/src/py/firestarr/publish_azure.py similarity index 100% rename from firestarr/src/py/firestarr/publish_azure.py rename to src/py/firestarr/publish_azure.py diff --git a/firestarr/src/py/firestarr/publish_geoserver.py b/src/py/firestarr/publish_geoserver.py similarity index 100% rename from firestarr/src/py/firestarr/publish_geoserver.py rename to src/py/firestarr/publish_geoserver.py diff --git a/firestarr/src/py/firestarr/redundancy.py b/src/py/firestarr/redundancy.py similarity index 100% rename from firestarr/src/py/firestarr/redundancy.py rename to src/py/firestarr/redundancy.py diff --git a/firestarr/src/py/firestarr/run.py b/src/py/firestarr/run.py similarity index 100% rename from firestarr/src/py/firestarr/run.py rename to src/py/firestarr/run.py diff --git a/firestarr/src/py/firestarr/sim_wrapper.py b/src/py/firestarr/sim_wrapper.py similarity index 100% rename from firestarr/src/py/firestarr/sim_wrapper.py rename to src/py/firestarr/sim_wrapper.py diff --git a/firestarr/src/py/firestarr/simulation.py b/src/py/firestarr/simulation.py similarity index 100% rename from firestarr/src/py/firestarr/simulation.py rename to src/py/firestarr/simulation.py diff --git a/firestarr/src/py/firestarr/tqdm_util.py b/src/py/firestarr/tqdm_util.py similarity index 100% rename from firestarr/src/py/firestarr/tqdm_util.py rename to src/py/firestarr/tqdm_util.py From 43bd7b23cfc20b99eeca5b2ab35794777b7c1d41 Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 10:11:41 -0400 Subject: [PATCH 03/38] fix .dockerignore to not include non-python files in src/py --- .docker/Dockerfile | 2 +- .dockerignore | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index a4f288d19..f8036a9d9 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -166,7 +166,7 @@ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/bounds.geojson /appl/firestarr/ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/fuel.lut /appl/firestarr/ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/settings.ini /appl/firestarr/ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/scripts /appl/firestarr/scripts/ -COPY --chown=${USERNAME}:${USERNAME} ./src/py/firestarr /appl/firestarr/src/py/firestarr/ +COPY --chown=${USERNAME}:${USERNAME} ./src/py/firestarr/ /appl/firestarr/src/py/firestarr/ WORKDIR /appl/firestarr/src/py/cffdrs-ng COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/NG_FWI.py . COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/old_cffdrs.py . diff --git a/.dockerignore b/.dockerignore index 294dd6746..b41ff4baf 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,13 +4,14 @@ !.docker/requirements*.txt !.docker/crontab !gis -!firestarr/src +!**/src !firestarr/CMakeLists.txt !firestarr/fuel.lut !firestarr/bounds.geojson !firestarr/scripts/*.sh !firestarr/settings.ini !./config -!src +**/src/py/**/*.sh +**/src/**/.git* **/__pycache__ From 148975b4e23cda4133e61e4433c8d57e39a6ac97 Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 10:24:32 -0400 Subject: [PATCH 04/38] remove /home/user/.cache and all __pycache__ directories from image --- .docker/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index f8036a9d9..ba8122666 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -147,7 +147,9 @@ ENTRYPOINT ["tail", "-f", "/dev/null"] FROM firestarr-app-base AS firestarr-prod-base-env ARG USERNAME USER root -RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +RUN apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /home/user/.cache +RUN find -type d -name __pycache__ / | xargs -tI {} rm -rf {} USER ${USERNAME} FROM scratch AS firestarr-prod-base From 02fccd2a8e95c780b802be76759802dbbe1288e5 Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 10:37:49 -0400 Subject: [PATCH 05/38] remove -svc containers and switch name to firestarr-app-dev --- .docker/Dockerfile | 30 ++-------------------- docker-compose.yml | 62 +++++----------------------------------------- 2 files changed, 8 insertions(+), 84 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index ba8122666..d20c8ff47 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -114,7 +114,7 @@ RUN apt-get update --fix-missing \ USER ${USERNAME} -FROM firestarr-app-gcc AS firestarr-dev +FROM firestarr-app-gcc AS firestarr-app-dev ARG USERNAME ARG VERSION ENV VERSION=${VERSION} @@ -122,26 +122,12 @@ ENV TMPDIR=/tmp WORKDIR /appl/firestarr/ -FROM firestarr-dev AS firestarr-dev-svc -ARG USERNAME -ARG VERSION -ENV VERSION=${VERSION} -ENV TMPDIR=/tmp -# run as root so we can redirect to where docker logs will show progress -COPY .docker/crontab /etc/crontab -USER ${USERNAME} -SHELL ["/bin/bash"] -ENTRYPOINT ["sudo", "cron", "-f"] - - -FROM firestarr-dev AS firestarr-setup-gis +FROM firestarr-app-dev AS firestarr-setup-gis ARG USERNAME USER ${USERNAME} ENV TMPDIR=/tmp WORKDIR /appl/gis/ RUN echo cd /appl/gis >> /home/${USERNAME}/.bashrc -SHELL ["/bin/bash"] -ENTRYPOINT ["tail", "-f", "/dev/null"] FROM firestarr-app-base AS firestarr-prod-base-env @@ -184,15 +170,3 @@ ENV TMPDIR=/tmp USER ${USERNAME} WORKDIR /appl/firestarr ENTRYPOINT [ "/appl/firestarr/scripts/publish_after.sh" ] - -FROM firestarr-app AS firestarr-app-svc -ARG USERNAME -ARG VERSION -ENV VERSION=${VERSION} -ENV TMPDIR=/tmp -# run as root so we can redirect to where docker logs will show progress -COPY .docker/crontab /etc/crontab -RUN sed -i 's/CRONJOB_RUN=.*/CRONJOB_RUN=1/g' /appl/config -WORKDIR /appl/firestarr -USER ${USERNAME} -ENTRYPOINT ["sudo", "cron", "-f"] diff --git a/docker-compose.yml b/docker-compose.yml index f91eaf0be..ef52605c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: USER_ID: ${USER_ID} tags: - firestarr-setup-gis:${VERSION} + entrypoint: tail -f /dev/null volumes: - /etc/ssl/certs:/etc/ssl/certs - type: bind @@ -24,50 +25,19 @@ services: env_file: - .env - firestarr-dev: - image: firestarr-dev + firestarr-app-dev: + image: firestarr-app-dev build: context: . - target: firestarr-dev + target: firestarr-app-dev dockerfile: .docker/Dockerfile args: VERSION: ${VERSION} USERNAME: ${USERNAME} USER_ID: ${USER_ID} tags: - - firestarr-dev:${VERSION} - security_opt: - - seccomp:unconfined - volumes: - - /etc/ssl/certs:/etc/ssl/certs - - type: bind - source: ./config - target: /appl/config - - data:/appl/data - - sims:/appl/data/sims - - py:/appl/firestarr/src/py - - type: bind - source: ./firestarr - target: /appl/firestarr - restart: on-failure - cap_add: - - SYS_PTRACE - - SYS_ADMIN - env_file: - - .env - - firestarr-dev-svc: - image: firestarr-dev-svc - build: - context: . - target: firestarr-dev-svc - dockerfile: .docker/Dockerfile - args: - VERSION: ${VERSION} - USERNAME: ${USERNAME} - USER_ID: ${USER_ID} - tags: - - firestarr-dev-svc:${VERSION} + - firestarr-app-dev:${VERSION} + entrypoint: tail -f /dev/null security_opt: - seccomp:unconfined volumes: @@ -107,26 +77,6 @@ services: env_file: - .env - firestarr-app-svc: - image: firestarr-app-svc - build: - context: . - target: firestarr-app-svc - dockerfile: .docker/Dockerfile - args: - VERSION: ${VERSION} - USERNAME: ${USERNAME} - USER_ID: ${USER_ID} - tags: - - firestarr-app-svc:${VERSION} - volumes: - - /etc/ssl/certs:/etc/ssl/certs - - data:/appl/data - - sims:/appl/data/sims - env_file: - - .env - restart: always - firestarr: image: firestarr build: From 8b9ac191381656acf389c0a79d57b2b7efa57d01 Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 10:56:29 -0400 Subject: [PATCH 06/38] remove unused crontab from old -svc containers --- .docker/crontab | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .docker/crontab diff --git a/.docker/crontab b/.docker/crontab deleted file mode 100644 index 3dea6c88a..000000000 --- a/.docker/crontab +++ /dev/null @@ -1,11 +0,0 @@ -# # don't try publishing in scheduled lock_run.sh because check_and_publish.sh will do it -# */5 * * * * root sudo -u user bash -c 'export IS_CRONJOB=1; /appl/firestarr/scripts/force_run.sh --no-merge --no-publish | tee -a /appl/data/logs/lock_run.log' -# # really don't like this but prevent deadlocks for now by killing things so they always run again -# */31 * * * * root bash -c 'killall lock_run.sh python force_run.sh & rm /appl/data/update.lock | tee -a /appl/data/logs/killall.log' -# * * * * * root sudo -u user bash -c 'date | tee -a /appl/data/test_cron' -# */13 * * * * root sudo -u user bash -c 'export IS_CRONJOB=1; /appl/firestarr/scripts/lock_publish.sh | tee -a /appl/data/logs/check_and_publish.log' -# * */20 * * * root sudo -u user bash -c '/appl/firestarr/scripts/archive_sims.sh | tee -a /appl/logs/archive.log' -# # make sure we turn cronjobs back on overnight in case we forgot while working on things -# * 0 * * * root sudo -u user bash -c 'sed -i "s/CRONJOB_RUN=.*/CRONJOB_RUN=1/g" /appl/data/config' -# # try merging every half hour but let things run like before in the morning first -# */30 0-5,12-23 * * * root sudo -u user bash -c 'export IS_CRONJOB=1; /appl/firestarr/scripts/lock_merge.sh | tee -a /appl/data/logs/merge_inputs.log' From df83d2e1b3f407135d5ada9524c42f7f80aca67b Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 10:40:08 -0400 Subject: [PATCH 07/38] add firestarr-dev container for c++ dev --- .docker/Dockerfile | 15 +++++++++++++++ docker-compose.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index d20c8ff47..a5c752289 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -29,6 +29,21 @@ RUN apt-get update --fix-missing \ cmake gcc g++ make +FROM gcc-cmake-with-libs AS firestarr-dev +ARG USERNAME +ARG VERSION +ENV VERSION=${VERSION} +RUN apt-get update --fix-missing \ + && apt-get install -y --no-install-recommends \ + git time nano less \ + libtiff-dev libgeotiff-dev \ + gdb valgrind libdwarf-dev libelf-dev libdw-dev linux-perf clang-format \ + cmake gcc g++ make +RUN git config --global core.editor "nano" +WORKDIR /appl/firestarr +USER ${USERNAME} + + FROM gcc-cmake-with-libs AS firestarr-build ARG USERNAME ARG VERSION diff --git a/docker-compose.yml b/docker-compose.yml index ef52605c8..b264fd13e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -98,6 +98,34 @@ services: env_file: - .env + firestarr-dev: + image: firestarr-dev + build: + context: . + target: firestarr-dev + dockerfile: .docker/Dockerfile + args: + VERSION: ${VERSION} + USERNAME: ${USERNAME} + USER_ID: ${USER_ID} + tags: + - firestarr-dev:${VERSION} + entrypoint: tail -f /dev/null + security_opt: + - seccomp:unconfined + volumes: + - /etc/ssl/certs:/etc/ssl/certs + - type: bind + source: ./firestarr + target: /appl/firestarr + - data:/appl/data + - sims:/appl/data/sims + cap_add: + - SYS_PTRACE + - SYS_ADMIN + env_file: + - .env + volumes: data: # symlink to your actual directory if you don't want it in the project folder From 55ada7b89af717065601777b73f536221d1cf999 Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 16:04:38 -0400 Subject: [PATCH 08/38] move files out of ./firestarr --- .docker/Dockerfile | 4 ++-- .dockerignore | 4 ++-- firestarr/bounds.geojson => bounds.geojson | 0 docker-compose.yml | 17 +++++++++++++++++ firestarr/data/.placeholder | 0 {firestarr => gis/symbology}/col.txt | 0 {firestarr => other}/dummy.asc | 0 {firestarr => other}/make_hist.R | 0 {firestarr/scripts => scripts}/archive_sims.sh | 0 .../scripts => scripts}/calc_all_times.sh | 0 .../scripts => scripts}/calc_count_sims.sh | 0 .../scripts => scripts}/calc_done_times.sh | 0 {firestarr/scripts => scripts}/calc_max_time.sh | 0 .../scripts => scripts}/calc_running_times.sh | 0 {firestarr/scripts => scripts}/calc_stats.sh | 0 {firestarr/scripts => scripts}/calc_time.sh | 0 {firestarr/scripts => scripts}/calc_wx.sh | 0 {firestarr/scripts => scripts}/calc_wx_stats.sh | 0 .../scripts => scripts}/check_and_publish.sh | 0 {firestarr/scripts => scripts}/check_wx.sh | 0 .../scripts => scripts}/find_no_perims.sh | 0 {firestarr/scripts => scripts}/fix_file.sh | 0 {firestarr/scripts => scripts}/force_run.sh | 0 {firestarr/scripts => scripts}/lock_merge.sh | 0 {firestarr/scripts => scripts}/lock_publish.sh | 0 {firestarr/scripts => scripts}/lock_run.sh | 0 {firestarr/scripts => scripts}/merge_folders.sh | 0 {firestarr/scripts => scripts}/merge_inputs.sh | 0 {firestarr/scripts => scripts}/pkg.sh | 0 {firestarr/scripts => scripts}/publish_after.sh | 0 .../scripts => scripts}/publish_geoserver.sh | 0 {firestarr/scripts => scripts}/show_running.sh | 0 {firestarr/scripts => scripts}/show_wx_stats.sh | 0 {firestarr/scripts => scripts}/update.sh | 0 .../scripts => scripts}/with_lock_publish.sh | 0 .../scripts => scripts}/with_lock_update.sh | 0 src/py/firestarr/common.py | 2 +- src/py/firestarr/make_bounds.py | 2 +- {firestarr/test => test}/wx.csv | 0 {firestarr/test => test}/wx_hourly_in.csv | 0 {firestarr/validate => validate}/calc_fbp.R | 0 .../validate => validate}/example/fire.geojson | 0 .../validate => validate}/example/fuel.lut | 0 .../validate => validate}/example/settings.ini | 0 {firestarr/validate => validate}/example/sim.sh | 0 {firestarr/validate => validate}/example/wx.csv | 0 46 files changed, 23 insertions(+), 6 deletions(-) rename firestarr/bounds.geojson => bounds.geojson (100%) delete mode 100644 firestarr/data/.placeholder rename {firestarr => gis/symbology}/col.txt (100%) rename {firestarr => other}/dummy.asc (100%) rename {firestarr => other}/make_hist.R (100%) rename {firestarr/scripts => scripts}/archive_sims.sh (100%) rename {firestarr/scripts => scripts}/calc_all_times.sh (100%) rename {firestarr/scripts => scripts}/calc_count_sims.sh (100%) rename {firestarr/scripts => scripts}/calc_done_times.sh (100%) rename {firestarr/scripts => scripts}/calc_max_time.sh (100%) rename {firestarr/scripts => scripts}/calc_running_times.sh (100%) rename {firestarr/scripts => scripts}/calc_stats.sh (100%) rename {firestarr/scripts => scripts}/calc_time.sh (100%) rename {firestarr/scripts => scripts}/calc_wx.sh (100%) rename {firestarr/scripts => scripts}/calc_wx_stats.sh (100%) rename {firestarr/scripts => scripts}/check_and_publish.sh (100%) rename {firestarr/scripts => scripts}/check_wx.sh (100%) rename {firestarr/scripts => scripts}/find_no_perims.sh (100%) rename {firestarr/scripts => scripts}/fix_file.sh (100%) rename {firestarr/scripts => scripts}/force_run.sh (100%) rename {firestarr/scripts => scripts}/lock_merge.sh (100%) rename {firestarr/scripts => scripts}/lock_publish.sh (100%) rename {firestarr/scripts => scripts}/lock_run.sh (100%) rename {firestarr/scripts => scripts}/merge_folders.sh (100%) rename {firestarr/scripts => scripts}/merge_inputs.sh (100%) rename {firestarr/scripts => scripts}/pkg.sh (100%) rename {firestarr/scripts => scripts}/publish_after.sh (100%) rename {firestarr/scripts => scripts}/publish_geoserver.sh (100%) rename {firestarr/scripts => scripts}/show_running.sh (100%) rename {firestarr/scripts => scripts}/show_wx_stats.sh (100%) rename {firestarr/scripts => scripts}/update.sh (100%) rename {firestarr/scripts => scripts}/with_lock_publish.sh (100%) rename {firestarr/scripts => scripts}/with_lock_update.sh (100%) rename {firestarr/test => test}/wx.csv (100%) rename {firestarr/test => test}/wx_hourly_in.csv (100%) rename {firestarr/validate => validate}/calc_fbp.R (100%) rename {firestarr/validate => validate}/example/fire.geojson (100%) rename {firestarr/validate => validate}/example/fuel.lut (100%) rename {firestarr/validate => validate}/example/settings.ini (100%) rename {firestarr/validate => validate}/example/sim.sh (100%) rename {firestarr/validate => validate}/example/wx.csv (100%) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index a5c752289..85772e06e 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -165,10 +165,10 @@ ENV TMPDIR=/tmp WORKDIR /appl/firestarr COPY --chown=${USERNAME}:${USERNAME} ./config /appl/ COPY --chown=${USERNAME}:${USERNAME} --from=firestarr-build /appl/firestarr/firestarr . -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/bounds.geojson /appl/firestarr/ +COPY --chown=${USERNAME}:${USERNAME} ./bounds.geojson /appl/ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/fuel.lut /appl/firestarr/ COPY --chown=${USERNAME}:${USERNAME} ./firestarr/settings.ini /appl/firestarr/ -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/scripts /appl/firestarr/scripts/ +COPY --chown=${USERNAME}:${USERNAME} ./scripts/ /appl/firestarr/scripts/ COPY --chown=${USERNAME}:${USERNAME} ./src/py/firestarr/ /appl/firestarr/src/py/firestarr/ WORKDIR /appl/firestarr/src/py/cffdrs-ng COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/NG_FWI.py . diff --git a/.dockerignore b/.dockerignore index b41ff4baf..6ad6df216 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,8 +7,8 @@ !**/src !firestarr/CMakeLists.txt !firestarr/fuel.lut -!firestarr/bounds.geojson -!firestarr/scripts/*.sh +!bounds.geojson +!**/scripts/*.sh !firestarr/settings.ini !./config diff --git a/firestarr/bounds.geojson b/bounds.geojson similarity index 100% rename from firestarr/bounds.geojson rename to bounds.geojson diff --git a/docker-compose.yml b/docker-compose.yml index b264fd13e..af03249b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,9 +48,14 @@ services: - data:/appl/data - sims:/appl/data/sims - py:/appl/firestarr/src/py + - scripts:/appl/firestarr/scripts + - cppscripts:/appl/cppscripts - type: bind source: ./firestarr target: /appl/firestarr + - type: bind + source: ./bounds.geojson + target: /appl/bounds.geojson restart: on-failure cap_add: - SYS_PTRACE @@ -147,3 +152,15 @@ volumes: type: none o: bind device: ./src/py + cppscripts: + driver: local + driver_opts: + type: none + o: bind + device: ./firestarr/scripts + scripts: + driver: local + driver_opts: + type: none + o: bind + device: ./scripts diff --git a/firestarr/data/.placeholder b/firestarr/data/.placeholder deleted file mode 100644 index e69de29bb..000000000 diff --git a/firestarr/col.txt b/gis/symbology/col.txt similarity index 100% rename from firestarr/col.txt rename to gis/symbology/col.txt diff --git a/firestarr/dummy.asc b/other/dummy.asc similarity index 100% rename from firestarr/dummy.asc rename to other/dummy.asc diff --git a/firestarr/make_hist.R b/other/make_hist.R similarity index 100% rename from firestarr/make_hist.R rename to other/make_hist.R diff --git a/firestarr/scripts/archive_sims.sh b/scripts/archive_sims.sh similarity index 100% rename from firestarr/scripts/archive_sims.sh rename to scripts/archive_sims.sh diff --git a/firestarr/scripts/calc_all_times.sh b/scripts/calc_all_times.sh similarity index 100% rename from firestarr/scripts/calc_all_times.sh rename to scripts/calc_all_times.sh diff --git a/firestarr/scripts/calc_count_sims.sh b/scripts/calc_count_sims.sh similarity index 100% rename from firestarr/scripts/calc_count_sims.sh rename to scripts/calc_count_sims.sh diff --git a/firestarr/scripts/calc_done_times.sh b/scripts/calc_done_times.sh similarity index 100% rename from firestarr/scripts/calc_done_times.sh rename to scripts/calc_done_times.sh diff --git a/firestarr/scripts/calc_max_time.sh b/scripts/calc_max_time.sh similarity index 100% rename from firestarr/scripts/calc_max_time.sh rename to scripts/calc_max_time.sh diff --git a/firestarr/scripts/calc_running_times.sh b/scripts/calc_running_times.sh similarity index 100% rename from firestarr/scripts/calc_running_times.sh rename to scripts/calc_running_times.sh diff --git a/firestarr/scripts/calc_stats.sh b/scripts/calc_stats.sh similarity index 100% rename from firestarr/scripts/calc_stats.sh rename to scripts/calc_stats.sh diff --git a/firestarr/scripts/calc_time.sh b/scripts/calc_time.sh similarity index 100% rename from firestarr/scripts/calc_time.sh rename to scripts/calc_time.sh diff --git a/firestarr/scripts/calc_wx.sh b/scripts/calc_wx.sh similarity index 100% rename from firestarr/scripts/calc_wx.sh rename to scripts/calc_wx.sh diff --git a/firestarr/scripts/calc_wx_stats.sh b/scripts/calc_wx_stats.sh similarity index 100% rename from firestarr/scripts/calc_wx_stats.sh rename to scripts/calc_wx_stats.sh diff --git a/firestarr/scripts/check_and_publish.sh b/scripts/check_and_publish.sh similarity index 100% rename from firestarr/scripts/check_and_publish.sh rename to scripts/check_and_publish.sh diff --git a/firestarr/scripts/check_wx.sh b/scripts/check_wx.sh similarity index 100% rename from firestarr/scripts/check_wx.sh rename to scripts/check_wx.sh diff --git a/firestarr/scripts/find_no_perims.sh b/scripts/find_no_perims.sh similarity index 100% rename from firestarr/scripts/find_no_perims.sh rename to scripts/find_no_perims.sh diff --git a/firestarr/scripts/fix_file.sh b/scripts/fix_file.sh similarity index 100% rename from firestarr/scripts/fix_file.sh rename to scripts/fix_file.sh diff --git a/firestarr/scripts/force_run.sh b/scripts/force_run.sh similarity index 100% rename from firestarr/scripts/force_run.sh rename to scripts/force_run.sh diff --git a/firestarr/scripts/lock_merge.sh b/scripts/lock_merge.sh similarity index 100% rename from firestarr/scripts/lock_merge.sh rename to scripts/lock_merge.sh diff --git a/firestarr/scripts/lock_publish.sh b/scripts/lock_publish.sh similarity index 100% rename from firestarr/scripts/lock_publish.sh rename to scripts/lock_publish.sh diff --git a/firestarr/scripts/lock_run.sh b/scripts/lock_run.sh similarity index 100% rename from firestarr/scripts/lock_run.sh rename to scripts/lock_run.sh diff --git a/firestarr/scripts/merge_folders.sh b/scripts/merge_folders.sh similarity index 100% rename from firestarr/scripts/merge_folders.sh rename to scripts/merge_folders.sh diff --git a/firestarr/scripts/merge_inputs.sh b/scripts/merge_inputs.sh similarity index 100% rename from firestarr/scripts/merge_inputs.sh rename to scripts/merge_inputs.sh diff --git a/firestarr/scripts/pkg.sh b/scripts/pkg.sh similarity index 100% rename from firestarr/scripts/pkg.sh rename to scripts/pkg.sh diff --git a/firestarr/scripts/publish_after.sh b/scripts/publish_after.sh similarity index 100% rename from firestarr/scripts/publish_after.sh rename to scripts/publish_after.sh diff --git a/firestarr/scripts/publish_geoserver.sh b/scripts/publish_geoserver.sh similarity index 100% rename from firestarr/scripts/publish_geoserver.sh rename to scripts/publish_geoserver.sh diff --git a/firestarr/scripts/show_running.sh b/scripts/show_running.sh similarity index 100% rename from firestarr/scripts/show_running.sh rename to scripts/show_running.sh diff --git a/firestarr/scripts/show_wx_stats.sh b/scripts/show_wx_stats.sh similarity index 100% rename from firestarr/scripts/show_wx_stats.sh rename to scripts/show_wx_stats.sh diff --git a/firestarr/scripts/update.sh b/scripts/update.sh similarity index 100% rename from firestarr/scripts/update.sh rename to scripts/update.sh diff --git a/firestarr/scripts/with_lock_publish.sh b/scripts/with_lock_publish.sh similarity index 100% rename from firestarr/scripts/with_lock_publish.sh rename to scripts/with_lock_publish.sh diff --git a/firestarr/scripts/with_lock_update.sh b/scripts/with_lock_update.sh similarity index 100% rename from firestarr/scripts/with_lock_update.sh rename to scripts/with_lock_update.sh diff --git a/src/py/firestarr/common.py b/src/py/firestarr/common.py index 5076cdb0e..cd87ddb07 100644 --- a/src/py/firestarr/common.py +++ b/src/py/firestarr/common.py @@ -163,7 +163,7 @@ def ensure_dir(dir): SECONDS_PER_MINUTE = 60 SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE -DEFAULT_BOUNDS = "bounds.geojson" +DEFAULT_BOUNDS = "/appl/bounds.geojson" FILE_LOCK_PUBLISH = os.path.join(DIR_OUTPUT, "publish") FILE_LOCK_PREPUBLISH = os.path.join(DIR_OUTPUT, "prepublish") diff --git a/src/py/firestarr/make_bounds.py b/src/py/firestarr/make_bounds.py index 0b18f2140..09b19f289 100644 --- a/src/py/firestarr/make_bounds.py +++ b/src/py/firestarr/make_bounds.py @@ -15,7 +15,7 @@ URL_PARKS = "https://clss.nrcan-rncan.gc.ca/data-donnees/nplb_llpn/CLAB_CA_2023-09-08.zip" centroids_canada = None DIR_BOUNDS = os.path.join(DIR_GENERATED, "bounds") -FILE_BOUNDS = "bounds.geojson" +FILE_BOUNDS = "/appl/bounds.geojson" COLUMN_ENGLISH_NAME = "PRENAME" diff --git a/firestarr/test/wx.csv b/test/wx.csv similarity index 100% rename from firestarr/test/wx.csv rename to test/wx.csv diff --git a/firestarr/test/wx_hourly_in.csv b/test/wx_hourly_in.csv similarity index 100% rename from firestarr/test/wx_hourly_in.csv rename to test/wx_hourly_in.csv diff --git a/firestarr/validate/calc_fbp.R b/validate/calc_fbp.R similarity index 100% rename from firestarr/validate/calc_fbp.R rename to validate/calc_fbp.R diff --git a/firestarr/validate/example/fire.geojson b/validate/example/fire.geojson similarity index 100% rename from firestarr/validate/example/fire.geojson rename to validate/example/fire.geojson diff --git a/firestarr/validate/example/fuel.lut b/validate/example/fuel.lut similarity index 100% rename from firestarr/validate/example/fuel.lut rename to validate/example/fuel.lut diff --git a/firestarr/validate/example/settings.ini b/validate/example/settings.ini similarity index 100% rename from firestarr/validate/example/settings.ini rename to validate/example/settings.ini diff --git a/firestarr/validate/example/sim.sh b/validate/example/sim.sh similarity index 100% rename from firestarr/validate/example/sim.sh rename to validate/example/sim.sh diff --git a/firestarr/validate/example/wx.csv b/validate/example/wx.csv similarity index 100% rename from firestarr/validate/example/wx.csv rename to validate/example/wx.csv From ebd6cb4054120b014dcbd83c8dc6f6da83dd98d6 Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 10:14:30 -0400 Subject: [PATCH 09/38] v0.9.5.5 --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 0f86c07ae..bc6755150 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -VERSION=0.9.5.4 +VERSION=0.9.5.5 USERNAME=user USER_ID=1000 From 59eedad8df016c356d9d309ed3f5ccbcf7fbc7f4 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Mon, 27 Apr 2026 12:35:37 -0400 Subject: [PATCH 10/38] add code changes from live azure container --- src/py/firestarr/azurebatch.py | 67 +++++++++++++++++++++++++++------ src/py/firestarr/common.py | 10 ++++- src/py/firestarr/make_bounds.py | 2 +- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/py/firestarr/azurebatch.py b/src/py/firestarr/azurebatch.py index 6d88431a3..160d8209a 100644 --- a/src/py/firestarr/azurebatch.py +++ b/src/py/firestarr/azurebatch.py @@ -7,6 +7,7 @@ import azure.batch as batch import azure.batch.batch_auth as batchauth import azure.batch.models as batchmodels +import azure.common.credentials as batchcredentials import pandas as pd from azurebatch_helpers import ( STANDARD_ERROR_FILE_NAME, @@ -35,6 +36,11 @@ _BATCH_ACCOUNT_NAME = CONFIG.get("BATCH_ACCOUNT_NAME") _BATCH_ACCOUNT_KEY = CONFIG.get("BATCH_ACCOUNT_KEY") _BATCH_POOL_ID = CONFIG.get("BATCH_POOL_ID") +_BATCH_TENANT_ID = CONFIG.get("BATCH_TENANT_ID") +_BATCH_CLIENT_ID = CONFIG.get("BATCH_CLIENT_ID") +_BATCH_SECRET = CONFIG.get("BATCH_SECRET") +_BATCH_SUBNET = CONFIG.get("BATCH_SUBNET") +_BATCH_RESOURCE = "https://batch.core.windows.net/" _STORAGE_ACCOUNT_NAME = CONFIG.get("STORAGE_ACCOUNT_NAME") _STORAGE_KEY = CONFIG.get("STORAGE_KEY") @@ -68,6 +74,21 @@ def get_container_registries(): ] +def get_network_configuration(): + if not _BATCH_SUBNET: + raise RuntimeError("No value specified for BATCH_SUBNET") + return batch.models.NetworkConfiguration( + subnet_id=_BATCH_SUBNET, + dynamic_vnet_assignment_scope="none", + endpoint_configuration=None, + public_ip_address_configuration=batch.models.PublicIPAddressConfiguration( + provision="NoPublicIPAddresses", + ip_address_ids=None, + ), + enable_accelerated_networking=False, + ) + + _VM_CONFIGURATION = batchmodels.VirtualMachineConfiguration( image_reference=batchmodels.ImageReference( publisher="microsoft-dsvm", @@ -83,16 +104,18 @@ def get_container_registries(): container_registries=get_container_registries(), ), ) -# _POOL_VM_SIZE = "STANDARD_F72S_V2" -# _POOL_VM_SIZE = "STANDARD_F32S_V2" -# only running 21 streams for now so don't use 32 cores -_POOL_VM_SIZE = "STANDARD_F16S_V2" -# # generally gets stuck on a few scenarios so scale way back until single-core performance improves for now -# _POOL_VM_SIZE = "STANDARD_F8S_V2" -# _POOL_VM_SIZE = "STANDARD_F4S_V2" -_MIN_NODES = 0 -# _MIN_NODES = 1 -_MAX_NODES = 200 + +_DEFAULT_MIN_NODES = 0 +_DEFAULT_MAX_NODES = 200 +_DEFAULT_VM_CORES = 16 +_DEFAULT_MAX_CORES = _DEFAULT_MAX_NODES * _DEFAULT_VM_CORES +_VM_CORES = int(CONFIG.get("BATCH_VM_CORES") or _DEFAULT_VM_CORES) +_MAX_CORES = int(CONFIG.get("BATCH_MAX_CORES") or _DEFAULT_MAX_CORES) +# if BATCH_MAX_NODES not specified then calculate it +_MAX_NODES = int(CONFIG.get("BATCH_MAX_NODES") or _MAX_CORES / _VM_CORES) +_MIN_NODES = min(_MAX_NODES, int(CONFIG.get("BATCH_MIN_CORES") or _DEFAULT_MIN_NODES)) + +_POOL_VM_SIZE = f"STANDARD_F{_VM_CORES}S_V2" _USE_LOW_PRIORITY = True @@ -255,6 +278,8 @@ def create_container_pool(pool_id=_BATCH_POOL_ID, force=False, client=None): id=pool_id, virtual_machine_configuration=_VM_CONFIGURATION, vm_size=_POOL_VM_SIZE, + # targetNodeCommunicationMode="simplified", + network_configuration=get_network_configuration(), # target_dedicated_nodes=1, enable_auto_scale=True, auto_scale_formula=create_autoscale_formula(), @@ -682,7 +707,9 @@ def wait_for_tasks_to_complete(job_id, client=None): def have_batch_config(): - return _BATCH_ACCOUNT_NAME and _BATCH_ACCOUNT_KEY + return bool( + (_BATCH_ACCOUNT_NAME and _BATCH_ACCOUNT_KEY) or (_BATCH_TENANT_ID and _BATCH_CLIENT_ID and _BATCH_SECRET) + ) def get_batch_client(): @@ -690,8 +717,24 @@ def get_batch_client(): if not have_batch_config(): return None if CLIENT is None: + have_key = bool(_BATCH_ACCOUNT_NAME and _BATCH_ACCOUNT_KEY) + have_tenant = bool(_BATCH_TENANT_ID and _BATCH_CLIENT_ID and _BATCH_SECRET) + credentials = None + if have_key: + if have_tenant: + logging.warning("Have BATCH_ACCOUNT_KEY and BATCH_TENANT_ID so ignoring BATCH_ACCOUNT_KEY") + else: + credentials = batchauth.SharedKeyCredentials(_BATCH_ACCOUNT_NAME, _BATCH_ACCOUNT_KEY) + if have_tenant: + if have_key: + logging.warning("Have BATCH_ACCOUNT_KEY and BATCH_TENANT_ID so using BATCH_TENANT_ID") + credentials = batchcredentials.ServicePrincipalCredentials( + client_id=_BATCH_CLIENT_ID, secret=_BATCH_SECRET, tenant=_BATCH_TENANT_ID, resource=_BATCH_RESOURCE + ) + if not credentials: + raise RuntimeError("No credentials for azure batch") CLIENT = batch.BatchServiceClient( - batchauth.SharedKeyCredentials(_BATCH_ACCOUNT_NAME, _BATCH_ACCOUNT_KEY), + credentials, batch_url=_BATCH_ACCOUNT_URL, ) # HACK: diff --git a/src/py/firestarr/common.py b/src/py/firestarr/common.py index cd87ddb07..45a09f39a 100644 --- a/src/py/firestarr/common.py +++ b/src/py/firestarr/common.py @@ -163,7 +163,7 @@ def ensure_dir(dir): SECONDS_PER_MINUTE = 60 SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE -DEFAULT_BOUNDS = "/appl/bounds.geojson" +DEFAULT_BOUNDS = "bounds.geojson" FILE_LOCK_PUBLISH = os.path.join(DIR_OUTPUT, "publish") FILE_LOCK_PREPUBLISH = os.path.join(DIR_OUTPUT, "prepublish") @@ -269,6 +269,14 @@ def read_config(force=False): "BATCH_ACCOUNT_NAME", "BATCH_ACCOUNT_KEY", "BATCH_POOL_ID", + "BATCH_TENANT_ID", + "BATCH_CLIENT_ID", + "BATCH_SECRET", + "BATCH_SUBNET", + "BATCH_MIN_NODES", + "BATCH_MAX_NODES", + "BATCH_VM_CORES", + "BATCH_MAX_CORES", "SPOTWX_API_KEY", "SPOTWX_API_LIMIT", "STORAGE_ACCOUNT_NAME", diff --git a/src/py/firestarr/make_bounds.py b/src/py/firestarr/make_bounds.py index 09b19f289..0b18f2140 100644 --- a/src/py/firestarr/make_bounds.py +++ b/src/py/firestarr/make_bounds.py @@ -15,7 +15,7 @@ URL_PARKS = "https://clss.nrcan-rncan.gc.ca/data-donnees/nplb_llpn/CLAB_CA_2023-09-08.zip" centroids_canada = None DIR_BOUNDS = os.path.join(DIR_GENERATED, "bounds") -FILE_BOUNDS = "/appl/bounds.geojson" +FILE_BOUNDS = "bounds.geojson" COLUMN_ENGLISH_NAME = "PRENAME" From c4567afdd690ff3aae31b7fa4fc92f36ba5c9a4b Mon Sep 17 00:00:00 2001 From: Jordan Evens Date: Wed, 30 Jul 2025 12:41:13 -0400 Subject: [PATCH 11/38] move cpp code into submodule --- .gitmodules | 3 + README.md | 11 + firestarr | 1 + firestarr/.clang-format | 68 -- firestarr/CMakeLists.txt | 130 -- firestarr/firestarr.sln | 31 - firestarr/fuel.lut | 150 --- firestarr/scripts/build.sh | 13 - firestarr/scripts/mk_clean.sh | 17 - firestarr/scripts/mk_verbose.sh | 2 - firestarr/scripts/profile.sh | 16 - firestarr/settings.ini | 40 - firestarr/src/cpp/Cell.h | 343 ------ firestarr/src/cpp/CellPoints.cpp | 300 ----- firestarr/src/cpp/CellPoints.h | 190 --- firestarr/src/cpp/ConstantGrid.h | 465 ------- firestarr/src/cpp/ConstantWeather.h | 88 -- firestarr/src/cpp/Duff.cpp | 27 - firestarr/src/cpp/Duff.h | 248 ---- firestarr/src/cpp/Environment.cpp | 151 --- firestarr/src/cpp/Environment.h | 489 -------- firestarr/src/cpp/EnvironmentInfo.cpp | 69 -- firestarr/src/cpp/EnvironmentInfo.h | 127 -- firestarr/src/cpp/Event.h | 207 ---- firestarr/src/cpp/EventCompare.h | 34 - firestarr/src/cpp/FBP45.cpp | 105 -- firestarr/src/cpp/FBP45.h | 1562 ------------------------ firestarr/src/cpp/FWI.cpp | 763 ------------ firestarr/src/cpp/FWI.h | 550 --------- firestarr/src/cpp/FireSpread.cpp | 389 ------ firestarr/src/cpp/FireSpread.h | 388 ------ firestarr/src/cpp/FireWeather.cpp | 80 -- firestarr/src/cpp/FireWeather.h | 174 --- firestarr/src/cpp/FireWeatherDaily.cpp | 576 --------- firestarr/src/cpp/FireWeatherDaily.h | 47 - firestarr/src/cpp/FuelLookup.cpp | 808 ------------ firestarr/src/cpp/FuelLookup.h | 140 --- firestarr/src/cpp/FuelType.cpp | 69 -- firestarr/src/cpp/FuelType.h | 532 -------- firestarr/src/cpp/Grid.cpp | 273 ----- firestarr/src/cpp/Grid.h | 881 ------------- firestarr/src/cpp/GridMap.h | 333 ----- firestarr/src/cpp/Index.h | 196 --- firestarr/src/cpp/InnerPos.cpp | 11 - firestarr/src/cpp/InnerPos.h | 90 -- firestarr/src/cpp/IntensityMap.cpp | 203 --- firestarr/src/cpp/IntensityMap.h | 158 --- firestarr/src/cpp/Iteration.cpp | 104 -- firestarr/src/cpp/Iteration.h | 104 -- firestarr/src/cpp/Location.cpp | 25 - firestarr/src/cpp/Location.h | 312 ----- firestarr/src/cpp/Log.cpp | 364 ------ firestarr/src/cpp/Log.h | 269 ---- firestarr/src/cpp/LogPoints.cpp | 9 - firestarr/src/cpp/LogPoints.h | 9 - firestarr/src/cpp/LookupTable.h | 72 -- firestarr/src/cpp/Main.cpp | 578 --------- firestarr/src/cpp/MergeIterator.cpp | 9 - firestarr/src/cpp/MergeIterator.h | 99 -- firestarr/src/cpp/Model.cpp | 1285 ------------------- firestarr/src/cpp/Model.h | 558 --------- firestarr/src/cpp/Observer.cpp | 10 - firestarr/src/cpp/Observer.h | 14 - firestarr/src/cpp/Perimeter.cpp | 147 --- firestarr/src/cpp/Perimeter.h | 83 -- firestarr/src/cpp/Point.h | 73 -- firestarr/src/cpp/ProbabilityMap.cpp | 243 ---- firestarr/src/cpp/ProbabilityMap.h | 175 --- firestarr/src/cpp/SafeVector.cpp | 65 - firestarr/src/cpp/SafeVector.h | 76 -- firestarr/src/cpp/Scenario.cpp | 1034 ---------------- firestarr/src/cpp/Scenario.h | 616 ---------- firestarr/src/cpp/Settings.cpp | 683 ----------- firestarr/src/cpp/Settings.h | 169 --- firestarr/src/cpp/SpreadAlgorithm.cpp | 360 ------ firestarr/src/cpp/SpreadAlgorithm.h | 73 -- firestarr/src/cpp/StandardFuel.cpp | 4 - firestarr/src/cpp/StandardFuel.h | 278 ----- firestarr/src/cpp/StartPoint.cpp | 123 -- firestarr/src/cpp/StartPoint.h | 72 -- firestarr/src/cpp/Startup.cpp | 30 - firestarr/src/cpp/Startup.h | 179 --- firestarr/src/cpp/Statistics.h | 359 ------ firestarr/src/cpp/Test.cpp | 537 -------- firestarr/src/cpp/Test.h | 37 - firestarr/src/cpp/TimeUtil.cpp | 15 - firestarr/src/cpp/TimeUtil.h | 14 - firestarr/src/cpp/Trim.cpp | 45 - firestarr/src/cpp/Trim.h | 44 - firestarr/src/cpp/UTM.cpp | 112 -- firestarr/src/cpp/UTM.h | 42 - firestarr/src/cpp/Util.cpp | 290 ----- firestarr/src/cpp/Util.h | 522 -------- firestarr/src/cpp/Weather.cpp | 23 - firestarr/src/cpp/Weather.h | 399 ------ firestarr/src/cpp/debug_settings.cpp | 67 - firestarr/src/cpp/debug_settings.h | 42 - firestarr/src/cpp/stdafx.cpp | 4 - firestarr/src/cpp/stdafx.h | 342 ------ firestarr/src/cpp/tbd.vcxproj | 255 ---- firestarr/src/cpp/tbd.vcxproj.filters | 255 ---- firestarr/src/cpp/tbd.vcxproj.user | 4 - firestarr/src/cpp/unstable.cpp | 16 - firestarr/src/cpp/unstable.h | 22 - firestarr/src/cpp/version.h | 5 - firestarr/vcpkg-configuration.json | 14 - firestarr/vcpkg.json | 7 - 107 files changed, 15 insertions(+), 23309 deletions(-) create mode 160000 firestarr delete mode 100644 firestarr/.clang-format delete mode 100644 firestarr/CMakeLists.txt delete mode 100644 firestarr/firestarr.sln delete mode 100644 firestarr/fuel.lut delete mode 100755 firestarr/scripts/build.sh delete mode 100755 firestarr/scripts/mk_clean.sh delete mode 100755 firestarr/scripts/mk_verbose.sh delete mode 100755 firestarr/scripts/profile.sh delete mode 100644 firestarr/settings.ini delete mode 100644 firestarr/src/cpp/Cell.h delete mode 100644 firestarr/src/cpp/CellPoints.cpp delete mode 100644 firestarr/src/cpp/CellPoints.h delete mode 100644 firestarr/src/cpp/ConstantGrid.h delete mode 100644 firestarr/src/cpp/ConstantWeather.h delete mode 100644 firestarr/src/cpp/Duff.cpp delete mode 100644 firestarr/src/cpp/Duff.h delete mode 100644 firestarr/src/cpp/Environment.cpp delete mode 100644 firestarr/src/cpp/Environment.h delete mode 100644 firestarr/src/cpp/EnvironmentInfo.cpp delete mode 100644 firestarr/src/cpp/EnvironmentInfo.h delete mode 100644 firestarr/src/cpp/Event.h delete mode 100644 firestarr/src/cpp/EventCompare.h delete mode 100644 firestarr/src/cpp/FBP45.cpp delete mode 100644 firestarr/src/cpp/FBP45.h delete mode 100644 firestarr/src/cpp/FWI.cpp delete mode 100644 firestarr/src/cpp/FWI.h delete mode 100644 firestarr/src/cpp/FireSpread.cpp delete mode 100644 firestarr/src/cpp/FireSpread.h delete mode 100644 firestarr/src/cpp/FireWeather.cpp delete mode 100644 firestarr/src/cpp/FireWeather.h delete mode 100644 firestarr/src/cpp/FireWeatherDaily.cpp delete mode 100644 firestarr/src/cpp/FireWeatherDaily.h delete mode 100644 firestarr/src/cpp/FuelLookup.cpp delete mode 100644 firestarr/src/cpp/FuelLookup.h delete mode 100644 firestarr/src/cpp/FuelType.cpp delete mode 100644 firestarr/src/cpp/FuelType.h delete mode 100644 firestarr/src/cpp/Grid.cpp delete mode 100644 firestarr/src/cpp/Grid.h delete mode 100644 firestarr/src/cpp/GridMap.h delete mode 100644 firestarr/src/cpp/Index.h delete mode 100644 firestarr/src/cpp/InnerPos.cpp delete mode 100644 firestarr/src/cpp/InnerPos.h delete mode 100644 firestarr/src/cpp/IntensityMap.cpp delete mode 100644 firestarr/src/cpp/IntensityMap.h delete mode 100644 firestarr/src/cpp/Iteration.cpp delete mode 100644 firestarr/src/cpp/Iteration.h delete mode 100644 firestarr/src/cpp/Location.cpp delete mode 100644 firestarr/src/cpp/Location.h delete mode 100644 firestarr/src/cpp/Log.cpp delete mode 100644 firestarr/src/cpp/Log.h delete mode 100644 firestarr/src/cpp/LogPoints.cpp delete mode 100644 firestarr/src/cpp/LogPoints.h delete mode 100644 firestarr/src/cpp/LookupTable.h delete mode 100644 firestarr/src/cpp/Main.cpp delete mode 100644 firestarr/src/cpp/MergeIterator.cpp delete mode 100644 firestarr/src/cpp/MergeIterator.h delete mode 100644 firestarr/src/cpp/Model.cpp delete mode 100644 firestarr/src/cpp/Model.h delete mode 100644 firestarr/src/cpp/Observer.cpp delete mode 100644 firestarr/src/cpp/Observer.h delete mode 100644 firestarr/src/cpp/Perimeter.cpp delete mode 100644 firestarr/src/cpp/Perimeter.h delete mode 100644 firestarr/src/cpp/Point.h delete mode 100644 firestarr/src/cpp/ProbabilityMap.cpp delete mode 100644 firestarr/src/cpp/ProbabilityMap.h delete mode 100644 firestarr/src/cpp/SafeVector.cpp delete mode 100644 firestarr/src/cpp/SafeVector.h delete mode 100644 firestarr/src/cpp/Scenario.cpp delete mode 100644 firestarr/src/cpp/Scenario.h delete mode 100644 firestarr/src/cpp/Settings.cpp delete mode 100644 firestarr/src/cpp/Settings.h delete mode 100644 firestarr/src/cpp/SpreadAlgorithm.cpp delete mode 100644 firestarr/src/cpp/SpreadAlgorithm.h delete mode 100644 firestarr/src/cpp/StandardFuel.cpp delete mode 100644 firestarr/src/cpp/StandardFuel.h delete mode 100644 firestarr/src/cpp/StartPoint.cpp delete mode 100644 firestarr/src/cpp/StartPoint.h delete mode 100644 firestarr/src/cpp/Startup.cpp delete mode 100644 firestarr/src/cpp/Startup.h delete mode 100644 firestarr/src/cpp/Statistics.h delete mode 100644 firestarr/src/cpp/Test.cpp delete mode 100644 firestarr/src/cpp/Test.h delete mode 100644 firestarr/src/cpp/TimeUtil.cpp delete mode 100644 firestarr/src/cpp/TimeUtil.h delete mode 100644 firestarr/src/cpp/Trim.cpp delete mode 100644 firestarr/src/cpp/Trim.h delete mode 100644 firestarr/src/cpp/UTM.cpp delete mode 100644 firestarr/src/cpp/UTM.h delete mode 100644 firestarr/src/cpp/Util.cpp delete mode 100644 firestarr/src/cpp/Util.h delete mode 100644 firestarr/src/cpp/Weather.cpp delete mode 100644 firestarr/src/cpp/Weather.h delete mode 100644 firestarr/src/cpp/debug_settings.cpp delete mode 100644 firestarr/src/cpp/debug_settings.h delete mode 100644 firestarr/src/cpp/stdafx.cpp delete mode 100644 firestarr/src/cpp/stdafx.h delete mode 100644 firestarr/src/cpp/tbd.vcxproj delete mode 100644 firestarr/src/cpp/tbd.vcxproj.filters delete mode 100755 firestarr/src/cpp/tbd.vcxproj.user delete mode 100644 firestarr/src/cpp/unstable.cpp delete mode 100644 firestarr/src/cpp/unstable.h delete mode 100644 firestarr/src/cpp/version.h delete mode 100644 firestarr/vcpkg-configuration.json delete mode 100644 firestarr/vcpkg.json diff --git a/.gitmodules b/.gitmodules index c49b7da42..eb4de8826 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = src/py/cffdrs-ng url = https://github.com/jordan-evens/cffdrs-ng.git branch = main +[submodule "firestarr"] + path = firestarr + url = git@github.com:CWFMF/firestarr-cpp.git diff --git a/README.md b/README.md index 08f8b5db1..2e1c25ee2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +This is used to create containers for collecting & processing inputs/outputs of +FireSTARR. The current workflow is to either run the containers locally or in Azure, +and (optionally) publish the results to a geoserver WMS. + +For performance reasons, the version of FireSTARR used right now is stripped down to just +the code required to create the burn probability maps that are published, and it does +not contain many of the options from the version at https://github.com/CWFMF/FireSTARR/tree/575f6c064b12eae8716a221d25343398073f0c97z. + +Eventually things will be re-implemented in the submodule repo at +https://github.com/cwmfmf/firestarr-cpp. + # About FireSTARR ## Overview diff --git a/firestarr b/firestarr new file mode 160000 index 000000000..a4deb756c --- /dev/null +++ b/firestarr @@ -0,0 +1 @@ +Subproject commit a4deb756c8153cc683fce6bdf635a9bc3744c3b0 diff --git a/firestarr/.clang-format b/firestarr/.clang-format deleted file mode 100644 index 675810ee7..000000000 --- a/firestarr/.clang-format +++ /dev/null @@ -1,68 +0,0 @@ -# Format Style Options - Created with Clang Power Tools ---- -AlignAfterOpenBracket: Align -AlignOperands: AlignAfterOperator -AllowAllArgumentsOnNextLine: false -AllowAllConstructorInitializersOnNextLine: false -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortLambdasOnASingleLine: All -AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: Never -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: Yes -BasedOnStyle: Mozilla -BinPackArguments: false -BinPackParameters: false -BitFieldColonSpacing: After -BraceWrapping: - AfterCaseLabel: true - AfterClass: true - AfterControlStatement: true - AfterEnum: true - AfterFunction: true - AfterNamespace: true - AfterObjCDeclaration: true - AfterStruct: true - AfterUnion: true - AfterExternBlock: true - BeforeCatch: true - BeforeElse: true - IndentBraces: false - SplitEmptyFunction: true - SplitEmptyRecord: true - SplitEmptyNamespace: true - BeforeLambdaBody: false - BeforeWhile: true -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Custom -BreakBeforeInheritanceComma: true -BreakBeforeTernaryOperators: true -BreakConstructorInitializers: BeforeColon -BreakStringLiterals: false -ColumnLimit: 0 -CompactNamespaces: true -ConstructorInitializerAllOnOneLineOrOnePerLine: false -ContinuationIndentWidth: 2 -Cpp11BracedListStyle: true -DeriveLineEnding: false -EmptyLineBeforeAccessModifier: Never -ExperimentalAutoDetectBinPacking: false -IncludeBlocks: Regroup -IndentCaseBlocks: true -IndentCaseLabels: true -IndentWidth: 2 -IndentWrappedFunctionNames: true -PenaltyBreakAssignment: 2000 -PenaltyReturnTypeOnItsOwnLine: 100000 -PointerAlignment: Left -ReflowComments: true -SortIncludes: false -SortUsingDeclarations: false -SpaceAfterTemplateKeyword: true -SpaceInEmptyBlock: true -SpacesBeforeTrailingComments: 3 -Standard: c++20 -TabWidth: 2 -... diff --git a/firestarr/CMakeLists.txt b/firestarr/CMakeLists.txt deleted file mode 100644 index 2e1bb2784..000000000 --- a/firestarr/CMakeLists.txt +++ /dev/null @@ -1,130 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -if(WIN32) - set(BUILD_SHARED_LIBS OFF) - set(VCPKG_LIBRARY_LINKAGE static) - set(VCPKG_TARGET_TRIPLET x64-windows-static) - if("${VCPKG_ROOT}" STREQUAL "") - set(VCPKG_ROOT $ENV{VCPKG_ROOT}) - endif() - set(CMAKE_TOOLCHAIN_FILE - "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "Vcpkg toolchain file") - set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib") -endif() - -# Enable Hot Reload for MSVC compilers if supported. -if(POLICY CMP0141) - cmake_policy(SET CMP0141 NEW) - string(CONCAT MSVC_OPTIONS "$,$>" - ",$<$:EditAndContinue>," - "$<$:ProgramDatabase>>") - set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT - ${MSVC_OPTIONS}) -endif() - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -# HACK: architecture that works on azure batch and local machine -set(ARCH_PREFERRED "skylake-avx512") - -if(WIN32) -set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "Vcpkg toolchain file") -endif() - -project(firestarr) - -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) -endif() -message("CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") - -# set(CMAKE_CXX_FLAGS "-Wall -Wextra -std=c++20") -if (WIN32) - # HACK: use same settings for RelWitDebInfo since can't figure out how to work with vcpkg - add_compile_options( - "$<$:/O2;/MT>" - "$<$:/Os;/MT>" - "$<$:/Od;/MTd>") -else() - # HACK: if gcc supports ARCH_PREFERRED on this then use that else native - execute_process(COMMAND bash -c "(gcc -march=native -Q --help=target | grep ${ARCH_PREFERRED} > /dev/null && echo -n ${ARCH_PREFERRED}) || (echo -n 'native')" - OUTPUT_VARIABLE USE_ARCH - ERROR_QUIET) - set(CMAKE_CXX_FLAGS "-Wall -Wextra") - if (USE_ARCH) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=\"${USE_ARCH}\"") - # # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=\"${USE_ARCH}\"") - message("USE_ARCH = ${USE_ARCH}") - endif() - message("Disabling AVX512 because performance is worse and results are wildly different with it on") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-avx512f") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfatal-errors") - if(CMAKE_VERBOSE_MAKEFILE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopt-info-vec-all") - endif() - add_compile_options( - "$<$:-O3;>" - "$<$:-O0;-fno-omit-frame-pointer;-g;>" - ) -endif() -message("CMAKE_CXX_FLAGS = ${CMAKE_CXX_FLAGS}") - -add_definitions(-D_POSIX_C_SOURCE=200809L) - -# add version to version.cpp from environment variable so C++ can compile it in -set(VERSION $ENV{VERSION}) -if ("${VERSION}" STREQUAL "") - message(WARNING "VERSION IS NOT SET") - file(STRINGS ../.env CONFIG REGEX "^[ ]*[A-Za-z0-9_]+[ ]*=") - list(TRANSFORM CONFIG STRIP) - list(TRANSFORM CONFIG REPLACE "([^=]+)=[ ]*(.*)" "set(\\1 \"\\2\")\n") - message(${CONFIG}) - cmake_language(EVAL CODE ${CONFIG}) - message("Parsed config") -endif() -message("VERSION = ${VERSION}") -string(TIMESTAMP COMPILE_DATE "%Y-%m-%dT%H:%MZ" UTC) -message("COMPILE_DATE = ${COMPILE_DATE}") -set(VERSION_CODE "const char* VERSION = \"${VERSION}\";\nconst char* COMPILE_DATE = \"${COMPILE_DATE}\";\n") -if(EXISTS src/cpp/version.cpp) - file(READ src/cpp/version.cpp VERSION_CODE_OLD) -else() - set(VERSION_CODE_OLD "") -endif() - -if (NOT "${VERSION_CODE}" STREQUAL "${VERSION_CODE_OLD}") - file(WRITE src/cpp/version.cpp "${VERSION_CODE}") -endif() - -file(GLOB SOURCES src/cpp/*.cpp) -if(NOT WIN32) - set_source_files_properties(src/cpp/unstable.cpp PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS_RELEASE}") -endif() - -add_executable(${PROJECT_NAME} ${SOURCES}) -set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) -if (WIN32) - find_package(GeoTIFF CONFIG REQUIRED) - find_package(PROJ CONFIG REQUIRED) - target_include_directories(${PROJECT_NAME} PRIVATE ${GEOTIFF_INCLUDE_DIR}) - target_link_libraries(${PROJECT_NAME} PRIVATE ${GEOTIFF_LIBRARIES} PROJ::proj) - target_compile_options(${PROJECT_NAME} PRIVATE /permissive- /EHsc) - target_link_options(${PROJECT_NAME} PRIVATE /NODEFAULTLIB:MSVCRT) - else() - list(APPEND CMAKE_MODULE_PATH "/usr/lib/x86_64-linux-gnu/cmake") - - # not seeing GeoTIFF.cmake on linux for some reason - # HACK: ubuntu hides headers in /usr/include/geotiff but other distros don't - if(EXISTS "/usr/include/geotiff") - include_directories("/usr/include/geotiff") - endif() - - find_package(PROJ REQUIRED CONFIG) - target_link_libraries(${PROJECT_NAME} PUBLIC geotiff tiff PROJ::proj) -endif() - -add_custom_command(TARGET ${PROJECT_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ ../) diff --git a/firestarr/firestarr.sln b/firestarr/firestarr.sln deleted file mode 100644 index 90897023c..000000000 --- a/firestarr/firestarr.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34330.188 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "firestarr", "src\cpp\firestarr.vcxproj", "{5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}.Debug|x64.ActiveCfg = Debug|x64 - {5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}.Debug|x64.Build.0 = Debug|x64 - {5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}.Debug|x86.ActiveCfg = Debug|Win32 - {5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}.Debug|x86.Build.0 = Debug|Win32 - {5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}.Release|x64.ActiveCfg = Release|x64 - {5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}.Release|x64.Build.0 = Release|x64 - {5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}.Release|x86.ActiveCfg = Release|Win32 - {5A086E89-CA67-4AA2-9293-B0F1B6B6F2DA}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B523FF47-5952-40F6-AF3E-1F7FD84E4475} - EndGlobalSection -EndGlobal diff --git a/firestarr/fuel.lut b/firestarr/fuel.lut deleted file mode 100644 index 6aa1ae3ce..000000000 --- a/firestarr/fuel.lut +++ /dev/null @@ -1,150 +0,0 @@ -grid_value, export_value, descriptive_name, fuel_type, r, g, b, h, s, l -1,1,Spruce-Lichen Woodland,C-1,209,255,115,57,255,185 -2,2,Boreal Spruce,C-2,34,102,51,95,128,68 -3,3,Mature Jack or Lodgepole Pine,C-3,131,199,149,96,96,165 -4,4,Immature Jack or Lodgepole Pine,C-4,112,168,0,57,255,84 -5,5,Red and White Pine,C-5,223,184,230,206,122,207 -6,6,Conifer Plantation,C-6,172,102,237,192,201,170 -7,7,Ponderosa Pine - Douglas-Fir,C-7,112,12,242,188,231,127 -11,11,Leafless Aspen,D-1,196,189,151,35,70,174 -12,12,Green Aspen (with BUI Thresholding),D-2,137,112,68,27,86,103 -13,13,Aspen,D-1/D-2,196,189,151,35,70,174 -21,21,Jack or Lodgepole Pine Slash,S-1,251,190,185,3,227,218 -22,22,White Spruce - Balsam Slash,S-2,247,104,161,-272,229,176 -23,23,Coastal Cedar - Hemlock - Douglas-Fir Slash,S-3,174,1,126,-285,252,88 -31,31,Matted Grass,O-1a,255,255,190,42,255,223 -32,32,Standing Grass,O-1b,230,230,0,42,255,115 -33,33,Grass,O-1,230,230,0,42,255,115 -40,40,Boreal Mixedwood - Leafless,M-1,255,211,127,28,255,191 -50,50,Boreal Mixedwood - Green,M-2,255,170,0,28,255,128 -60,60,Boreal Mixedwood,M-1/M-2,255,211,127,28,255,191 -70,70,Dead Balsam Fir Mixedwood - Leafless,M-3,99,0,0,0,255,50 -80,80,Dead Balsam Fir Mixedwood - Green,M-4,170,0,0,0,255,85 -90,90,Dead Balsam Fir Mixedwood,M-3/M-4,99,0,0,0,255,50 -100,100,Not Available,D-2,255,255,255,170,0,255 -101,101,Non-fuel,D-2,130,130,130,170,0,130 -102,102,Water,Non-fuel,115,223,255,138,255,185 -103,103,Unknown,D-2,0,0,0,170,0,0 -104,104,Unclassified,D-1/D-2,166,166,166,170,0,166 -105,105,Vegetated Non-Fuel,M-1/M-2 (25 PC),204,204,204,170,0,204 -106,106,Urban,D-1/D-2,166,166,166,170,0,166 -400,400,Boreal Mixedwood - Leafless (00% Conifer),M-1 (00 PC),255,211,127,28,255,191 -405,405,Boreal Mixedwood - Leafless (05% Conifer),M-1 (05 PC),255,211,127,28,255,191 -410,410,Boreal Mixedwood - Leafless (10% Conifer),M-1 (10 PC),255,211,127,28,255,191 -415,415,Boreal Mixedwood - Leafless (15% Conifer),M-1 (15 PC),255,211,127,28,255,191 -420,420,Boreal Mixedwood - Leafless (20% Conifer),M-1 (20 PC),255,211,127,28,255,191 -425,425,Boreal Mixedwood - Leafless (25% Conifer),M-1 (25 PC),255,211,127,28,255,191 -430,430,Boreal Mixedwood - Leafless (30% Conifer),M-1 (30 PC),255,211,127,28,255,191 -435,435,Boreal Mixedwood - Leafless (35% Conifer),M-1 (35 PC),255,211,127,28,255,191 -440,440,Boreal Mixedwood - Leafless (40% Conifer),M-1 (40 PC),255,211,127,28,255,191 -445,445,Boreal Mixedwood - Leafless (45% Conifer),M-1 (45 PC),255,211,127,28,255,191 -450,450,Boreal Mixedwood - Leafless (50% Conifer),M-1 (50 PC),255,211,127,28,255,191 -455,455,Boreal Mixedwood - Leafless (55% Conifer),M-1 (55 PC),255,211,127,28,255,191 -460,460,Boreal Mixedwood - Leafless (60% Conifer),M-1 (60 PC),255,211,127,28,255,191 -465,465,Boreal Mixedwood - Leafless (65% Conifer),M-1 (65 PC),255,211,127,28,255,191 -470,470,Boreal Mixedwood - Leafless (70% Conifer),M-1 (70 PC),255,211,127,28,255,191 -475,475,Boreal Mixedwood - Leafless (75% Conifer),M-1 (75 PC),255,211,127,28,255,191 -480,480,Boreal Mixedwood - Leafless (80% Conifer),M-1 (80 PC),255,211,127,28,255,191 -485,485,Boreal Mixedwood - Leafless (85% Conifer),M-1 (85 PC),255,211,127,28,255,191 -490,490,Boreal Mixedwood - Leafless (90% Conifer),M-1 (90 PC),255,211,127,28,255,191 -495,495,Boreal Mixedwood - Leafless (95% Conifer),M-1 (95 PC),255,211,127,28,255,191 -500,500,Boreal Mixedwood - Green (00% Conifer),M-2 (00 PC),255,170,0,28,255,128 -505,505,Boreal Mixedwood - Green (05% Conifer),M-2 (05 PC),255,170,0,28,255,128 -510,510,Boreal Mixedwood - Green (10% Conifer),M-2 (10 PC),255,170,0,28,255,128 -515,515,Boreal Mixedwood - Green (15% Conifer),M-2 (15 PC),255,170,0,28,255,128 -520,520,Boreal Mixedwood - Green (20% Conifer),M-2 (20 PC),255,170,0,28,255,128 -525,525,Boreal Mixedwood - Green (25% Conifer),M-2 (25 PC),255,170,0,28,255,128 -530,530,Boreal Mixedwood - Green (30% Conifer),M-2 (30 PC),255,170,0,28,255,128 -535,535,Boreal Mixedwood - Green (35% Conifer),M-2 (35 PC),255,170,0,28,255,128 -540,540,Boreal Mixedwood - Green (40% Conifer),M-2 (40 PC),255,170,0,28,255,128 -545,545,Boreal Mixedwood - Green (45% Conifer),M-2 (45 PC),255,170,0,28,255,128 -550,550,Boreal Mixedwood - Green (50% Conifer),M-2 (50 PC),255,170,0,28,255,128 -555,555,Boreal Mixedwood - Green (55% Conifer),M-2 (55 PC),255,170,0,28,255,128 -560,560,Boreal Mixedwood - Green (60% Conifer),M-2 (60 PC),255,170,0,28,255,128 -565,565,Boreal Mixedwood - Green (65% Conifer),M-2 (65 PC),255,170,0,28,255,128 -570,570,Boreal Mixedwood - Green (70% Conifer),M-2 (70 PC),255,170,0,28,255,128 -575,575,Boreal Mixedwood - Green (75% Conifer),M-2 (75 PC),255,170,0,28,255,128 -580,580,Boreal Mixedwood - Green (80% Conifer),M-2 (80 PC),255,170,0,28,255,128 -585,585,Boreal Mixedwood - Green (85% Conifer),M-2 (85 PC),255,170,0,28,255,128 -590,590,Boreal Mixedwood - Green (90% Conifer),M-2 (90 PC),255,170,0,28,255,128 -595,595,Boreal Mixedwood - Green (95% Conifer),M-2 (95 PC),255,170,0,28,255,128 -600,600,Boreal Mixedwood (00% Conifer),M-1/M-2 (00 PC),255,211,127,28,255,191 -605,605,Boreal Mixedwood (05% Conifer),M-1/M-2 (05 PC),255,211,127,28,255,191 -610,610,Boreal Mixedwood (10% Conifer),M-1/M-2 (10 PC),255,211,127,28,255,191 -615,615,Boreal Mixedwood (15% Conifer),M-1/M-2 (15 PC),255,211,127,28,255,191 -620,620,Boreal Mixedwood (20% Conifer),M-1/M-2 (20 PC),255,211,127,28,255,191 -625,625,Boreal Mixedwood (25% Conifer),M-1/M-2 (25 PC),255,211,127,28,255,191 -630,630,Boreal Mixedwood (30% Conifer),M-1/M-2 (30 PC),255,211,127,28,255,191 -635,635,Boreal Mixedwood (35% Conifer),M-1/M-2 (35 PC),255,211,127,28,255,191 -640,640,Boreal Mixedwood (40% Conifer),M-1/M-2 (40 PC),255,211,127,28,255,191 -645,645,Boreal Mixedwood (45% Conifer),M-1/M-2 (45 PC),255,211,127,28,255,191 -650,650,Boreal Mixedwood (50% Conifer),M-1/M-2 (50 PC),255,211,127,28,255,191 -655,655,Boreal Mixedwood (55% Conifer),M-1/M-2 (55 PC),255,211,127,28,255,191 -660,660,Boreal Mixedwood (60% Conifer),M-1/M-2 (60 PC),255,211,127,28,255,191 -665,665,Boreal Mixedwood (65% Conifer),M-1/M-2 (65 PC),255,211,127,28,255,191 -670,670,Boreal Mixedwood (70% Conifer),M-1/M-2 (70 PC),255,211,127,28,255,191 -675,675,Boreal Mixedwood (75% Conifer),M-1/M-2 (75 PC),255,211,127,28,255,191 -680,680,Boreal Mixedwood (80% Conifer),M-1/M-2 (80 PC),255,211,127,28,255,191 -685,685,Boreal Mixedwood (85% Conifer),M-1/M-2 (85 PC),255,211,127,28,255,191 -690,690,Boreal Mixedwood (90% Conifer),M-1/M-2 (90 PC),255,211,127,28,255,191 -695,695,Boreal Mixedwood (95% Conifer),M-1/M-2 (95 PC),255,211,127,28,255,191 -700,700,Dead Balsam Fir Mixedwood - Leafless (00% Dead Fir),M-3 (00 PDF),99,0,0,0,255,50 -705,705,Dead Balsam Fir Mixedwood - Leafless (05% Dead Fir),M-3 (05 PDF),99,0,0,0,255,50 -710,710,Dead Balsam Fir Mixedwood - Leafless (10% Dead Fir),M-3 (10 PDF),99,0,0,0,255,50 -715,715,Dead Balsam Fir Mixedwood - Leafless (15% Dead Fir),M-3 (15 PDF),99,0,0,0,255,50 -720,720,Dead Balsam Fir Mixedwood - Leafless (20% Dead Fir),M-3 (20 PDF),99,0,0,0,255,50 -725,725,Dead Balsam Fir Mixedwood - Leafless (25% Dead Fir),M-3 (25 PDF),99,0,0,0,255,50 -730,730,Dead Balsam Fir Mixedwood - Leafless (30% Dead Fir),M-3 (30 PDF),99,0,0,0,255,50 -735,735,Dead Balsam Fir Mixedwood - Leafless (35% Dead Fir),M-3 (35 PDF),99,0,0,0,255,50 -740,740,Dead Balsam Fir Mixedwood - Leafless (40% Dead Fir),M-3 (40 PDF),99,0,0,0,255,50 -745,745,Dead Balsam Fir Mixedwood - Leafless (45% Dead Fir),M-3 (45 PDF),99,0,0,0,255,50 -750,750,Dead Balsam Fir Mixedwood - Leafless (50% Dead Fir),M-3 (50 PDF),99,0,0,0,255,50 -755,755,Dead Balsam Fir Mixedwood - Leafless (55% Dead Fir),M-3 (55 PDF),99,0,0,0,255,50 -760,760,Dead Balsam Fir Mixedwood - Leafless (60% Dead Fir),M-3 (60 PDF),99,0,0,0,255,50 -765,765,Dead Balsam Fir Mixedwood - Leafless (65% Dead Fir),M-3 (65 PDF),99,0,0,0,255,50 -770,770,Dead Balsam Fir Mixedwood - Leafless (70% Dead Fir),M-3 (70 PDF),99,0,0,0,255,50 -775,775,Dead Balsam Fir Mixedwood - Leafless (75% Dead Fir),M-3 (75 PDF),99,0,0,0,255,50 -780,780,Dead Balsam Fir Mixedwood - Leafless (80% Dead Fir),M-3 (80 PDF),99,0,0,0,255,50 -785,785,Dead Balsam Fir Mixedwood - Leafless (85% Dead Fir),M-3 (85 PDF),99,0,0,0,255,50 -790,790,Dead Balsam Fir Mixedwood - Leafless (90% Dead Fir),M-3 (90 PDF),99,0,0,0,255,50 -795,795,Dead Balsam Fir Mixedwood - Leafless (95% Dead Fir),M-3 (95 PDF),99,0,0,0,255,50 -800,800,Dead Balsam Fir Mixedwood - Green (00% Dead Fir),M-4 (00 PDF),170,0,0,0,255,85 -805,805,Dead Balsam Fir Mixedwood - Green (05% Dead Fir),M-4 (05 PDF),170,0,0,0,255,85 -810,810,Dead Balsam Fir Mixedwood - Green (10% Dead Fir),M-4 (10 PDF),170,0,0,0,255,85 -815,815,Dead Balsam Fir Mixedwood - Green (15% Dead Fir),M-4 (15 PDF),170,0,0,0,255,85 -820,820,Dead Balsam Fir Mixedwood - Green (20% Dead Fir),M-4 (20 PDF),170,0,0,0,255,85 -825,825,Dead Balsam Fir Mixedwood - Green (25% Dead Fir),M-4 (25 PDF),170,0,0,0,255,85 -830,830,Dead Balsam Fir Mixedwood - Green (30% Dead Fir),M-4 (30 PDF),170,0,0,0,255,85 -835,835,Dead Balsam Fir Mixedwood - Green (35% Dead Fir),M-4 (35 PDF),170,0,0,0,255,85 -840,840,Dead Balsam Fir Mixedwood - Green (40% Dead Fir),M-4 (40 PDF),170,0,0,0,255,85 -845,845,Dead Balsam Fir Mixedwood - Green (45% Dead Fir),M-4 (45 PDF),170,0,0,0,255,85 -850,850,Dead Balsam Fir Mixedwood - Green (50% Dead Fir),M-4 (50 PDF),170,0,0,0,255,85 -855,855,Dead Balsam Fir Mixedwood - Green (55% Dead Fir),M-4 (55 PDF),170,0,0,0,255,85 -860,860,Dead Balsam Fir Mixedwood - Green (60% Dead Fir),M-4 (60 PDF),170,0,0,0,255,85 -865,865,Dead Balsam Fir Mixedwood - Green (65% Dead Fir),M-4 (65 PDF),170,0,0,0,255,85 -870,870,Dead Balsam Fir Mixedwood - Green (70% Dead Fir),M-4 (70 PDF),170,0,0,0,255,85 -875,875,Dead Balsam Fir Mixedwood - Green (75% Dead Fir),M-4 (75 PDF),170,0,0,0,255,85 -880,880,Dead Balsam Fir Mixedwood - Green (80% Dead Fir),M-4 (80 PDF),170,0,0,0,255,85 -885,885,Dead Balsam Fir Mixedwood - Green (85% Dead Fir),M-4 (85 PDF),170,0,0,0,255,85 -890,890,Dead Balsam Fir Mixedwood - Green (90% Dead Fir),M-4 (90 PDF),170,0,0,0,255,85 -895,895,Dead Balsam Fir Mixedwood - Green (95% Dead Fir),M-4 (95 PDF),170,0,0,0,255,85 -900,900,Dead Balsam Fir Mixedwood (00% Dead Fir),M-3/M-4 (00 PDF),99,0,0,0,255,50 -905,905,Dead Balsam Fir Mixedwood (05% Dead Fir),M-3/M-4 (05 PDF),99,0,0,0,255,50 -910,910,Dead Balsam Fir Mixedwood (10% Dead Fir),M-3/M-4 (10 PDF),99,0,0,0,255,50 -915,915,Dead Balsam Fir Mixedwood (15% Dead Fir),M-3/M-4 (15 PDF),99,0,0,0,255,50 -920,920,Dead Balsam Fir Mixedwood (20% Dead Fir),M-3/M-4 (20 PDF),99,0,0,0,255,50 -925,925,Dead Balsam Fir Mixedwood (25% Dead Fir),M-3/M-4 (25 PDF),99,0,0,0,255,50 -930,930,Dead Balsam Fir Mixedwood (30% Dead Fir),M-3/M-4 (30 PDF),99,0,0,0,255,50 -935,935,Dead Balsam Fir Mixedwood (35% Dead Fir),M-3/M-4 (35 PDF),99,0,0,0,255,50 -940,940,Dead Balsam Fir Mixedwood (40% Dead Fir),M-3/M-4 (40 PDF),99,0,0,0,255,50 -945,945,Dead Balsam Fir Mixedwood (45% Dead Fir),M-3/M-4 (45 PDF),99,0,0,0,255,50 -950,950,Dead Balsam Fir Mixedwood (50% Dead Fir),M-3/M-4 (50 PDF),99,0,0,0,255,50 -955,955,Dead Balsam Fir Mixedwood (55% Dead Fir),M-3/M-4 (55 PDF),99,0,0,0,255,50 -960,960,Dead Balsam Fir Mixedwood (60% Dead Fir),M-3/M-4 (60 PDF),99,0,0,0,255,50 -965,965,Dead Balsam Fir Mixedwood (65% Dead Fir),M-3/M-4 (65 PDF),99,0,0,0,255,50 -970,970,Dead Balsam Fir Mixedwood (70% Dead Fir),M-3/M-4 (70 PDF),99,0,0,0,255,50 -975,975,Dead Balsam Fir Mixedwood (75% Dead Fir),M-3/M-4 (75 PDF),99,0,0,0,255,50 -980,980,Dead Balsam Fir Mixedwood (80% Dead Fir),M-3/M-4 (80 PDF),99,0,0,0,255,50 -985,985,Dead Balsam Fir Mixedwood (85% Dead Fir),M-3/M-4 (85 PDF),99,0,0,0,255,50 -990,990,Dead Balsam Fir Mixedwood (90% Dead Fir),M-3/M-4 (90 PDF),99,0,0,0,255,50 -995,995,Dead Balsam Fir Mixedwood (95% Dead Fir),M-3/M-4 (95 PDF),99,0,0,0,255,50 diff --git a/firestarr/scripts/build.sh b/firestarr/scripts/build.sh deleted file mode 100755 index 73026b5de..000000000 --- a/firestarr/scripts/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -DIR=`dirname $(realpath "$0")` -# didn't figure out how to do this with cmake yet but this works for now -DIR_BUILD=/appl/firestarr/build -VARIANT="$*" -if [ -z "${VARIANT}" ]; then - VARIANT=Release -fi -pushd "${DIR}/.." -echo Set VARIANT=${VARIANT} -/usr/bin/cmake --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=${VARIANT} -S/appl/firestarr -B${DIR_BUILD} -G "Unix Makefiles" \ - && /usr/bin/cmake --build ${DIR_BUILD} --config ${VARIANT} --target all -j 50 -- -popd diff --git a/firestarr/scripts/mk_clean.sh b/firestarr/scripts/mk_clean.sh deleted file mode 100755 index 62dee92e0..000000000 --- a/firestarr/scripts/mk_clean.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -# didn't figure out how to do this with cmake yet but this works for now -DIR_BUILD=/appl/firestarr/build -VARIANT="${1}" -if ( [ -z "${VARIANT}" ] || ( [ "Release" != "${VARIANT}" ] && [ "Debug" != ${VARIANT} ] ) ); then - # assume that argument is an arg to pass to cmake - VARIANT="Release" - echo "${@}" - # don't shift because $1 wasn't the variant -else - shift; -fi -echo "${@}" -echo Set VARIANT=${VARIANT} -rm -rf ${DIR_BUILD} \ - && /usr/bin/cmake --no-warn-unused-cli "${@}" -DCMAKE_BUILD_TYPE:STRING=${VARIANT} -S/appl/firestarr -B${DIR_BUILD} -G "Unix Makefiles" \ - && /usr/bin/cmake --build ${DIR_BUILD} --config ${VARIANT} --target all -j 50 -- diff --git a/firestarr/scripts/mk_verbose.sh b/firestarr/scripts/mk_verbose.sh deleted file mode 100755 index 03b686ffe..000000000 --- a/firestarr/scripts/mk_verbose.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -scripts/mk_clean.sh ${1} -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON diff --git a/firestarr/scripts/profile.sh b/firestarr/scripts/profile.sh deleted file mode 100755 index 6cc2b7718..000000000 --- a/firestarr/scripts/profile.sh +++ /dev/null @@ -1,16 +0,0 @@ -DIR_PERF=${TMPDIR}/perf -DIR_BUILD=./build -FILE_PERF=${DIR_PERF}/perf.data -FREQ=100 -DIR_FG=/appl/FlameGraph -sudo sysctl -w kernel.perf_event_paranoid=1 -[ ! -d "${DIR_PERF}" ] && mkdir -p "${DIR_PERF}" -# don't make this an actual submodule because it's just this script that needs it -[ ! -d "${DIR_FG}" ] && sudo apt install git && sudo git clone https://github.com/brendangregg/FlameGraph.git ${DIR_FG} && sudo chown -R ${USER}:${USER} ${DIR_FG} -rm -rf ${DIR_BUILD} -cmake --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo -S/appl/firestarr -B${DIR_BUILD} -G "Unix Makefiles" -cmake --build ${DIR_BUILD} --config Debug --target all -j $(nproc) -- -cp ${DIR_BUILD}/firestarr /appl/firestarr/firestarr -time perf record -o ${FILE_PERF} -F ${FREQ} -g --call-graph=dwarf -- $* > ${DIR_PERF}/run.log -chown ${USER}:${USER} ${FILE_PERF} -perf script -i ${FILE_PERF} | ${DIR_FG}/stackcollapse-perf.pl | ${DIR_FG}/flamegraph.pl > flame.html diff --git a/firestarr/settings.ini b/firestarr/settings.ini deleted file mode 100644 index 9ac90f454..000000000 --- a/firestarr/settings.ini +++ /dev/null @@ -1,40 +0,0 @@ -# root directory to read rasters from -RASTER_ROOT = ../data/generated/grid/100m -# minimum rate of spread before fire is considered to actually be spreading -MINIMUM_ROS = 0.0 -# maximum distance that head can spread per step (* cell size) -MAX_SPREAD_DISTANCE = 0.4 -# days to output probability contours for -OUTPUT_DATE_OFFSETS = [1,2,3,7,14] -# lookup table for fuels (prometheus format) -FUEL_LOOKUP_TABLE = ./fuel.lut -# minimum ffmc for fire to spread -MINIMUM_FFMC = 65.0 -# minimum ffmc for fire to spread at night -MINIMUM_FFMC_AT_NIGHT = 85.0 -# time after sunrise to start burning (hours) -OFFSET_SUNRISE = 0.0 -# time before sunset to stop burning (hours) -OFFSET_SUNSET = 0.0 -# weight given to the scenario thresholds -THRESHOLD_SCENARIO_WEIGHT = 1.0 -# weight given to the daily thresholds -THRESHOLD_DAILY_WEIGHT = 3.0 -# weight given to the hourly thresholds -THRESHOLD_HOURLY_WEIGHT = 2.0 -# default M-1/M-2 percent conifer if none specified -DEFAULT_PERCENT_CONIFER = 50 -# default M-3/M-4 percent dead fir if none specified -DEFAULT_PERCENT_DEAD_FIR = 30 -# maximum amount of time to take for simulation (seconds) -MAXIMUM_TIME = 36000 -# amount of time between generating interim outputs (seconds) -INTERIM_OUTPUT_INTERVAL = 240 -# maximum number of simulations to do -MAXIMUM_SIMULATIONS = 10000 -# maximum percent change in statistics between runs before results are consider stable [0 - 1] -CONFIDENCE_LEVEL = 0.1 -# intensity considered to be top of the range (kW/m) -INTENSITY_MAX_LOW = 2000 -# intensity considered to be top of the range (kW/m) -INTENSITY_MAX_MODERATE = 4000 diff --git a/firestarr/src/cpp/Cell.h b/firestarr/src/cpp/Cell.h deleted file mode 100644 index 1d1da6e00..000000000 --- a/firestarr/src/cpp/Cell.h +++ /dev/null @@ -1,343 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include "Location.h" -#include "FuelType.h" -#include "Util.h" -namespace fs::topo -{ -using SpreadKey = uint32_t; -using fuel::INVALID_FUEL_CODE; -/** - * \brief A Position with a Slope, Aspect, and Fuel. - */ -class Cell - : public Position -{ -public: - /** - * \brief Default constructor - */ - constexpr Cell() noexcept - : Cell(-1, - -1, - numeric_limits::min(), - numeric_limits::min(), - numeric_limits::min()) - { - } - /** - * \brief Full stored hash that may contain data from subclasses - * \return Full stored hash that may contain data from subclasses - */ - [[nodiscard]] constexpr Topo fullHash() const - { - return topo_data_; - } - /** - * \brief Hash attributes into a Topo value - * \param slope Slope - * \param aspect Aspect - * \param fuel Fuel - * \return Hash - */ - [[nodiscard]] static constexpr Topo hashCell(const SlopeSize slope, - const AspectSize aspect, - const FuelCodeSize& fuel) noexcept - { - // HACK: so we can call and set it all invalid if anything is - // if any are invalid then they all should be - const auto do_hash_cell = [](const SlopeSize s, const AspectSize a, const FuelCodeSize f) { - return static_cast(f) << FuelShift - | static_cast(s) << SlopeShift - | static_cast(a) << AspectShift; - }; - if ( - INVALID_SLOPE == slope - || INVALID_ASPECT == aspect - || INVALID_FUEL_CODE == fuel) - { - return do_hash_cell(INVALID_SLOPE, INVALID_ASPECT, INVALID_FUEL_CODE); - } - // if slope is 0 make aspect north so less unique keys - return do_hash_cell(slope, 0 == slope ? 0 : aspect, fuel); - } - /** - * \brief Construct from hash value - * \param hash Hash defining all attributes - */ - explicit constexpr Cell(const Topo hash) noexcept - : Position(hash) - { - } - /** - * \brief Construct based on given attributes - * \param hash Hash of row and column - * \param slope Slope - * \param aspect Aspect - * \param fuel Fuel - */ - constexpr Cell(const HashSize hash, - const SlopeSize slope, - const AspectSize aspect, - const FuelCodeSize& fuel) noexcept - : Position( - static_cast(hash & HashMask) - | hashCell(slope, aspect, fuel)) - { - } - /** - * \brief Constructor - * \param row Row - * \param column Column - * \param slope Slope - * \param aspect Aspect - * \param fuel Fuel - */ - constexpr Cell(const Idx row, - const Idx column, - const SlopeSize slope, - const AspectSize aspect, - const FuelCodeSize& fuel) noexcept - : Position( - static_cast(doHash(row, column)) - | hashCell(slope, aspect, fuel)) - { - } - /** - * \brief A key defining Slope, Aspect, and Fuel, used for determining Cells that spread the same - * \param value Topo to extract from - * \return A key defining Slope, Aspect, and Fuel - */ - [[nodiscard]] static constexpr SpreadKey key(const Topo value) noexcept - { - // can just shift since these are the only bits left after - return static_cast(value >> FuelShift); - } - /** - * \brief Aspect (degrees) - * \param value SpreadKey to extract from - * \return Aspect (degrees) - */ - [[nodiscard]] static constexpr AspectSize aspect(const SpreadKey value) noexcept - { - return static_cast((value & (AspectMask >> FuelShift)) >> (AspectShift - FuelShift)); - } - /** - * \brief Fuel - * \param value SpreadKey to extract from - * \return Fuel - */ - - [[nodiscard]] static constexpr FuelCodeSize fuelCode(const SpreadKey value) noexcept - { - return static_cast((value & (FuelMask >> FuelShift)) >> (FuelShift - FuelShift)); - } - /** - * \brief Slope (degrees) - * \param value SpreadKey to extract from - * \return Slope (degrees) - */ - [[nodiscard]] static constexpr SlopeSize slope(const SpreadKey value) noexcept - { - return static_cast((value & (SlopeMask >> FuelShift)) >> (SlopeShift - FuelShift)); - } - /** - * \brief Aspect (degrees) - * \param value Topo to extract from - * \return Aspect (degrees) - */ - [[nodiscard]] static constexpr AspectSize aspect(const Topo value) noexcept - { - return static_cast((value & AspectMask) >> AspectShift); - } - /** - * \brief Fuel - * \param value Topo to extract from - * \return Fuel - */ - - [[nodiscard]] static constexpr FuelCodeSize fuelCode(const Topo value) noexcept - { - return static_cast((value & FuelMask) >> FuelShift); - } - /** - * \brief Slope (degrees) - * \param value Topo to extract from - * \return Slope (degrees) - */ - [[nodiscard]] static constexpr SlopeSize slope(const Topo value) noexcept - { - return static_cast((value & SlopeMask) >> SlopeShift); - } - /** - * \brief Topo that contains Cell data - * \param value Topo to extract from - * \return Topo that contains Cell data - */ - [[nodiscard]] static constexpr Topo topoHash(const Topo value) noexcept - { - return static_cast(value) & CellMask; - } - /** - * \brief A key defining Slope, Aspect, and Fuel, used for determining Cells that spread the same - * \return A key defining Slope, Aspect, and Fuel - */ - [[nodiscard]] constexpr SpreadKey key() const noexcept - { - return Cell::key(topo_data_); - } - /** - * \brief Aspect (degrees) - * \return Aspect (degrees) - */ - [[nodiscard]] constexpr AspectSize aspect() const noexcept - { - return Cell::aspect(topo_data_); - } - /** - * \brief Fuel - * \return Fuel - */ - [[nodiscard]] constexpr FuelCodeSize fuelCode() const noexcept - { - return Cell::fuelCode(topo_data_); - } - /** - * \brief Slope (degrees) - * \return Slope (degrees) - */ - [[nodiscard]] constexpr SlopeSize slope() const noexcept - { - return Cell::slope(topo_data_); - } - /** - * \brief Topo that contains Cell data - * \return Topo that contains Cell data - */ - [[nodiscard]] constexpr Topo topoHash() const noexcept - { - return Cell::topoHash(topo_data_); - } -protected: - /* - * Field Natural Range Used Range Bits Bit Range - * Row 0 - 4095 0 - 4095 12 0 - 4095 - * Column 0 - 4095 0 - 4095 12 0 - 4095 - * PADDING 10 - * Fuel 0 - 140 0 - 140 8 0 - 255 - * Aspect 0 - 359 0 - 359 9 0 - 511 - * Slope 0 - infinity 0 - 511 9 0 - 511 - * Extra 8 - * - * Rows and Columns are restricted to 4096 since that's what gets clipped out of - * the GIS outputs. - * - * Fuel is tied to how many variations of percent conifer/dead fir we want to use, and - * if we want to allow M1/M2/M3/M4 on their own, or just use the ones that are - * automatically tied to the green-up. - * - * Aspect is calculated to be in degrees, so 0 - 359. - * - * Slope is truncated to 0 - 70 for slope effect calculations speed for slopes, - * but keep higher range because there's an issue with this when it tries to calculate the - * horizontal rate of spread since if the slope has been truncated and the distance - * calculated will be wrong. - */ - /** - * \brief Shift for fuel bitmask - */ - static constexpr uint32_t FuelShift = 32; - // Need to make sure that fuel, slope & aspect aren't in first 32 bits - static_assert(32 <= FuelShift); - /** - * \brief Number of bits in fuel bitmask - */ - static constexpr uint32_t FuelBits = std::bit_width(NUMBER_OF_FUELS); - /** - * \brief Bitmask for fuel information in Topo before shift - */ - static constexpr Topo FuelBitMask = util::bit_mask(); - static_assert(FuelBitMask == 0xFF); - static_assert(FuelBitMask >= NUMBER_OF_FUELS); - /** - * \brief Bitmask for fuel information in Topo - */ - static constexpr Topo FuelMask = FuelBitMask << FuelShift; - /** - * \brief Shift for aspect bitmask - */ - static constexpr uint32_t AspectShift = FuelBits + FuelShift; - /** - * \brief Number of bits in aspect bitmask - */ - static constexpr uint32_t AspectBits = std::bit_width(MAX_ASPECT); - /** - * \brief Bitmask for aspect in Topo before shift - */ - static constexpr Topo AspectBitMask = util::bit_mask(); - static_assert(AspectBitMask == 0x1FF); - static_assert(AspectBitMask >= INVALID_ASPECT); - /** - * \brief Bitmask for aspect in Topo - */ - static constexpr Topo AspectMask = AspectBitMask << AspectShift; - /** - * \brief Shift for slope bitmask - */ - static constexpr uint32_t SlopeShift = AspectBits + AspectShift; - /** - * \brief Number of bits in slope bitmask - */ - static constexpr uint32_t SlopeBits = std::bit_width(MAX_SLOPE_FOR_DISTANCE); - static_assert(SlopeBits == 9); - /** - * \brief Bitmask for slope in Topo before shift - */ - static constexpr Topo SlopeBitMask = util::bit_mask(); - static_assert(SlopeBitMask == 0x1FF); - static_assert(SlopeBitMask >= INVALID_SLOPE); - /** - * \brief Bitmask for slope in Topo - */ - static constexpr Topo SlopeMask = SlopeBitMask << SlopeShift; - /** - * \brief Bitmask for Cell information in Topo - */ - static constexpr Topo CellMask = HashMask | FuelMask | AspectMask | SlopeMask; - static_assert(static_cast(std::bit_width(std::numeric_limits::max())) >= SlopeBits + SlopeShift); -}; -/** - * \brief Less than operator - * \param lhs First Cell - * \param rhs Second Cell - * \return Whether or not first is less than second - */ -constexpr bool operator<(const Cell& lhs, const Cell& rhs) -{ - return lhs.topoHash() < rhs.topoHash(); -} -} -namespace std -{ -/** - * \brief Provides hashing of Cell objects - */ -template <> -struct hash -{ - /** - * \brief Provides hash for Cell objects - * \param k Cell to get hash for - * \return Hash value - */ - std::size_t operator()(const fs::topo::Cell& k) const noexcept - { - return k.fullHash(); - } -}; -} diff --git a/firestarr/src/cpp/CellPoints.cpp b/firestarr/src/cpp/CellPoints.cpp deleted file mode 100644 index ea3354c82..000000000 --- a/firestarr/src/cpp/CellPoints.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/* Copyright (c) Jordan Evens, 2005, 2021 */ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "CellPoints.h" -#include "Log.h" -#include "Location.h" -#include "Scenario.h" - -namespace fs::sim -{ -constexpr InnerSize DIST_22_5 = static_cast(0.2071067811865475244008443621048490392848359376884740365883398689); -constexpr InnerSize P_0_5 = static_cast(0.5) + DIST_22_5; -constexpr InnerSize M_0_5 = static_cast(0.5) - DIST_22_5; -// static constexpr auto INVALID_DISTANCE = std::numeric_limits::max(); -// not sure what's going on with this and wondering if it doesn't keep number exactly -// shouldn't be any way to be further than twice the entire width of the area -static const auto INVALID_DISTANCE = static_cast(MAX_ROWS * MAX_ROWS); -static const XYPos INVALID_XY_POSITION{}; -static const pair INVALID_XY_PAIR{INVALID_DISTANCE, {}}; -static const XYSize INVALID_XY_LOCATION = INVALID_XY_PAIR.second.first; -static const InnerPos INVALID_INNER_POSITION{}; -static const pair INVALID_INNER_PAIR{INVALID_DISTANCE, {}}; -static const InnerSize INVALID_INNER_LOCATION = INVALID_INNER_PAIR.second.first; -static const SpreadData INVALID_SPREAD_DATA{ - INVALID_TIME}; -set CellPoints::unique() const noexcept -{ - // // if any point is invalid then they all have to be - if (INVALID_DISTANCE == pts_.distances()[0]) - { - return {}; - } - else - { - const auto& pts_all = std::views::transform( - pts_.points(), - [this](const auto& p) { - return XYPos(p.first + cell_x_y_.first, p.second + cell_x_y_.second); - }); - return {pts_all.begin(), pts_all.end()}; - } -} -#ifdef DEBUG_CELLPOINTS -size_t CellPoints::size() const noexcept -{ - return unique().size(); -} -#endif -CellPoints::CellPoints(const Idx cell_x, const Idx cell_y) noexcept - : pts_({}), - cell_x_y_(cell_x, cell_y) -{ - std::fill(pts_.distances().begin(), pts_.distances().end(), INVALID_DISTANCE); - std::fill(pts_.points().begin(), pts_.points().end(), INVALID_INNER_POSITION); - -#ifdef DEBUG_CELLPOINTS - logging::note("CellPoints is size %ld after creation and should be empty", size()); -#endif -} -CellPoints::CellPoints() noexcept - : CellPoints(INVALID_XY_LOCATION, INVALID_XY_LOCATION) -{ -} -CellPoints::CellPoints(const CellPoints* rhs) noexcept - : CellPoints() -{ - logging::check_fatal(nullptr == rhs, "Initializing CellPoints from nullptr"); - *this = *rhs; -} -CellPoints::CellPoints( - const XYSize x, - const XYSize y) noexcept - : CellPoints(static_cast(x), static_cast(y)) -{ - insert( - x, - y); -} - -using DISTANCE_PAIR = pair; -#define D_PTS(x, y) (DISTANCE_PAIR{static_cast(x), static_cast(y)}) -constexpr std::array POINTS_OUTER{ - D_PTS(0.5, 1.0), - // north-northeast is closest to point (0.5 + 0.207, 1.0) - D_PTS(P_0_5, 1.0), - // northeast is closest to point (1.0, 1.0) - D_PTS(1.0, 1.0), - // east-northeast is closest to point (1.0, 0.5 + 0.207) - D_PTS(1.0, P_0_5), - // east is closest to point (1.0, 0.5) - D_PTS(1.0, 0.5), - // east-southeast is closest to point (1.0, 0.5 - 0.207) - D_PTS(1.0, M_0_5), - // southeast is closest to point (1.0, 0.0) - D_PTS(1.0, 0.0), - // south-southeast is closest to point (0.5 + 0.207, 0.0) - D_PTS(P_0_5, 0.0), - // south is closest to point (0.5, 0.0) - D_PTS(0.5, 0.0), - // south-southwest is closest to point (0.5 - 0.207, 0.0) - D_PTS(M_0_5, 0.0), - // southwest is closest to point (0.0, 0.0) - D_PTS(0.0, 0.0), - // west-southwest is closest to point (0.0, 0.5 - 0.207) - D_PTS(0.0, M_0_5), - // west is closest to point (0.0, 0.5) - D_PTS(0.0, 0.5), - // west-northwest is closest to point (0.0, 0.5 + 0.207) - D_PTS(0.0, P_0_5), - // northwest is closest to point (0.0, 1.0) - D_PTS(0.0, 1.0), - // north-northwest is closest to point (0.5 - 0.207, 1.0) - D_PTS(M_0_5, 1.0)}; - -// TODO: add angle -CellPoints& CellPoints::insert( - const XYSize x, - const XYSize y) noexcept -{ - // NOTE: use location inside cell so smaller types can be more precise - // since digits aren't wasted on cell - const auto p0 = InnerPos( - static_cast(x - cell_x_y_.first), - static_cast(y - cell_x_y_.second)); - const auto x0 = static_cast(p0.first); - const auto y0 = static_cast(p0.second); - // CHECK: FIX: is this initializing everything to false or just one element? - // static_assert(pts_.first.size() == NUM_DIRECTIONS); - for (size_t i = 0; i < NUM_DIRECTIONS; ++i) - { - const auto& p1 = POINTS_OUTER[i]; - const auto& x1 = p1.first; - const auto& y1 = p1.second; - const auto d = ((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1)); - auto& p_d = pts_.distances()[i]; - auto& p_p = pts_.points()[i]; - // closer[i] = (d < p_d); - // p_p = closer[i] ? p0 : p_p; - // p_d = closer[i] ? d : p_d; - p_p = (d < p_d) ? p0 : p_p; - p_d = (d < p_d) ? d : p_d; - // // worse than two checks + assignment - // const auto& [p_new, d_new] = - // (d < p_d) - // ? std::make_tuple(p0, d) - // : std::make_tuple(p_p, p_d); - // p_p = p_new; - // p_d = d_new; - // // worse than two checks + assignment - // std::tie(p_d, p_p) = - // (d < p_d) - // ? std::make_tuple(d, p0) - // : std::make_tuple(p_d, p_p); - } - // FIX: do something with spread on exit - return *this; -} -#undef D_PTS -CellPoints::CellPoints(const XYPos& p) noexcept - : CellPoints(p.first, p.second) -{ -} - -CellPoints& CellPoints::insert(const InnerPos& p) noexcept -{ - // HACK: FIX: just do something for now - insert( - p.first, - p.second); - return *this; -} - -CellPoints& CellPoints::merge(const CellPoints& rhs) -{ - // either both invalid or lower one is valid - cell_x_y_ = min(cell_x_y_, rhs.cell_x_y_); - auto& d0 = pts_.distances(); - auto& d1 = rhs.pts_.distances(); - auto& p0 = pts_.points(); - auto& p1 = rhs.pts_.points(); - // we know distances in each direction so just pick closer - for (size_t i = 0; i < d0.size(); ++i) - { - if (d1[i] < d0[i]) - { - d0[i] = d1[i]; - p0[i] = p1[i]; - } - } - return *this; -} -bool CellPoints::operator<(const CellPoints& rhs) const noexcept -{ - if (cell_x_y_ == rhs.cell_x_y_) - { - return pts_.points() < rhs.pts_.points(); - } - return cell_x_y_ < rhs.cell_x_y_; -} -bool CellPoints::operator==(const CellPoints& rhs) const noexcept -{ - return ( - cell_x_y_ == rhs.cell_x_y_ - && pts_.points() == rhs.pts_.points()); -} -bool CellPoints::empty() const -{ - // NOTE: if anything is invalid then everything must be - return (INVALID_DISTANCE == pts_.distances()[0]); -} -[[nodiscard]] Location CellPoints::location() const noexcept -{ - return Location{cell_x_y_.second, cell_x_y_.first}; -} -CellPointsMap::CellPointsMap() - : map_({}) -{ -} -CellPoints& CellPointsMap::insert( - const XYSize x, - const XYSize y) noexcept -{ -#ifdef DEBUG_CELLPOINTS - const auto n0 = size(); -#endif - const Location location{static_cast(y), static_cast(x)}; - auto e = map_.try_emplace(location, - x, - y); - CellPoints& cell_pts = e.first->second; - if (!e.second) - { - // FIX: should use max of whatever ROS has entered during this time and not just first ros - // tried to add new CellPoints but already there - cell_pts.insert( - x, - y); - } - return cell_pts; -} -CellPointsMap& CellPointsMap::merge( - const BurnedData& unburnable, - const CellPointsMap& rhs) noexcept -{ - // FIX: if we iterate through both they should be sorted - for (const auto& kv : rhs.map_) - { - const auto h = kv.first.hash(); - if (!unburnable[h]) - { - const CellPoints& pts = kv.second; - const Location location = pts.location(); - auto e = map_.try_emplace(location, pts); - CellPoints& cell_pts = e.first->second; - if (!e.second) - { - // couldn't insert - cell_pts.merge(pts); - } - } - } - return *this; -} -void CellPointsMap::remove_if(std::function&)> F) noexcept -{ - auto it = map_.begin(); - while (map_.end() != it) - { - if (F(*it)) - { - it = map_.erase(it); - } - else - { - ++it; - } - } -} -set CellPointsMap::unique() const noexcept -{ - set r{}; - for (auto& lp : map_) - { - for (auto& p : lp.second.unique()) - { - r.insert(p); - } - } - return r; -} -#ifdef DEBUG_CELLPOINTS -size_t CellPointsMap::size() const noexcept -{ - return unique().size(); -} -#endif -} diff --git a/firestarr/src/cpp/CellPoints.h b/firestarr/src/cpp/CellPoints.h deleted file mode 100644 index 3bb58837d..000000000 --- a/firestarr/src/cpp/CellPoints.h +++ /dev/null @@ -1,190 +0,0 @@ -/* Copyright (c) Jordan Evens, 2005, 2021 */ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "stdafx.h" -#include "InnerPos.h" -#include "IntensityMap.h" - -namespace fs::sim -{ -using fs::wx::Direction; -// using sim::CellPoints; -using topo::Cell; -using topo::SpreadKey; -class SpreadData : std::tuple -{ -public: - using std::tuple::tuple; - DurationSize time() const - { - return std::get<0>(*this); - } -}; - -static constexpr size_t FURTHEST_N = 0; -static constexpr size_t FURTHEST_NNE = 1; -static constexpr size_t FURTHEST_NE = 2; -static constexpr size_t FURTHEST_ENE = 3; -static constexpr size_t FURTHEST_E = 4; -static constexpr size_t FURTHEST_ESE = 5; -static constexpr size_t FURTHEST_SE = 6; -static constexpr size_t FURTHEST_SSE = 7; -static constexpr size_t FURTHEST_S = 8; -static constexpr size_t FURTHEST_SSW = 9; -static constexpr size_t FURTHEST_SW = 10; -static constexpr size_t FURTHEST_WSW = 11; -static constexpr size_t FURTHEST_W = 12; -static constexpr size_t FURTHEST_WNW = 13; -static constexpr size_t FURTHEST_NW = 14; -static constexpr size_t FURTHEST_NNW = 15; -static constexpr size_t NUM_DIRECTIONS = 16; - -static constexpr auto MASK_NE = DIRECTION_N & DIRECTION_NE & DIRECTION_E; -static constexpr auto MASK_SE = DIRECTION_S & DIRECTION_SE & DIRECTION_E; -static constexpr auto MASK_SW = DIRECTION_S & DIRECTION_SW & DIRECTION_W; -static constexpr auto MASK_NW = DIRECTION_N & DIRECTION_NW & DIRECTION_W; -// mask of sides that would need to be burned for direction to not matter -static constexpr std::array DIRECTION_MASKS{ - DIRECTION_N, - MASK_NE, - MASK_NE, - MASK_NE, - DIRECTION_E, - MASK_SE, - MASK_SE, - MASK_SE, - DIRECTION_S, - MASK_SW, - MASK_SW, - MASK_SW, - DIRECTION_W, - MASK_NW, - MASK_NW, - MASK_NW}; - -class CellPointsMap; -// using dist_pt = pair; -// using array_cellpts = std::array; -using array_dists = std::array; -using array_pts = std::array; -using array_cellpts = std::tuple; -// using array_cellpts = std::array; -class CellPointArrays - : public array_cellpts -{ -public: - using array_cellpts::array_cellpts; - inline const array_dists& distances() const - { - return std::get<0>(*this); - } - inline const array_pts& points() const - { - return std::get<1>(*this); - } - inline array_dists& distances() - { - return std::get<0>(*this); - } - inline array_pts& points() - { - return std::get<1>(*this); - } -}; -/** - * Points in a cell furthest in each direction - */ -class CellPoints -{ -public: - using spreading_points = map>>; - CellPoints() noexcept; - // // HACK: so we can emplace with NULL - // CellPoints(size_t) noexcept; - // HACK: so we can emplace with nullptr - CellPoints(const CellPoints* rhs) noexcept; - // CellPoints(const vector& pts) noexcept; - CellPoints( - const XYSize x, - const XYSize y) noexcept; - CellPoints(CellPoints&& rhs) noexcept = default; - CellPoints(const CellPoints& rhs) noexcept = default; - CellPoints& operator=(CellPoints&& rhs) noexcept = default; - CellPoints& operator=(const CellPoints& rhs) noexcept = default; - CellPoints& insert( - const XYSize x, - const XYSize y) noexcept; - CellPoints& insert(const InnerPos& p) noexcept; - // template - // CellPoints& insert(_ForwardIterator begin, _ForwardIterator end) - // { - // // don't do anything if empty - // if (end != begin) - // { - // auto it = begin; - // while (end != it) - // { - // (*it); - // ++it; - // } - // } - // return *this; - // } - CellPoints& merge(const CellPoints& rhs); - set unique() const noexcept; - bool operator<(const CellPoints& rhs) const noexcept; - bool operator==(const CellPoints& rhs) const noexcept; - [[nodiscard]] Location location() const noexcept; - void clear(); - // const array_pts points() const; - bool empty() const; - // DurationSize arrival_time_; - // IntensitySize intensity_at_arrival_; - // ROSSize ros_at_arrival_; - // Direction raz_at_arrival_; - // friend CellPointsMap; - // FIX: just access directly for now -public: - CellPointArrays pts_; - // use Idx instead of Location so it can be negative (invalid) - CellPos cell_x_y_; -private: - CellPoints(const Idx cell_x, const Idx cell_y) noexcept; - CellPoints(const XYPos& p) noexcept; -}; - -using spreading_points = CellPoints::spreading_points; -class Scenario; -// map that merges items when try_emplace doesn't insert -class CellPointsMap -{ -public: - CellPointsMap(); - // CellPoints& insert( - // const DurationSize& arrival_time, - // const IntensitySize intensity, - // const ROSSize& ros, - // const Direction& raz, - // const XYSize x, - // const XYSize y) noexcept; - CellPoints& insert( - const XYSize x, - const XYSize y) noexcept; - CellPointsMap& merge( - const BurnedData& unburnable, - const CellPointsMap& rhs) noexcept; - set unique() const noexcept; -#ifdef DEBUG_CELLPOINTS - size_t size() const noexcept; -#endif - // apply function to each CellPoints within and remove matches - void remove_if(std::function&)> F) noexcept; - // FIX: public for debugging right now - // private: - map map_; -}; -} diff --git a/firestarr/src/cpp/ConstantGrid.h b/firestarr/src/cpp/ConstantGrid.h deleted file mode 100644 index 23c1589bc..000000000 --- a/firestarr/src/cpp/ConstantGrid.h +++ /dev/null @@ -1,465 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include -#include -#include -#include -#include "Grid.h" -#include "Util.h" -#include "Settings.h" -#include "FuelType.h" -namespace fs::data -{ -/** - * \brief A GridData> that cannot change once initialized. - * \tparam T The initialization value type. - * \tparam V The initialized value type. - */ -template -class ConstantGrid - : public GridData> -{ -public: - /** - * \brief Value for grid at given Location. - * \param location Location to get value for. - * \return Value at grid Location. - */ - [[nodiscard]] constexpr T at(const Location& location) const noexcept override - { -#ifdef DEBUG_GRIDS - logging::check_fatal(location.row() >= this->rows() || location.column() >= this->columns(), "Out of bounds (%d, %d)", location.row(), location.column()); -#endif -#ifdef DEBUG_POINTS - { - const Location loc{location.row(), location.column()}; - logging::check_equal( - loc.column(), - location.column(), - "column"); - logging::check_equal( - loc.row(), - location.row(), - "row"); - // if we're going to use the hash then we need to make sure it actually matches - logging::check_equal( - loc.hash(), - location.hash(), - "hash"); - } -#endif - // return at(location.hash()); - return this->data.at(location.hash()); - } - template - [[nodiscard]] constexpr T at(const Position

& position) const noexcept - { - return at(Location{position.hash()}); - } - /** - * \brief Value for grid at given Location. - * \param hash HashSize hash for Location to get value for. - * \return Value at grid Location. - */ - // [[nodiscard]] constexpr T at(const HashSize hash) const noexcept - // { - // return this->data.at(hash); - // } - /** - * \brief Throw an error because ConstantGrid can't change values. - */ - // ! @cond Doxygen_Suppress - void set(const Location&, const T) override - // ! @endcond - { - throw runtime_error("Cannot change ConstantGrid"); - } - ~ConstantGrid() = default; - ConstantGrid(const ConstantGrid& rhs) noexcept = delete; - ConstantGrid(ConstantGrid&& rhs) noexcept = delete; - ConstantGrid& operator=(const ConstantGrid& rhs) noexcept = delete; - ConstantGrid& operator=(ConstantGrid&& rhs) noexcept = delete; - /** - * \brief Constructor - * \param cell_size Cell width and height (m) - * \param rows Number of rows - * \param columns Number of columns - * \param nodata_input Value that represents no data for type V - * \param nodata_value Value that represents no data for type T - * \param xllcorner Lower left corner X coordinate (m) - * \param yllcorner Lower left corner Y coordinate (m) - * \param xurcorner Upper right corner X coordinate (m) - * \param yurcorner Upper right corner Y coordinate (m) - * \param proj4 Proj4 projection definition - * \param data Data to set as grid data - */ - ConstantGrid(const MathSize cell_size, - const Idx rows, - const Idx columns, - const V nodata_input, - const T nodata_value, - const MathSize xllcorner, - const MathSize yllcorner, - const MathSize xurcorner, - const MathSize yurcorner, - string&& proj4, - vector&& data) - : GridData>(cell_size, - rows, - columns, - nodata_input, - nodata_value, - xllcorner, - yllcorner, - xurcorner, - yurcorner, - std::forward(proj4), - std::move(data)) - { - } - /** - * \brief Constructor - * \param cell_size Cell width and height (m) - * \param rows Number of rows - * \param columns Number of columns - * \param nodata_input Value that represents no data for type V - * \param nodata_value Value that represents no data for type T - * \param xllcorner Lower left corner X coordinate (m) - * \param yllcorner Lower left corner Y coordinate (m) - * \param proj4 Proj4 projection definition - * \param initialization_value Value to initialize entire grid with - */ - ConstantGrid(const MathSize cell_size, - const Idx rows, - const Idx columns, - const V nodata_input, - const T nodata_value, - const MathSize xllcorner, - const MathSize yllcorner, - const string& proj4, - const T& initialization_value) noexcept - : ConstantGrid(cell_size, - rows, - columns, - nodata_input, - nodata_value, - xllcorner, - yllcorner, - proj4, - std::move(vector(static_cast(MAX_ROWS) * MAX_COLUMNS, - initialization_value))) - { - } - /** - * \brief Read a section of a TIFF into a ConstantGrid - * \param filename File name to read from - * \param tif Pointer to open TIFF denoted by filename - * \param gtif Pointer to open geotiff denoted by filename - * \param point Point to center ConstantGrid on - * \param convert Function taking int and nodata int value that returns T - * \return ConstantGrid containing clipped data for TIFF - */ - [[nodiscard]] static ConstantGrid* readTiff(const string& filename, - TIFF* tif, - GTIF* gtif, - const topo::Point& point, - std::function convert) - { - logging::info("Reading file %s", filename.c_str()); -#ifdef DEBUG_GRIDS - // auto min_value = std::numeric_limits::max(); - // auto max_value = std::numeric_limits::min(); - auto min_value = std::numeric_limits::max(); - auto max_value = std::numeric_limits::min(); -#endif - logging::debug("Reading a raster where T = %s, V = %s", - typeid(T).name(), - typeid(V).name()); - logging::debug("Raster type V has limits %ld, %ld", - std::numeric_limits::min(), - std::numeric_limits::max()); - const GridBase grid_info = read_header(tif, gtif); - uint32_t tile_width; - uint32_t tile_length; - TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_length); - void* data; - uint32_t count; - TIFFGetField(tif, TIFFTAG_GDAL_NODATA, &count, &data); - logging::check_fatal(0 == count, "NODATA value is not set in input"); - logging::debug("NODATA value is '%s'", static_cast(data)); - const auto nodata_orig = stoi(string(static_cast(data))); - logging::debug("NODATA value is originally parsed as %d", nodata_orig); - const auto nodata_input = static_cast(stoi(string(static_cast(data)))); - logging::debug("NODATA value is parsed as %d", nodata_input); - auto actual_rows = grid_info.calculateRows(); - auto actual_columns = grid_info.calculateColumns(); - const auto coordinates = grid_info.findFullCoordinates(point, true); - logging::note("Coordinates before reading are (%d, %d => %f, %f)", - std::get<0>(*coordinates), - std::get<1>(*coordinates), - std::get<0>(*coordinates) + std::get<2>(*coordinates) / 1000.0, - std::get<1>(*coordinates) + std::get<3>(*coordinates) / 1000.0); - auto min_column = max(static_cast(0), - static_cast(std::get<1>(*coordinates) - static_cast(MAX_COLUMNS) / static_cast(2))); - if (min_column + MAX_COLUMNS >= actual_columns) - { - min_column = max(static_cast(0), actual_columns - MAX_COLUMNS); - } - // make sure we're at the start of a tile - const auto tile_column = tile_width * static_cast(min_column / tile_width); - const auto max_column = static_cast(min(min_column + MAX_COLUMNS - 1, actual_columns)); -#ifdef DEBUG_GRIDS - logging::check_fatal(min_column < 0, "Column can't be less than 0"); - logging::check_fatal(max_column - min_column > MAX_COLUMNS, "Can't have more than %d columns", MAX_COLUMNS); - logging::check_fatal(max_column > actual_columns, "Can't have more than actual %d columns", actual_columns); -#endif - auto min_row = max(static_cast(0), - static_cast(std::get<0>(*coordinates) - static_cast(MAX_ROWS) / static_cast(2))); - if (min_row + MAX_COLUMNS >= actual_rows) - { - min_row = max(static_cast(0), actual_rows - MAX_ROWS); - } - const auto tile_row = tile_width * static_cast(min_row / tile_width); - const auto max_row = static_cast(min(min_row + MAX_ROWS - 1, actual_rows)); -#ifdef DEBUG_GRIDS - logging::check_fatal(min_row < 0, "Row can't be less than 0 but is %d", min_row); - logging::check_fatal(max_row - min_row > MAX_ROWS, "Can't have more than %d rows but have %d", MAX_ROWS, max_row - min_row); - logging::check_fatal(max_row > actual_rows, "Can't have more than actual %d rows", actual_rows); -#endif - T nodata_value = convert(nodata_input, nodata_input); - logging::check_fatal( - convert(nodata_input, nodata_input) != nodata_value, - "Expected nodata value to be returned from convert()"); - vector values(static_cast(MAX_ROWS) * MAX_COLUMNS, nodata_value); - logging::verbose("%s: malloc start", filename.c_str()); - int bps = std::numeric_limits::digits + (1 * std::numeric_limits::is_signed); - uint16_t bps_file; - TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps_file); - logging::check_fatal(bps != bps_file, - "Raster %s type is not expected type (%d bits instead of %d)", - filename.c_str(), - bps_file, - bps); -#ifdef DEBUG_GRIDS - int bps_int16_t = std::numeric_limits::digits + (1 * std::numeric_limits::is_signed); - logging::debug("Size of pointer to int is %ld vs %ld", sizeof(int16_t*), sizeof(V*)); - logging::debug("Raster %s calculated bps for type V is %ld; tif says bps is %ld; int16_t is %ld", - filename.c_str(), - bps, - bps_file, - bps_int16_t); -#endif - const auto tile_size = TIFFTileSize(tif); - logging::debug("Tile size for reading %s is %ld", filename.c_str(), tile_size); - const auto buf = _TIFFmalloc(tile_size); - logging::verbose("%s: read start", filename.c_str()); - const tsample_t smp{}; - logging::debug("Want to clip grid to (%d, %d) => (%d, %d) for a %dx%d raster", - min_row, - min_column, - max_row, - max_column, - actual_rows, - actual_columns); - for (auto h = tile_row; h <= max_row; h += tile_length) - { - for (auto w = tile_column; w <= max_column; w += tile_width) - { - TIFFReadTile(tif, buf, static_cast(w), static_cast(h), 0, smp); - for ( - FullIdx y = 0; - (y < static_cast(tile_length)) && (y + h <= max_row); - ++y) - { - // read in so that (0, 0) has a hash of 0 - const auto y_row = static_cast((h - min_row) + y); - const auto actual_row = (max_row - min_row) - y_row; - if (actual_row >= 0 && actual_row < MAX_ROWS) - { - for ( - auto x = 0; - (x < static_cast(tile_width)) && (x + w <= max_column); - ++x) - { - const auto offset = y * tile_width + x; - const auto actual_column = ((w - min_column) + x); - if (actual_column >= 0 && actual_column < MAX_ROWS) - { - const auto cur_hash = actual_row * MAX_COLUMNS + actual_column; - auto cur = *(static_cast(buf) + offset); -#ifdef DEBUG_GRIDS - min_value = min(cur, min_value); - max_value = max(cur, max_value); -#endif - // try - // { - values.at(cur_hash) = convert(cur, nodata_input); - // logging::check_fatal(Settings::fuelLookup().values.at(cur_hash)); - // } - // // catch (const std::out_of_range& err) - // catch (const std::exception& err) - // { - // logging::error("Error trying to read tiff"); - // logging::debug("cur = %d", cur); - // logging::debug("nodata_input = %d", nodata_input); - // logging::debug("T = %s, V = %s", typeid(T).name(), typeid(V).name()); - // if constexpr (std::is_same_v) - // { - // auto f = static_cast(values.at(cur_hash)); - // logging::debug("fuel %s has code %d", - // fs::fuel::FuelType::safeName(f), - // fs::fuel::FuelType::safeCode(f)); - // } - // logging::warning(err.what()); - // // logging::fatal(err.what()); - // fs::sim::Settings::fuelLookup().listFuels(); - // throw err; - // } - } - } - } - } - } - } - logging::verbose("%s: read end", filename.c_str()); - _TIFFfree(buf); - logging::verbose("%s: free end", filename.c_str()); - const auto new_xll = grid_info.xllcorner() + (static_cast(min_column) * grid_info.cellSize()); - const auto new_yll = grid_info.yllcorner() - + (static_cast(actual_rows) - static_cast(max_row)) - * grid_info.cellSize(); -#ifdef DEBUG_GRIDS - logging::check_fatal(new_yll < grid_info.yllcorner(), - "New yllcorner is outside original grid"); -#endif - logging::verbose("Translated lower left is (%f, %f) from (%f, %f)", - new_xll, - new_yll, - grid_info.xllcorner(), - grid_info.yllcorner()); - const auto num_rows = max_row - min_row + 1; - const auto num_columns = max_column - min_column + 1; - auto result = new ConstantGrid(grid_info.cellSize(), - num_rows, - num_columns, - nodata_input, - nodata_value, - new_xll, - new_yll, - new_xll + (static_cast(num_columns) + 1) * grid_info.cellSize(), - new_yll + (static_cast(num_rows) + 1) * grid_info.cellSize(), - string(grid_info.proj4()), - std::move(values)); - auto new_location = result->findCoordinates(point, false); -#ifdef DEBUG_GRIDS - logging::check_fatal(nullptr == new_location, "Invalid location after reading"); -#endif - logging::note("Coordinates are (%d, %d => %f, %f)", - std::get<0>(*new_location), - std::get<1>(*new_location), - std::get<0>(*new_location) + std::get<2>(*new_location) / 1000.0, - std::get<1>(*new_location) + std::get<3>(*new_location) / 1000.0); -#ifdef DEBUG_GRIDS - logging::note("Values for %s range from %d to %d", - filename.c_str(), - min_value, - max_value); -#endif - return result; - } - /** - * \brief Read a section of a TIFF into a ConstantGrid - * \param filename File name to read from - * \param point Point to center ConstantGrid on - * \param convert Function taking V and nodata V value that returns T - * \return ConstantGrid containing clipped data for TIFF - */ - [[nodiscard]] static ConstantGrid* readTiff(const string& filename, - const topo::Point& point, - std::function convert) - { - return with_tiff*>( - filename, - [&filename, &convert, &point](TIFF* tif, GTIF* gtif) { return readTiff(filename, tif, gtif, point, convert); }); - } - /** - * \brief Read a section of a TIFF into a ConstantGrid - * \param filename File name to read from - * \param point Point to center ConstantGrid on - * \return ConstantGrid containing clipped data for TIFF - */ - [[nodiscard]] static ConstantGrid* readTiff(const string& filename, - const topo::Point& point) - { - return readTiff(filename, point, util::no_convert); - } -protected: - tuple dataBounds() const override - { - Idx min_row = 0; - Idx max_row = this->rows(); - Idx min_column = 0; - Idx max_column = this->columns(); - // const Location loc_min{min_row, min_column}; - // const Location loc_max{max_row, max_column}; - // // #ifdef DEBUG_POINTS - // logging::check_equal( - // loc_min.column(), - // min_column, - // "min_column"); - // logging::check_equal( - // loc_min.row(), - // min_row, - // "min_row"); - // logging::check_equal( - // loc_max.column(), - // max_column, - // "max_column"); - // logging::check_equal( - // loc_max.row(), - // max_row, - // "max_row"); - // // #endif - return tuple{ - min_column, - min_row, - max_column, - max_row}; - } -private: - /** - * \brief Constructor - * \param grid_info GridBase defining Grid area - * \param nodata_input Value that represents no data for type V - * \param nodata_value Value that represents no data for type T - * \param values Values to initialize grid with - */ - ConstantGrid(const GridBase& grid_info, - const V nodata_input, - const T nodata_value, - vector&& values) - : ConstantGrid(grid_info.cellSize(), - nodata_input, - nodata_value, - grid_info.xllcorner(), - grid_info.yllcorner(), - grid_info.xurcorner(), - grid_info.yurcorner(), - string(grid_info.proj4()), - std::move(values)) - { -#ifdef DEBUG_GRIDS - logging::check_fatal( - this->data.size() != static_cast(MAX_ROWS) * MAX_COLUMNS, - "Invalid grid size"); -#endif - } -}; -} diff --git a/firestarr/src/cpp/ConstantWeather.h b/firestarr/src/cpp/ConstantWeather.h deleted file mode 100644 index 2f992d03a..000000000 --- a/firestarr/src/cpp/ConstantWeather.h +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "FireWeather.h" - -namespace fs::sim -{ -static vector* make_constant_weather(const wx::Dc& dc, - const wx::Dmc& dmc, - const wx::Ffmc& ffmc, - const wx::Wind& wind) -{ - static constexpr wx::Temperature TEMP(20.0); - static constexpr wx::RelativeHumidity RH(30.0); - static constexpr wx::Precipitation PREC(0.0); - const auto bui = wx::Bui(dmc, dc); - auto wx = new vector{static_cast(YEAR_HOURS)}; - std::generate(wx->begin(), - wx->end(), - [&wind, &ffmc, &dmc, &dc, &bui]() { - return make_unique( - TEMP, - RH, - wind, - PREC, - ffmc, - dmc, - dc, - wx::Isi(wind.speed(), ffmc), - bui, - wx::Fwi(wx::Isi(wind.speed(), ffmc), bui)) - .release(); - }); - return wx; -} -/** - * \brief A FireWeather stream with the same value for every date and time. - */ -class ConstantWeather final - : public wx::FireWeather -{ -public: - ~ConstantWeather() override = default; - ConstantWeather(const ConstantWeather& rhs) = delete; - ConstantWeather(ConstantWeather&& rhs) = delete; - ConstantWeather& operator=(const ConstantWeather& rhs) = delete; - ConstantWeather& operator=(ConstantWeather&& rhs) = delete; - /** - * \brief A Constant weather stream with only one possible fuel - * \param fuel Fuel to use - * \param start_date Start date for stream - * \param dc Drought Code - * \param bui Build-up Index - * \param dmc Duff Moisture Code - * \param ffmc Fine Fuel Moisture Code - * \param wind Wind - */ - ConstantWeather(const fuel::FuelType* fuel, - const Day start_date, - const wx::Dc& dc, - const wx::Dmc& dmc, - const wx::Ffmc& ffmc, - const wx::Wind& wind) - : ConstantWeather(set{fuel}, - start_date, - dc, - dmc, - ffmc, - wind) - { - } - ConstantWeather(const set& used_fuels, - const Day start_date, - const wx::Dc& dc, - const wx::Dmc& dmc, - const wx::Ffmc& ffmc, - const wx::Wind& wind) - : FireWeather(used_fuels, - start_date, - MAX_DAYS - 1, - make_constant_weather(dc, dmc, ffmc, wind)) - { - } -}; -} diff --git a/firestarr/src/cpp/Duff.cpp b/firestarr/src/cpp/Duff.cpp deleted file mode 100644 index 25d13fab9..000000000 --- a/firestarr/src/cpp/Duff.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Duff.h" -namespace fs::fuel -{ -// const DuffType<172, 464, 139873, -3296, 4904, 568> Duff::FeatherMossUpper{}; -// const DuffType<191, 389, 132628, -1167, 3308, -2604> Duff::FeatherMossLower{}; -const DuffType<124, 218, -88306, -608, 8095, 2735> Duff::SphagnumUpper{}; -// const DuffType<567, 1190, 3273347, -37655, -87849, 26684> Duff::SphagnumLower{}; -const DuffType<181, 427, 90970, -1040, 1165, -646> Duff::FeatherMoss{}; -const DuffType<261, 563, 80359, -393, -591, -340> Duff::Reindeer{}; -// const DuffType<233, 694, 398477, -1800, -3727, -1874> Duff::SedgeMeadowUpper{}; -// const DuffType<449, 915, 290818, -2059, -2319, -420> Duff::SedgeMeadowLower{}; -const DuffType<359, 1220, 3325604, -12220, -21024, -12619> Duff::WhiteSpruce{}; -const DuffType<94, 2220, -198198, -1169, 10414, 782> Duff::Peat{}; -const DuffType<349, 2030, 372276, -1876, -2833, -951> Duff::PeatMuck{}; -// const DuffType<354, 1830, 71813, -1413, -1253, 390> Duff::SedgeMeadowSeney{}; -const DuffType<365, 1900, 451778, -3227, -3644, -362> Duff::PineSeney{}; -const DuffType<307, 1160, 586921, -2737, -5413, -1246> Duff::SprucePine{}; -// const DuffType<352, 1200, 2362934, -8423, -25097, -4902> Duff::GrassSedgeMarsh{}; -// const DuffType<680, 1120, 586921, -2737, -5413, -1246> Duff::SouthernPine{}; -// const DuffType<182, 1380, 336907, -2946, -3002, -4040> Duff::HardwoodSwamp{}; -} diff --git a/firestarr/src/cpp/Duff.h b/firestarr/src/cpp/Duff.h deleted file mode 100644 index 948485001..000000000 --- a/firestarr/src/cpp/Duff.h +++ /dev/null @@ -1,248 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "LookupTable.h" -namespace fs::fuel -{ -/*! \page survival Probability of fire survival - * - * Fire survival is determined for each point at each time step. Survival is - * dependent on FFMC, DMC, and fuel in the cell. If the fire is determined to have - * not survived in both the FFMC and DMC fuels then it is considered extinguished - * and all points in that cell are removed from the simulation. Since the cell is - * already marked as burned at this point, it will not burn again. - * - * Probability of survival is determined as per the papers referenced, with the - * FBP fuel types having been assigned an FFMC duff type and a DMC duff type, - * and the moisture for each type being calculated based off the current indices. - * - * \section References - * - * Lawson, B.D.; Frandsen, W.H.; Hawkes, B.C.; Dalrymple, G.N. 1997. - * Probability of sustained smoldering ignitions for some boreal forest duff types. - * https://cfs.nrcan.gc.ca/pubwarehouse/pdfs/11900.pdf - * - * Frandsen, W.H. 1997. - * Ignition probability of organic soils. - * https://www.nrcresearchpress.com/doi/pdf/10.1139/x97-106 - * - * Anderson, Kerry 2002. - * A model to predict lightning-caused fire occurrences. - * International Journal of Wildland Fire 11, 163-172. - * https://doi.org/10.1071/WF02001 - */ -template -class DuffType; -/** - * \brief Base class for DuffType. - */ -class Duff -{ -public: - virtual ~Duff() = default; - Duff(const Duff& rhs) noexcept = delete; - Duff(Duff&& rhs) noexcept = delete; - Duff& operator=(const Duff& rhs) noexcept = delete; - Duff& operator=(Duff&& rhs) noexcept = delete; - /** - * \brief Equality operator - * \param rhs Duff to compare to - * \return Whether or not these are identical - */ - [[nodiscard]] constexpr bool operator==(const Duff& rhs) const - { - // HACK: only equivalent if identical - return this == &rhs; - } - /** - * \brief Inequality operator - * \param rhs Duff to compare to - * \return Whether or not these are not identical - */ - [[nodiscard]] constexpr bool operator!=(const Duff& rhs) const - { - return !operator==(rhs); - } - /** - * \brief Survival probability calculated using probability of ony survival based on multiple formulae - * \param mc_pct Moisture content (%) - * \return Chance of survival (% / 100) - */ - [[nodiscard]] virtual ThresholdSize probabilityOfSurvival(MathSize mc_pct) const noexcept = 0; - // /** - // * \brief Feather moss (upper) [Frandsen table 2/3] - // */ - // static const DuffType<172, 464, 139873, -3296, 4904, 568> FeatherMossUpper; - // /** - // * \brief Feather moss (lower) [Frandsen table 2/3] - // */ - // static const DuffType<191, 389, 132628, -1167, 3308, -2604> FeatherMossLower; - /** - * \brief Sphagnum (upper) [Frandsen table 2/3] - */ - static const DuffType<124, 218, -88306, -608, 8095, 2735> SphagnumUpper; - // /** - // * \brief Sphagnum (lower) [Frandsen table 2/3] - // */ - // static const DuffType<567, 1190, 3273347, -37655, -87849, 26684> SphagnumLower; - /** - * \brief Feather [Frandsen table 3] - */ - static const DuffType<181, 427, 90970, -1040, 1165, -646> FeatherMoss; - /** - * \brief Reindeer/feather [Frandsen table 2/3] - */ - static const DuffType<261, 563, 80359, -393, -591, -340> Reindeer; - // /** - // * \brief Sedge meadow (upper) [Frandsen table 2/3] - // */ - // static const DuffType<233, 694, 398477, -1800, -3727, -1874> SedgeMeadowUpper; - // /** - // * \brief Sedge meadow (lower) [Frandsen table 2/3] - // */ - // static const DuffType<449, 915, 290818, -2059, -2319, -420> SedgeMeadowLower; - /** - * \brief White spruce duff [Frandsen table 2/3] - */ - static const DuffType<359, 1220, 3325604, -12220, -21024, -12619> WhiteSpruce; - /** - * \brief Peat [Frandsen table 2/3] - */ - static const DuffType<94, 2220, -198198, -1169, 10414, 782> Peat; - /** - * \brief Peat muck [Frandsen table 2/3] - */ - static const DuffType<349, 2030, 372276, -1876, -2833, -951> PeatMuck; - // /** - // * \brief Sedge meadow (Seney) [Frandsen table 2/3] - // */ - // static const DuffType<354, 1830, 71813, -1413, -1253, 390> SedgeMeadowSeney; - /** - * \brief Pine duff (Seney) [Frandsen table 2/3] - */ - static const DuffType<365, 1900, 451778, -3227, -3644, -362> PineSeney; - /** - * \brief Spruce/pine duff [Frandsen table 2/3] - */ - static const DuffType<307, 1160, 586921, -2737, -5413, -1246> SprucePine; - // /** - // * \brief Grass/sedge marsh [Frandsen table 2/3] - // */ - // static const DuffType<352, 1200, 2362934, -8423, -25097, -4902> GrassSedgeMarsh; - // /** - // * \brief Southern pine duff [Frandsen table 2/3] - // */ - // static const DuffType<680, 1120, 586921, -2737, -5413, -1246> SouthernPine; - // /** - // * \brief Hardwood swamp (upper) [Frandsen table 2/3] - // */ - // static const DuffType<182, 1380, 336907, -2946, -3002, -4040> HardwoodSwamp; - // coefficients aren't defined in the table these came from - // static const DuffType Pocosin; - // static const DuffType SwampForest; - // static const DuffType Flatwoods; -protected: - Duff() = default; -}; -/** - * \brief A specific type of Duff layer, and the associated smouldering coefficients. - */ -template -class DuffType final - : public Duff -{ -public: - DuffType() = default; - ~DuffType() override = default; - DuffType(const DuffType& rhs) noexcept = delete; - DuffType(DuffType&& rhs) noexcept = delete; - DuffType& operator=(const DuffType& rhs) noexcept = delete; - DuffType& operator=(DuffType&& rhs) noexcept = delete; - /** - * \brief Probability of survival (% / 100) [eq Ig-1] - * \param mc_pct Moisture content, percentage dry oven weight - * \return Probability of survival (% / 100) [eq Ig-1] - */ - [[nodiscard]] ThresholdSize probabilityOfSurvival(const MathSize mc_pct) const noexcept override - { - return probability_of_survival_(mc_pct); - } - /** - * \brief Inorganic content, percentage oven dry weight - * \return Inorganic content, percentage oven dry weight - */ - [[nodiscard]] static constexpr MathSize ash() - { - return Ash / 10.0; - } - /** - * \brief Organic bulk density (kg/m^3) - * \return Organic bulk density (kg/m^3) - */ - [[nodiscard]] static constexpr MathSize rho() - { - return Rho / 10.0; - } - /** - * \brief B_0 [table 2] - * \return B_0 [table 2] - */ - [[nodiscard]] static constexpr MathSize b0() - { - return B0 / 10000.0; - } - /** - * \brief B_1 [table 2] - * \return B_1 [table 2] - */ - [[nodiscard]] static constexpr MathSize b1() - { - return B1 / 10000.0; - } - /** - * \brief B_2 [table 2] - * \return B_2 [table 2] - */ - [[nodiscard]] static constexpr MathSize b2() - { - return B2 / 10000.0; - } - /** - * \brief B_3 [table 2] - * \return B_3 [table 2] - */ - [[nodiscard]] static constexpr MathSize b3() - { - return B3 / 10000.0; - } -private: - /** - * \brief Constant part of ignition probability equation [eq Ig-1] - */ - static constexpr auto ConstantPart = b0() + b2() * ash() + b3() * rho(); - /** - * \brief Ignition Probability (% / 100) [eq Ig-1] - * \param mc_pct Moisture content, percentage dry oven weight - * \return Ignition Probability (% / 100) [eq Ig-1] - */ - [[nodiscard]] static ThresholdSize - duffFunction(const MathSize mc_pct) noexcept - { - const auto d = 1 + exp(-(b1() * mc_pct + ConstantPart)); - if (0 == d) - { - return 1.0; - } - return 1.0 / d; - } - /** - * \brief Ignition Probability (% / 100) [eq Ig-1] - * \param mc_pct Moisture content, percentage dry oven weight - * \return Ignition Probability (% / 100) [eq Ig-1] - */ - util::LookupTable<&duffFunction> probability_of_survival_{}; -}; -} diff --git a/firestarr/src/cpp/Environment.cpp b/firestarr/src/cpp/Environment.cpp deleted file mode 100644 index 965f0c700..000000000 --- a/firestarr/src/cpp/Environment.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Environment.h" -#include "EnvironmentInfo.h" -#include "FuelLookup.h" -#include "ProbabilityMap.h" -#include "Scenario.h" -#include "Settings.h" - -namespace fs -{ -namespace topo -{ -using util::sq; -Environment::~Environment() -{ - delete cells_; -} -Environment Environment::load(const string dir_out, - const Point& point, - const string& in_fuel, - const string& in_elevation) -{ - logging::note("Fuel raster is %s", in_fuel.c_str()); - logging::debug("Loading grids async"); - auto fuel = async(launch::async, [&in_fuel, &point]() { return FuelGrid::readTiff(in_fuel, point, sim::Settings::fuelLookup()); }); - auto elevation = async(launch::async, [&in_elevation, &point]() { return ElevationGrid::readTiff(in_elevation, point); }); - logging::debug("Waiting for grids"); - return Environment(dir_out, - *unique_ptr(fuel.get()), - *unique_ptr(elevation.get()), - point); -} -sim::ProbabilityMap* Environment::makeProbabilityMap(const DurationSize time, - const DurationSize start_time) const -{ - return new sim::ProbabilityMap(dir_out_, - time, - start_time, - *cells_); -} -Environment Environment::loadEnvironment(const string dir_out, - const string& path, - const Point& point, - const string& perimeter, - const int year) -{ - logging::note("Using ignition point (%f, %f)", point.latitude(), point.longitude()); - logging::info("Running using inputs directory '%s'", path.c_str()); - auto rasters = util::find_rasters(path, year); - auto best_score = numeric_limits::min(); - unique_ptr env_info = nullptr; - unique_ptr for_info = nullptr; - string best_fuel = ""; - string best_elevation = ""; - auto found_best = false; - if (!perimeter.empty()) - { - for_info = make_unique(data::read_header(perimeter)); - logging::info("Perimeter projection is %s", for_info->proj4().c_str()); - } - for (const auto& raster : rasters) - { - auto fuel = raster; - logging::verbose("Replacing directory separators in path for: %s\n", fuel.c_str()); - // make sure we're using a consistent directory separator - std::replace(fuel.begin(), fuel.end(), '\\', '/'); - // HACK: assume there's only one instance of 'fuel' in the file name we want to change - const auto find_what = string("fuel"); - const auto find_len = find_what.length(); - const auto find_start = fuel.find(find_what, fuel.find_last_of('/')); - const auto elevation = string(fuel).replace(find_start, find_len, "dem"); - unique_ptr cur_info = EnvironmentInfo::loadInfo( - fuel, - elevation); - // want the raster that's going to give us the most room to spread, so pick the one with the most - // cells between the ignition and the edge on the side where it's closest to the edge - // FIX: need to pick raster that aligns with perimeter if we have one - // - for now at least ensure the same projection - if (nullptr != for_info && 0 != strcmp(for_info->proj4().c_str(), cur_info->proj4().c_str())) - { - continue; - } - // FIX: just worrying about distance from specified lat/long for now, but should pick based on bounds of perimeter - // flipped because we're reading from a raster so change (left, top) to (left, bottom) - const auto coordinates = cur_info->findFullCoordinates(point, true); - if (nullptr != coordinates) - { - auto actual_rows = cur_info->calculateRows(); - auto actual_columns = cur_info->calculateColumns(); - const auto x = std::get<0>(*coordinates); - const auto y = std::get<1>(*coordinates); - logging::note("Coordinates before reading are (%d, %d => %f, %f)", - x, - y, - x + std::get<2>(*coordinates) / 1000.0, - y + std::get<3>(*coordinates) / 1000.0); - // if it's not in the raster then this is not an option - // FIX: are these +/-1 because of counting the cell itself and starting from 0? - const auto dist_W = x; - const auto dist_E = actual_columns - x; - const auto dist_N = actual_rows - y; - const auto dist_S = y; - // FIX: should take size of cells into account too? But is largest areas or highest resolution the priority? - logging::note( - "Coordinates distance to bottom left is: (%d, %d) and top right is (%d, %d)", - dist_W, - dist_S, - dist_E, - dist_N); - // shortest hypoteneuse is the closest corner to the origin, so want highest value for this - const auto cur_score = sq(min(dist_W, dist_E)) + sq(min(dist_N, dist_S)); - if (cur_score > best_score) - { - best_score = cur_score; - best_fuel = fuel; - best_elevation = elevation; - found_best = true; - } - } - } - if (nullptr == env_info && found_best) - { - env_info = EnvironmentInfo::loadInfo( - best_fuel, - best_elevation); - } - logging::check_fatal( - nullptr == env_info, - "Could not find an environment to use for (%f, %f)", - point.latitude(), - point.longitude()); - logging::debug( - "Best match for (%f, %f) has projection '%s'", - point.latitude(), - point.longitude(), - env_info->proj4().c_str()); - // envInfo should get deleted automatically because it uses unique_ptr - return env_info->load(dir_out, point); -} -unique_ptr Environment::findCoordinates(const Point& point, - const bool flipped) const -{ - return cells_->findCoordinates(point, flipped); -} -} -} diff --git a/firestarr/src/cpp/Environment.h b/firestarr/src/cpp/Environment.h deleted file mode 100644 index 21b0bbc30..000000000 --- a/firestarr/src/cpp/Environment.h +++ /dev/null @@ -1,489 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "stdafx.h" -#include "Cell.h" -#include "ConstantGrid.h" -#include "Event.h" -#include "FuelType.h" -#include "GridMap.h" -#include "IntensityMap.h" -#include "Point.h" -#include "Settings.h" - -namespace fs::topo -{ -using FuelGrid = data::ConstantGrid; -using ElevationGrid = data::ConstantGrid; -using CellGrid = data::ConstantGrid; -/*! - * \page environment Fire environment - * - * The fuel, slope, aspect, and elevation used in simulations is consistent through - * all simulations. These attributes are loaded from rasters at the start of the - * process. These rasters must be in a UTM projection, and all rasters for a zone - * must be named consistently across the different attributes. The GIS scripts - * provided in the FireSTARR project can generate these rasters for you. - * - * Elevation is only read at the ignition point and it is assumed that elevation - * is the same wherever it is used in a calculation. Despite this, slope and aspect - * are used for calculations in each cell, and it is only where elevation is - * specifically used in a formula that this value is referenced. - * - * Fuel requires a .lut lookup table file, in the same format that Prometheus - * expects. - * - * Grass curing and leaf-on/off are determined based on time of year, as described - * elsewhere. - * - * Weather and ignition point(s) are the only things that vary between simulations - * at this point. - */ -/** - * \brief The area that a Model is run for, with Fuel, Slope, and Aspect grids. - */ -class Environment -{ -public: - /** - * \brief Load from rasters in folder that have same projection as Perimeter - * \param dir_out Folder to save outputs to - * \param path Folder to read rasters from - * \param point Origin point - * \param perimeter Perimeter to use projection from - * \param year Year to look for rasters for if available - * \return Environment - */ - [[nodiscard]] static Environment loadEnvironment( - const string dir_out, - const string& path, - const Point& point, - const string& perimeter, - int year); - /** - * \brief Load from rasters - * \param point Origin point - * \param in_fuel Fuel raster - * \param in_elevation Elevation raster - * \return Environment - */ - [[nodiscard]] static Environment load(const string dir_out, - const Point& point, - const string& in_fuel, - const string& in_elevation); - ~Environment(); - /** - * \brief Determine Coordinates in the grid for the Point - * \param point Point to find Coordinates for - * \param flipped Whether the grid data is flipped across the horizontal axis - * \return Coordinates that would be at Point within this EnvironmentInfo, or nullptr if it is not - */ - [[nodiscard]] unique_ptr findCoordinates( - const Point& point, - bool flipped) const; - /** - * \brief Move constructor - * \param rhs Environment to move from - */ - Environment(Environment&& rhs) noexcept = default; - /** - * \brief Copy constructor - * \param rhs Environment to copy from - */ - Environment(const Environment& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs Environment to move from - * \return This, after assignment - */ - Environment& operator=(Environment&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs Environment to copy from - * \return This, after assignment - */ - Environment& operator=(const Environment& rhs) noexcept = default; - /** - * \brief UTM projection that this uses - * \return UTM projection that this uses - */ - [[nodiscard]] constexpr const string& proj4() const - { - return cells_->proj4(); - } - /** - * \brief Number of rows in grid - * \return Number of rows in grid - */ - [[nodiscard]] constexpr Idx rows() const - { - return cells_->rows(); - } - /** - * \brief Number of columns in grid - * \return Number of columns in grid - */ - [[nodiscard]] constexpr Idx columns() const - { - return cells_->columns(); - } - /** - * \brief Cell width and height (m) - * \return Cell width and height (m) - */ - [[nodiscard]] constexpr MathSize cellSize() const - { - return cells_->cellSize(); - } - /** - * \brief Elevation of the origin Point - * \return Elevation of the origin Point - */ - [[nodiscard]] constexpr ElevationSize elevation() const - { - return elevation_; - } - /** - * \brief Cell at given row and column - * \param row Row - * \param column Column - * \return Cell at given row and column - */ - [[nodiscard]] -#ifdef NDEBUG - CONSTEXPR -#endif - Cell - cell(const Idx row, const Idx column) const - { - return cells_->at(Location(row, column)); - } - /** - * \brief Cell at given Location - * \param location Location - * \return Cell at given Location - */ - template - [[nodiscard]] constexpr Cell cell(const Position

& position) const - { - return cells_->at(position); - } - /** - * \brief Cell at Location with given hash - * \param hash_size Hash - * \return Cell at Location with given hash - */ - // [[nodiscard]] constexpr Cell cell(const HashSize hash_size) const - // { - // return cells_->at(hash_size); - // } - /** - * \brief Cell at Location with offset of row and column from Location of Event - * \param event Event to use for base Location - * \param row - * \param column - * \return - */ - [[nodiscard]] -#ifdef NDEBUG - CONSTEXPR -#endif - Cell - offset(const sim::Event& event, - const Idx row, - const Idx column) const - { - const auto& p = event.cell(); - // return cell(p.hash() + column + static_cast(MAX_COLUMNS) * row); - return cell(Location(p.row() + row, p.column() + column)); - } - /** - * \brief Make a ProbabilityMap that covers this Environment - * \param time Time in simulation this ProbabilityMap represents - * \param start_time Start time of simulation - * \param min_value Lower bound of 'low' intensity range - * \param low_max Upper bound of 'low' intensity range - * \param med_max Upper bound of 'moderate' intensity range - * \param max_value Upper bound of 'high' intensity range - * \return ProbabilityMap with the same extent as this - */ - [[nodiscard]] sim::ProbabilityMap* makeProbabilityMap(DurationSize time, - DurationSize start_time) const; - /** - * \brief Create a GridMap covering this Environment - * \tparam Other Type of GridMap - * \param nodata Value that represents no data - * \return GridMap covering this Environment - */ - template - [[nodiscard]] unique_ptr> makeMap(const Other nodata) const - { - return make_unique>(*cells_, nodata); - } - /** - * \brief Create BurnedData and set burned bits based on Perimeter - * \return BurnedData with all initially burned locations set - */ - [[nodiscard]] unique_ptr makeBurnedData() const - { - auto result = make_unique(*not_burnable_); - return result; - } - /** - * \brief Reset with known non-fuel cells - * \param data BurnedData to reset - */ - void resetBurnedData(sim::BurnedData* data) const noexcept - { - // setting to {} probably makes a bitset of the same size on the stack? - // *data = {}; - *data = *not_burnable_; - } -protected: - /** - * \brief Combine rasters into ConstantGrid - * \param elevation Elevation raster - * \return - */ - [[nodiscard]] static CellGrid* makeCells( - const FuelGrid& fuel, - const ElevationGrid& elevation) - { - logging::check_equal(fuel.yllcorner(), elevation.yllcorner(), "yllcorner"); - static Cell nodata{}; - auto values = vector{fuel.data.size()}; - vector hashes{}; - // for (HashSize h = 0; h < static_cast(MAX_ROWS) * MAX_COLUMNS; ++h) - // { - // hashes.emplace_back(h); - // } - for (Idx r = 0; r < fuel.rows(); ++r) - { - for (Idx c = 0; c < fuel.columns(); ++c) - { - const auto h = hashes.emplace_back(Location(r, c).hash()); - // hashes.emplace_back(Location(r, c).hash()); - // } - // } - // std::for_each( - // std::execution::par_unseq, - // hashes.begin(), - // hashes.end(), - // [&fuel, &values, &elevation](auto&& h) - // { - const topo::Location loc{r, c, h}; - // const topo::Location loc{static_cast(h / MAX_COLUMNS), - // static_cast(h % MAX_COLUMNS), - // h}; - // const auto r = loc.row(); - // const auto c = loc.column(); - // const auto f = fuel::FuelType::safeCode(fuel.at(h)); - if (r >= 0 && r < fuel.rows() && c >= 0 && c < fuel.columns()) - { - // NOTE: this needs to translate to internal codes? - const auto f = fuel::FuelType::safeCode(fuel.at(loc)); - auto s = static_cast(INVALID_SLOPE); - auto a = static_cast(INVALID_ASPECT); - // HACK: don't calculate for outside box of cells - if (r > 0 && r < fuel.rows() - 1 && c > 0 && c < fuel.columns() - 1) - { - MathSize dem[9]; - bool valid = true; - for (int i = -1; i < 2; ++i) - { - for (int j = -1; j < 2; ++j) - { - // grid is (0, 0) at bottom left, but want [0] in array to be NW corner - auto actual_row = static_cast(r - i); - auto actual_column = static_cast(c + j); - auto cur_loc = Location{actual_row, actual_column}; - // auto cur_h = cur_loc.hash(); - // dem[3 * (i + 1) + (j + 1)] = 1.0 * elevation.at(cur_h); - const auto v = elevation.at(cur_loc); - // can't calculate slope & aspect if any surrounding cell is nodata - if (elevation.nodataValue() == v) - { - valid = false; - break; - } - dem[3 * (i + 1) + (j + 1)] = 1.0 * v; - } - if (!valid) - { - break; - } - } - if (valid) - { - // Horn's algorithm - const MathSize dx = ((dem[2] + dem[5] + dem[5] + dem[8]) - - (dem[0] + dem[3] + dem[3] + dem[6])) - / elevation.cellSize(); - const MathSize dy = ((dem[6] + dem[7] + dem[7] + dem[8]) - - (dem[0] + dem[1] + dem[1] + dem[2])) - / elevation.cellSize(); - const MathSize key = (dx * dx + dy * dy); - auto slope_pct = static_cast(100 * (sqrt(key) / 8.0)); - s = min(static_cast(MAX_SLOPE_FOR_DISTANCE), static_cast(round(slope_pct))); - static_assert(std::numeric_limits::max() >= MAX_SLOPE_FOR_DISTANCE); - MathSize aspect_azimuth = 0.0; - - if (s > 0 && (dx != 0 || dy != 0)) - { - aspect_azimuth = atan2(dy, -dx) * M_RADIANS_TO_DEGREES; - // NOTE: need to change this out of 'math' direction into 'real' direction (i.e. N is 0, not E) - aspect_azimuth = (aspect_azimuth > 90.0) ? (450.0 - aspect_azimuth) : (90.0 - aspect_azimuth); - if (aspect_azimuth == 360.0) - { - aspect_azimuth = 0.0; - } - } - - a = static_cast(round(aspect_azimuth)); - } - } - const auto cell = Cell{h, s, a, f}; - values.at(h) = cell; -#ifdef DEBUG_GRIDS -#ifndef VLD_RPTHOOK_INSTALL - logging::check_equal(cell.row(), r, "Cell row"); - logging::check_equal(cell.column(), c, "Cell column"); - const auto v = values.at(h); - logging::check_equal(v.row(), r, "Row"); - logging::check_equal(v.column(), c, "Column"); - if (!(INVALID_SLOPE == cell.slope() || INVALID_ASPECT == cell.aspect() || INVALID_FUEL_CODE == cell.fuelCode())) - { - logging::check_equal(cell.slope(), s, "Cell slope"); - logging::check_equal(v.slope(), s, "Slope"); - if (0 != s) - { - logging::check_equal(cell.aspect(), a, "Cell aspect"); - logging::check_equal(v.aspect(), a, "Aspect"); - } - else - { - logging::check_equal(cell.aspect(), a, "Cell aspect when slope is 0"); - logging::check_equal(v.aspect(), 0, "Aspect when slope is 0"); - } - logging::check_equal(v.fuelCode(), f, "Fuel"); - logging::check_equal(cell.fuelCode(), f, "Cell fuel"); - } - else - { - logging::check_equal(cell.slope(), INVALID_SLOPE, "Invalid Cell slope"); - logging::check_equal(cell.aspect(), INVALID_ASPECT, "Invalid Cell aspect"); - logging::check_equal(cell.fuelCode(), INVALID_FUEL_CODE, "Invalid Cell fuel"); - logging::check_equal(v.slope(), INVALID_SLOPE, "Invalid slope"); - logging::check_equal(v.aspect(), INVALID_ASPECT, "Invalid aspect"); - logging::check_equal(v.fuelCode(), INVALID_FUEL_CODE, "Invalid fuel"); - } -#endif -#endif - } - // }); - } - } - return new topo::CellGrid( - fuel.cellSize(), - fuel.rows(), - fuel.columns(), - nodata.fullHash(), - nodata, - fuel.xllcorner(), - fuel.yllcorner(), - fuel.xurcorner(), - fuel.yurcorner(), - string(fuel.proj4()), - std::move(values)); - } - /** - * \brief Creates a map of areas that are not burnable either because of fuel or the initial perimeter. - */ - shared_ptr initializeNotBurnable(const CellGrid& cells) const - { - // shared_ptr result{}; - // std::fill(not_burnable_.begin(), not_burnable_.end(), false); - // make a template we can copy to reset things - auto result = make_shared(); - for (Idx r = 0; r < rows(); ++r) - { - for (Idx c = 0; c < columns(); ++c) - { - const Location location(r, c); - // HACK: just mark outside edge as unburnable so we never need to check - bool is_outer = 0 == r - || 0 == c - || (rows() - 1) == r - || (columns() - 1) == c; - // (*result)[location.hash()] = (nullptr == fuel::fuel_by_code(cells.at(location).fuelCode())); - (*result)[location.hash()] = is_outer || fuel::is_null_fuel(cells.at(location)); - // if (fuel::is_null_fuel(cell(location))) - // { - // not_burnable_[location.hash()] = true; - // } - } - } - return result; - } - /** - * \brief Construct from cells and elevation - * \param cells Cells representing Environment - * \param elevation Elevation at origin Point - */ - Environment(const string dir_out, - CellGrid* cells, - const ElevationSize elevation) noexcept - : dir_out_(dir_out), - cells_(cells), - not_burnable_(initializeNotBurnable(*cells)), - elevation_(elevation) - { - // try - // { - // initializeNotBurnable(); - // } - // catch (...) - // { - // std::terminate(); - // } - } - /** - * \brief Load from rasters - * \param fuel Fuel raster - * \param elevation Elevation raster - */ - Environment( - const string dir_out, - const FuelGrid& fuel, - const ElevationGrid& elevation, - const Point& point) - : Environment(dir_out, - makeCells(fuel, - elevation), - elevation.at(Location(*elevation.findCoordinates(point, false).get()))) - { - // take elevation at point so that if max grid size changes elevation doesn't - logging::note("Start elevation is %d", elevation_); - } -private: -#ifdef CPP23 - const -#endif - string dir_out_; - /** - * \brief Cells representing Environment - */ - CellGrid* cells_; - /** - * \brief BurnedData of cells that are not burnable - */ - shared_ptr not_burnable_; - /** - * \brief Elevation at StartPoint - */ - ElevationSize elevation_; -}; -} diff --git a/firestarr/src/cpp/EnvironmentInfo.cpp b/firestarr/src/cpp/EnvironmentInfo.cpp deleted file mode 100644 index a7f520d23..000000000 --- a/firestarr/src/cpp/EnvironmentInfo.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "EnvironmentInfo.h" -#include "Environment.h" -#include "Settings.h" -namespace fs -{ -namespace topo -{ -EnvironmentInfo::~EnvironmentInfo() = default; -EnvironmentInfo::EnvironmentInfo(string in_fuel, - string in_elevation, - data::GridBase&& fuel, - data::GridBase&& elevation) noexcept - : fuel_(std::move(fuel)), - elevation_(std::move(elevation)), - in_fuel_(std::move(in_fuel)), - in_elevation_(std::move(in_elevation)) -{ - logging::debug("fuel: %dx%d => (%f, %f)", fuel.calculateColumns(), fuel.calculateRows(), fuel.xllcorner(), fuel.yllcorner()); - logging::debug("elevation: %dx%d => (%f, %f)", elevation.calculateColumns(), elevation.calculateRows(), elevation.xllcorner(), elevation.yllcorner()); - logging::check_fatal(!(fuel.calculateRows() == elevation.calculateRows() - && fuel.calculateColumns() == elevation.calculateColumns() - && fuel.cellSize() == elevation.cellSize() - && fuel.xllcorner() == elevation.xllcorner() - && fuel.yllcorner() == elevation.yllcorner()), - "Grids are not aligned"); -} -EnvironmentInfo::EnvironmentInfo(const string& in_fuel, - const string& in_elevation) - : EnvironmentInfo(in_fuel, - in_elevation, - data::read_header(in_fuel), - data::read_header(in_elevation)) -{ -} -unique_ptr EnvironmentInfo::loadInfo(const string& in_fuel, - const string& in_elevation) -{ - auto fuel_async = async(launch::async, [in_fuel]() { return data::read_header(in_fuel); }); - auto elevation_async = async(launch::async, [in_elevation]() { return data::read_header(in_elevation); }); - const auto e = new EnvironmentInfo(in_fuel, - in_elevation, - fuel_async.get(), - elevation_async.get()); - return unique_ptr(e); -} -Environment EnvironmentInfo::load(const string dir_out, const Point& point) const -{ - return Environment::load(dir_out, point, in_fuel_, in_elevation_); -} -unique_ptr EnvironmentInfo::findCoordinates( - const Point& point, - const bool flipped) const -{ - return fuel_.findCoordinates(point, flipped); -} -unique_ptr EnvironmentInfo::findFullCoordinates( - const Point& point, - const bool flipped) const -{ - return fuel_.findFullCoordinates(point, flipped); -} -} -} diff --git a/firestarr/src/cpp/EnvironmentInfo.h b/firestarr/src/cpp/EnvironmentInfo.h deleted file mode 100644 index f67d0d370..000000000 --- a/firestarr/src/cpp/EnvironmentInfo.h +++ /dev/null @@ -1,127 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include "Environment.h" -#include "Grid.h" -namespace fs::topo -{ -/** - * \brief Information regarding an Environment, such as grids to read and location. - */ -class EnvironmentInfo -{ -public: - /** - * \brief Load EnvironmentInfo from given rasters - * \param in_fuel Fuel raster - * \param in_elevation Elevation raster - * \return EnvironmentInfo - */ - [[nodiscard]] static unique_ptr loadInfo(const string& in_fuel, - const string& in_elevation); - ~EnvironmentInfo(); - /** - * \brief Construct from given rasters - * \param in_fuel Fuel raster - * \param in_elevation Elevation raster - */ - EnvironmentInfo(const string& in_fuel, - const string& in_elevation); - /** - * \brief Move constructor - * \param rhs EnvironmentInfo to move from - */ - EnvironmentInfo(EnvironmentInfo&& rhs) noexcept = default; - EnvironmentInfo(const EnvironmentInfo& rhs) = delete; - /** - * \brief Move assignment - * \param rhs EnvironmentInfo to move from - * \return This, after assignment - */ - EnvironmentInfo& operator=(EnvironmentInfo&& rhs) noexcept = default; - EnvironmentInfo& operator=(const EnvironmentInfo& rhs) = delete; - /** - * \brief Determine Coordinates in the grid for the Point - * \param point Point to find Coordinates for - * \param flipped Whether the grid data is flipped across the horizontal axis - * \return Coordinates that would be at Point within this EnvironmentInfo, or nullptr if it is not - */ - [[nodiscard]] unique_ptr findCoordinates( - const Point& point, - bool flipped) const; - /** - * \brief Determine FullCoordinates in the grid for the Point - * \param point Point to find FullCoordinates for - * \param flipped Whether the grid data is flipped across the horizontal axis - * \return Coordinates that would be at Point within this EnvironmentInfo, or nullptr if it is not - */ - [[nodiscard]] unique_ptr findFullCoordinates( - const Point& point, - bool flipped) const; - /** - * \brief Load the full Environment using the given FuelLookup to determine fuels - * \param dir_out Folder to save outputs to - * \param point Origin Point - * \return - */ - [[nodiscard]] Environment load(const string dir_out, - const Point& point) const; - /** - * \brief Number of rows in grid - * \return Number of rows in grid - */ - [[nodiscard]] constexpr FullIdx calculateRows() const - { - return fuel_.calculateRows(); - } - /** - * \brief Number of columns in grid - * \return Number of columns in grid - */ - [[nodiscard]] constexpr FullIdx calculateColumns() const - { - return fuel_.calculateColumns(); - } - /** - * \brief UTM projection that this uses - * \return UTM projection that this uses - */ - [[nodiscard]] constexpr const string& proj4() const - { - return fuel_.proj4(); - } -private: - /** - * \brief Information about fuel raster - */ - data::GridBase fuel_; - /** - * \brief Information about elevation raster - */ - data::GridBase elevation_; - /** - * \brief Fuel raster path - */ - string in_fuel_; - /** - * \brief Elevation raster path - */ - string in_elevation_; - /** - * \brief Constructor - * \param in_fuel Fuel raster path - * \param in_elevation Elevation raster path - * \param fuel Information about fuel raster - * \param elevation Information about elevation raster - */ - EnvironmentInfo(string in_fuel, - string in_elevation, - data::GridBase&& fuel, - data::GridBase&& elevation) noexcept; -}; -} diff --git a/firestarr/src/cpp/Event.h b/firestarr/src/cpp/Event.h deleted file mode 100644 index ca48feb35..000000000 --- a/firestarr/src/cpp/Event.h +++ /dev/null @@ -1,207 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "stdafx.h" -#include "Cell.h" -#include "FireSpread.h" - -namespace fs::sim -{ -using topo::Cell; -using fs::wx::Direction; -/** - * \brief A specific Event scheduled in a specific Scenario. - */ -class Event -{ -public: - /** - * \brief Cell representing no location - */ - static constexpr Cell NoLocation{}; - // HACK: use type, so we can sort without having to give different times to them - /** - * \brief Type of Event - */ - enum Type - { - SAVE, - END_SIMULATION, - NEW_FIRE, - FIRE_SPREAD, - }; - [[nodiscard]] static Event makeEvent( - const DurationSize time, - const Cell& cell, - const Type type) - { - return { - time, - cell, - type, - 0}; - } - /** - * \brief Make simulation end event - * \param time Time to schedule for - * \return Event created - */ - [[nodiscard]] static Event makeEnd(const DurationSize time) - { - return makeEvent( - time, - NoLocation, - END_SIMULATION); - } - /** - * \brief Make new fire event - * \param time Time to schedule for - * \param cell Cell to start new fire in - * \return Event created - */ - [[nodiscard]] static Event makeNewFire( - const DurationSize time, - const Cell& cell) - { - return makeEvent( - time, - cell, - NEW_FIRE); - } - /** - * \brief Make simulation save event - * \param time Time to schedule for - * \return Event created - */ - [[nodiscard]] static Event makeSave(const DurationSize time) - { - return makeEvent( - time, - NoLocation, - SAVE); - } - /** - * \brief Make fire spread event - * \param time Time to schedule for - * \return Event created - */ - [[nodiscard]] static Event makeFireSpread(const DurationSize time) - { - return makeEvent( - time, - NoLocation, - FIRE_SPREAD); - } - /** - * \brief Make fire spread event - * \param time Time to schedule for - * \param intensity Intensity to spread with (kW/m) - * \param cell Cell to spread in - * \return Event created - */ - [[nodiscard]] static Event makeFireSpread( - const DurationSize time, - const Cell& cell) - { - return { - time, - cell, - FIRE_SPREAD, - 0}; - } - ~Event() = default; - /** - * \brief Move constructor - * \param rhs Event to move from - */ - Event(Event&& rhs) noexcept = default; - /** - * \brief Copy constructor - * \param rhs Event to copy from - */ - Event(const Event& rhs) = delete; - /** - * \brief Move assignment - * \param rhs Event to move from - * \return This, after assignment - */ - Event& operator=(Event&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs Event to copy from - * \return This, after assignment - */ - Event& operator=(const Event& rhs) = delete; - /** - * \brief Time of Event (decimal days) - * \return Time of Event (decimal days) - */ - [[nodiscard]] constexpr DurationSize time() const - { - return time_; - } - /** - * \brief Type of Event - * \return Type of Event - */ - [[nodiscard]] constexpr Type type() const - { - return type_; - } - /** - * \brief Duration that Event Cell has been burning (decimal days) - * \return Duration that Event Cell has been burning (decimal days) - */ - [[nodiscard]] constexpr DurationSize timeAtLocation() const - { - return time_at_location_; - } - /** - * \brief Cell Event takes place in - * \return Cell Event takes place in - */ - [[nodiscard]] constexpr const Cell& cell() const - { - return cell_; - } -private: - /** - * \brief Constructor - * \param time Time to schedule for - * \param cell CellIndex for relative Cell that spread into from - * \param source Source that Event is coming from - * \param type Type of Event - * \param intensity Intensity to spread with (kW/m) - * \param time_at_location Duration that Event Cell has been burning (decimal days) - */ - constexpr Event(const DurationSize time, - const Cell& cell, - const Type type, - const DurationSize time_at_location) - : time_(time), - time_at_location_(time_at_location), - cell_(cell), - type_(type) - { - } - /** - * \brief Time to schedule for - */ - DurationSize time_; - /** - * \brief Duration that Event Cell has been burning (decimal days) - */ - DurationSize time_at_location_; - /** - * \brief Cell to spread in - */ - Cell cell_; - /** - * \brief Type of Event - */ - Type type_; -}; -} diff --git a/firestarr/src/cpp/EventCompare.h b/firestarr/src/cpp/EventCompare.h deleted file mode 100644 index 77d8aad4b..000000000 --- a/firestarr/src/cpp/EventCompare.h +++ /dev/null @@ -1,34 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "Event.h" -namespace fs::sim -{ -/** - * \brief Defines how Events are compared for sorting. - */ -struct EventCompare -{ - /** - * \brief Defines ordering on Events - * \param x First Event - * \param y Second Event - * \return Whether first Event is less than second Event - */ - [[nodiscard]] constexpr bool operator()(const Event& x, const Event& y) const - { - if (x.time() == y.time()) - { - if (x.type() == y.type()) - { - return x.cell().hash() < y.cell().hash(); - } - return x.type() < y.type(); - } - return x.time() < y.time(); - } -}; -} diff --git a/firestarr/src/cpp/FBP45.cpp b/firestarr/src/cpp/FBP45.cpp deleted file mode 100644 index 79a454f1e..000000000 --- a/firestarr/src/cpp/FBP45.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "FBP45.h" -using fs::util::LookupTable; -namespace fs::fuel::fbp -{ -MathSize FuelD1::isfD1(const SpreadInfo& spread, - const MathSize ros_multiplier, - const MathSize isi) const noexcept -{ - return limitIsf(ros_multiplier, - spread.slopeFactor() * (ros_multiplier * a()) - * pow(1.0 - exp(negB() * isi), c())); -} -/** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [GLC-X-10 eq 9a/9b] - * \param ffmc Fine Fuel Moisture Code - * \return Surface Fuel Consumption (SFC) (kg/m^2) [GLC-X-10 eq 9a/9b] - */ -[[nodiscard]] static constexpr MathSize - calculate_surface_fuel_consumption_c1(const MathSize ffmc) noexcept -{ - return max(0.0, - 0.75 + ((ffmc > 84) ? 0.75 : -0.75) * sqrt(1 - exp(-0.23 * (ffmc - 84)))); -} -/** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [GLC-X-10 eq 9a/9b] - * \return Surface Fuel Consumption (SFC) (kg/m^2) [GLC-X-10 eq 9a/9b] - */ -static LookupTable<&calculate_surface_fuel_consumption_c1> SURFACE_FUEL_CONSUMPTION_C1{}; -MathSize FuelC1::surfaceFuelConsumption(const SpreadInfo& spread) const noexcept -{ - return SURFACE_FUEL_CONSUMPTION_C1(spread.ffmc().asValue()); -} -MathSize FuelC2::surfaceFuelConsumption(const SpreadInfo& spread) const noexcept -{ - return SURFACE_FUEL_CONSUMPTION_MIXED_OR_C2(spread.bui().asValue()); -} -MathSize FuelC6::finalRos(const SpreadInfo& spread, - const MathSize isi, - const MathSize cfb, - const MathSize rss) const noexcept -{ - const auto rsc = crownRateOfSpread(isi, spread.foliarMoisture()); - // using max with 0 is the same as ensuring rsc > rss - return rss + cfb * max(0.0, rsc - rss); -} -/** - * \brief Forest Floor Consumption (FFC) (kg/m^2) [ST-X-3 eq 13] - * \param ffmc Fine Fuel Moisture Code - * \return Forest Floor Consumption (FFC) (kg/m^2) [ST-X-3 eq 13] - */ -[[nodiscard]] static constexpr MathSize - calculate_surface_fuel_consumption_c7_ffmc(const MathSize ffmc) noexcept -{ - return (ffmc > 70) - ? 2.0 * (1.0 - exp(-0.104 * (ffmc - 70.0))) - : 0.0; -} -/** - * \brief Forest Floor Consumption (FFC) (kg/m^2) [ST-X-3 eq 13] - * \return Forest Floor Consumption (FFC) (kg/m^2) [ST-X-3 eq 13] - */ -static LookupTable<&calculate_surface_fuel_consumption_c7_ffmc> - SURFACE_FUEL_CONSUMPTION_C7_FFMC{}; -/** - * \brief Woody Fuel Consumption (WFC) (kg/m^2) [ST-X-3 eq 14] - * \return Woody Fuel Consumption (WFC) (kg/m^2) [ST-X-3 eq 14] - */ -[[nodiscard]] static MathSize - calculate_surface_fuel_consumption_c7_bui(const MathSize bui) noexcept -{ - return 1.5 * (1.0 - exp(-0.0201 * bui)); -} -/** - * \brief Woody Fuel Consumption (WFC) (kg/m^2) [ST-X-3 eq 14] - * \return Woody Fuel Consumption (WFC) (kg/m^2) [ST-X-3 eq 14] - */ -static LookupTable<&calculate_surface_fuel_consumption_c7_bui> - SURFACE_FUEL_CONSUMPTION_C7_BUI{}; -MathSize FuelC7::surfaceFuelConsumption(const SpreadInfo& spread) const noexcept -{ - return SURFACE_FUEL_CONSUMPTION_C7_FFMC(spread.ffmc().asValue()) + SURFACE_FUEL_CONSUMPTION_C7_BUI(spread.bui().asValue()); -} -[[nodiscard]] static constexpr MathSize - calculate_surface_fuel_consumption_d2(const MathSize bui) noexcept -{ - return bui >= 80 ? 1.5 * (1.0 - exp(-0.0183 * bui)) : 0.0; -} -static LookupTable<&calculate_surface_fuel_consumption_d2> SURFACE_FUEL_CONSUMPTION_D2{}; -MathSize FuelD2::surfaceFuelConsumption(const SpreadInfo& spread) const noexcept -{ - return SURFACE_FUEL_CONSUMPTION_D2(spread.bui().asValue()); -} -MathSize FuelD2::calculateRos(const int, - const wx::FwiWeather& wx, - const MathSize isi) const noexcept -{ - return (wx.bui().asValue() >= 80) ? rosBasic(isi) : 0.0; -} -} diff --git a/firestarr/src/cpp/FBP45.h b/firestarr/src/cpp/FBP45.h deleted file mode 100644 index 9c0442257..000000000 --- a/firestarr/src/cpp/FBP45.h +++ /dev/null @@ -1,1562 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "Duff.h" -#include "FireSpread.h" -#include "LookupTable.h" -#include "StandardFuel.h" -#ifdef DEBUG_FUEL_VARIABLE -#include "Log.h" -#endif -namespace fs::fuel -{ -/** - * \brief Calculate if green-up has occurred - * \param nd Difference between date and the date of minimum foliar moisture content - * \return Whether or no green-up has occurred - */ -[[nodiscard]] constexpr bool calculate_is_green(const int nd) -{ - return nd >= 30; -} -/** - * \brief Use intersection of parabola with y = 120.0 line as point where grass greening starts happening. - */ -static constexpr int START_GREENING = -43; -[[nodiscard]] constexpr int calculate_grass_curing(const int nd) -{ - const auto curing = (nd < START_GREENING) - ? // we're before foliar moisture dip has started - 100 - : (nd >= 50) - ? 0 // foliar moisture is at 120.0, so grass should be totally uncured - // HACK: invent a formula that has 50% curing at the bottom of the foliar - // moisture dip foliar moisture above ranges between 120 and 85, with 85 - // being at the point where we want 50% cured Curing: - // -43 => 100, 0 => 50, 50 => 0 least-squares best fit: - : static_cast(52.5042 - 1.07324 * nd); - return max(0, min(100, curing)); -} -[[nodiscard]] static MathSize - calculate_surface_fuel_consumption_mixed_or_c2(const MathSize bui) noexcept -{ - return 5.0 * (1.0 - exp(-0.0115 * bui)); -} -static const util::LookupTable<&calculate_surface_fuel_consumption_mixed_or_c2> - SURFACE_FUEL_CONSUMPTION_MIXED_OR_C2{}; -[[nodiscard]] static MathSize - calculate_surface_fuel_consumption_d1(const MathSize bui) noexcept -{ - return 1.5 * (1.0 - exp(-0.0183 * bui)); -} -static util::LookupTable<&calculate_surface_fuel_consumption_d1> - SURFACE_FUEL_CONSUMPTION_D1{}; -/** - * \brief A StandardFuel that is not made of multiple fuels. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - * \tparam Bui0 Average Build-up Index for the fuel type [ST-X-3 table 7] - * \tparam Cbh Crown base height (m) [ST-X-3 table 8] - * \tparam Cfl Crown fuel load (kg/m^2) [ST-X-3 table 8] - * \tparam BulkDensity Duff Bulk Density (kg/m^3) [Anderson table 1] * 1000 - * \tparam InorganicPercent Inorganic percent of Duff layer (%) [Anderson table 1] - * \tparam DuffDepth Depth of Duff layer (cm * 10) [Anderson table 1] - */ -template -class FuelNonMixed - : public StandardFuel -{ -public: - FuelNonMixed() = delete; - ~FuelNonMixed() override = default; - FuelNonMixed(const FuelNonMixed& rhs) noexcept = delete; - FuelNonMixed(FuelNonMixed&& rhs) noexcept = delete; - FuelNonMixed& operator=(const FuelNonMixed& rhs) noexcept = delete; - FuelNonMixed& operator=(FuelNonMixed&& rhs) noexcept = delete; -protected: - using StandardFuel:: - StandardFuel; - /** - * \brief ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41] - * \param spread SpreadInfo to use in calculations - * \param isi Initial Spread Index - * \return ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41] - */ - [[nodiscard]] MathSize calculateIsf(const SpreadInfo& spread, - const MathSize isi) const noexcept override - { - return this->limitIsf(1.0, - calculateRos(spread.nd(), - *spread.weather(), - isi) - * spread.slopeFactor()); - } - virtual /** - * \brief Initial rate of spread (m/min) [ST-X-3 eq 26] - * \param isi Initial Spread Index - * \return Initial rate of spread (m/min) [ST-X-3 eq 26] - */ - MathSize - calculateRos(const int, - const wx::FwiWeather&, - const MathSize isi) const noexcept override - { - return this->rosBasic(isi); - } -}; -/** - * \brief A conifer fuel type. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - * \tparam Bui0 Average Build-up Index for the fuel type [ST-X-3 table 7] - * \tparam Cbh Crown base height (m) [ST-X-3 table 8] - * \tparam Cfl Crown fuel load (kg/m^2) [ST-X-3 table 8] - * \tparam BulkDensity Duff Bulk Density (kg/m^3) [Anderson table 1] * 1000 - * \tparam InorganicPercent Inorganic percent of Duff layer (%) [Anderson table 1] - * \tparam DuffDepth Depth of Duff layer (cm * 10) [Anderson table 1] - */ -template -class FuelConifer - : public FuelNonMixed -{ -public: - FuelConifer() = delete; - ~FuelConifer() override = default; - FuelConifer(const FuelConifer& rhs) noexcept = delete; - FuelConifer(FuelConifer&& rhs) noexcept = delete; - FuelConifer& operator=(const FuelConifer& rhs) noexcept = delete; - FuelConifer& operator=(FuelConifer&& rhs) noexcept = delete; -protected: - /** - * \brief A conifer FBP fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param log_q Log value of q [ST-X-3 table 7] - * \param duff_ffmc Type of duff near the surface - * \param duff_dmc Type of duff deeper underground - */ - constexpr FuelConifer(const FuelCodeSize& code, - const char* name, - const LogValue log_q, - const Duff* duff_ffmc, - const Duff* duff_dmc) - : FuelNonMixed(code, - name, - true, - log_q, - duff_ffmc, - duff_dmc) - { - } - /** - * \brief A conifer FBP fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param log_q Log value of q [ST-X-3 table 7] - * \param duff Type of duff near the surface and deeper underground - */ - constexpr FuelConifer(const FuelCodeSize& code, - const char* name, - const LogValue log_q, - const Duff* duff) - : FuelConifer(code, - name, - log_q, - duff, - duff) - { - } -}; -/** - * \brief Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 11] - * \param bui Build-up Index - * \return Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 11] - */ -[[nodiscard]] static MathSize - calculate_surface_fuel_consumption_jackpine( - const MathSize bui) noexcept -{ - return 5.0 * pow(1.0 - exp(-0.0164 * bui), 2.24); -} -/** - * \brief Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 11] - * \return Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 11] - */ -static util::LookupTable<&calculate_surface_fuel_consumption_jackpine> - SURFACE_FUEL_CONSUMPTION_JACKPINE{}; -/** - * \brief A fuel with jackpine as base fuel type. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - * \tparam Bui0 Average Build-up Index for the fuel type [ST-X-3 table 7] - * \tparam Cbh Crown base height (m) [ST-X-3 table 8] - * \tparam Cfl Crown fuel load (kg/m^2) [ST-X-3 table 8] - * \tparam BulkDensity Duff Bulk Density (kg/m^3) [Anderson table 1] * 1000 - * \tparam DuffDepth Depth of Duff layer (cm * 10) [Anderson table 1] - */ -template -class FuelJackpine - : public FuelConifer -{ -public: - FuelJackpine() = delete; - ~FuelJackpine() override = default; - FuelJackpine(const FuelJackpine& rhs) noexcept = delete; - FuelJackpine(FuelJackpine&& rhs) noexcept = delete; - FuelJackpine& operator=(const FuelJackpine& rhs) noexcept = delete; - FuelJackpine& operator=(FuelJackpine&& rhs) noexcept = delete; - using FuelConifer::FuelConifer; - /** - * \brief Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 11] - * \param spread SpreadInfo to use - * \return Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 11] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override - { - return SURFACE_FUEL_CONSUMPTION_JACKPINE(spread.bui().asValue()); - } -}; -/** - * \brief Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 12] - * \param bui Build-up Index - * \return Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 12] - */ -[[nodiscard]] static MathSize - calculate_surface_fuel_consumption_pine(const MathSize bui) noexcept -{ - return 5.0 * pow(1.0 - exp(-0.0149 * bui), 2.48); -} -/** - * \brief Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 12] - * \param bui Build-up Index - * \return Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 12] - */ -static util::LookupTable<&calculate_surface_fuel_consumption_pine> - SURFACE_FUEL_CONSUMPTION_PINE{}; -/** - * \brief A fuel with pine as the base fuel type. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - * \tparam Bui0 Average Build-up Index for the fuel type [ST-X-3 table 7] - * \tparam Cbh Crown base height (m) [ST-X-3 table 8] - * \tparam Cfl Crown fuel load (kg/m^2) [ST-X-3 table 8] - * \tparam BulkDensity Duff Bulk Density (kg/m^3) [Anderson table 1] * 1000 - * \tparam DuffDepth Depth of Duff layer (cm * 10) [Anderson table 1] - */ -template -class FuelPine : public FuelConifer -{ -public: - FuelPine() = delete; - ~FuelPine() override = default; - FuelPine(const FuelPine& rhs) noexcept = delete; - FuelPine(FuelPine&& rhs) noexcept = delete; - FuelPine& operator=(const FuelPine& rhs) noexcept = delete; - FuelPine& operator=(FuelPine&& rhs) noexcept = delete; - using FuelConifer::FuelConifer; - /** - * \brief Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 12] - * \param spread SpreadInfo to use - * \return Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 12] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override - { - return SURFACE_FUEL_CONSUMPTION_PINE(spread.bui().asValue()); - } -}; -namespace fbp -{ -/** - * \brief FBP fuel type D-1. - */ -class FuelD1 : public FuelNonMixed<30, 232, 160, 32, 0, 0, 61, 59, 24> -{ -public: - FuelD1() = delete; - ~FuelD1() override = default; - FuelD1(const FuelD1& rhs) noexcept = delete; - FuelD1(FuelD1&& rhs) noexcept = delete; - FuelD1& operator=(const FuelD1& rhs) noexcept = delete; - FuelD1& operator=(FuelD1&& rhs) noexcept = delete; - /** - * \brief FBP fuel type D-1 - * \param code Code to identify fuel with - */ - explicit constexpr FuelD1(const FuelCodeSize& code) noexcept - : FuelNonMixed(code, - "D-1", - false, - data::LOG_0_90, - &Duff::Peat) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 25] - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 25] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override - { - return SURFACE_FUEL_CONSUMPTION_D1(spread.bui().asValue()); - } - /** - * \brief Calculate ISI with slope influence and zero wind (ISF) for D-1 [ST-X-3 eq 41] - * \param spread SpreadInfo to use - * \param ros_multiplier Rate of spread multiplier [ST-X-3 eq 27/28, GLC-X-10 eq 29/30] - * \param isi Initial Spread Index - * \return ISI with slope influence and zero wind (ISF) for D-1 [ST-X-3 eq 41] - */ - [[nodiscard]] MathSize isfD1(const SpreadInfo& spread, - MathSize ros_multiplier, - MathSize isi) const noexcept; -}; -} -/** - * \brief A mixedwood fuel type. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - * \tparam Bui0 Average Build-up Index for the fuel type [ST-X-3 table 7] - * \tparam RosMultiplier Rate of spread multiplier * 10 [ST-X-3 eq 27/28, GLC-X-10 eq 29/30] - * \tparam PercentMixed Percent conifer or dead fir - * \tparam BulkDensity Duff Bulk Density (kg/m^3) [Anderson table 1] * 1000 - * \tparam InorganicPercent Inorganic percent of Duff layer (%) [Anderson table 1] - * \tparam DuffDepth Depth of Duff layer (cm * 10) [Anderson table 1] - */ -template -class FuelMixed - : public StandardFuel -{ -public: - FuelMixed() = delete; - ~FuelMixed() = default; - FuelMixed(const FuelMixed& rhs) noexcept = delete; - FuelMixed(FuelMixed&& rhs) noexcept = delete; - FuelMixed& operator=(const FuelMixed& rhs) noexcept = delete; - FuelMixed& operator=(FuelMixed&& rhs) noexcept = delete; - /** - * \brief A mixed FBP fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param log_q Log value of q [ST-X-3 table 7] - */ - constexpr FuelMixed(const FuelCodeSize& code, - const char* name, - const LogValue log_q) - : StandardFuel(code, - name, - true, - log_q, - &Duff::Peat, - &Duff::Peat) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 10] - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 10] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override - { - return SURFACE_FUEL_CONSUMPTION_MIXED_OR_C2(spread.bui().asValue()); - } - /** - * \brief Crown Fuel Consumption (CFC) (kg/m^2) [ST-X-3 eq 66, pg 38] - * \param cfb Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \return Crown Fuel Consumption (CFC) (kg/m^2) [ST-X-3 eq 66, pg 38] - */ - [[nodiscard]] MathSize crownConsumption(const MathSize cfb) const noexcept override - { - return ratioConifer() * StandardFuel::crownConsumption(cfb); - } - /** - * \brief Calculate rate of spread (m/min) [ST-X-3 27/28, GLC-X-10 29/31] - * \param isi Initial Spread Index - * \return Calculate rate of spread (m/min) [ST-X-3 27/28, GLC-X-10 29/31] - */ - [[nodiscard]] MathSize calculateRos(const int, - const wx::FwiWeather&, - const MathSize isi) const noexcept override - { - static const fbp::FuelD1 F{14}; - return ratioConifer() * this->rosBasic(isi) + rosMultiplier() * ratioDeciduous() * F.rosBasic(isi); - } - /** - * \brief Calculate ISI with slope influence and zero wind (ISF) [ST-X-3 eq 42] - * \param spread SpreadInfo to use - * \param isi Initial Spread Index - * \return ISI with slope influence and zero wind (ISF) [ST-X-3 eq 42] - */ - [[nodiscard]] MathSize calculateIsf(const SpreadInfo& spread, - const MathSize isi) const noexcept override - { - return ratioConifer() * this->limitIsf(1.0, spread.slopeFactor() * this->rosBasic(isi)) - + ratioDeciduous() * isfD1(spread, isi); - } - /** - * \brief Percent Conifer (% / 100) - * \return Percent Conifer (% / 100) - */ - [[nodiscard]] static constexpr MathSize ratioConifer() - { - return PercentMixed / 100.0; - } - /** - * \brief Percent Deciduous (% / 100) - * \return Percent Deciduous (% / 100) - */ - [[nodiscard]] static constexpr MathSize ratioDeciduous() - { - return 1.0 - (PercentMixed / 100.0); - } -protected: - /** - * \brief Rate of spread multiplier [ST-X-3 eq 27/28, GLC-X-10 eq 29/30] - * \return Rate of spread multiplier [ST-X-3 eq 27/28, GLC-X-10 eq 29/30] - */ - [[nodiscard]] static constexpr MathSize rosMultiplier() - { - return RosMultiplier / 10.0; - } - /** - * \brief Calculate ISI with slope influence and zero wind (ISF) for D-1 [ST-X-3 eq 41] - * \param spread SpreadInfo to use - * \param isi Initial Spread Index - * \return ISI with slope influence and zero wind (ISF) for D-1 [ST-X-3 eq 41] - */ - [[nodiscard]] static MathSize - isfD1(const SpreadInfo& spread, - const MathSize isi) noexcept - { - static const fbp::FuelD1 F{14}; - return F.isfD1(spread, rosMultiplier(), isi); - } -}; -/** - * \brief A fuel made of dead fir and D1. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - * \tparam Bui0 Average Build-up Index for the fuel type [ST-X-3 table 7] - * \tparam RosMultiplier Rate of spread multiplier * 10 [ST-X-3 eq 27/28, GLC-X-10 eq 29/30] - * \tparam PercentDeadFir Percent dead fir in the stand. - */ -template -class FuelMixedDead - : public FuelMixed -{ -public: - FuelMixedDead() = delete; - ~FuelMixedDead() = default; - FuelMixedDead(const FuelMixedDead& rhs) noexcept = delete; - FuelMixedDead(FuelMixedDead&& rhs) noexcept = delete; - FuelMixedDead& operator=(const FuelMixedDead& rhs) noexcept = delete; - FuelMixedDead& operator=(FuelMixedDead&& rhs) noexcept = delete; - /** - * \brief A mixed dead FBP fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param log_q Log value of q [ST-X-3 table 7] - */ - constexpr FuelMixedDead(const FuelCodeSize& code, - const char* name, - const LogValue log_q) - : FuelMixed(code, - name, - log_q) - { - } -}; -/** - * \brief A fuel composed of C2 and D1 mixed. - * \tparam RosMultiplier Rate of spread multiplier * 10 [ST-X-3 eq 27/28, GLC-X-10 eq 29/30] - * \tparam RatioMixed Percent conifer - */ -template -class FuelMixedWood - : public FuelMixed<110, 282, 150, 50, RosMultiplier, RatioMixed, 108, 25, 50> -{ -public: - FuelMixedWood() = delete; - ~FuelMixedWood() = default; - FuelMixedWood(const FuelMixedWood& rhs) noexcept = delete; - FuelMixedWood(FuelMixedWood&& rhs) noexcept = delete; - FuelMixedWood& operator=(const FuelMixedWood& rhs) noexcept = delete; - FuelMixedWood& operator=(FuelMixedWood&& rhs) noexcept = delete; - /** - * \brief A mixedwood FBP fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - */ - constexpr FuelMixedWood(const FuelCodeSize& code, - const char* name) - : FuelMixed<110, 282, 150, 50, RosMultiplier, RatioMixed, 108, 25, 50>(code, - name, - data::LOG_0_80) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 17] - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 17] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override - { - return this->ratioConifer() * FuelMixed<110, 282, 150, 50, RosMultiplier, RatioMixed, 108, 25, 50>::surfaceFuelConsumption(spread) - + this->ratioDeciduous() * SURFACE_FUEL_CONSUMPTION_D1(spread.bui().asValue()); - } -}; -/** - * \brief Length to Breadth ratio [ST-X-3 eq 80/81] - */ -[[nodiscard]] static constexpr MathSize - calculate_length_to_breadth_grass(const MathSize ws) noexcept -{ - return ws < 1.0 ? 1.0 : (1.1 * pow(ws, 0.464)); -} -/** - * \brief Length to Breadth ratio [ST-X-3 eq 80/81] - */ -static util::LookupTable LENGTH_TO_BREADTH_GRASS{}; -/** - * \brief Base multiplier for rate of spread [GLC-X-10 eq 35a/35b] - * \param curing Grass fuel curing rate (%) - * \return Base multiplier for rate of spread [GLC-X-10 eq 35a/35b] - */ -[[nodiscard]] static constexpr MathSize - calculate_base_multiplier_curing(const MathSize curing) noexcept -{ - return (curing >= 58.8) - ? (0.176 + 0.02 * (curing - 58.8)) - : (0.005 * expm1(0.061 * curing)); -} -/** - * \brief Base multiplier for rate of spread [GLC-X-10 eq 35a/35b] - * \return Base multiplier for rate of spread [GLC-X-10 eq 35a/35b] - */ -static util::LookupTable<&calculate_base_multiplier_curing> BASE_MULTIPLIER_CURING{}; -/** - * \brief A grass fuel type. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - */ -template -class FuelGrass - : public StandardFuel(DUFF_FFMC_DEPTH * 10.0)> -{ -public: - FuelGrass() = delete; - ~FuelGrass() override = default; - FuelGrass(const FuelGrass& rhs) noexcept = delete; - FuelGrass(FuelGrass&& rhs) noexcept = delete; - FuelGrass& operator=(const FuelGrass& rhs) noexcept = delete; - FuelGrass& operator=(FuelGrass&& rhs) noexcept = delete; - /** - * \brief A grass fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param log_q Log value of q [ST-X-3 table 7] - */ - constexpr FuelGrass(const FuelCodeSize& code, - const char* name, - const LogValue log_q) - // HACK: grass assumes no duff (total duff depth == ffmc depth => dmc depth is 0) - : StandardFuel(DUFF_FFMC_DEPTH * 10.0)>(code, - name, - false, - log_q, - &Duff::PeatMuck, - &Duff::PeatMuck) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 pg 21] - * \return Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 pg 21] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo&) const noexcept override - { - return DEFAULT_GRASS_FUEL_LOAD; - } - /** - * \brief Grass curing - * \return Grass curing (or -1 if invalid for this fuel type) - */ - [[nodiscard]] MathSize grass_curing(const int nd, const wx::FwiWeather& wx) const override - { - return wx.dc().asValue() > 500 - ? // we're in drought conditions - 100 - : calculate_grass_curing(nd); - } - /** - * \brief Calculate base rate of spread multiplier - * \param nd Difference between date and the date of minimum foliar moisture content - * \param wx FwiWeather to use for calculation - * \return Base rate of spread multiplier - */ - [[nodiscard]] MathSize baseMultiplier(const int nd, - const wx::FwiWeather& wx) const noexcept - { - return BASE_MULTIPLIER_CURING(grass_curing(nd, wx)); - } - /** - * \brief Calculate ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41] - * \param spread SpreadInfo to use - * \param isi Initial Spread Index - * \return ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41] - */ - [[nodiscard]] MathSize calculateIsf(const SpreadInfo& spread, - const MathSize isi) const noexcept override - { - const auto mu = baseMultiplier(spread.nd(), *spread.weather()); - // prevent divide by 0 - const auto mu_not_zero = max(0.001, mu); - return this->limitIsf(mu_not_zero, calculateRos(mu, isi) * spread.slopeFactor()); - } - /** - * \brief Calculate rate of spread (m/min) - * \param nd Difference between date and the date of minimum foliar moisture content - * \param wx FwiWeather to use for calculation - * \param isi Initial Spread Index (may differ from wx because of slope) - * \return Rate of spread (m/min) - */ - [[nodiscard]] MathSize calculateRos(const int nd, - const wx::FwiWeather& wx, - const MathSize isi) const noexcept override - { - return calculateRos(baseMultiplier(nd, wx), isi); - } -protected: - /** - * \brief Length to Breadth ratio [ST-X-3 eq 80/81] - * \param ws Wind Speed (km/h) - * \return Length to Breadth ratio [ST-X-3 eq 80/81] - */ - [[nodiscard]] MathSize lengthToBreadth(const MathSize ws) const noexcept override - { - return LENGTH_TO_BREADTH_GRASS(ws); - } -private: - /** - * \brief Calculate rate of spread (m/min) - * \param multiplier Rate of spread multiplier - * \param isi Initial Spread Index (may differ from wx because of slope) - * \return Rate of spread (m/min) - */ - [[nodiscard]] MathSize calculateRos(const MathSize multiplier, - const MathSize isi) const noexcept - { - return multiplier * this->rosBasic(isi); - } -}; -namespace fbp -{ -/** - * \brief FBP fuel type C-1. - */ -class FuelC1 : public FuelConifer<90, 649, 450, 72, 2, 75, 45, 5, 34> -{ -public: - FuelC1() = delete; - ~FuelC1() override = default; - FuelC1(const FuelC1& rhs) noexcept = delete; - FuelC1(FuelC1&& rhs) noexcept = delete; - FuelC1& operator=(const FuelC1& rhs) noexcept = delete; - FuelC1& operator=(FuelC1&& rhs) noexcept = delete; - /** - * \brief FBP fuel type C-1 - * \param code Code to identify fuel with - */ - explicit constexpr FuelC1(const FuelCodeSize& code) noexcept - : FuelConifer(code, - "C-1", - data::LOG_0_90, - &Duff::Reindeer, - &Duff::Peat) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [GLC-X-10 eq 9a/9b] - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) [GLC-X-10 eq 9a/9b] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override; -}; -/** - * \brief FBP fuel type C-2. - */ -class FuelC2 : public FuelConifer<110, 282, 150, 64, 3, 80, 34, 0, 100> -{ -public: - FuelC2() = delete; - ~FuelC2() override = default; - FuelC2(const FuelC2& rhs) noexcept = delete; - FuelC2(FuelC2&& rhs) noexcept = delete; - FuelC2& operator=(const FuelC2& rhs) noexcept = delete; - FuelC2& operator=(FuelC2&& rhs) noexcept = delete; - /** - * \brief FBP fuel type C-2 - * \param code Code to identify fuel with - */ - explicit constexpr FuelC2(const FuelCodeSize& code) noexcept - : FuelConifer(code, - "C-2", - data::LOG_0_70, - &Duff::SphagnumUpper) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 10] - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 10] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override; -}; -/** - * \brief FBP fuel type C-3. - */ -class FuelC3 : public FuelJackpine<110, 444, 300, 62, 8, 115, 20, 65> -{ -public: - FuelC3() = delete; - ~FuelC3() override = default; - FuelC3(const FuelC3& rhs) noexcept = delete; - FuelC3(FuelC3&& rhs) noexcept = delete; - FuelC3& operator=(const FuelC3& rhs) noexcept = delete; - FuelC3& operator=(FuelC3&& rhs) noexcept = delete; - /** - * \brief FBP fuel type C-3 - * \param code Code to identify fuel with - */ - explicit constexpr FuelC3(const FuelCodeSize& code) noexcept - : FuelJackpine(code, - "C-3", - data::LOG_0_75, - &Duff::FeatherMoss, - &Duff::PineSeney) - { - } -}; -/** - * \brief FBP fuel type C-4. - */ -class FuelC4 : public FuelJackpine<110, 293, 150, 66, 4, 120, 31, 62> -{ -public: - FuelC4() = delete; - ~FuelC4() override = default; - FuelC4(const FuelC4& rhs) noexcept = delete; - FuelC4(FuelC4&& rhs) noexcept = delete; - FuelC4& operator=(const FuelC4& rhs) noexcept = delete; - FuelC4& operator=(FuelC4&& rhs) noexcept = delete; - /** - * \brief FBP fuel type C-4 - * \param code Code to identify fuel with - */ - explicit constexpr FuelC4(const FuelCodeSize& code) noexcept - : FuelJackpine(code, - "C-4", - data::LOG_0_80, - &Duff::PineSeney) - { - } -}; -/** - * \brief FBP fuel type C-5. - */ -class FuelC5 : public FuelPine<30, 697, 400, 56, 18, 120, 93, 46> -{ -public: - FuelC5() = delete; - ~FuelC5() override = default; - FuelC5(const FuelC5& rhs) noexcept = delete; - FuelC5(FuelC5&& rhs) noexcept = delete; - FuelC5& operator=(const FuelC5& rhs) noexcept = delete; - FuelC5& operator=(FuelC5&& rhs) noexcept = delete; - /** - * \brief FBP fuel type C-5 - * \param code Code to identify fuel with - */ - explicit constexpr FuelC5(const FuelCodeSize& code) noexcept - : FuelPine(code, - "C-5", - data::LOG_0_80, - &Duff::PineSeney) - { - } -}; -/** - * \brief FBP fuel type C-6. - */ -class FuelC6 : public FuelPine<30, 800, 300, 62, 7, 180, 50, 50> -{ -public: - FuelC6() = delete; - ~FuelC6() override = default; - FuelC6(const FuelC6& rhs) noexcept = delete; - FuelC6(FuelC6&& rhs) noexcept = delete; - FuelC6& operator=(const FuelC6& rhs) noexcept = delete; - FuelC6& operator=(FuelC6&& rhs) noexcept = delete; - /** - * \brief FBP fuel type C-6 - * \param code Code to identify fuel with - */ - explicit constexpr FuelC6(const FuelCodeSize& code) noexcept - : FuelPine(code, - "C-6", - data::LOG_0_80, - &Duff::PineSeney) - { - } -protected: - /** - * \brief Final rate of spread (m/min) - * \param spread SpreadInfo to use - * \param isi Initial Spread Index (may differ from wx because of slope) - * \param cfb Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \param rss Surface Rate of spread (ROS) (m/min) [ST-X-3 eq 55] - * \return Final rate of spread (m/min) - */ - [[nodiscard]] MathSize finalRos(const SpreadInfo& spread, - MathSize isi, - MathSize cfb, - MathSize rss) const noexcept override; -}; -/** - * \brief FBP fuel type C-7. - */ -class FuelC7 : public FuelConifer<45, 305, 200, 106, 10, 50, 20, 15, 50> -{ -public: - FuelC7() = delete; - ~FuelC7() override = default; - FuelC7(const FuelC7& rhs) noexcept = delete; - FuelC7(FuelC7&& rhs) noexcept = delete; - FuelC7& operator=(const FuelC7& rhs) noexcept = delete; - FuelC7& operator=(FuelC7&& rhs) noexcept = delete; - /** - * \brief FBP fuel type C-7 - * \param code Code to identify fuel with - */ - explicit constexpr FuelC7(const FuelCodeSize& code) noexcept - : FuelConifer(code, - "C-7", - data::LOG_0_85, - &Duff::SprucePine) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 15] - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 15] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override; -}; -/** - * \brief FBP fuel type D-2. - */ -class FuelD2 : public FuelNonMixed<6, 232, 160, 32, 0, 0, 61, 59, 24> -{ -public: - FuelD2() = delete; - ~FuelD2() override = default; - FuelD2(const FuelD2& rhs) noexcept = delete; - FuelD2(FuelD2&& rhs) noexcept = delete; - FuelD2& operator=(const FuelD2& rhs) noexcept = delete; - FuelD2& operator=(FuelD2&& rhs) noexcept = delete; - // HACK: assume same bulk_density and inorganicContent as D1 - /** - * \brief FBP fuel type D-2 - * \param code Code to identify fuel with - */ - explicit constexpr FuelD2(const FuelCodeSize& code) noexcept - : FuelNonMixed(code, - "D-2", - false, - data::LOG_0_90, - &Duff::Peat) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override; - /** - * \brief Calculate rate of spread (m/min) - * \param nd Difference between date and the date of minimum foliar moisture content - * \param wx FwiWeather to use for calculation - * \param isi Initial Spread Index (may differ from wx because of slope) - * \return Rate of spread (m/min) - */ - [[nodiscard]] MathSize calculateRos(int nd, - const wx::FwiWeather& wx, - MathSize isi) const noexcept override; -}; -/** - * \brief FBP fuel type M-1. - * \tparam PercentConifer Percent conifer - */ -template -class FuelM1 : public FuelMixedWood<10, PercentConifer> -{ -public: - FuelM1() = delete; - ~FuelM1() = default; - FuelM1(const FuelM1& rhs) noexcept = delete; - FuelM1(FuelM1&& rhs) noexcept = delete; - FuelM1& operator=(const FuelM1& rhs) noexcept = delete; - FuelM1& operator=(FuelM1&& rhs) noexcept = delete; - /** - * \brief FBP fuel type M-1 - * \param code Code to identify fuel with - * \param name Name of the fuel - */ - constexpr FuelM1(const FuelCodeSize& code, const char* name) - : FuelMixedWood<10, PercentConifer>(code, name) - { - } -}; -/** - * \brief FBP fuel type M-2. - * \tparam PercentConifer Percent conifer - */ -template -class FuelM2 : public FuelMixedWood<2, PercentConifer> -{ -public: - FuelM2() = delete; - ~FuelM2() = default; - FuelM2(const FuelM2& rhs) noexcept = delete; - FuelM2(FuelM2&& rhs) noexcept = delete; - FuelM2& operator=(const FuelM2& rhs) noexcept = delete; - FuelM2& operator=(FuelM2&& rhs) noexcept = delete; - /** - * \brief FBP fuel type M-2 - * \param code Code to identify fuel with - * \param name Name of the fuel - */ - constexpr FuelM2(const FuelCodeSize& code, const char* name) - : FuelMixedWood<2, PercentConifer>(code, name) - { - } -}; -/** - * \brief FBP fuel type M-3. - * \tparam PercentDeadFir Percent dead fir - */ -template -class FuelM3 : public FuelMixedDead<120, 572, 140, 50, 10, PercentDeadFir> -{ -public: - FuelM3() = delete; - ~FuelM3() = default; - FuelM3(const FuelM3& rhs) noexcept = delete; - FuelM3(FuelM3&& rhs) noexcept = delete; - FuelM3& operator=(const FuelM3& rhs) noexcept = delete; - FuelM3& operator=(FuelM3&& rhs) noexcept = delete; - /** - * \brief FBP fuel type M-3 - * \param code Code to identify fuel with - * \param name Name of the fuel - */ - constexpr FuelM3(const FuelCodeSize& code, const char* name) - : FuelMixedDead<120, 572, 140, 50, 10, PercentDeadFir>(code, - name, - data::LOG_0_80) - { - } -}; -/** - * \brief FBP fuel type M-4. - * \tparam PercentDeadFir Percent dead fir - */ -template -class FuelM4 : public FuelMixedDead<100, 404, 148, 50, 2, PercentDeadFir> -{ -public: - FuelM4() = delete; - ~FuelM4() = default; - FuelM4(const FuelM4& rhs) noexcept = delete; - FuelM4(FuelM4&& rhs) noexcept = delete; - FuelM4& operator=(const FuelM4& rhs) noexcept = delete; - FuelM4& operator=(FuelM4&& rhs) noexcept = delete; - /** - * \brief FBP fuel type M-4 - * \param code Code to identify fuel with - * \param name Name of the fuel - */ - constexpr FuelM4(const FuelCodeSize& code, const char* name) - : FuelMixedDead<100, 404, 148, 50, 2, PercentDeadFir>(code, - name, - data::LOG_0_80) - { - } -}; -/** - * \brief FBP fuel type O-1a. - */ -class FuelO1A : public FuelGrass<190, 310, 140> -{ -public: - FuelO1A() = delete; - ~FuelO1A() override = default; - FuelO1A(const FuelO1A& rhs) noexcept = delete; - FuelO1A(FuelO1A&& rhs) noexcept = delete; - FuelO1A& operator=(const FuelO1A& rhs) noexcept = delete; - FuelO1A& operator=(FuelO1A&& rhs) noexcept = delete; - /** - * \brief FBP fuel type O-1a. - * \param code Code to identify fuel with - */ - explicit constexpr FuelO1A(const FuelCodeSize& code) noexcept - : FuelGrass(code, "O-1a", data::LOG_1_00) - { - } -}; -/** - * \brief FBP fuel type O-1b. - */ -class FuelO1B : public FuelGrass<250, 350, 170> -{ -public: - FuelO1B() = delete; - ~FuelO1B() override = default; - FuelO1B(const FuelO1B& rhs) noexcept = delete; - FuelO1B(FuelO1B&& rhs) noexcept = delete; - FuelO1B& operator=(const FuelO1B& rhs) noexcept = delete; - FuelO1B& operator=(FuelO1B&& rhs) noexcept = delete; - /** - * \brief FBP fuel type O-1b. - * \param code Code to identify fuel with - */ - explicit constexpr FuelO1B(const FuelCodeSize& code) noexcept - : FuelGrass(code, "O-1b", data::LOG_1_00) - { - } -}; -} -/** - * \brief A slash fuel type. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - * \tparam Bui0 Average Build-up Index for the fuel type [ST-X-3 table 7] - * \tparam FfcA Forest Floor Consumption parameter a [ST-X-3 eq 19/21/23] - * \tparam FfcB Forest Floor Consumption parameter b * 10000 [ST-X-3 eq 19/21/23] - * \tparam WfcA Woody Fuel Consumption parameter a [ST-X-3 eq 20/22/24] - * \tparam WfcB Woody Fuel Consumption parameter b * 10000 [ST-X-3 eq 20/22/24] - * \tparam BulkDensity Duff Bulk Density (kg/m^3) [Anderson table 1] * 1000 - */ -template -class FuelSlash : public FuelConifer -{ -public: - FuelSlash() = delete; - ~FuelSlash() override = default; - FuelSlash(const FuelSlash& rhs) noexcept = delete; - FuelSlash(FuelSlash&& rhs) noexcept = delete; - FuelSlash& operator=(const FuelSlash& rhs) noexcept = delete; - FuelSlash& operator=(FuelSlash&& rhs) noexcept = delete; - /** - * \brief A slash fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param log_q Log value of q [ST-X-3 table 7] - * \param duff_ffmc Type of duff near the surface - * \param duff_dmc Type of duff deeper underground - */ - constexpr FuelSlash(const FuelCodeSize& code, - const char* name, - const LogValue log_q, - const Duff* duff_ffmc, - const Duff* duff_dmc) - : FuelConifer(code, - name, - log_q, - duff_ffmc, - duff_dmc) - { - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 25] - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 25] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const noexcept override - { - return ffcA() * (1.0 - exp(ffcB() * spread.bui().asValue())) - + wfcA() * (1.0 - exp(wfcB() * spread.bui().asValue())); - } -private: - /** - * \brief Forest Floor Consumption parameter a [ST-X-3 eq 19/21/23] - * \return Forest Floor Consumption parameter a [ST-X-3 eq 19/21/23] - */ - [[nodiscard]] static constexpr MathSize ffcA() - { - return FfcA; - } - /** - * \brief Forest Floor Consumption parameter b [ST-X-3 eq 19/21/23] - * \return Forest Floor Consumption parameter b [ST-X-3 eq 19/21/23] - */ - [[nodiscard]] static constexpr MathSize ffcB() - { - return FfcB / 10000.0; - } - /** - * \brief Woody Fuel Consumption parameter a [ST-X-3 eq 20/22/24] - * \return Woody Fuel Consumption parameter a [ST-X-3 eq 20/22/24] - */ - [[nodiscard]] static constexpr MathSize wfcA() - { - return WfcA; - } - /** - * \brief Woody Fuel Consumption parameter b [ST-X-3 eq 20/22/24] - * \return Woody Fuel Consumption parameter b [ST-X-3 eq 20/22/24] - */ - [[nodiscard]] static constexpr MathSize wfcB() - { - return WfcB / 10000.0; - } -}; -namespace fbp -{ -/** - * \brief FBP fuel type S-1. - */ -class FuelS1 : public FuelSlash<75, 297, 130, 38, 4, -250, 4, -340, 78> -{ -public: - FuelS1() = delete; - ~FuelS1() override = default; - FuelS1(const FuelS1& rhs) noexcept = delete; - FuelS1(FuelS1&& rhs) noexcept = delete; - FuelS1& operator=(const FuelS1& rhs) noexcept = delete; - FuelS1& operator=(FuelS1&& rhs) noexcept = delete; - /** - * \brief FBP fuel type S-1 - * \param code Code to identify fuel with - */ - explicit constexpr FuelS1(const FuelCodeSize& code) noexcept - : FuelSlash(code, - "S-1", - data::LOG_0_75, - &Duff::FeatherMoss, - &Duff::PineSeney) - { - } -}; -/** - * \brief FBP fuel type S-2. - */ -class FuelS2 : public FuelSlash<40, 438, 170, 63, 10, -130, 6, -600, 132> -{ -public: - FuelS2() = delete; - ~FuelS2() override = default; - FuelS2(const FuelS2& rhs) noexcept = delete; - FuelS2(FuelS2&& rhs) noexcept = delete; - FuelS2& operator=(const FuelS2& rhs) noexcept = delete; - FuelS2& operator=(FuelS2&& rhs) noexcept = delete; - /** - * \brief FBP fuel type S-2 - * \param code Code to identify fuel with - */ - explicit constexpr FuelS2(const FuelCodeSize& code) noexcept - : FuelSlash(code, - "S-2", - data::LOG_0_75, - &Duff::FeatherMoss, - &Duff::WhiteSpruce) - { - } -}; -/** - * \brief FBP fuel type S-3. - */ -class FuelS3 : public FuelSlash<55, 829, 320, 31, 12, -166, 20, -210, 100> -{ -public: - FuelS3() = delete; - ~FuelS3() override = default; - FuelS3(const FuelS3& rhs) noexcept = delete; - FuelS3(FuelS3&& rhs) noexcept = delete; - FuelS3& operator=(const FuelS3& rhs) noexcept = delete; - FuelS3& operator=(FuelS3&& rhs) noexcept = delete; - /** - * \brief FBP fuel type S-3 - * \param code Code to identify fuel with - */ - explicit constexpr FuelS3(const FuelCodeSize& code) noexcept - : FuelSlash(code, - "S-3", - data::LOG_0_75, - &Duff::FeatherMoss, - &Duff::PineSeney) - { - } -}; -} -template -class FuelVariable; -template -[[nodiscard]] const FuelType& find_fuel_by_season(const int nd, - const FuelVariable< - FuelSpring, - FuelSummer>& fuel) noexcept -{ - // if not green yet, then still in spring conditions - return calculate_is_green(nd) - ? fuel.summer() - : fuel.spring(); -} -template -[[nodiscard]] MathSize compare_by_season(const FuelVariable& fuel, - const function& fct) -{ - // HACK: no way to tell which is which, so let's assume they have to be the same?? - // HACK: use a function so that DEBUG section doesn't get out of sync - const auto for_spring = fct(fuel.spring()); -#ifdef DEBUG_FUEL_VARIABLE - const auto for_summer = fct(fuel.summer()); - logging::check_fatal(for_spring != for_summer, "Expected spring and summer cfb to be identical"); -#endif - return for_spring; -} -/** - * \brief A fuel type that changes based on the season. - * \tparam FuelSpring Fuel type to use in the spring - * \tparam FuelSummer Fuel type to use in the summer - */ -template -class FuelVariable : public FuelType -{ -public: - // don't delete pointers since they're handled elsewhere - ~FuelVariable() override = default; - /** - * \brief A slash fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param can_crown Whether or not this fuel can have a crown fire - * \param spring Fuel type to use in the spring - * \param summer Fuel type to use in the summer - */ - constexpr FuelVariable(const FuelCodeSize& code, - const char* name, - const bool can_crown, - const FuelSpring* const spring, - const FuelSummer* const summer) - : FuelType(code, name, can_crown), - spring_(spring), - summer_(summer) - { - } - FuelVariable(FuelVariable&& rhs) noexcept = delete; - FuelVariable(const FuelVariable& rhs) = delete; - FuelVariable& operator=(FuelVariable&& rhs) noexcept = delete; - FuelVariable& operator=(const FuelVariable& rhs) = delete; - /** - * \brief BUI Effect on surface fire rate of spread [ST-X-3 eq 54] - * \param bui Build-up Index - * \return BUI Effect on surface fire rate of spread [ST-X-3 eq 54] - */ - [[nodiscard]] MathSize buiEffect(MathSize bui) const override - { - return compare_by_season(*this, [bui](const FuelType& fuel) { return fuel.buiEffect(bui); }); - } - /** - * \brief Grass curing - * \return Grass curing (or -1 if invalid for this fuel type) - */ - [[nodiscard]] MathSize grass_curing(const int nd, const wx::FwiWeather& wx) const override - { - return compare_by_season(*this, [&nd, &wx](const FuelType& fuel) { return fuel.grass_curing(nd, wx); }); - } - /** - * \brief Crown base height (m) [ST-X-3 table 8] - * \return Crown base height (m) [ST-X-3 table 8] - */ - [[nodiscard]] MathSize cbh() const override - { - return compare_by_season(*this, [](const FuelType& fuel) { return fuel.cbh(); }); - } - /** - * \brief Crown fuel load (kg/m^2) [ST-X-3 table 8] - * \return Crown fuel load (kg/m^2) [ST-X-3 table 8] - */ - [[nodiscard]] MathSize cfl() const override - { - return compare_by_season(*this, [](const FuelType& fuel) { return fuel.cfl(); }); - } - /** - * \brief Crown Fuel Consumption (CFC) (kg/m^2) [ST-X-3 eq 66] - * \param cfb Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \return Crown Fuel Consumption (CFC) (kg/m^2) [ST-X-3 eq 66] - */ - [[nodiscard]] MathSize crownConsumption(const MathSize cfb) const override - { - return compare_by_season(*this, [cfb](const FuelType& fuel) { return fuel.crownConsumption(cfb); }); - } - /** - * \brief Initial rate of spread (m/min) [ST-X-3 eq 26] - * \param nd Difference between date and the date of minimum foliar moisture content - * \param wx FwiWeather to use - * \param isi Initial Spread Index - * \return Initial rate of spread (m/min) [ST-X-3 eq 26] - */ - [[nodiscard]] MathSize calculateRos(const int nd, - const wx::FwiWeather& wx, - const MathSize isi) const override - { - return find_fuel_by_season(nd, *this).calculateRos(nd, wx, isi); - } - /** - * \brief Calculate ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41] - * \param spread SpreadInfo to use - * \param isi Initial Spread Index - * \return ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41] - */ - [[nodiscard]] MathSize calculateIsf(const SpreadInfo& spread, - const MathSize isi) const override - { - return find_fuel_by_season(spread.nd(), *this).calculateIsf(spread, isi); - } - /** - * \brief Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 9-25] - * \param spread SpreadInfo to use - * \return Surface Fuel Consumption (SFC) (kg/m^2) [ST-X-3 eq 9-25] - */ - [[nodiscard]] MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const override - { - return find_fuel_by_season(spread.nd(), *this).surfaceFuelConsumption(spread); - } - /** - * \brief Length to Breadth ratio [ST-X-3 eq 79] - * \param ws Wind Speed (km/h) - * \return Length to Breadth ratio [ST-X-3 eq 79] - */ - [[nodiscard]] MathSize lengthToBreadth(const MathSize ws) const override - { - return compare_by_season(*this, [ws](const FuelType& fuel) { return fuel.lengthToBreadth(ws); }); - } - /** - * \brief Final rate of spread (m/min) - * \param spread SpreadInfo to use - * \param isi Initial Spread Index (may differ from wx because of slope) - * \param cfb Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \param rss Surface Rate of spread (ROS) (m/min) [ST-X-3 eq 55] - * \return Final rate of spread (m/min) - */ - [[nodiscard]] MathSize finalRos(const SpreadInfo& spread, - const MathSize isi, - const MathSize cfb, - const MathSize rss) const override - { - return find_fuel_by_season(spread.nd(), *this).finalRos(spread, isi, cfb, rss); - } - /** - * \brief Critical Surface Fire Intensity (CSI) [ST-X-3 eq 56] - * \param spread SpreadInfo to use in calculation - * \return Critical Surface Fire Intensity (CSI) [ST-X-3 eq 56] - */ - [[nodiscard]] MathSize criticalSurfaceIntensity( - const SpreadInfo& spread) const override - { - return find_fuel_by_season(spread.nd(), *this).criticalSurfaceIntensity(spread); - } - /** - * \brief Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \param rss Surface Rate of spread (ROS) (m/min) [ST-X-3 eq 55] - * \param rso Critical surface fire spread rate (RSO) [ST-X-3 eq 57] - * \return Crown Fraction Burned (CFB) [ST-X-3 eq 58] - */ - [[nodiscard]] MathSize crownFractionBurned(const MathSize rss, - const MathSize rso) const noexcept override - { - return spring().crownFractionBurned(rss, rso); - } - /** - * \brief Calculate probability of burning [Anderson eq 1] - * \param mc_fraction moisture content (% / 100) - * \return Calculate probability of burning [Anderson eq 1] - */ - [[nodiscard]] MathSize probabilityPeat(const MathSize mc_fraction) const noexcept override - { - return spring().probabilityPeat(mc_fraction); - } - /** - * \brief Survival probability calculated using probability of ony survival based on multiple formulae - * \param wx FwiWeather to calculate survival probability for - * \return Chance of survival (% / 100) - */ - [[nodiscard]] MathSize survivalProbability(const wx::FwiWeather& wx) const noexcept - override - { - return spring().survivalProbability(wx); - } - /** - * \brief Fuel to use before green-up - * \return Fuel to use before green-up - */ - [[nodiscard]] constexpr const FuelType& spring() const - { - return *spring_; - } - /** - * \brief Fuel to use after green-up - * \return Fuel to use after green-up - */ - [[nodiscard]] constexpr const FuelType& summer() const - { - return *summer_; - } -private: - /** - * \brief Fuel to use before green-up - */ - const FuelSpring* const spring_; - /** - * \brief Fuel to use after green-up - */ - const FuelSummer* const summer_; -}; -namespace fbp -{ -/** - * \brief FBP fuel type D-1/D-2. - */ -class FuelD1D2 : public FuelVariable -{ -public: - FuelD1D2() = delete; - ~FuelD1D2() override = default; - FuelD1D2(const FuelD1D2& rhs) noexcept = delete; - FuelD1D2(FuelD1D2&& rhs) noexcept = delete; - FuelD1D2& operator=(const FuelD1D2& rhs) noexcept = delete; - FuelD1D2& operator=(FuelD1D2&& rhs) noexcept = delete; - /** - * \brief A fuel that changes between D-1/D-2 depending on green-up - * \param code Code to identify fuel with - * \param d1 D-1 fuel to use before green-up - * \param d2 D-2 fuel to use after green-up - */ - constexpr FuelD1D2(const FuelCodeSize& code, - const FuelD1* d1, - const FuelD2* d2) noexcept - : FuelVariable(code, "D-1/D-2", false, d1, d2) - { - } -}; -/** - * \brief FBP fuel type M-1/M-2. - * \tparam PercentConifer Percent conifer - */ -template -class FuelM1M2 : public FuelVariable, FuelM2> -{ -public: - FuelM1M2() = delete; - ~FuelM1M2() = default; - FuelM1M2(const FuelM1M2& rhs) noexcept = delete; - FuelM1M2(FuelM1M2&& rhs) noexcept = delete; - FuelM1M2& operator=(const FuelM1M2& rhs) noexcept = delete; - FuelM1M2& operator=(FuelM1M2&& rhs) noexcept = delete; - // HACK: it's up to you to make sure these match - /** - * \brief A fuel that changes between M-1/M-2 depending on green-up - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param m1 M-1 fuel to use before green-up - * \param m2 M-2 fuel to use after green-up - */ - constexpr FuelM1M2(const FuelCodeSize& code, - const char* name, - const FuelM1* m1, - const FuelM2* m2) - : FuelVariable, FuelM2>(code, name, true, m1, m2) - { - } -}; -/** - * \brief FBP fuel type M-3/M-4. - * \tparam PercentDeadFir Percent dead fir - */ -template -class FuelM3M4 : public FuelVariable, FuelM4> -{ -public: - FuelM3M4() = delete; - ~FuelM3M4() = default; - FuelM3M4(const FuelM3M4& rhs) noexcept = delete; - FuelM3M4(FuelM3M4&& rhs) noexcept = delete; - FuelM3M4& operator=(const FuelM3M4& rhs) noexcept = delete; - FuelM3M4& operator=(FuelM3M4&& rhs) noexcept = delete; - /** - * \brief A fuel that changes between M-3/M-4 depending on green-up - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param m3 M-3 fuel to use before green-up - * \param m4 M-4 fuel to use after green-up - */ - constexpr FuelM3M4(const FuelCodeSize& code, - const char* name, - const FuelM3* m3, - const FuelM4* m4) - : FuelVariable, FuelM4>(code, name, true, m3, m4) - { - } -}; -/** - * \brief FBP fuel type O-1. - */ -class FuelO1 : public FuelVariable -{ -public: - FuelO1() = delete; - ~FuelO1() = default; - FuelO1(const FuelO1& rhs) noexcept = delete; - FuelO1(FuelO1&& rhs) noexcept = delete; - FuelO1& operator=(const FuelO1& rhs) noexcept = delete; - FuelO1& operator=(FuelO1&& rhs) noexcept = delete; - /** - * \brief A fuel that changes between O-1a/O-1b depending on green-up - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param o1a O1-a fuel to use before green-up - * \param o1b O1-b fuel to use after green-up - */ - constexpr FuelO1(const FuelCodeSize& code, - const char* name, - const FuelO1A* o1a, - const FuelO1B* o1b) - : FuelVariable(code, name, true, o1a, o1b) - { - } -}; -} -} diff --git a/firestarr/src/cpp/FWI.cpp b/firestarr/src/cpp/FWI.cpp deleted file mode 100644 index af8d7f9b8..000000000 --- a/firestarr/src/cpp/FWI.cpp +++ /dev/null @@ -1,763 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Weather.h" -#include "FWI.h" -#include "Log.h" -// #define CHECK_CALCULATION 1 -#ifndef DEBUG_FWI_WEATHER -#undef CHECK_CALCULATION -#endif -#define USE_GIVEN -#define CHECK_EPSILON 0.1 -// adapted from http://www.columbia.edu/~rf2426/index_files/FWI.vba -//****************************************************************************************** -// -// Description: VBA module containing functions to calculate the components of -// the Canadian Fire Weather Index system, as described in -// -// Van Wagner, C.E. 1987. Development and structure of the Canadian Forest Fire -// Weather Index System. Canadian Forest Service, Ottawa, Ont. For. Tech. Rep. 35. -// 37 p. -// -// Equation numbers from VW87 are listed throughout, to the right of the equations -// in the code. -// -// A more recent technical description can be found in: -// http://www.cawcr.gov.au/publications/technicalreports/CTR_010.pdf -// -// This module is essentially a direct C to VBA translation of Kerry Anderson's -// fwi84.c code. The latitude adjustments were developed by Marty Alexander, and used -// over Indonesia in the paper: -// -// Field, R.D., Y. Wang, O. Roswintiarti, and Guswanto. A drought-based predictor of -// recent haze events in western Indonesia. Atmospheric Environment, 38, 1869-1878, -// 2004. -// -// A technical description of the latitude adjustments can be found in Appendix 3 of: -// http://cfs.nrcan.gc.ca/pubwarehouse/pdfs/29152.pdf -// -// Robert Field, robert.field@utoronto.ca -//****************************************************************************************** -namespace fs::wx -{ -const Ffmc Ffmc::Zero = Ffmc(0); -const Dmc Dmc::Zero = Dmc(0); -const Dc Dc::Zero = Dc(0); -const Bui Bui::Zero = Bui(0); -const Isi Isi::Zero = Isi(0); -const Fwi Fwi::Zero = Fwi(0); -// HACK: can't use the ::Zero fields for these because we don't know when they initialize -const FwiWeather FwiWeather::Zero{ - Temperature::Zero, - RelativeHumidity::Zero, - Wind::Zero, - Precipitation::Zero, - Ffmc::Zero, - Dmc::Zero, - Dc::Zero, - Isi::Zero, - Bui::Zero, - Fwi::Zero}; -const Ffmc Ffmc::Invalid = Ffmc(-1); -const Dmc Dmc::Invalid = Dmc(-1); -const Dc Dc::Invalid = Dc(-1); -const Bui Bui::Invalid = Bui(-1); -const Isi Isi::Invalid = Isi(-1); -const Fwi Fwi::Invalid = Fwi(-1); -// HACK: can't use the ::Invalid fields for these because we don't know when they initialize -const FwiWeather FwiWeather::Invalid{ - Temperature::Invalid, - RelativeHumidity::Invalid, - Wind::Invalid, - Precipitation::Invalid, - Ffmc::Invalid, - Dmc::Invalid, - Dc::Invalid, - Isi::Invalid, - Bui::Invalid, - Fwi::Invalid}; -// The following two functions refer to the MEA day length adjustment 'note'. -// -//****************************************************************************************** -// Function Name: DayLengthFactor -// Description: Calculates latitude/date dependent day length factor for Drought Code -// Parameters: -// Latitude is latitude in decimal degrees of calculation location -// Month is integer (1..12) value of month of year for calculation -// -//****************************************************************************************** -static MathSize day_length_factor(const MathSize latitude, const int month) noexcept -{ - static constexpr MathSize LfN[] = { - -1.6, - -1.6, - -1.6, - 0.9, - 3.8, - 5.8, - 6.4, - 5.0, - 2.4, - 0.4, - -1.6, - -1.6}; - static constexpr MathSize LfS[] = { - 6.4, - 5.0, - 2.4, - 0.4, - -1.6, - -1.6, - -1.6, - -1.6, - -1.6, - 0.9, - 3.8, - 5.8}; - //' '/* Use Northern hemisphere numbers */ - //' '/* something goes wrong with >= */ - if (latitude > 15.0) - { - return LfN[month]; - } - //' '/* Use Equatorial numbers */ - if ((latitude <= 15.0) && (latitude > -15.0)) - { - return 1.39; - } - //' '/* Use Southern hemisphere numbers */ - if (latitude <= -15.0) - { - return LfS[month]; - } - return logging::fatal("Unable to calculate DayLengthFactor"); -} -using MonthArray = array; -static constexpr MonthArray DAY_LENGTH46_N{ - 6.5, - 7.5, - 9.0, - 12.8, - 13.9, - 13.9, - 12.4, - 10.9, - 9.4, - 8.0, - 7.0, - 6.0}; -static constexpr MonthArray DAY_LENGTH20_N{ - 7.9, - 8.4, - 8.9, - 9.5, - 9.9, - 10.2, - 10.1, - 9.7, - 9.1, - 8.6, - 8.1, - 7.8}; -static constexpr MonthArray DAY_LENGTH20_S{ - 10.1, - 9.6, - 9.1, - 8.5, - 8.1, - 7.8, - 7.9, - 8.3, - 8.9, - 9.4, - 9.9, - 10.2}; -static constexpr MonthArray DAY_LENGTH40_S{ - 11.5, - 10.5, - 9.2, - 7.9, - 6.8, - 6.2, - 6.5, - 7.4, - 8.7, - 10.0, - 11.2, - 11.8}; -//****************************************************************************************** -// Function Name: DayLength -// Description: Calculates latitude/date dependent day length for DMC calculation -// Parameters: -// Latitude is latitude in decimal degrees of calculation location -// Month is integer (1..12) value of month of year for calculation -// -//****************************************************************************************** -static constexpr MathSize day_length(const MathSize latitude, const int month) noexcept -{ - //'''/* Day Length Arrays for four different latitude ranges '*/ - //''/* - //' Use four ranges which respectively span: - //' - 90N to 33 N - //' - 33 N to 0 - //' - 0 to -30 - //' - -30 to -90 - /// - if ((latitude <= 90) && (latitude > 33.0)) - { - return DAY_LENGTH46_N.at(static_cast(month) - 1); - } - if ((latitude <= 33.0) && (latitude > 15.0)) - { - return DAY_LENGTH20_N.at(static_cast(month) - 1); - } - if ((latitude <= 15.0) && (latitude > -15.0)) - { - return 9.0; - } - if ((latitude <= -15.0) && (latitude > -30.0)) - { - return DAY_LENGTH20_S.at(static_cast(month) - 1); - } - if ((latitude <= -30.0) && (latitude >= -90.0)) - { - return DAY_LENGTH40_S.at(static_cast(month) - 1); - } - return logging::fatal("Unable to calculate DayLength"); -} -MathSize find_m(const Temperature& temperature, const RelativeHumidity& rh, const Speed& wind, const MathSize mo) noexcept -{ - //'''/* 4 '*/ - const auto ed = 0.942 * pow(rh.asValue(), 0.679) + 11.0 * exp((rh.asValue() - 100.0) / 10.0) + 0.18 * (21.1 - temperature.asValue()) * (1.0 - exp(-0.115 * rh.asValue())); - if (mo > ed) - { - //'''/* 6a '*/ - const auto ko = 0.424 * (1.0 - pow(rh.asValue() / 100.0, 1.7)) + 0.0694 * sqrt(wind.asValue()) * (1.0 - util::pow_int<8>(rh.asValue() / 100.0)); - //'''/* 6b '*/ - const auto kd = ko * 0.581 * exp(0.0365 * temperature.asValue()); - //'''/* 8 '*/ - return ed + (mo - ed) * pow(10.0, -kd); - } - //'''/* 5 '*/ - const auto ew = 0.618 * pow(rh.asValue(), 0.753) + 10.0 * exp((rh.asValue() - 100.0) / 10.0) + 0.18 * (21.1 - temperature.asValue()) * (1.0 - exp(-0.115 * rh.asValue())); - if (mo < ew) - { - //'''/* 7a '*/ - const auto kl = 0.424 * (1.0 - pow((100.0 - rh.asValue()) / 100.0, 1.7)) + 0.0694 * sqrt(wind.asValue()) * (1 - util::pow_int<8>((100.0 - rh.asValue()) / 100.0)); - //'''/* 7b '*/ - const auto kw = kl * 0.581 * exp(0.0365 * temperature.asValue()); - //'''/* 9 '*/ - return ew - (ew - mo) * pow(10.0, -kw); - } - return mo; -} -//****************************************************************************************** -// Function Name: FFMC -// Description: Calculates today's Fine Fuel Moisture Code -// Parameters: -// temperature is the 12:00 LST temperature in degrees celsius -// rh is the 12:00 LST relative humidity in % -// wind is the 12:00 LST wind speed in kph -// rain is the 24-hour accumulated rainfall in mm, calculated at 12:00 LST -// ffmc_previous is the previous day's FFMC -//****************************************************************************************** -static MathSize calculate_ffmc(const Temperature& temperature, - const RelativeHumidity& rh, - const Speed& wind, - const Precipitation& rain, - const Ffmc& ffmc_previous) noexcept -{ - //'''/* 1 '*/ - auto mo = ffmc_to_moisture(ffmc_previous); - if (rain.asValue() > 0.5) - { - //'''/* 2 '*/ - const auto rf = rain.asValue() - 0.5; - //'''/* 3a '*/ - auto mr = mo + 42.5 * rf * (exp(-100.0 / (251.0 - mo))) * (1 - exp(-6.93 / rf)); - if (mo > 150.0) - { - //'''/* 3b '*/ - mr += 0.0015 * util::pow_int<2>(mo - 150.0) * sqrt(rf); - } - if (mr > 250.0) - { - mr = 250.0; - } - mo = mr; - } - const auto m = find_m(temperature, rh, wind, mo); - //'''/* 10 '*/ - return moisture_to_ffmc(m); -} -Ffmc::Ffmc(const Temperature& temperature, - const RelativeHumidity& rh, - const Speed& wind, - const Precipitation& rain, - const Ffmc& ffmc_previous) noexcept - : Ffmc(calculate_ffmc(temperature, rh, wind, rain, ffmc_previous)) -{ -} -//****************************************************************************************** -// Function Name: DMC -// Description: Calculates today's Duff Moisture Code -// Parameters: -// temperature is the 12:00 LST temperature in degrees celsius -// rh is the 12:00 LST relative humidity in % -// rain is the 24-hour accumulated rainfall in mm, calculated at 12:00 LST -// dmc_previous is the previous day's DMC -// latitude is the latitude in decimal degrees of the location for calculation -// month is the month of Year (1..12) for the current day's calculations. -//****************************************************************************************** -static MathSize calculate_dmc(const Temperature& temperature, - const RelativeHumidity& rh, - const Precipitation& rain, - const Dmc& dmc_previous, - const int month, - const MathSize latitude) noexcept -{ - auto previous = dmc_previous.asValue(); - if (rain.asValue() > 1.5) - { - //'''/* 11 '*/ - const auto re = 0.92 * rain.asValue() - 1.27; - //'''/* 12 '*/ - // const auto mo = 20.0 + exp(5.6348 - previous / 43.43); - // Alteration to Eq. 12 to calculate more accurately - const auto mo = 20 + 280 / exp(0.023 * previous); - const auto b = (previous <= 33.0) - ? //'''/* 13a '*/ - 100.0 / (0.5 + 0.3 * previous) - : ((previous <= 65.0) - ? //'''/* 13b '*/ - 14.0 - 1.3 * (log(previous)) - : //'''/* 13c '*/ - 6.2 * log(previous) - 17.2); - //'''/* 14 '*/ - const auto mr = mo + 1000.0 * re / (48.77 + b * re); - //'''/* 15 '*/ - // const auto pr = 244.72 - 43.43 * log(mr - 20.0); - // Alteration to Eq. 15 to calculate more accurately - const auto pr = 43.43 * (5.6348 - log(mr - 20)); - previous = max(pr, 0.0); - } - const auto k = (temperature.asValue() > -1.1) - ? 1.894 * (temperature.asValue() + 1.1) * (100.0 - rh.asValue()) * day_length(latitude, month) * 0.0001 - : 0.0; - //'''/* 17 '*/ - return (previous + k); -} -Dmc::Dmc(const Temperature& temperature, - const RelativeHumidity& rh, - const Precipitation& rain, - const Dmc& dmc_previous, - const int month, - const MathSize latitude) noexcept - : Dmc(calculate_dmc(temperature, rh, rain, dmc_previous, month, latitude)) -{ -} -//****************************************************************************************** -// Function Name: DC -// Description: Calculates today's Drought Code -// Parameters: -// temperature is the 12:00 LST temperature in degrees celsius -// rain is the 24-hour accumulated rainfall in mm, calculated at 12:00 LST -// dc_previous is the previous day's DC -// latitude is the latitude in decimal degrees of the location for calculation -// month is the month of Year (1..12) for the current day's calculations. -//****************************************************************************************** -static MathSize calculate_dc(const Temperature& temperature, - const Precipitation& rain, - const Dc& dc_previous, - const int month, - const MathSize latitude) noexcept -{ - auto previous = dc_previous.asValue(); - if (rain.asValue() > 2.8) - { - //'/* 18 */ - const auto rd = 0.83 * (rain.asValue()) - 1.27; - //'/* 19 */ - const auto qo = 800.0 * exp(-previous / 400.0); - //'/* 20 */ - const auto qr = qo + 3.937 * rd; - //'/* 21 */ - const auto dr = 400.0 * log(800.0 / qr); - previous = (dr > 0.0) ? dr : 0.0; - } - const auto lf = day_length_factor(latitude, month - 1); - //'/* 22 */ - const auto v = max(0.0, - (temperature.asValue() > -2.8) - ? 0.36 * (temperature.asValue() + 2.8) + lf - : lf); - //'/* 23 */ - const auto d = previous + 0.5 * v; - // HACK: don't allow negative values - return max(0.0, d); -} -Dc::Dc(const Temperature& temperature, - const Precipitation& rain, - const Dc& dc_previous, - const int month, - const MathSize latitude) noexcept - : Dc(calculate_dc(temperature, rain, dc_previous, month, latitude)) -{ -} -MathSize ffmc_effect(const Ffmc& ffmc) noexcept -{ - //'''/* 1 '*/ - const auto mc = ffmc_to_moisture(ffmc); - //'''/* 25 '*/ - return 91.9 * exp(-0.1386 * mc) * (1 + pow(mc, 5.31) / 49300000.0); -} -//****************************************************************************************** -// Function Name: ISI -// Description: Calculates today's Initial Spread Index -// Parameters: -// wind is the 12:00 LST wind speed in kph -// ffmc is the current day's FFMC -//****************************************************************************************** -static MathSize calculate_isi(const Speed& wind, const Ffmc& ffmc) noexcept -{ - //'''/* 24 '*/ - const auto f_wind = exp(0.05039 * wind.asValue()); - const auto f_f = ffmc_effect(ffmc); - //'''/* 26 '*/ - return (0.208 * f_wind * f_f); -} -Isi::Isi(const Speed& wind, const Ffmc& ffmc) noexcept - : Isi(calculate_isi(wind, ffmc)) -{ -} -Isi::Isi(MathSize -#if defined(CHECK_CALCULATION) | defined(USE_GIVEN) - value -#endif - , - const Speed& -#if defined(CHECK_CALCULATION) | !defined(USE_GIVEN) - wind -#endif - , - const Ffmc& -#if defined(CHECK_CALCULATION) | !defined(USE_GIVEN) - ffmc -#endif - ) noexcept -#ifdef USE_GIVEN - : Isi(value) -{ -#ifdef CHECK_CALCULATION - const auto cmp = Isi(wind, ffmc).asValue(); -#endif -#else - : Isi(wind, ffmc) -{ -#ifdef CHECK_CALCULATION - const auto cmp = value; -#endif -#endif -#ifdef CHECK_CALCULATION - logging::check_fatal(abs(asValue() - cmp) >= CHECK_EPSILON, - "ISI is incorrect %f, %f => %f not %f", - wind.asValue(), - ffmc.asValue(), - asValue(), - cmp); -#endif -} -//****************************************************************************************** -// Function Name: BUI -// Description: Calculates today's Buildup Index -// Parameters: -// DMC is the current day's Duff Moisture Code -// DC is the current day's Drought Code -//****************************************************************************************** -static MathSize calculate_bui(const Dmc& dmc, const Dc& dc) noexcept -{ - if (dmc.asValue() <= 0.4 * dc.asValue()) - { - // HACK: this isn't normally part of it, but it's division by 0 without this - if (0 == dc.asValue()) - { - return 0; - } - //'''/* 27a '*/ - return max(0.0, - 0.8 * dmc.asValue() * dc.asValue() / (dmc.asValue() + 0.4 * dc.asValue())); - } - //'''/* 27b '*/ - return max(0.0, - dmc.asValue() - (1.0 - 0.8 * dc.asValue() / (dmc.asValue() + 0.4 * dc.asValue())) * (0.92 + pow(0.0114 * dmc.asValue(), 1.7))); -} -Bui::Bui(MathSize -#if defined(CHECK_CALCULATION) | defined(USE_GIVEN) - value -#endif - , - const Dmc& -#if defined(CHECK_CALCULATION) | !defined(USE_GIVEN) - dmc -#endif - , - const Dc& -#if defined(CHECK_CALCULATION) | !defined(USE_GIVEN) - dc -#endif - ) noexcept -#ifdef USE_GIVEN - : Bui(value) -{ -#ifdef CHECK_CALCULATION - const auto cmp = Bui(dmc, dc).asValue(); -#endif -#else - : Bui(dmc, dc) -{ -#ifdef CHECK_CALCULATION - const auto cmp = value; -#endif -#endif -#ifdef CHECK_CALCULATION - logging::check_fatal(abs(asValue() - cmp) >= CHECK_EPSILON, - "BUI is incorrect %f, %f => %f not %f", - dmc.asValue(), - dc.asValue(), - asValue(), - cmp); -#endif -} -Bui::Bui(const Dmc& dmc, const Dc& dc) noexcept - : Bui(calculate_bui(dmc, dc)) -{ -} -//****************************************************************************************** -// Function Name: FWI -// Description: Calculates today's Fire Weather Index -// Parameters: -// ISI is current day's ISI -// BUI is the current day's BUI -//****************************************************************************************** -static MathSize calculate_fwi(const Isi& isi, const Bui& bui) noexcept -{ - const auto f_d = (bui.asValue() <= 80.0) - ? //'''/* 28a '*/ - 0.626 * pow(bui.asValue(), 0.809) + 2.0 - : //'''/* 28b '*/ - 1000.0 / (25.0 + 108.64 * exp(-0.023 * bui.asValue())); - //'''/* 29 '*/ - const auto b = 0.1 * isi.asValue() * f_d; - if (b > 1.0) - { - //'''/* 30a '*/ - return exp(2.72 * pow(0.434 * log(b), 0.647)); - } - //'''/* 30b '*/ - return b; -} -Fwi::Fwi(MathSize -#if defined(CHECK_CALCULATION) | defined(USE_GIVEN) - value -#endif - , - const Isi& -#if defined(CHECK_CALCULATION) | !defined(USE_GIVEN) - isi -#endif - , - const Bui& -#if defined(CHECK_CALCULATION) | !defined(USE_GIVEN) - bui -#endif - ) noexcept -#ifdef USE_GIVEN - : Fwi(value) -{ -#ifdef CHECK_CALCULATION - const auto cmp = Fwi(isi, bui).asValue(); -#endif -#else - : Fwi(isi, bui) -{ -#ifdef CHECK_CALCULATION - const auto cmp = value; -#endif -#endif -#ifdef CHECK_CALCULATION - logging::check_fatal(abs(asValue() - cmp) >= CHECK_EPSILON, - "FWI is incorrect %f, %f => %f not %f", - isi.asValue(), - bui.asValue(), - asValue(), - cmp); -#endif -} -Fwi::Fwi(const Isi& isi, const Bui& bui) noexcept - : Fwi(calculate_fwi(isi, bui)) -{ -} -//****************************************************************************************** -// Function Name: DSR -// Description: Calculates today's Daily Severity Rating -// Parameters: -// FWI is current day's FWI -//****************************************************************************************** -static MathSize calculate_dsr(const Fwi& fwi) noexcept -{ - //'''/* 41 '*/ - return (0.0272 * pow(fwi.asValue(), 1.77)); -} -Dsr::Dsr(const Fwi& fwi) noexcept - : Dsr(calculate_dsr(fwi)) -{ -} -inline MathSize stod(const string* const str) -{ - return stod(*str); -} -FwiWeather read(istringstream* iss, - string* str) -{ - // PREC - util::getline(iss, str, ','); - logging::extensive("PREC is %s", str->c_str()); - const Precipitation prec(stod(str)); - // TEMP - util::getline(iss, str, ','); - logging::extensive("TEMP is %s", str->c_str()); - const Temperature temp(stod(str)); - // RH - util::getline(iss, str, ','); - logging::extensive("RH is %s", str->c_str()); - const RelativeHumidity rh(stod(str)); - // WS - util::getline(iss, str, ','); - logging::extensive("WS is %s", str->c_str()); - const Speed ws(stod(str)); - // WD - util::getline(iss, str, ','); - logging::extensive("WD is %s", str->c_str()); - const Direction wd(stod(str), false); - const Wind wind(wd, ws); - // FIX: pretend we're checking these but the flag is unset for now - util::getline(iss, str, ','); - logging::extensive("FFMC is %s", str->c_str()); - const Ffmc ffmc(stod(str)); - util::getline(iss, str, ','); - logging::extensive("DMC is %s", str->c_str()); - const Dmc dmc(stod(str)); - util::getline(iss, str, ','); - logging::extensive("DC is %s", str->c_str()); - const Dc dc(stod(str)); - util::getline(iss, str, ','); - logging::extensive("ISI is %s", str->c_str()); - const Isi isi(stod(str), ws, ffmc); - util::getline(iss, str, ','); - logging::extensive("BUI is %s", str->c_str()); - const Bui bui(stod(str), dmc, dc); - util::getline(iss, str, ','); - logging::extensive("FWI is %s", str->c_str()); - const Fwi fwi(stod(str), isi, bui); - return {temp, rh, wind, prec, ffmc, dmc, dc, isi, bui, fwi}; -} -FwiWeather::FwiWeather(istringstream* iss, - string* str) - : FwiWeather(read(iss, str)) -{ -} -FwiWeather::FwiWeather(const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec, - const Ffmc& ffmc, - const Dmc& dmc, - const Dc& dc, - const Isi& isi, - const Bui& bui, - const Fwi& fwi) noexcept - : Weather(temp, rh, wind, prec), - ffmc_(ffmc), - dmc_(dmc), - dc_(dc), - // HACK: recalculate so that we can check that things are within tolerances - isi_(Isi(isi.asValue(), wind.speed(), ffmc)), - bui_(Bui(bui.asValue(), dmc, dc)), - fwi_(Fwi(fwi.asValue(), isi, bui)), - // FIX: this is duplicated in ffmc_effect - mc_ffmc_pct_(ffmc_to_moisture(ffmc)), - mc_dmc_pct_(exp((dmc.asValue() - 244.72) / -43.43) + 20), - ffmc_effect_(ffmc_effect(ffmc)) -{ -} -FwiWeather::FwiWeather(const FwiWeather& yesterday, - const int month, - const MathSize latitude, - const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec) - : FwiWeather(temp, - rh, - wind, - prec, - Ffmc(temp, rh, wind.speed(), prec, yesterday.ffmc()), - Dmc(temp, rh, prec, yesterday.dmc(), month, latitude), - Dc(temp, prec, yesterday.dc(), month, latitude)) -{ -} -FwiWeather::FwiWeather(const FwiWeather& wx, - const Wind& wind, - const Ffmc& ffmc, - const Isi& isi) noexcept - : FwiWeather(wx.temp(), - wx.rh(), - wind, - wx.prec(), - ffmc, - wx.dmc(), - wx.dc(), - isi, - wx.bui(), - Fwi(isi, wx.bui())) -{ -} -FwiWeather::FwiWeather(const FwiWeather& wx, const Wind& wind, const Ffmc& ffmc) noexcept - : FwiWeather(wx, wind, ffmc, Isi(wind.speed(), ffmc)) -{ -} -FwiWeather::FwiWeather(const FwiWeather& wx, const Speed& ws, const Ffmc& ffmc) noexcept - : FwiWeather(wx, Wind(wx.wind().direction(), ws), ffmc) -{ -} -FwiWeather::FwiWeather() noexcept - : FwiWeather(Zero) -{ -} -FwiWeather::FwiWeather(const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec, - const Ffmc& ffmc, - const Dmc& dmc, - const Dc& dc, - const Isi& isi, - const Bui& bui) noexcept - : FwiWeather(temp, rh, wind, prec, ffmc, dmc, dc, isi, bui, Fwi(isi, bui)) -{ -} -FwiWeather::FwiWeather(const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec, - const Ffmc& ffmc, - const Dmc& dmc, - const Dc& dc) noexcept - : FwiWeather(temp, rh, wind, prec, ffmc, dmc, dc, Isi(wind.speed(), ffmc), Bui(dmc, dc)) -{ -} -} diff --git a/firestarr/src/cpp/FWI.h b/firestarr/src/cpp/FWI.h deleted file mode 100644 index 2d335bcf8..000000000 --- a/firestarr/src/cpp/FWI.h +++ /dev/null @@ -1,550 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include "Weather.h" -namespace fs::wx -{ -/** - * \brief Fine Fuel Moisture Code value. - */ -class Ffmc : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond - /** - * \brief Calculate Fine Fuel Moisture Code - * \param temperature Temperature (Celsius) - * \param rh Relative Humidity (%) - * \param ws Wind Speed (km/h) - * \param prec Precipitation (24hr accumulated, noon-to-noon) (mm) - * \param ffmc_previous Fine Fuel Moisture Code for previous day - */ - Ffmc(const Temperature& temperature, - const RelativeHumidity& rh, - const Speed& ws, - const Precipitation& prec, - const Ffmc& ffmc_previous) noexcept; - /** - * \brief Fine Fuel Moisture Code of 0 - */ - static const Ffmc Zero; - static const Ffmc Invalid; -}; -/** - * \brief Duff Moisture Code value. - */ -class Dmc : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond - /** - * \brief Duff Moisture Code - * \param temperature Temperature (Celsius) - * \param rh Relative Humidity (%) - * \param prec Precipitation (24hr accumulated, noon-to-noon) (mm) - * \param dmc_previous Duff Moisture Code for previous day - * \param month Month to calculate for - * \param latitude Latitude to calculate for - */ - Dmc(const Temperature& temperature, - const RelativeHumidity& rh, - const Precipitation& prec, - const Dmc& dmc_previous, - int month, - MathSize latitude) noexcept; - /** - * \brief Duff Moisture Code of 0 - */ - static const Dmc Zero; - static const Dmc Invalid; -}; -/** - * \brief Drought Code value. - */ -class Dc : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond - /** - * \brief Calculate Drought Code - * \param temperature Temperature (Celsius) - * \param prec Precipitation (24hr accumulated, noon-to-noon) (mm) - * \param dc_previous Drought Code from the previous day - * \param month Month to calculate for - * \param latitude Latitude to calculate for - */ - Dc(const Temperature& temperature, - const Precipitation& prec, - const Dc& dc_previous, - int month, - MathSize latitude) noexcept; - /** - * \brief Drought Code of 0 - */ - static const Dc Zero; - static const Dc Invalid; -}; -/** - * \brief Initial Spread Index value. - */ -class Isi : public Index -{ -public: - /** - * \brief Calculate Initial Spread Index and verify previous value is within tolerance of calculated value - * \param value Value to check is within tolerance of calculated value - * \param ws Wind Speed (km/h) - * \param ffmc Fine Fuel Moisture Code - */ - Isi(MathSize value, const Speed& ws, const Ffmc& ffmc) noexcept; - /** - * \brief Calculate Initial Spread Index - * \param ws Wind Speed (km/h) - * \param ffmc Fine Fuel Moisture Code - */ - Isi(const Speed& ws, const Ffmc& ffmc) noexcept; - /** - * \brief Initial Spread Index of 0 - */ - static const Isi Zero; - static const Isi Invalid; -private: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond -}; -/** - * \brief Build-up Index value. - */ -class Bui : public Index -{ -public: - /** - * \brief Calculate Build-up Index and verify previous value is within tolerance of calculated value - * \param value Value to check is within tolerance of calculated value - * \param dmc Duff Moisture Code - * \param dc Drought Code - */ - Bui(MathSize value, const Dmc& dmc, const Dc& dc) noexcept; - /** - * \brief Calculate Build-up Index - * \param dmc Duff Moisture Code - * \param dc Drought Code - */ - Bui(const Dmc& dmc, const Dc& dc) noexcept; - /** - * \brief Build-up Index of 0 - */ - static const Bui Zero; - static const Bui Invalid; -private: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond -}; -/** - * \brief Fire Weather Index value. - */ -class Fwi : public Index -{ -public: - /** - * \brief Calculate Fire Weather Index and verify previous value is within tolerance of calculated value - * \param value Value to check is within tolerance of calculated value - * \param isi Initial Spread Index - * \param bui Build-up Index - */ - Fwi(MathSize value, const Isi& isi, const Bui& bui) noexcept; - /** - * \brief Calculate Fire Weather Index - * \param isi Initial Spread Index - * \param bui Build-up Index - */ - Fwi(const Isi& isi, const Bui& bui) noexcept; - /** - * \brief Fire Weather Index of 0 - */ - static const Fwi Zero; - static const Fwi Invalid; -private: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond -}; -/** - * \brief Danger Severity Rating value. - */ -class Dsr : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond - /** - * \brief Calculate Danger Severity Rating - * \param fwi Fire Weather Index - */ - explicit Dsr(const Fwi& fwi) noexcept; - /** - * \brief Danger Severity Rating of 0 - */ - static const Dsr Zero; - static const Dsr Invalids; -}; -/** - * \brief A Weather value with calculated FWI indices. - */ -class FwiWeather - : public Weather -{ -public: - /** - * \brief FwiWeather with 0 for all Indices - */ - static const FwiWeather Zero; - static const FwiWeather Invalid; - /** - * \brief Construct with 0 for all values - */ - FwiWeather() noexcept; - /** - * \brief Construct by reading from istringstream - * \param iss Stream to parse - * \param str string to read into - */ - FwiWeather(istringstream* iss, - string* str); - /** - * \brief construct by applying noon weather to yesterday's indices - * \param yesterday FwiWeather yesterday used for startup indices - * \param month Month to calculate for - * \param latitude Latitude to calculate for - * \param temp Temperature (Celsius) - * \param rh Relative Humidity (%) - * \param wind Wind (km/h) - * \param prec Precipitation (24hr accumulated, noon-to-noon) (mm) - */ - FwiWeather(const FwiWeather& yesterday, - const int month, - const MathSize latitude, - const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec); - /** - * \brief Constructor - * \param temp Temperature (Celsius) - * \param rh Relative Humidity (%) - * \param wind Wind (km/h) - * \param prec Precipitation (1hr accumulation) (mm) - * \param ffmc Fine Fuel Moisture Code - * \param dmc Duff Moisture Code - * \param dc Drought Code - * \param isi Initial Spread Index - * \param bui Build-up Index - * \param fwi Fire Weather Index - */ - FwiWeather(const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec, - const Ffmc& ffmc, - const Dmc& dmc, - const Dc& dc, - const Isi& isi, - const Bui& bui, - const Fwi& fwi) noexcept; - /** - * \brief Construct by calculating FWI - * \param temp Temperature (Celsius) - * \param rh Relative Humidity (%) - * \param wind Wind (km/h) - * \param prec Precipitation (1hr accumulation) (mm) - * \param ffmc Fine Fuel Moisture Code - * \param dmc Duff Moisture Code - * \param dc Drought Code - * \param isi Initial Spread Index - * \param bui Build-up Index - */ - FwiWeather(const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec, - const Ffmc& ffmc, - const Dmc& dmc, - const Dc& dc, - const Isi& isi, - const Bui& bui) noexcept; - /** - * \brief Construct by calculating ISI, BUI, & FWI - * \param temp Temperature (Celsius) - * \param rh Relative Humidity (%) - * \param wind Wind (km/h) - * \param prec Precipitation (1hr accumulation) (mm) - * \param ffmc Fine Fuel Moisture Code - * \param dmc Duff Moisture Code - * \param dc Drought Code - */ - FwiWeather(const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec, - const Ffmc& ffmc, - const Dmc& dmc, - const Dc& dc) noexcept; - /** - * \brief Construct by recalculating with different wind Speed and Ffmc - * \param wx Original weather values - * \param ws Wind Speed to use - * \param ffmc Fine Fuel Moisture Code to use - */ - FwiWeather(const FwiWeather& wx, const Speed& ws, const Ffmc& ffmc) noexcept; - /** - * \brief Destructor - */ - ~FwiWeather() override = default; - /** - * \brief Move constructor - * \param rhs FwiWeather to move from - */ - constexpr FwiWeather(FwiWeather&& rhs) noexcept = default; - /** - * \brief Copy constructor - * \param rhs FwiWeather to copy from - */ - constexpr FwiWeather(const FwiWeather& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs FwiWeather to move from - * \return This, after assignment - */ - FwiWeather& operator=(FwiWeather&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs FwiWeather to copy from - * \return This, after assignment - */ - FwiWeather& operator=(const FwiWeather& rhs) = default; - /** - * \brief Fine Fuel Moisture Code - * \return Fine Fuel Moisture Code - */ - [[nodiscard]] constexpr const Ffmc& ffmc() const - { - return ffmc_; - } - /** - * \brief Duff Moisture Code - * \return Duff Moisture Code - */ - [[nodiscard]] constexpr const Dmc& dmc() const - { - return dmc_; - } - /** - * \brief Drought Code - * \return Drought Code - */ - [[nodiscard]] constexpr const Dc& dc() const - { - return dc_; - } - /** - * \brief Initial Spread Index - * \return Initial Spread Index - */ - [[nodiscard]] constexpr const Isi& isi() const - { - return isi_; - } - /** - * \brief Build-up Index - * \return Build-up Index - */ - [[nodiscard]] constexpr const Bui& bui() const - { - return bui_; - } - /** - * \brief Fire Weather Index - * \return Fire Weather Index - */ - [[nodiscard]] constexpr const Fwi& fwi() const - { - return fwi_; - } - /** - * \brief Moisture content (%) based on Ffmc - * \return Moisture content (%) based on Ffmc - */ - [[nodiscard]] constexpr MathSize mcFfmcPct() const - { - return mc_ffmc_pct_; - } - /** - * \brief Moisture content (%) based on Dmc - * \return Moisture content (%) based on Dmc - */ - [[nodiscard]] constexpr MathSize mcDmcPct() const - { - return mc_dmc_pct_; - } - /** - * \brief Moisture content (ratio) based on Ffmc - * \return Moisture content (ratio) based on Ffmc - */ - [[nodiscard]] constexpr MathSize mcFfmc() const - { - return mcFfmcPct() / 100.0; - } - /** - * \brief Moisture content (ratio) based on Dmc - * \return Moisture content (ratio) based on Dmc - */ - [[nodiscard]] constexpr MathSize mcDmc() const - { - return mcDmcPct() / 100.0; - } - /** - * \brief Ffmc effect used for spread - * \return Ffmc effect used for spread - */ - [[nodiscard]] constexpr MathSize ffmcEffect() const - { - return ffmc_effect_; - } -private: - /** - * \brief Calculate based on indices plus new Wind, Ffmc, and Isi - * \param wx FwiWeather to use most indices from - * \param wind Wind to override with - * \param ffmc Ffmc to override with - * \param isi Isi calculated from given Wind and Ffmc to override with - */ - FwiWeather(const FwiWeather& wx, - const Wind& wind, - const Ffmc& ffmc, - const Isi& isi) noexcept; - /** - * \brief Calculate based on indices plus new Wind and Ffmc - * \param wx FwiWeather to use most indices from - * \param wind Wind to override with - * \param ffmc Ffmc to override with - */ - FwiWeather(const FwiWeather& wx, const Wind& wind, const Ffmc& ffmc) noexcept; - /** - * \brief Fine Fuel Moisture Code - */ - Ffmc ffmc_; - /** - * \brief Duff Moisture Code - */ - Dmc dmc_; - /** - * \brief Drought Code - */ - Dc dc_; - /** - * \brief Initial Spread Index - */ - Isi isi_; - /** - * \brief Build-up Index - */ - Bui bui_; - /** - * \brief Fire Weather Index - */ - Fwi fwi_; - /** - * \brief Moisture content (ratio) based on Ffmc - */ - MathSize mc_ffmc_pct_; - /** - * \brief Moisture content (ratio) based on Dmc - */ - MathSize mc_dmc_pct_; - /** - * \brief Ffmc effect used for spread - */ - MathSize ffmc_effect_; -}; -[[nodiscard]] constexpr bool operator<(const FwiWeather& lhs, const FwiWeather& rhs) -{ - if (lhs.temp() == rhs.temp()) - { - if (lhs.rh() == rhs.rh()) - { - if (lhs.wind() == rhs.wind()) - { - if (lhs.prec() == rhs.prec()) - { - if (lhs.ffmc() == rhs.ffmc()) - { - if (lhs.dmc() == rhs.dmc()) - { - if (lhs.dc() == rhs.dc()) - { - assert(lhs.isi() == rhs.isi()); - assert(lhs.bui() == rhs.bui()); - assert(lhs.fwi() == rhs.fwi()); - } - return lhs.dc() < rhs.dc(); - } - return lhs.dmc() < rhs.dmc(); - } - return lhs.ffmc() < rhs.ffmc(); - } - return lhs.prec() < rhs.prec(); - } - return lhs.wind() < rhs.wind(); - } - return lhs.rh() < rhs.rh(); - } - return lhs.temp() < rhs.temp(); -} -[[nodiscard]] constexpr bool operator!=(const FwiWeather& lhs, const FwiWeather& rhs) -{ - return lhs.temp() != rhs.temp() - || lhs.rh() != rhs.rh() - || lhs.wind() != rhs.wind() - || lhs.prec() != rhs.prec() - || lhs.ffmc() != rhs.ffmc() - || lhs.dmc() != rhs.dmc() - || lhs.dc() != rhs.dc() - || lhs.isi() != rhs.isi() - || lhs.bui() != rhs.bui() - || lhs.fwi() != rhs.fwi(); -} -[[nodiscard]] constexpr bool operator==(const FwiWeather& lhs, const FwiWeather& rhs) -{ - return !(lhs != rhs); -} -constexpr auto FFMC_MOISTURE_CONSTANT = 147.27723; -constexpr MathSize ffmc_to_moisture(const MathSize ffmc) noexcept -{ - return FFMC_MOISTURE_CONSTANT * (101.0 - ffmc) / (59.5 + ffmc); -} -constexpr MathSize ffmc_to_moisture(const Ffmc& ffmc) noexcept -{ - return ffmc_to_moisture(ffmc.asValue()); -} -constexpr MathSize moisture_to_ffmc(const MathSize m) noexcept -{ - return (59.5 * (250.0 - m) / (FFMC_MOISTURE_CONSTANT + m)); -} -constexpr Ffmc ffmc_from_moisture(const MathSize m) noexcept -{ - return Ffmc(moisture_to_ffmc(m)); -} -} diff --git a/firestarr/src/cpp/FireSpread.cpp b/firestarr/src/cpp/FireSpread.cpp deleted file mode 100644 index 3b0a5ac23..000000000 --- a/firestarr/src/cpp/FireSpread.cpp +++ /dev/null @@ -1,389 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "FireSpread.h" -#include "FuelLookup.h" -#include "FuelType.h" -#include "Scenario.h" -#include "Settings.h" -#include "unstable.h" -#include "SpreadAlgorithm.h" - -namespace fs::sim -{ -// number of degrees between spread directions -// if not defined then use variable step degrees -// #define STEP - -/** - * \brief Maximum slope that affects ISI - everything after this is the same factor - */ -static constexpr auto MAX_SLOPE_FOR_FACTOR = 69; - -SlopeTableArray make_slope_table() noexcept -{ - // HACK: slope can be infinite, but anything > max is the same as max - // ST-X-3 Eq. 39 - Calculate Spread Factor - // GLC-X-10 39a/b increase to 70% limit - SlopeTableArray result{}; - for (size_t i = 0; i <= MAX_SLOPE_FOR_FACTOR; ++i) - { - result.at(i) = exp(3.533 * pow(i / 100.0, 1.2)); - } - constexpr auto MAX_SLOPE = MAX_SLOPE_FOR_FACTOR + 1; - // anything >=70 is just 10 - std::fill( - &(result[MAX_SLOPE]), - &(result[MAX_SLOPE_FOR_DISTANCE]), - 10.0); - static_assert(result.size() == MAX_SLOPE_FOR_DISTANCE + 1); - return result; -} -const SlopeTableArray SpreadInfo::SlopeTable = make_slope_table(); -int calculate_nd_ref_for_point(const int elevation, const topo::Point& point) noexcept -{ - // NOTE: cffdrs R package stores longitude West as a positive, so this would be `- long` - const auto latn = elevation <= 0 - ? (46.0 + 23.4 * exp(-0.0360 * (150 + point.longitude()))) - : (43.0 + 33.7 * exp(-0.0351 * (150 + point.longitude()))); - // add 0.5 to round by truncating - return static_cast(truncl( - 0.5 + (elevation <= 0 ? 151.0 * (point.latitude() / latn) : 142.1 * (point.latitude() / latn) + 0.0172 * elevation))); -} -int calculate_nd_for_point(const Day day, const int elevation, const topo::Point& point) -{ - return static_cast(abs(day - calculate_nd_ref_for_point(elevation, point))); -} -static MathSize calculate_standard_back_isi_wsv(const MathSize v) noexcept -{ - return 0.208 * exp(-0.05039 * v); -} -static const util::LookupTable<&calculate_standard_back_isi_wsv> STANDARD_BACK_ISI_WSV{}; -static constexpr MathSize calculate_standard_wsv(const MathSize v) noexcept -{ - return v < 40.0 - ? exp(0.05039 * v) - : 12.0 * (1.0 - exp(-0.0818 * (v - 28))); -} -static const util::LookupTable<&calculate_standard_wsv> STANDARD_WSV{}; -SpreadInfo::SpreadInfo(const Scenario& scenario, - const DurationSize time, - const topo::SpreadKey& key, - const int nd, - const wx::FwiWeather* weather) - : SpreadInfo(scenario, time, key, nd, weather, scenario.weather_daily(time)) -{ -} -MathSize SpreadInfo::initial(SpreadInfo& spread, - const wx::FwiWeather& weather, - MathSize& ffmc_effect, - MathSize& wsv, - MathSize& rso, - const fuel::FuelType* const fuel, - bool has_no_slope, - MathSize heading_sin, - MathSize heading_cos, - MathSize bui_eff, - MathSize min_ros, - MathSize critical_surface_intensity) -{ - ffmc_effect = spread.ffmcEffect(); - // needs to be non-const so that we can update if slopeEffect changes direction - MathSize raz = spread.wind().heading(); - const auto isz = 0.208 * ffmc_effect; - wsv = spread.wind().speed().asValue(); - if (!has_no_slope) - { - const auto isf1 = fuel->calculateIsf(spread, isz); - // const auto isf = (0.0 == isf1) ? isz : isf1; - // we know const auto isz = 0.208 * ffmc_effect; - auto wse = 0.0 == isf1 ? 0 : log(isf1 / isz) / 0.05039; - if (wse > 40) - { - wse = 28.0 - log(1.0 - min(0.999 * 2.496 * ffmc_effect, isf1) / (2.496 * ffmc_effect)) / 0.0818; - } - // we know that at->raz is already set to be the wind heading - const auto wsv_x = spread.wind().wsvX() + wse * heading_sin; - const auto wsv_y = spread.wind().wsvY() + wse * heading_cos; - wsv = sqrt(wsv_x * wsv_x + wsv_y * wsv_y); - raz = (0 == wsv) ? 0 : acos(wsv_y / wsv); - if (wsv_x < 0) - { - raz = util::RAD_360 - raz; - } - } - spread.raz_ = fs::wx::Direction(raz, true); - const auto isi = isz * STANDARD_WSV(wsv); - // FIX: make this a member function so we don't need to preface head_ros_ - spread.head_ros_ = fuel->calculateRos(spread.nd(), - weather, - isi) - * bui_eff; - if (min_ros > spread.head_ros_) - { - spread.head_ros_ = INVALID_ROS; - } - else - { - spread.sfc_ = fuel->surfaceFuelConsumption(spread); - rso = fuel::FuelType::criticalRos(spread.sfc_, critical_surface_intensity); - const auto sfi = fuel::fire_intensity(spread.sfc_, spread.head_ros_); - spread.is_crown_ = fuel::FuelType::isCrown(critical_surface_intensity, - sfi); - if (spread.is_crown_) - { - spread.head_ros_ = fuel->finalRos(spread, - isi, - fuel->crownFractionBurned(spread.head_ros_, rso), - spread.head_ros_); - } - } - return spread.head_ros_; -} -static MathSize find_min_ros(const Scenario& scenario, const DurationSize time) -{ - return std::max(scenario.spreadThresholdByRos(time), - Settings::minimumRos()); -} -SpreadInfo::SpreadInfo(const Scenario& scenario, - const DurationSize time, - const topo::SpreadKey& key, - const int nd, - const wx::FwiWeather* weather, - const wx::FwiWeather* weather_daily) - : SpreadInfo(time, - find_min_ros(scenario, time), - scenario.cellSize(), - key, - nd, - weather, - weather_daily) -{ -} -static topo::SpreadKey make_key(const SlopeSize slope, - const AspectSize aspect, - const char* fuel_name) -{ - const auto lookup = fs::sim::Settings::fuelLookup(); - const auto key = topo::Cell::key(topo::Cell::hashCell(slope, - aspect, - fuel::FuelType::safeCode(lookup.byName(fuel_name)))); - const auto a = topo::Cell::aspect(key); - const auto s = topo::Cell::slope(key); - const auto fuel = fuel::fuel_by_code(topo::Cell::fuelCode(key)); - logging::check_equal(s, slope, "slope"); - logging::check_equal(a, (0 == slope ? 0 : aspect), "aspect"); - logging::check_equal(fuel->name(), fuel_name, "fuel"); - return key; -} -SpreadInfo::SpreadInfo( - const int year, - const int month, - const int day, - const int hour, - const int minute, - const MathSize latitude, - const MathSize longitude, - const ElevationSize elevation, - const SlopeSize slope, - const AspectSize aspect, - const char* fuel_name, - const wx::FwiWeather* weather) - : SpreadInfo(util::to_tm(year, month, day, hour, minute), - latitude, - longitude, - elevation, - slope, - aspect, - fuel_name, - weather) -{ -} -SpreadInfo::SpreadInfo( - const tm& start_date, - const MathSize latitude, - const MathSize longitude, - const ElevationSize elevation, - const SlopeSize slope, - const AspectSize aspect, - const char* fuel_name, - const wx::FwiWeather* weather) - : SpreadInfo(util::to_time(start_date), - 0.0, - 100.0, - slope, - aspect, - fuel_name, - calculate_nd_for_point(start_date.tm_yday, elevation, fs::topo::Point(latitude, longitude)), - weather) -{ -} -SpreadInfo::SpreadInfo(const DurationSize time, - const MathSize min_ros, - const MathSize cell_size, - const SlopeSize slope, - const AspectSize aspect, - const char* fuel_name, - const int nd, - const wx::FwiWeather* weather) - : SpreadInfo(time, min_ros, cell_size, make_key(slope, aspect, fuel_name), nd, weather, weather) -{ -} -SpreadInfo::SpreadInfo(const DurationSize time, - const MathSize min_ros, - const MathSize cell_size, - const topo::SpreadKey& key, - const int nd, - const wx::FwiWeather* weather) - : SpreadInfo(time, min_ros, cell_size, key, nd, weather, weather) -{ -} -SpreadInfo::SpreadInfo(const DurationSize time, - const MathSize min_ros, - const MathSize cell_size, - const topo::SpreadKey& key, - const int nd, - const wx::FwiWeather* weather, - const wx::FwiWeather* weather_daily) - : offsets_({}), - max_intensity_(INVALID_INTENSITY), - key_(key), - weather_(weather), - time_(time), - head_ros_(INVALID_ROS), - cfb_(-1), - cfc_(-1), - tfc_(-1), - sfc_(-1), - is_crown_(false), - raz_(fs::wx::Direction::Invalid), - nd_(nd) -{ - // HACK: use weather_daily to figure out probability of spread but hourly for ROS - const auto slope_azimuth = topo::Cell::aspect(key_); - const auto fuel = fuel::fuel_by_code(topo::Cell::fuelCode(key_)); - const auto has_no_slope = 0 == percentSlope(); - MathSize heading_sin = 0; - MathSize heading_cos = 0; - if (!has_no_slope) - { - const auto heading = util::to_heading( - util::to_radians(static_cast(slope_azimuth))); - heading_sin = _sin(heading); - heading_cos = _cos(heading); - } - // HACK: only use BUI from hourly weather for both calculations - const auto _bui = bui().asValue(); - const auto bui_eff = fuel->buiEffect(_bui); - // FIX: gets calculated when not necessary sometimes - const auto critical_surface_intensity = fuel->criticalSurfaceIntensity(*this); - MathSize ffmc_effect; - MathSize wsv; - MathSize rso; - if (min_ros > SpreadInfo::initial( - *this, - *weather_daily, - ffmc_effect, - wsv, - rso, - fuel, - has_no_slope, - heading_sin, - heading_cos, - bui_eff, - min_ros, - critical_surface_intensity) - || sfc_ < COMPARE_LIMIT) - { - return; - } - // Now use hourly weather for actual spread calculations - // don't check again if pointing at same weather - if (weather != weather_daily) - { - if ((min_ros > SpreadInfo::initial(*this, - *weather, - ffmc_effect, - wsv, - rso, - fuel, - has_no_slope, - heading_sin, - heading_cos, - bui_eff, - min_ros, - critical_surface_intensity) - || sfc_ < COMPARE_LIMIT)) - { - // no spread with hourly weather - // NOTE: only would happen if FFMC hourly is lower than FFMC daily? - return; - } - } - logging::verbose("initial ros is %f", head_ros_); - const auto back_isi = ffmc_effect * STANDARD_BACK_ISI_WSV(wsv); - auto back_ros = fuel->calculateRos(nd, - *weather, - back_isi) - * bui_eff; - if (is_crown_) - { - back_ros = fuel->finalRos(*this, - back_isi, - fuel->crownFractionBurned(back_ros, rso), - back_ros); - } - tfc_ = sfc_; - // don't need to re-evaluate if crown with new head_ros_ because it would only go up if is_crown_ - if (fuel->canCrown() && is_crown_) - { - // wouldn't be crowning if ros is 0 so that's why this is in an else - cfb_ = fuel->crownFractionBurned(head_ros_, rso); - cfc_ = fuel->crownConsumption(cfb_); - tfc_ += cfc_; - } - // max intensity should always be at the head - max_intensity_ = fuel::fire_intensity(tfc_, head_ros_); - l_b_ = fuel->lengthToBreadth(wsv); - const HorizontalAdjustment correction_factor = horizontal_adjustment(slope_azimuth, percentSlope()); - // const auto spread_algorithm = OriginalSpreadAlgorithm(1.0, cell_size, min_ros); - const auto spread_algorithm = WidestEllipseAlgorithm(MAX_SPREAD_ANGLE, cell_size, min_ros); - offsets_ = spread_algorithm.calculate_offsets(correction_factor, - tfc_, - raz_.asRadians(), - head_ros_, - back_ros, - l_b_); - // might not be correct depending on slope angle correction - // #ifdef DEBUG_POINTS - // // if (head_ros_ >= min_ros) - // { - // logging::check_fatal( - // offsets_.empty(), - // "Empty when ros of %f >= %f", - // head_ros_, - // min_ros); - // } - // #endif - // if no offsets then not spreading so invalidate head_ros_ - if (0 == offsets_.size()) - { - head_ros_ = INVALID_ROS; - max_intensity_ = INVALID_INTENSITY; - cfb_ = -1; - cfc_ = -1; - tfc_ = -1; - sfc_ = -1; - is_crown_ = false; - raz_ = fs::wx::Direction::Invalid; - } -} -// MathSize SpreadInfo::calculateSpreadProbability(const MathSize ros) -// { -// // note: based off spread event probability from wotton -// return 1 / (1 + exp(1.64 - 0.16 * ros)); -// } -} diff --git a/firestarr/src/cpp/FireSpread.h b/firestarr/src/cpp/FireSpread.h deleted file mode 100644 index 4d70e360b..000000000 --- a/firestarr/src/cpp/FireSpread.h +++ /dev/null @@ -1,388 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "Cell.h" -#include "FWI.h" -#include "Point.h" -#include "Settings.h" -#include "Util.h" -#include "InnerPos.h" -namespace fs::sim -{ - -static constexpr int MAX_SPREAD_ANGLE = 5.0; -static constexpr MathSize INVALID_ROS = -1.0; -static constexpr MathSize INVALID_INTENSITY = -1.0; - -class Scenario; -/** - * \brief Possible results of an attempt to spread. - */ -int calculate_nd_ref_for_point(const int elevation, const topo::Point& point) noexcept; -int calculate_nd_for_point(const Day day, const int elevation, const topo::Point& point); -/** - * \brief Information regarding spread within a Cell for a specific Scenario and time. - */ -class SpreadInfo -{ -public: - /** - * \brief Lookup table for Slope Factor calculated from Percent Slope - */ - static const SlopeTableArray SlopeTable; - ~SpreadInfo() = default; - SpreadInfo(const Scenario& scenario, - DurationSize time, - const topo::SpreadKey& key, - int nd, - const wx::FwiWeather* weather); - /** - * \brief Calculate fire spread for time and place - * \param scenario Scenario this is spreading in - * \param time Time spread is occurring - * \param key Attributes for Cell spread is occurring in - * \param nd Difference between date and the date of minimum foliar moisture content - * \param weather FwiWeather to use for calculations - */ - SpreadInfo(const Scenario& scenario, - DurationSize time, - const topo::SpreadKey& key, - int nd, - const wx::FwiWeather* weather, - const wx::FwiWeather* weather_daily); - CONSTEXPR SpreadInfo(SpreadInfo&& rhs) noexcept = default; - SpreadInfo(const SpreadInfo& rhs) noexcept = default; - CONSTEXPR SpreadInfo& operator=(SpreadInfo&& rhs) noexcept = default; - SpreadInfo& operator=(const SpreadInfo& rhs) noexcept = default; - // static MathSize calculateSpreadProbability(MathSize ros); - /** - * \brief Determine rate of spread from probability of spread threshold - * \param threshold Probability of spread threshold - * \return Rate of spread at given threshold (m/min) - */ - [[nodiscard]] static constexpr MathSize calculateRosFromThreshold(const ThresholdSize threshold) - { - // for some reason it returns -nan instead of nan if it's 1, so return this instead - if (1.0 == threshold) - { - return std::numeric_limits::infinity(); - } - if (0.0 == threshold) - { - return 0.0; - } - // NOTE: based off spread event probability from wotton - // should be the inverse of calculateSpreadProbability() - return 25.0 / 4.0 * log(-(exp(41.0 / 25.0) * threshold) / (threshold - 1)); - } - /** - * \brief Maximum intensity in any direction for spread (kW/m) - * \return Maximum intensity in any direction for spread (kW/m) - */ - [[nodiscard]] MathSize maxIntensity() const noexcept - { - return max_intensity_; - } - /** - * \brief Offsets from origin point that represent spread under these conditions - * \return Offsets from origin point that represent spread under these conditions - */ - [[nodiscard]] const OffsetSet& offsets() const - { - return offsets_; - } - /** - * \brief Whether or not there is no spread - * \return Whether or not there is no spread - */ - [[nodiscard]] constexpr bool isNotSpreading() const - { - return isInvalid(); - } - /** - * \brief Difference between date and the date of minimum foliar moisture content - * \return Difference between date and the date of minimum foliar moisture content - */ - [[nodiscard]] constexpr int nd() const - { - return nd_; - } - /** - * \brief FwiWeather used for spread - * \return FwiWeather used for spread - */ - [[nodiscard]] constexpr const wx::FwiWeather* weather() const - { - return weather_; - } - /** - * \brief Wind used for spread - * \return Wind used for spread - */ - [[nodiscard]] constexpr const wx::Wind& wind() const - { - return weather()->wind(); - } - /** - * \brief Fine Fuel Moisture Code used for spread - * \return Fine Fuel Moisture Code used for spread - */ - [[nodiscard]] constexpr const wx::Ffmc& ffmc() const - { - return weather()->ffmc(); - } - /** - * \brief Build-up Index used for spread - * \return Build-up Index used for spread - */ - [[nodiscard]] constexpr const wx::Bui& bui() const - { - return weather()->bui(); - } - /** - * \brief Duff Moisture Code used for spread - * \return Duff Moisture Code used for spread - */ - [[nodiscard]] constexpr const wx::Dmc& dmc() const - { - return weather()->dmc(); - } - /** - * \brief Drought Code used for spread - * \return Drought Code used for spread - */ - [[nodiscard]] constexpr const wx::Dc& dc() const - { - return weather()->dc(); - } - /** - * \brief FFMC effect used for spread - * \return FFMC effect used for spread - */ - [[nodiscard]] constexpr MathSize ffmcEffect() const - { - return weather()->ffmcEffect(); - } - /** - * \brief Time used for spread - * \return Time used for spread - */ - [[nodiscard]] constexpr DurationSize time() const - { - return time_; - } - /** - * \brief Length to breadth ratio used for spread - * \return Length to breadth ratio used for spread - */ - [[nodiscard]] constexpr MathSize lengthToBreadth() const - { - return l_b_; - } - /** - * \brief Slope used for spread (%) - * \return Slope used for spread (%) - */ - [[nodiscard]] constexpr SlopeSize percentSlope() const - { - return topo::Cell::slope(key_); - } - /** - * \brief Aspect used for spread (degrees) - * \return Aspect used for spread (degrees) - */ - [[nodiscard]] constexpr AspectSize slopeAzimuth() const - { - return topo::Cell::aspect(key_); - } - /** - * \brief Head fire rate of spread (m/min) - * \return Head fire rate of spread (m/min) - */ - [[nodiscard]] constexpr MathSize headRos() const - { - return head_ros_; - } - /** - * \brief Head fire spread direction - * \return Head fire spread direction - */ - [[nodiscard]] constexpr fs::wx::Direction headDirection() const - { - return raz_; - } - /** - * \brief Slope factor calculated from percent slope - * \return Slope factor calculated from percent slope - */ - [[nodiscard]] constexpr MathSize slopeFactor() const - { - // HACK: slope can be infinite, but anything > 60 is the same as 60 - // we already capped the percent slope when making the Cells - return SlopeTable.at(percentSlope()); - } - /** - * \brief Calculate foliar moisture - * \return Calculated foliar moisture - */ - [[nodiscard]] constexpr MathSize foliarMoisture() const - { - const auto nd = abs(nd_); - // don't need to check `&& nd_ < 50` in second part because of reordering - return nd >= 50 - ? 120.0 - : nd >= 30 - ? 32.9 + 3.17 * nd - 0.0288 * nd * nd - : 85.0 + 0.0189 * nd * nd; - } - /** - * \brief Whether or not there is no spread for given conditions - * \return Whether or not there is no spread for given conditions - */ - [[nodiscard]] constexpr bool isInvalid() const - { - return -1 == head_ros_; - } - // required for making a map of SpreadInfo objects - SpreadInfo() noexcept - : offsets_({}), - max_intensity_(INVALID_INTENSITY), - key_(0), - weather_(nullptr), - time_(-1), - l_b_(-1), - head_ros_(INVALID_ROS), - cfb_(-1), - cfc_(-1), - tfc_(-1), - sfc_(-1), - is_crown_(false), - raz_(fs::wx::Direction::Invalid), - nd_(-1) { - }; - SpreadInfo( - const int year, - const int month, - const int day, - const int hour, - const int minute, - const MathSize latitude, - const MathSize longitude, - const ElevationSize elevation, - const SlopeSize slope, - const AspectSize aspect, - const char* fuel_name, - const wx::FwiWeather* weather); - SpreadInfo( - const tm& start_date, - const MathSize latitude, - const MathSize longitude, - const ElevationSize elevation, - const SlopeSize slope, - const AspectSize aspect, - const char* fuel_name, - const wx::FwiWeather* weather); - MathSize crownFractionBurned() const - { - return cfb_; - } - MathSize crownFuelConsumption() const - { - return cfc_; - } - char fireDescription() const - { - return cfb_ >= 0.9 ? 'C' : (cfb_ < 0.1 ? 'S' : 'I'); - } - MathSize surfaceFuelConsumption() const - { - return sfc_; - } - MathSize totalFuelConsumption() const - { - return tfc_; - } -private: - /** - * Actual fire spread calculation without needing to worry about settings or scenarios - */ - SpreadInfo(DurationSize time, - MathSize min_ros, - MathSize cell_size, - const SlopeSize slope, - const AspectSize aspect, - const char* fuel_name, - int nd, - const wx::FwiWeather* weather); - SpreadInfo(DurationSize time, - MathSize min_ros, - MathSize cell_size, - const topo::SpreadKey& key, - int nd, - const wx::FwiWeather* weather); - SpreadInfo(DurationSize time, - MathSize min_ros, - MathSize cell_size, - const topo::SpreadKey& key, - int nd, - const wx::FwiWeather* weather, - const wx::FwiWeather* weather_daily); - /** - * Do initial spread calculations - * \return Initial head ros calculation (-1 for none) - */ - static MathSize initial(SpreadInfo& spread, - const wx::FwiWeather& weather, - MathSize& ffmc_effect, - MathSize& wsv, - MathSize& rsoi, - const fuel::FuelType* const fuel, - bool has_no_slope, - MathSize heading_sin, - MathSize heading_cos, - MathSize bui_eff, - MathSize min_ros, - MathSize critical_surface_intensity); - /** - * \brief Offsets from origin point that represent spread under these conditions - */ - OffsetSet offsets_{}; - /** - * \brief Maximum intensity in any direction for spread (kW/m) - */ - MathSize max_intensity_; - /** - * \brief Attributes for Cell spread is occurring in - */ - topo::SpreadKey key_; - /** - * \brief FwiWeather determining spread - */ - wx::FwiWeather const* weather_; - /** - * \brief Time that spread is occurring - */ - DurationSize time_; - MathSize l_b_; - /** - * \brief Head fire rate of spread (m/min) - */ - MathSize head_ros_; - MathSize cfb_; - MathSize cfc_; - MathSize tfc_; - MathSize sfc_; - bool is_crown_; - /** - * \brief Head fire spread direction - */ - fs::wx::Direction raz_; - /** - * \brief Difference between date and the date of minimum foliar moisture content (from ST-X-3) - */ - int nd_; -}; -} diff --git a/firestarr/src/cpp/FireWeather.cpp b/firestarr/src/cpp/FireWeather.cpp deleted file mode 100644 index 9d3f0f1f5..000000000 --- a/firestarr/src/cpp/FireWeather.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "FireWeather.h" -#include "FuelType.h" -#include "Settings.h" -namespace fs::wx -{ -/*! - * \page weather Hourly fire and weather indices - * - * Hourly weather is read from input and used as-is with no validation. - */ -FireWeather::~FireWeather() -{ - delete weather_by_hour_by_day_; - delete survival_probability_; -} -static unique_ptr make_survival( - const set& used_fuels, - const Day min_date, - const Day max_date, - const vector& weather_by_hour_by_day) -{ - auto result = make_unique(); - for (const auto& in_fuel : used_fuels) - { - if (nullptr != in_fuel - && 0 != strcmp("Invalid", fuel::FuelType::safeName(in_fuel)) - && 0 != strcmp("Non-fuel", fuel::FuelType::safeName(in_fuel))) - { - // initialize with proper size - const auto code = fuel::FuelType::safeCode(in_fuel); - auto by_fuel = vector{}; - by_fuel.resize((static_cast(max_date) - min_date + 2) * DAY_HOURS); - // calculate the entire stream for this fuel - for (auto day = min_date; day <= max_date; ++day) - { - for (auto h = 0; h < DAY_HOURS; ++h) - { - const auto wx = weather_by_hour_by_day.at(util::time_index(day, h, min_date)); - const auto i = util::time_index(day, h, min_date); - by_fuel.at(i) = static_cast(nullptr != wx - ? ( - in_fuel->survivalProbability(*wx)) - : 0.0); - } - } - result->at(code) = std::move(by_fuel); - } - } - return result; -} -FireWeather::FireWeather(const set& used_fuels, - const Day min_date, - const Day max_date, - vector* weather_by_hour_by_day) - : weather_by_hour_by_day_(weather_by_hour_by_day), - survival_probability_( - make_survival(used_fuels, min_date, max_date, *weather_by_hour_by_day).release()), - min_date_(min_date), - max_date_(max_date) -{ - weighted_dsr_ = 0; - // make it so that dsr near start of scenario matters more - auto weight = 1000000000.0; - for (auto& w : *weather_by_hour_by_day_) - { - if (nullptr != w) - { - const auto dsr = 0.0272 * pow(w->fwi().asValue(), 1.77); - weighted_dsr_ += static_cast(weight * dsr); - weight *= 0.8; - } - } -} -} diff --git a/firestarr/src/cpp/FireWeather.h b/firestarr/src/cpp/FireWeather.h deleted file mode 100644 index d7fd176ff..000000000 --- a/firestarr/src/cpp/FireWeather.h +++ /dev/null @@ -1,174 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include -#include "FuelLookup.h" -#include "FWI.h" -#ifdef DEBUG_FWI_WEATHER -#include "Log.h" -#endif -namespace fs -{ -namespace fuel -{ -class FuelType; -} -namespace wx -{ -// use an array instead of a map since number of values is so small and access should be faster -using SurvivalMap = array, NUMBER_OF_FUELS>; -/** - * \brief A stream of weather that gets used by a Scenario every Iteration. - */ -class FireWeather -{ -public: - /** - * \brief Destructor - */ - virtual ~FireWeather(); - /** - * \brief Move constructor - * \param rhs FireWeather to move from - */ - FireWeather(FireWeather&& rhs) = default; - FireWeather(const FireWeather& rhs) = delete; - /** - * \brief Move assignment - * \param rhs FireWeather to move from - * \return This, after assignment - */ - FireWeather& operator=(FireWeather&& rhs) noexcept = default; - FireWeather& operator=(const FireWeather& rhs) = delete; - /** - * \brief Get FwiWeather for given time - * \param time Time to get weather for - * \return FwiWeather for given time - */ - [[nodiscard]] const FwiWeather* at(const DurationSize time) const - { -#ifdef DEBUG_FWI_WEATHER - logging::check_fatal(time < 0 || time >= MAX_DAYS, "Invalid weather time %f", time); -#endif - return weather_by_hour_by_day_->at(util::time_index(time, min_date_)); - } - /** - * \brief Probability of survival in given fuel at given time - * \param time Time to get survival probability for - * \param in_fuel FuelCodeSize of FuelType to use - * \return Probability of survival in given fuel at given time - */ - [[nodiscard]] ThresholdSize survivalProbability(const DurationSize time, - const FuelCodeSize& in_fuel) const - { - return survival_probability_->at(in_fuel).at(util::time_index(time, min_date_)); - } - /** - * \brief Minimum date present in FireWeather - * \return Minimum date present in FireWeather - */ - [[nodiscard]] constexpr Day minDate() const - { - return min_date_; - } - /** - * \brief Maximum date present in FireWeather - * \return Maximum date present in FireWeather - */ - [[nodiscard]] constexpr Day maxDate() const - { - return max_date_; - } - /** - * \brief Weighted Danger Severity Rating for the stream - * \return Weighted Danger Severity Rating for the stream - */ - [[nodiscard]] constexpr size_t weightedDsr() const noexcept - { - return weighted_dsr_; - } - /** - * \brief Weather by hour by day - * \return Weather by hour by day - */ - [[nodiscard]] const vector* getWeather() - { - return weather_by_hour_by_day_; - } - - /** - * \brief Constructor - * \param used_fuels set of FuelTypes that are used in the simulation - * \param min_date Minimum date present in stream - * \param max_date Maximum date present in stream - * \param weather_by_hour_by_day FwiWeather by hour by Day - */ - FireWeather(const set& used_fuels, - Day min_date, - Day max_date, - vector* weather_by_hour_by_day); -private: - /** - * \brief FwiWeather by hour by Day - */ - const vector* weather_by_hour_by_day_; - /** - * \brief Probability of survival for fuels fuel at each time - */ - const SurvivalMap* survival_probability_; - /** - * \brief Minimum date present in stream - */ - Day min_date_; - /** - * \brief Maximum date present in stream - */ - Day max_date_; - /** - * \brief Weighted Danger Severity Rating for the stream - */ - size_t weighted_dsr_; -}; -/** - * \brief Equality operator - * \param lhs First FireWeather - * \param rhs Second FireWeather - * \return Whether or not the two FireWeathers are equal - */ -[[nodiscard]] constexpr bool operator==(const FireWeather& lhs, const FireWeather& rhs) -{ - if (!(lhs.maxDate() == rhs.maxDate() && lhs.minDate() == rhs.minDate())) - { - return false; - } - // FIX: why is this a warning? - for (Day day = lhs.minDate() + static_cast(1); day <= lhs.maxDate(); ++day) - { - for (auto hour = 0; hour < DAY_HOURS; ++hour) - { - const auto time = static_cast(day) + hour / 24.0; - if (lhs.at(time) != rhs.at(time)) - { - return false; - } - } - } - return true; -} -/** - * \brief Inequality operator - * \param lhs First FireWeather - * \param rhs Second FireWeather - * \return Whether or not they are not equivalent - */ -[[nodiscard]] constexpr bool operator!=(const FireWeather& lhs, const FireWeather& rhs) -{ - return !(lhs == rhs); -} -} -} diff --git a/firestarr/src/cpp/FireWeatherDaily.cpp b/firestarr/src/cpp/FireWeatherDaily.cpp deleted file mode 100644 index b53fbfc1b..000000000 --- a/firestarr/src/cpp/FireWeatherDaily.cpp +++ /dev/null @@ -1,576 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "FireWeatherDaily.h" -#include "FuelType.h" -namespace fs::wx -{ -/*! - * \page weather Hourly fire and weather indices - * - * Generally, indices are constant for all cells for the day in each simulation. - * The exceptions to this are Wind and FFMC, and by extension, ISI and FWI. - * - * Hourly wind is calculated based on a simple statistical analysis, with wind - * for each hour being a proportion of the daily wind. - * - * FFMC is calculated using the method described in - * https://www.for.gov.bc.ca/hfd/pubs/Docs/Frr/FRR245.pdf - */ -// adjust based on statistical analysis of hourly wind -static array BY_HOUR = { - .570, - .565, - .563, - .563, - .564, - .581, - .642, - .725, - .808, - .880, - .936, - .977, - 1, - 1.008, - .999, - .973, - .915, - .831, - .724, - .631, - .593, - .586, - .584, - .579}; -inline MathSize wind_speed_adjustment(const int hour) noexcept -{ - return BY_HOUR.at(static_cast(hour)); -} -inline Ffmc ffmc_1200(const MathSize x, - const MathSize x_sq, - const MathSize x_cu, - const MathSize rt_x, - const MathSize exp_neg_x) noexcept -{ - if (x < 21) - { - constexpr auto a = 1.460075956; - constexpr auto b = -0.00039079; - constexpr auto c = 0.28156683; - constexpr auto d = -0.00153983; - constexpr auto e = -0.01282069; - return ffmc_from_moisture(pow((a + c * x + e * x_sq) / (1 + b * x + d * x_sq), 2)); - } - constexpr auto a = -60.0581786; - constexpr auto b = -0.79226507; - constexpr auto c = 1.04936e-05; - constexpr auto d = 24.04228773; - constexpr auto e = -4.7906e+09; - return ffmc_from_moisture(a + b * x + c * x_cu + d * rt_x + e * exp_neg_x); -} -inline Ffmc ffmc_1300(const MathSize x, - const MathSize x_sq, - const MathSize x_cu, - const MathSize rt_x, - const MathSize ln_x) noexcept -{ - if (x < 22) - { - constexpr auto a = 1.255216373; - constexpr auto b = 0.022921707; - constexpr auto c = 0.35809518; - constexpr auto d = -0.00333111; - constexpr auto e = -0.01642423; - constexpr auto f = 3.05664e-05; - return ffmc_from_moisture( - pow((a + c * x + e * x_sq) / (1 + b * x + d * x_sq + f * x_cu), 2)); - } - constexpr auto a = 806.4657627; - constexpr auto b = -1.49162346; - constexpr auto c = 0.000887319; - constexpr auto d = -11465.7458; - constexpr auto e = 12093.7804; - return ffmc_from_moisture(a + b * x + c * x_sq * ln_x + d / rt_x + e * ln_x / x); -} -inline Ffmc ffmc_1400(const MathSize x, - const MathSize x_sq, - const MathSize rt_x, - const MathSize ln_x, - const MathSize exp_x) noexcept -{ - if (x < 23) - { - constexpr auto a = 0.908217387; - constexpr auto b = 0.989724752; - constexpr auto c = 0.001041606; - constexpr auto d = 4.634e-11; - constexpr auto e = -0.00558197; - return ffmc_from_moisture(a + b * x + c * x_sq * rt_x + d * exp_x + e * ln_x); - } - constexpr auto a = 6403.107753; - constexpr auto b = 352.7042531; - constexpr auto c = 873.3642944; - constexpr auto d = -3766.49257; - constexpr auto e = 3580.933366; - return ffmc_from_moisture(a + b * x + c * rt_x * ln_x + d * x / ln_x + e / x_sq); -} -inline Ffmc ffmc_1500(const MathSize x, - const MathSize x_sq, - const MathSize x_cu, - const MathSize rt_x, - const MathSize ln_x) noexcept -{ - if (x < 23) - { - constexpr auto a = 0.248711327; - constexpr auto b = 0.9000214139; - constexpr auto c = 0.965899432; - constexpr auto d = 0.007692506; - constexpr auto e = -0.00030317; - constexpr auto f = 1.12165e-05; - return ffmc_from_moisture( - sqrt(a + b * x + c * x_sq + d * x_cu + e * x_sq * x_sq + f * x_sq * x_cu)); - } - constexpr auto a = 3201.553847; - constexpr auto b = 176.852125; - constexpr auto c = 436.6821439; - constexpr auto d = -1883.24627; - constexpr auto e = 1790.467302; - return ffmc_from_moisture(a + b * x + c * rt_x * ln_x + d * x / ln_x + e / x_sq); -} -inline Ffmc ffmc_1700(const MathSize x, - const MathSize x_sq, - const MathSize rt_x, - const MathSize ln_x, - const MathSize exp_neg_x) noexcept -{ - if (x < 40) - { - constexpr auto a = 0.357837756; - constexpr auto b = 1.043214753; - constexpr auto c = -0.0013703; - constexpr auto d = -8.5092e-05; - constexpr auto e = 0.158059188; - return ffmc_from_moisture(a + b * x + c * x_sq + d * x_sq * rt_x + e * exp_neg_x); - } - constexpr auto a = 2776.473019; - constexpr auto b = 153.8288088; - constexpr auto c = -0.0001011; - constexpr auto d = 371.9483315; - constexpr auto e = -1620.09304; - return ffmc_from_moisture(a + b * x + c * x_sq * rt_x + d * rt_x * ln_x + e * x / ln_x); -} -inline Ffmc ffmc_1800(const MathSize x, - const MathSize x_sq, - const MathSize x_cu, - const MathSize rt_x, - const MathSize ln_x) noexcept -{ - if (x < 40) - { - constexpr auto a = 1.071980333; - constexpr auto b = 1.36047785; - constexpr auto c = 1.201854444; - constexpr auto d = -0.00827306; - return ffmc_from_moisture(sqrt(a + b * x + c * x_sq + d * x_cu)); - } - constexpr auto a = 5552.947643; - constexpr auto b = 306.6577058; - constexpr auto c = -0.00020219; - constexpr auto d = 743.89688; - constexpr auto e = -3240.18702; - return ffmc_from_moisture(a + b * x + c * x_sq * rt_x + d * rt_x * ln_x + e * x / ln_x); -} -inline Ffmc ffmc_1900(const MathSize x, - const MathSize x_sq, - const MathSize rt_x, - const MathSize exp_x, - const MathSize exp_neg_x) noexcept -{ - if (x < 42) - { - constexpr auto a = 1.948509314; - constexpr auto b = 1.124895722; - constexpr auto c = -0.00510068; - constexpr auto d = 8.90555e-20; - constexpr auto e = 0.262028658; - return ffmc_from_moisture(a + b * x + c * x_sq + d * exp_x + e * exp_neg_x); - } - constexpr auto a = 28.7672909; - constexpr auto b = -1.51195157; - constexpr auto c = 0.421751405; - constexpr auto d = -0.02633183; - constexpr auto e = 0.000585907; - return ffmc_from_moisture(a + b * x + c * x * rt_x + d * x_sq + e * x_sq * rt_x); -} -inline Ffmc ffmc_2000(const MathSize x, - const MathSize x_sq, - const MathSize x_cu, - const MathSize rt_x, - const MathSize ln_x, - const MathSize exp_neg_x) noexcept -{ - if (x < 49) - { - constexpr auto a = 3.367449306; - constexpr auto b = 1.0839743; - constexpr auto c = 0.007668483; - constexpr auto d = -0.00361458; - constexpr auto e = 0.000267591; - return ffmc_from_moisture(a + b * x + c * x_sq + d * x_sq * rt_x + e * x_cu); - } - constexpr auto a = -111.658439; - constexpr auto b = 1.238144219; - constexpr auto c = -1.74e-06; - constexpr auto d = 379.1717488; - constexpr auto e = -5.512e+20; - return ffmc_from_moisture(a + b * x + c * x_cu + d / ln_x + e * exp_neg_x); -} -inline Ffmc ffmc_0600_high(const MathSize x) noexcept -{ - // default: for unknown or RH > 87 - constexpr auto a = 14.89281073; - constexpr auto b = 194.5261398; - constexpr auto c = 2159.088828; - constexpr auto d = 2.390534289; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0700_high(const MathSize x) noexcept -{ - // default: for unknown or RH > 77 - constexpr auto a = 12.52268635; - constexpr auto b = 160.3933412; - constexpr auto c = 1308.435221; - constexpr auto d = 2.26945513; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0800_high(const MathSize x) noexcept -{ - // default: for unknown or RH > 67 - constexpr auto a = 10.21004191; - constexpr auto b = 136.7485497; - constexpr auto c = 848.3773713; - constexpr auto d = 2.154869886; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0900_high(const MathSize x) noexcept -{ - // default: for unknown or RH > 62 - constexpr auto a = 9.099751897; - constexpr auto b = 127.608943; - constexpr auto c = 1192.457539; - constexpr auto d = 2.288739471; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_1000_high(const MathSize x) noexcept -{ - // default: for unknown or RH > 57 - constexpr auto a = 7.891852885; - constexpr auto b = 126.9570677; - constexpr auto c = 2357.682971; - constexpr auto d = 2.538559055; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_1100_high(const MathSize ln_x, const MathSize ln_x_sq) noexcept -{ - // default: for unknown or RH > 54.5 - constexpr auto a = 7.934004974; - constexpr auto b = -0.2113458; - constexpr auto c = -0.29835869; - constexpr auto d = 0.015806934; - constexpr auto e = 0.590134367; - return ffmc_from_moisture((a + c * ln_x + e * ln_x_sq) / (1 + b * ln_x + d * ln_x_sq)); -} -inline Ffmc ffmc_0600_med(const MathSize x) noexcept -{ - // default: 68 <= RH <= 87 - constexpr auto a = 11.80584752; - constexpr auto b = 145.1618675; - constexpr auto c = 1610.269345; - constexpr auto d = 2.412647414; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0700_med(const MathSize x) noexcept -{ - // default: 58 <= RH <= 77 - constexpr auto a = 10.62087345; - constexpr auto b = 120.3071748; - constexpr auto c = 843.7712567; - constexpr auto d = 2.143231971; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0800_med(const MathSize x) noexcept -{ - // default: 48 <= RH <= 67 - constexpr auto a = 9.179219105; - constexpr auto b = 105.6311973; - constexpr auto c = 547.1226761; - constexpr auto d = 1.946001003; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0900_med(const MathSize x) noexcept -{ - // default: 43 <= RH <= 62 - constexpr auto a = 6.381382418; - constexpr auto b = 88.54320781; - constexpr auto c = 544.0978144; - constexpr auto d = 2.000706808; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_1000_med(const MathSize x) noexcept -{ - // default: 38 <= RH <= 57 - constexpr auto a = 3.497497088; - constexpr auto b = 71.24103374; - constexpr auto c = 525.2068553; - constexpr auto d = 2.010941812; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_1100_med(const MathSize x) noexcept -{ - // default: 35.5 <= RH <= 54.5 - constexpr auto a = 0.514536459; - constexpr auto b = 53.63085254; - constexpr auto c = 461.9583952; - constexpr auto d = 2.149631748; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0600_low(const MathSize x) noexcept -{ - // default: RH < 68 - constexpr auto a = 6.966628145; - constexpr auto b = 65.41928741; - constexpr auto c = 192.8242799; - constexpr auto d = 1.748892433; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0700_low(const MathSize x) noexcept -{ - // default: RH < 58 - constexpr auto a = 6.221403215; - constexpr auto b = 61.83553856; - constexpr auto c = 216.2009556; - constexpr auto d = 1.812026562; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0800_low(const MathSize x) noexcept -{ - // default: RH < 48 - constexpr auto a = 5.454482668; - constexpr auto b = 58.64610176; - constexpr auto c = 253.0830911; - constexpr auto d = 1.896023728; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_0900_low(const MathSize x) noexcept -{ - // default: RH < 43 - constexpr auto a = 3.966946509; - constexpr auto b = 47.66100216; - constexpr auto c = 206.2626505; - constexpr auto d = 1.814962092; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_1000_low(const MathSize x) noexcept -{ - // default: RH < 38 - constexpr auto a = 2.509991705; - constexpr auto b = 37.42399135; - constexpr auto c = 161.7254088; - constexpr auto d = 1.710574764; - return ffmc_from_moisture(a + b * exp(-0.5 * pow(log(x / c) / d, 2))); -} -inline Ffmc ffmc_1100_low(const MathSize ln_x, const MathSize ln_x_sq) noexcept -{ - // default: for RH < 35.5 - constexpr auto a = 1.291826916; - constexpr auto b = -0.38168658; - constexpr auto c = 0.15814773; - constexpr auto d = 0.051353647; - constexpr auto e = 0.356051255; - return ffmc_from_moisture((a + c * ln_x + e * ln_x_sq) / (1 + b * ln_x + d * ln_x_sq)); -} -static const FwiWeather* make_wx(const Speed& speed, - const FwiWeather& wx, - const Ffmc& ffmc, - const int hour) -{ - static set all_weather{}; - // HACK: assign rain to noon only - const FwiWeather result(wx.temp(), - wx.rh(), - Wind(wx.wind().direction(), speed), - 12 == hour ? wx.prec() : Precipitation::Zero, - ffmc, - wx.dmc(), - wx.dc()); - const auto& wx_inserted = all_weather.insert(result); - // doesn't matter if was already there or just inserted - return &(*(wx_inserted.first)); -} -static const FwiWeather* make_wx(const FwiWeather& wx_wind, - const FwiWeather& wx, - const Ffmc& ffmc, - const int hour) -{ - return make_wx(Speed(wx_wind.wind().speed().asValue() * wind_speed_adjustment(hour)), - wx, - ffmc, - hour); -} -static const FwiWeather* make_wx(const FwiWeather& wx, const Ffmc& ffmc, const int hour) -{ - return make_wx(wx, wx, ffmc, hour); -} -unique_ptr> make_vector(map data) -{ - const auto min_date = data.begin()->first; - const auto max_date = data.rbegin()->first; - auto r = make_unique>((max_date - min_date + 2) * DAY_HOURS); - // HACK: just approximate last day - for (const auto& kv : data) - { - const auto day = kv.first; - // use first day's weather for min date instead of all 0's - const auto& wx = (day == min_date ? data.at(day + 1) : data.at(day)); - const auto x = wx.mcFfmcPct(); - const auto x_sq = x * x; - const auto x_cu = x * x * x; - const auto rt_x = sqrt(x); - const auto ln_x = log(x); - const auto exp_x = exp(x); - const auto exp_neg_x = exp(-x); - const auto add_wx = - [&r, &day, &wx, &min_date](const int hour, const Ffmc& ffmc) { - r->at(util::time_index(day, hour, min_date)) = make_wx(wx, ffmc, hour); - }; - add_wx(12, ffmc_1200(x, x_sq, x_cu, rt_x, exp_neg_x)); - add_wx(13, ffmc_1300(x, x_sq, x_cu, rt_x, ln_x)); - add_wx(14, ffmc_1400(x, x_sq, rt_x, ln_x, exp_x)); - add_wx(15, ffmc_1500(x, x_sq, x_cu, rt_x, ln_x)); - add_wx(16, wx.ffmc()); - add_wx(17, ffmc_1700(x, x_sq, rt_x, ln_x, exp_neg_x)); - add_wx(18, ffmc_1800(x, x_sq, x_cu, rt_x, ln_x)); - add_wx(19, ffmc_1900(x, x_sq, rt_x, exp_x, exp_neg_x)); - add_wx(20, ffmc_2000(x, x_sq, x_cu, rt_x, ln_x, exp_neg_x)); - } - // just use high curve for last day - const auto& wx_last = data.at(max_date); - const auto x_last = wx_last.mcFfmcPct(); - const auto add_last = - [&r, &max_date, &min_date, &wx_last](const int hour, const Ffmc& ffmc) { - r->at(util::time_index(max_date + 1, hour, min_date)) = make_wx(wx_last, ffmc, hour); - }; - add_last(6, ffmc_0600_high(x_last)); - add_last(7, ffmc_0700_high(x_last)); - add_last(8, ffmc_0800_high(x_last)); - add_last(9, ffmc_0900_high(x_last)); - add_last(10, ffmc_1000_high(x_last)); - add_last(11, ffmc_1100_high(log(x_last), pow(log(x_last), 2))); - // need to look at 1200 for tomorrow to figure out if this matches for today - for (auto day = static_cast(max_date - 1); day >= min_date; --day) - { - // use first day's weather for min date instead of all 0's - const auto& wx = (day == min_date ? data.at(day + 1) : data.at(day)); - // make sure we use tomorrow for the wind after midnight - const auto& wx_wind = data.at(static_cast(day + 1)); - const auto x = wx.mcFfmcPct(); - const auto ln_x = log(x); - const auto ln_x_sq = ln_x * ln_x; - const auto& at_1200 = r->at(util::time_index(day + 1, 12, min_date))->ffmc(); - // figure out which is the closest match and use that curve - const auto at_1100_high = ffmc_1100_high(ln_x, ln_x_sq); - const auto at_1100_med = ffmc_1100_med(x); - const auto at_1100_low = ffmc_1100_low(ln_x, ln_x_sq); - const auto for1200 = at_1200.asValue(); - const auto for1100_high = at_1100_high.asValue(); - const auto for1100_med = at_1100_med.asValue(); - const auto for1100_low = at_1100_low.asValue(); - const auto diff_high = abs(for1200 - for1100_high); - const auto diff_med = abs(for1200 - for1100_med); - const auto diff_low = abs(for1200 - for1100_low); - const auto add_wx = - [&r, &day, &wx_wind, &wx, &min_date](const int hour, const Ffmc& ffmc) { - r->at(util::time_index(day + 1, hour, min_date)) = make_wx(wx_wind, wx, ffmc, hour); - }; - // don't want to have 1100 be higher than 1200 but maybe that can happen - if (for1200 >= for1100_low && diff_low <= diff_med && diff_low <= diff_high) - { - // note("low RH"); - add_wx(6, ffmc_0600_low(x)); - add_wx(7, ffmc_0700_low(x)); - add_wx(8, ffmc_0800_low(x)); - add_wx(9, ffmc_0900_low(x)); - add_wx(10, ffmc_1000_low(x)); - add_wx(11, at_1100_low); - } - else if (for1200 >= for1100_med && diff_med <= diff_high && diff_med <= diff_low) - { - // note("med RH"); - add_wx(6, ffmc_0600_med(x)); - add_wx(7, ffmc_0700_med(x)); - add_wx(8, ffmc_0800_med(x)); - add_wx(9, ffmc_0900_med(x)); - add_wx(10, ffmc_1000_med(x)); - add_wx(11, at_1100_med); - } - else - { - // note("high RH"); - add_wx(6, ffmc_0600_high(x)); - add_wx(7, ffmc_0700_high(x)); - add_wx(8, ffmc_0800_high(x)); - add_wx(9, ffmc_0900_high(x)); - add_wx(10, ffmc_1000_high(x)); - add_wx(11, at_1100_high); - } - } - for (auto day = static_cast(max_date); day >= min_date; --day) - { - // use first day's weather for min date instead of all 0's - const auto& wx = (day == min_date ? data.at(day + 1) : data.at(day)); - const auto ffmc_at_0600 = r->at(util::time_index(day + 1, 6, min_date))->ffmc().asValue(); - const auto ffmc_at_2000 = r->at(util::time_index(day, 20, min_date))->ffmc().asValue(); - // need linear interpolation between 2000 and 0600 - const auto ffmc_slope = (ffmc_at_0600 - ffmc_at_2000) / 10.0; - const auto wind_at_0600 = r->at(util::time_index(day + 1, 6, min_date))->wind().speed().asValue(); - const auto wind_at_2000 = r->at(util::time_index(day, 20, min_date))->wind().speed().asValue(); - // need linear interpolation between 2000 and 0600 - const auto wind_slope = (wind_at_0600 - wind_at_2000) / 10.0; - const auto add_wx = - [&r, &day, &wx, &min_date, &wind_at_2000, &ffmc_at_2000, &wind_slope, &ffmc_slope]( - const Day day_offset, - const int hour, - const int offset) { - const auto i = util::time_index(day + day_offset, hour, min_date); - r->at(i) = make_wx(Speed(wind_at_2000 + wind_slope * offset), - wx, - Ffmc(ffmc_at_2000 + ffmc_slope * offset), - hour); - }; - add_wx(0, 21, 1); - add_wx(0, 22, 2); - add_wx(0, 23, 3); - add_wx(1, 0, 4); - add_wx(1, 1, 5); - add_wx(1, 2, 6); - add_wx(1, 3, 7); - add_wx(1, 4, 8); - add_wx(1, 5, 9); - } - return r; -} -FireWeatherDaily::FireWeatherDaily( - const set& used_fuels, - const map& data) - : FireWeather(used_fuels, - data.begin()->first, - data.rbegin()->first, - make_vector(data).release()) -{ -} -} diff --git a/firestarr/src/cpp/FireWeatherDaily.h b/firestarr/src/cpp/FireWeatherDaily.h deleted file mode 100644 index cdfab9702..000000000 --- a/firestarr/src/cpp/FireWeatherDaily.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include -#include "FuelLookup.h" -#include "FWI.h" -#include "FireWeather.h" -namespace fs::wx -{ -/** - * \brief A stream of weather that gets used by a Scenario every Iteration. - */ -class FireWeatherDaily - : public FireWeather -{ -public: - /** - * \brief Destructor - */ - virtual ~FireWeatherDaily() = default; - /** - * \brief Constructor - * \param used_fuels set of FuelTypes that are used in the simulation - * \param data map of Day to FwiWeather to use for weather stream - */ - FireWeatherDaily(const set& used_fuels, - const map& data); - /** - * \brief Move constructor - * \param rhs FireWeatherDaily to move from - */ - FireWeatherDaily(FireWeatherDaily&& rhs) = default; - FireWeatherDaily(const FireWeatherDaily& rhs) = delete; - /** - * \brief Move assignment - * \param rhs FireWeatherDaily to move from - * \return This, after assignment - */ - FireWeatherDaily& operator=(FireWeatherDaily&& rhs) noexcept = default; - FireWeatherDaily& operator=(const FireWeatherDaily& rhs) = delete; -}; -} diff --git a/firestarr/src/cpp/FuelLookup.cpp b/firestarr/src/cpp/FuelLookup.cpp deleted file mode 100644 index a543d53b8..000000000 --- a/firestarr/src/cpp/FuelLookup.cpp +++ /dev/null @@ -1,808 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "FuelType.h" -#include "FuelLookup.h" -#include "FBP45.h" -#include "Log.h" -#include "Settings.h" -namespace fs::fuel -{ -string simplify_fuel_name(const string& fuel) -{ - auto simple_fuel_name{fuel}; - simple_fuel_name.erase( - std::remove(simple_fuel_name.begin(), simple_fuel_name.end(), '-'), - simple_fuel_name.end()); - simple_fuel_name.erase( - std::remove(simple_fuel_name.begin(), simple_fuel_name.end(), ' '), - simple_fuel_name.end()); - simple_fuel_name.erase( - std::remove(simple_fuel_name.begin(), simple_fuel_name.end(), '('), - simple_fuel_name.end()); - simple_fuel_name.erase( - std::remove(simple_fuel_name.begin(), simple_fuel_name.end(), ')'), - simple_fuel_name.end()); - simple_fuel_name.erase( - std::remove(simple_fuel_name.begin(), simple_fuel_name.end(), '/'), - simple_fuel_name.end()); - std::transform( - simple_fuel_name.begin(), - simple_fuel_name.end(), - simple_fuel_name.begin(), - ::toupper); - // remove PDF & PC - const auto pc = simple_fuel_name.find("PC"); - if (string::npos != pc) - { - simple_fuel_name.erase(pc); - } - const auto pdf = simple_fuel_name.find("PDF"); - if (string::npos != pdf) - { - simple_fuel_name.erase(pdf); - } - return simple_fuel_name; -} -static const map DEFAULT_TYPES{ - {"Spruce-Lichen Woodland", "C-1"}, - {"Boreal Spruce", "C-2"}, - {"Mature Jack or Lodgepole Pine", "C-3"}, - {"Immature Jack or Lodgepole Pine", "C-4"}, - {"Red and White Pine", "C-5"}, - {"Conifer Plantation", "C-6"}, - {"Ponderosa Pine - Douglas-Fir", "C-7"}, - {"Leafless Aspen", "D-1"}, - {"Green Aspen (with BUI Thresholding)", "D-2"}, - {"Aspen", "D-1/D-2"}, - {"Jack or Lodgepole Pine Slash", "S-1"}, - {"White Spruce - Balsam Slash", "S-2"}, - {"Coastal Cedar - Hemlock - Douglas-Fir Slash", "S-3"}, - {"Matted Grass", "O-1a"}, - {"Standing Grass", "O-1b"}, - {"Grass", "O-1"}, - {"Boreal Mixedwood - Leafless", "M-1"}, - {"Boreal Mixedwood - Green", "M-2"}, - {"Boreal Mixedwood", "M-1/M-2"}, - {"Dead Balsam Fir Mixedwood - Leafless", "M-3"}, - {"Dead Balsam Fir Mixedwood - Green", "M-4"}, - {"Dead Balsam Fir Mixedwood", "M-3/M-4"}, - {"Not Available", "Non-fuel"}, - {"Non-fuel", "Non-fuel"}, - {"Water", "Non-fuel"}, - {"Urban", "Non-fuel"}, - {"Unknown", "Non-fuel"}, - {"Unclassified", "D-1/D-2"}, - {"Vegetated Non-Fuel", "M-1/M-2 (25 PC)"}, - {"Boreal Mixedwood - Leafless (00% Conifer)", "M-1 (00 PC)"}, - {"Boreal Mixedwood - Leafless (05% Conifer)", "M-1 (05 PC)"}, - {"Boreal Mixedwood - Leafless (10% Conifer)", "M-1 (10 PC)"}, - {"Boreal Mixedwood - Leafless (15% Conifer)", "M-1 (15 PC)"}, - {"Boreal Mixedwood - Leafless (20% Conifer)", "M-1 (20 PC)"}, - {"Boreal Mixedwood - Leafless (25% Conifer)", "M-1 (25 PC)"}, - {"Boreal Mixedwood - Leafless (30% Conifer)", "M-1 (30 PC)"}, - {"Boreal Mixedwood - Leafless (35% Conifer)", "M-1 (35 PC)"}, - {"Boreal Mixedwood - Leafless (40% Conifer)", "M-1 (40 PC)"}, - {"Boreal Mixedwood - Leafless (45% Conifer)", "M-1 (45 PC)"}, - {"Boreal Mixedwood - Leafless (50% Conifer)", "M-1 (50 PC)"}, - {"Boreal Mixedwood - Leafless (55% Conifer)", "M-1 (55 PC)"}, - {"Boreal Mixedwood - Leafless (60% Conifer)", "M-1 (60 PC)"}, - {"Boreal Mixedwood - Leafless (65% Conifer)", "M-1 (65 PC)"}, - {"Boreal Mixedwood - Leafless (70% Conifer)", "M-1 (70 PC)"}, - {"Boreal Mixedwood - Leafless (75% Conifer)", "M-1 (75 PC)"}, - {"Boreal Mixedwood - Leafless (80% Conifer)", "M-1 (80 PC)"}, - {"Boreal Mixedwood - Leafless (85% Conifer)", "M-1 (85 PC)"}, - {"Boreal Mixedwood - Leafless (90% Conifer)", "M-1 (90 PC)"}, - {"Boreal Mixedwood - Leafless (95% Conifer)", "M-1 (95 PC)"}, - {"Boreal Mixedwood - Green (00% Conifer)", "M-2 (00 PC)"}, - {"Boreal Mixedwood - Green (05% Conifer)", "M-2 (05 PC)"}, - {"Boreal Mixedwood - Green (10% Conifer)", "M-2 (10 PC)"}, - {"Boreal Mixedwood - Green (15% Conifer)", "M-2 (15 PC)"}, - {"Boreal Mixedwood - Green (20% Conifer)", "M-2 (20 PC)"}, - {"Boreal Mixedwood - Green (25% Conifer)", "M-2 (25 PC)"}, - {"Boreal Mixedwood - Green (30% Conifer)", "M-2 (30 PC)"}, - {"Boreal Mixedwood - Green (35% Conifer)", "M-2 (35 PC)"}, - {"Boreal Mixedwood - Green (40% Conifer)", "M-2 (40 PC)"}, - {"Boreal Mixedwood - Green (45% Conifer)", "M-2 (45 PC)"}, - {"Boreal Mixedwood - Green (50% Conifer)", "M-2 (50 PC)"}, - {"Boreal Mixedwood - Green (55% Conifer)", "M-2 (55 PC)"}, - {"Boreal Mixedwood - Green (60% Conifer)", "M-2 (60 PC)"}, - {"Boreal Mixedwood - Green (65% Conifer)", "M-2 (65 PC)"}, - {"Boreal Mixedwood - Green (70% Conifer)", "M-2 (70 PC)"}, - {"Boreal Mixedwood - Green (75% Conifer)", "M-2 (75 PC)"}, - {"Boreal Mixedwood - Green (80% Conifer)", "M-2 (80 PC)"}, - {"Boreal Mixedwood - Green (85% Conifer)", "M-2 (85 PC)"}, - {"Boreal Mixedwood - Green (90% Conifer)", "M-2 (90 PC)"}, - {"Boreal Mixedwood - Green (95% Conifer)", "M-2 (95 PC)"}, - {"Boreal Mixedwood (00% Conifer)", "M-1/M-2 (00 PC)"}, - {"Boreal Mixedwood (05% Conifer)", "M-1/M-2 (05 PC)"}, - {"Boreal Mixedwood (10% Conifer)", "M-1/M-2 (10 PC)"}, - {"Boreal Mixedwood (15% Conifer)", "M-1/M-2 (15 PC)"}, - {"Boreal Mixedwood (20% Conifer)", "M-1/M-2 (20 PC)"}, - {"Boreal Mixedwood (25% Conifer)", "M-1/M-2 (25 PC)"}, - {"Boreal Mixedwood (30% Conifer)", "M-1/M-2 (30 PC)"}, - {"Boreal Mixedwood (35% Conifer)", "M-1/M-2 (35 PC)"}, - {"Boreal Mixedwood (40% Conifer)", "M-1/M-2 (40 PC)"}, - {"Boreal Mixedwood (45% Conifer)", "M-1/M-2 (45 PC)"}, - {"Boreal Mixedwood (50% Conifer)", "M-1/M-2 (50 PC)"}, - {"Boreal Mixedwood (55% Conifer)", "M-1/M-2 (55 PC)"}, - {"Boreal Mixedwood (60% Conifer)", "M-1/M-2 (60 PC)"}, - {"Boreal Mixedwood (65% Conifer)", "M-1/M-2 (65 PC)"}, - {"Boreal Mixedwood (70% Conifer)", "M-1/M-2 (70 PC)"}, - {"Boreal Mixedwood (75% Conifer)", "M-1/M-2 (75 PC)"}, - {"Boreal Mixedwood (80% Conifer)", "M-1/M-2 (80 PC)"}, - {"Boreal Mixedwood (85% Conifer)", "M-1/M-2 (85 PC)"}, - {"Boreal Mixedwood (90% Conifer)", "M-1/M-2 (90 PC)"}, - {"Boreal Mixedwood (95% Conifer)", "M-1/M-2 (95 PC)"}, - {"Dead Balsam Fir Mixedwood - Leafless (00% Dead Fir)", "M-3 (00 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (05% Dead Fir)", "M-3 (05 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (10% Dead Fir)", "M-3 (10 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (15% Dead Fir)", "M-3 (15 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (20% Dead Fir)", "M-3 (20 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (25% Dead Fir)", "M-3 (25 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (30% Dead Fir)", "M-3 (30 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (35% Dead Fir)", "M-3 (35 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (40% Dead Fir)", "M-3 (40 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (45% Dead Fir)", "M-3 (45 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (50% Dead Fir)", "M-3 (50 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (55% Dead Fir)", "M-3 (55 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (60% Dead Fir)", "M-3 (60 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (65% Dead Fir)", "M-3 (65 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (70% Dead Fir)", "M-3 (70 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (75% Dead Fir)", "M-3 (75 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (80% Dead Fir)", "M-3 (80 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (85% Dead Fir)", "M-3 (85 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (90% Dead Fir)", "M-3 (90 PDF)"}, - {"Dead Balsam Fir Mixedwood - Leafless (95% Dead Fir)", "M-3 (95 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (00% Dead Fir)", "M-4 (00 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (05% Dead Fir)", "M-4 (05 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (10% Dead Fir)", "M-4 (10 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (15% Dead Fir)", "M-4 (15 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (20% Dead Fir)", "M-4 (20 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (25% Dead Fir)", "M-4 (25 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (30% Dead Fir)", "M-4 (30 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (35% Dead Fir)", "M-4 (35 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (40% Dead Fir)", "M-4 (40 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (45% Dead Fir)", "M-4 (45 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (50% Dead Fir)", "M-4 (50 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (55% Dead Fir)", "M-4 (55 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (60% Dead Fir)", "M-4 (60 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (65% Dead Fir)", "M-4 (65 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (70% Dead Fir)", "M-4 (70 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (75% Dead Fir)", "M-4 (75 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (80% Dead Fir)", "M-4 (80 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (85% Dead Fir)", "M-4 (85 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (90% Dead Fir)", "M-4 (90 PDF)"}, - {"Dead Balsam Fir Mixedwood - Green (95% Dead Fir)", "M-4 (95 PDF)"}, - {"Dead Balsam Fir Mixedwood (00% Dead Fir)", "M-3/M-4 (00 PDF)"}, - {"Dead Balsam Fir Mixedwood (05% Dead Fir)", "M-3/M-4 (05 PDF)"}, - {"Dead Balsam Fir Mixedwood (10% Dead Fir)", "M-3/M-4 (10 PDF)"}, - {"Dead Balsam Fir Mixedwood (15% Dead Fir)", "M-3/M-4 (15 PDF)"}, - {"Dead Balsam Fir Mixedwood (20% Dead Fir)", "M-3/M-4 (20 PDF)"}, - {"Dead Balsam Fir Mixedwood (25% Dead Fir)", "M-3/M-4 (25 PDF)"}, - {"Dead Balsam Fir Mixedwood (30% Dead Fir)", "M-3/M-4 (30 PDF)"}, - {"Dead Balsam Fir Mixedwood (35% Dead Fir)", "M-3/M-4 (35 PDF)"}, - {"Dead Balsam Fir Mixedwood (40% Dead Fir)", "M-3/M-4 (40 PDF)"}, - {"Dead Balsam Fir Mixedwood (45% Dead Fir)", "M-3/M-4 (45 PDF)"}, - {"Dead Balsam Fir Mixedwood (50% Dead Fir)", "M-3/M-4 (50 PDF)"}, - {"Dead Balsam Fir Mixedwood (55% Dead Fir)", "M-3/M-4 (55 PDF)"}, - {"Dead Balsam Fir Mixedwood (60% Dead Fir)", "M-3/M-4 (60 PDF)"}, - {"Dead Balsam Fir Mixedwood (65% Dead Fir)", "M-3/M-4 (65 PDF)"}, - {"Dead Balsam Fir Mixedwood (70% Dead Fir)", "M-3/M-4 (70 PDF)"}, - {"Dead Balsam Fir Mixedwood (75% Dead Fir)", "M-3/M-4 (75 PDF)"}, - {"Dead Balsam Fir Mixedwood (80% Dead Fir)", "M-3/M-4 (80 PDF)"}, - {"Dead Balsam Fir Mixedwood (85% Dead Fir)", "M-3/M-4 (85 PDF)"}, - {"Dead Balsam Fir Mixedwood (90% Dead Fir)", "M-3/M-4 (90 PDF)"}, - {"Dead Balsam Fir Mixedwood (95% Dead Fir)", "M-3/M-4 (95 PDF)"}, -}; -// FIX: ensure actual code use in compilation doesn't matter and don't need to be speicified manually in sequence -static_assert(0 == INVALID_FUEL_CODE); -static InvalidFuel NULL_FUEL{INVALID_FUEL_CODE, "Non-fuel"}; -static InvalidFuel INVALID{1, "Invalid"}; -static fbp::FuelC1 C1{2}; -static fbp::FuelC2 C2{3}; -static fbp::FuelC3 C3{4}; -static fbp::FuelC4 C4{5}; -static fbp::FuelC5 C5{6}; -static fbp::FuelC6 C6{7}; -static fbp::FuelC7 C7{8}; -static fbp::FuelD1 D1{9}; -static fbp::FuelD2 D2{10}; -static fbp::FuelO1A O1_A{11}; -static fbp::FuelO1B O1_B{12}; -static fbp::FuelS1 S1{13}; -static fbp::FuelS2 S2{14}; -static fbp::FuelS3 S3{15}; -static fbp::FuelD1D2 D1_D2{16, &D1, &D2}; -static fbp::FuelM1<5> M1_05{17, "M-1 (05 PC)"}; -static fbp::FuelM1<10> M1_10{18, "M-1 (10 PC)"}; -static fbp::FuelM1<15> M1_15{19, "M-1 (15 PC)"}; -static fbp::FuelM1<20> M1_20{20, "M-1 (20 PC)"}; -static fbp::FuelM1<25> M1_25{21, "M-1 (25 PC)"}; -static fbp::FuelM1<30> M1_30{22, "M-1 (30 PC)"}; -static fbp::FuelM1<35> M1_35{23, "M-1 (35 PC)"}; -static fbp::FuelM1<40> M1_40{24, "M-1 (40 PC)"}; -static fbp::FuelM1<45> M1_45{25, "M-1 (45 PC)"}; -static fbp::FuelM1<50> M1_50{26, "M-1 (50 PC)"}; -static fbp::FuelM1<55> M1_55{27, "M-1 (55 PC)"}; -static fbp::FuelM1<60> M1_60{28, "M-1 (60 PC)"}; -static fbp::FuelM1<65> M1_65{29, "M-1 (65 PC)"}; -static fbp::FuelM1<70> M1_70{30, "M-1 (70 PC)"}; -static fbp::FuelM1<75> M1_75{31, "M-1 (75 PC)"}; -static fbp::FuelM1<80> M1_80{32, "M-1 (80 PC)"}; -static fbp::FuelM1<85> M1_85{33, "M-1 (85 PC)"}; -static fbp::FuelM1<90> M1_90{34, "M-1 (90 PC)"}; -static fbp::FuelM1<95> M1_95{35, "M-1 (95 PC)"}; -static fbp::FuelM2<5> M2_05{36, "M-2 (05 PC)"}; -static fbp::FuelM2<10> M2_10{37, "M-2 (10 PC)"}; -static fbp::FuelM2<15> M2_15{38, "M-2 (15 PC)"}; -static fbp::FuelM2<20> M2_20{39, "M-2 (20 PC)"}; -static fbp::FuelM2<25> M2_25{40, "M-2 (25 PC)"}; -static fbp::FuelM2<30> M2_30{41, "M-2 (30 PC)"}; -static fbp::FuelM2<35> M2_35{42, "M-2 (35 PC)"}; -static fbp::FuelM2<40> M2_40{43, "M-2 (40 PC)"}; -static fbp::FuelM2<45> M2_45{44, "M-2 (45 PC)"}; -static fbp::FuelM2<50> M2_50{45, "M-2 (50 PC)"}; -static fbp::FuelM2<55> M2_55{46, "M-2 (55 PC)"}; -static fbp::FuelM2<60> M2_60{47, "M-2 (60 PC)"}; -static fbp::FuelM2<65> M2_65{48, "M-2 (65 PC)"}; -static fbp::FuelM2<70> M2_70{49, "M-2 (70 PC)"}; -static fbp::FuelM2<75> M2_75{50, "M-2 (75 PC)"}; -static fbp::FuelM2<80> M2_80{51, "M-2 (80 PC)"}; -static fbp::FuelM2<85> M2_85{52, "M-2 (85 PC)"}; -static fbp::FuelM2<90> M2_90{53, "M-2 (90 PC)"}; -static fbp::FuelM2<95> M2_95{54, "M-2 (95 PC)"}; -static fbp::FuelM1M2<5> M1_M2_05{55, "M-1/M-2 (05 PC)", &M1_05, &M2_05}; -static fbp::FuelM1M2<10> M1_M2_10{56, "M-1/M-2 (10 PC)", &M1_10, &M2_10}; -static fbp::FuelM1M2<15> M1_M2_15{57, "M-1/M-2 (15 PC)", &M1_15, &M2_15}; -static fbp::FuelM1M2<20> M1_M2_20{58, "M-1/M-2 (20 PC)", &M1_20, &M2_20}; -static fbp::FuelM1M2<25> M1_M2_25{59, "M-1/M-2 (25 PC)", &M1_25, &M2_25}; -static fbp::FuelM1M2<30> M1_M2_30{60, "M-1/M-2 (30 PC)", &M1_30, &M2_30}; -static fbp::FuelM1M2<35> M1_M2_35{61, "M-1/M-2 (35 PC)", &M1_35, &M2_35}; -static fbp::FuelM1M2<40> M1_M2_40{62, "M-1/M-2 (40 PC)", &M1_40, &M2_40}; -static fbp::FuelM1M2<45> M1_M2_45{63, "M-1/M-2 (45 PC)", &M1_45, &M2_45}; -static fbp::FuelM1M2<50> M1_M2_50{64, "M-1/M-2 (50 PC)", &M1_50, &M2_50}; -static fbp::FuelM1M2<55> M1_M2_55{65, "M-1/M-2 (55 PC)", &M1_55, &M2_55}; -static fbp::FuelM1M2<60> M1_M2_60{66, "M-1/M-2 (60 PC)", &M1_60, &M2_60}; -static fbp::FuelM1M2<65> M1_M2_65{67, "M-1/M-2 (65 PC)", &M1_65, &M2_65}; -static fbp::FuelM1M2<70> M1_M2_70{68, "M-1/M-2 (70 PC)", &M1_70, &M2_70}; -static fbp::FuelM1M2<75> M1_M2_75{69, "M-1/M-2 (75 PC)", &M1_75, &M2_75}; -static fbp::FuelM1M2<80> M1_M2_80{70, "M-1/M-2 (80 PC)", &M1_80, &M2_80}; -static fbp::FuelM1M2<85> M1_M2_85{71, "M-1/M-2 (85 PC)", &M1_85, &M2_85}; -static fbp::FuelM1M2<90> M1_M2_90{72, "M-1/M-2 (90 PC)", &M1_90, &M2_90}; -static fbp::FuelM1M2<95> M1_M2_95{73, "M-1/M-2 (95 PC)", &M1_95, &M2_95}; -static fbp::FuelM3<5> M3_05{74, "M-3 (05 PDF)"}; -static fbp::FuelM3<10> M3_10{75, "M-3 (10 PDF)"}; -static fbp::FuelM3<15> M3_15{76, "M-3 (15 PDF)"}; -static fbp::FuelM3<20> M3_20{77, "M-3 (20 PDF)"}; -static fbp::FuelM3<25> M3_25{78, "M-3 (25 PDF)"}; -static fbp::FuelM3<30> M3_30{79, "M-3 (30 PDF)"}; -static fbp::FuelM3<35> M3_35{80, "M-3 (35 PDF)"}; -static fbp::FuelM3<40> M3_40{81, "M-3 (40 PDF)"}; -static fbp::FuelM3<45> M3_45{82, "M-3 (45 PDF)"}; -static fbp::FuelM3<50> M3_50{83, "M-3 (50 PDF)"}; -static fbp::FuelM3<55> M3_55{84, "M-3 (55 PDF)"}; -static fbp::FuelM3<60> M3_60{85, "M-3 (60 PDF)"}; -static fbp::FuelM3<65> M3_65{86, "M-3 (65 PDF)"}; -static fbp::FuelM3<70> M3_70{87, "M-3 (70 PDF)"}; -static fbp::FuelM3<75> M3_75{88, "M-3 (75 PDF)"}; -static fbp::FuelM3<80> M3_80{89, "M-3 (80 PDF)"}; -static fbp::FuelM3<85> M3_85{90, "M-3 (85 PDF)"}; -static fbp::FuelM3<90> M3_90{91, "M-3 (90 PDF)"}; -static fbp::FuelM3<95> M3_95{92, "M-3 (95 PDF)"}; -static fbp::FuelM3<100> M3_100{93, "M-3 (100 PDF)"}; -static fbp::FuelM4<5> M4_05{94, "M-4 (05 PDF)"}; -static fbp::FuelM4<10> M4_10{95, "M-4 (10 PDF)"}; -static fbp::FuelM4<15> M4_15{96, "M-4 (15 PDF)"}; -static fbp::FuelM4<20> M4_20{97, "M-4 (20 PDF)"}; -static fbp::FuelM4<25> M4_25{98, "M-4 (25 PDF)"}; -static fbp::FuelM4<30> M4_30{99, "M-4 (30 PDF)"}; -static fbp::FuelM4<35> M4_35{100, "M-4 (35 PDF)"}; -static fbp::FuelM4<40> M4_40{101, "M-4 (40 PDF)"}; -static fbp::FuelM4<45> M4_45{102, "M-4 (45 PDF)"}; -static fbp::FuelM4<50> M4_50{103, "M-4 (50 PDF)"}; -static fbp::FuelM4<55> M4_55{104, "M-4 (55 PDF)"}; -static fbp::FuelM4<60> M4_60{105, "M-4 (60 PDF)"}; -static fbp::FuelM4<65> M4_65{106, "M-4 (65 PDF)"}; -static fbp::FuelM4<70> M4_70{107, "M-4 (70 PDF)"}; -static fbp::FuelM4<75> M4_75{108, "M-4 (75 PDF)"}; -static fbp::FuelM4<80> M4_80{109, "M-4 (80 PDF)"}; -static fbp::FuelM4<85> M4_85{110, "M-4 (85 PDF)"}; -static fbp::FuelM4<90> M4_90{111, "M-4 (90 PDF)"}; -static fbp::FuelM4<95> M4_95{112, "M-4 (95 PDF)"}; -static fbp::FuelM4<100> M4_100{113, "M-4 (100 PDF)"}; -static fbp::FuelM3M4<5> M3_M4_05{114, "M-3/M-4 (05 PDF)", &M3_05, &M4_05}; -static fbp::FuelM3M4<10> M3_M4_10{115, "M-3/M-4 (10 PDF)", &M3_10, &M4_10}; -static fbp::FuelM3M4<15> M3_M4_15{116, "M-3/M-4 (15 PDF)", &M3_15, &M4_15}; -static fbp::FuelM3M4<20> M3_M4_20{117, "M-3/M-4 (20 PDF)", &M3_20, &M4_20}; -static fbp::FuelM3M4<25> M3_M4_25{118, "M-3/M-4 (25 PDF)", &M3_25, &M4_25}; -static fbp::FuelM3M4<30> M3_M4_30{119, "M-3/M-4 (30 PDF)", &M3_30, &M4_30}; -static fbp::FuelM3M4<35> M3_M4_35{120, "M-3/M-4 (35 PDF)", &M3_35, &M4_35}; -static fbp::FuelM3M4<40> M3_M4_40{121, "M-3/M-4 (40 PDF)", &M3_40, &M4_40}; -static fbp::FuelM3M4<45> M3_M4_45{122, "M-3/M-4 (45 PDF)", &M3_45, &M4_45}; -static fbp::FuelM3M4<50> M3_M4_50{123, "M-3/M-4 (50 PDF)", &M3_50, &M4_50}; -static fbp::FuelM3M4<55> M3_M4_55{124, "M-3/M-4 (55 PDF)", &M3_55, &M4_55}; -static fbp::FuelM3M4<60> M3_M4_60{125, "M-3/M-4 (60 PDF)", &M3_60, &M4_60}; -static fbp::FuelM3M4<65> M3_M4_65{126, "M-3/M-4 (65 PDF)", &M3_65, &M4_65}; -static fbp::FuelM3M4<70> M3_M4_70{127, "M-3/M-4 (70 PDF)", &M3_70, &M4_70}; -static fbp::FuelM3M4<75> M3_M4_75{128, "M-3/M-4 (75 PDF)", &M3_75, &M4_75}; -static fbp::FuelM3M4<80> M3_M4_80{129, "M-3/M-4 (80 PDF)", &M3_80, &M4_80}; -static fbp::FuelM3M4<85> M3_M4_85{130, "M-3/M-4 (85 PDF)", &M3_85, &M4_85}; -static fbp::FuelM3M4<90> M3_M4_90{131, "M-3/M-4 (90 PDF)", &M3_90, &M4_90}; -static fbp::FuelM3M4<95> M3_M4_95{132, "M-3/M-4 (95 PDF)", &M3_95, &M4_95}; -static fbp::FuelM3M4<100> M3_M4_100{133, "M-3/M-4 (100 PDF)", &M3_100, &M4_100}; -static fbp::FuelM1<0> M1_00{134, "M-1 (00 PC)"}; -static fbp::FuelM2<0> M2_00{135, "M-2 (00 PC)"}; -static fbp::FuelM1M2<0> M1_M2_00{136, "M-1/M-2 (00 PC)", &M1_00, &M2_00}; -static fbp::FuelM3<0> M3_00{137, "M-3 (00 PDF)"}; -static fbp::FuelM4<0> M4_00{138, "M-4 (00 PDF)"}; -static fbp::FuelM3M4<0> M3_M4_00{139, "M-3/M-4 (00 PDF)", &M3_00, &M4_00}; -static fbp::FuelO1 O1{140, "O-1", &O1_A, &O1_B}; -/** - * \brief Implementation class for FuelLookup - */ -class FuelLookupImpl -{ -public: - // do it this way so the entire array is filled with a single invalid fuel - /** - * \brief Construct by reading from a file - * \param filename File to read from. Uses .lut format from Prometheus - */ - explicit FuelLookupImpl(const char* filename) - : fuel_types_(new array::max()>{&INVALID}) - { - for (auto i : FuelLookup::Fuels) - { - emplaceFuel(i); - } - // HACK: use offset from base fuel type - const auto pc = sim::Settings::defaultPercentConifer(); - logging::check_fatal(0 >= pc || 100 <= pc || (pc % 5) != 0, - "Invalid default percent conifer (%d)", - pc); - const auto pc_offset = (static_cast(pc) / 5) - 1; - emplaceFuel("M-1", FuelLookup::Fuels.at(pc_offset + FuelType::safeCode(&M1_05))); - emplaceFuel("M-2", FuelLookup::Fuels.at(pc_offset + FuelType::safeCode(&M2_05))); - emplaceFuel("M-1/M-2", - FuelLookup::Fuels.at(pc_offset + FuelType::safeCode(&M1_M2_05))); - const auto pdf = sim::Settings::defaultPercentDeadFir(); - logging::check_fatal(0 > pdf || 100 < pdf || (pdf % 5) != 0, - "Invalid default percent dead fir (%d)", - pdf); - const auto pdf_offset = static_cast(pdf) / 5 - 1; - emplaceFuel("M-3", FuelLookup::Fuels.at(pdf_offset + FuelType::safeCode(&M3_05))); - emplaceFuel("M-4", FuelLookup::Fuels.at(pdf_offset + FuelType::safeCode(&M4_05))); - emplaceFuel("M-3/M-4", - FuelLookup::Fuels.at(pdf_offset + FuelType::safeCode(&M3_M4_05))); - ifstream in; - in.open(filename); - bool read_ok = false; - if (in.is_open()) - { - string str; - logging::info("Reading fuel lookup table from '%s'", filename); - // read header line - getline(in, str); - while (getline(in, str)) - { - istringstream iss(str); - if (getline(iss, str, ',')) - { - // grid_value - const auto value = static_cast(stoi(str)); - // export_value - getline(iss, str, ','); - // descriptive_name - getline(iss, str, ','); - const auto name = str; - // fuel_type - getline(iss, str, ','); - const auto fuel = str; - logging::debug("Fuel %s has code %d", fuel.c_str(), value); - const auto by_name = fuel_by_name_.find(str); - if (by_name != fuel_by_name_.end()) - { - const auto fuel_obj = (*by_name).second; - fuel_types_->at(value) = fuel_obj; - // const auto find_default = DEFAULT_TYPES.find(name); - // HACK: can't figure out how to compare to fuel from .find() result so keep using .at() for now - if (!DEFAULT_TYPES.contains(name) - || DEFAULT_TYPES.at(name) != fuel - || "Not Available" == name - || "Non-fuel" == name - || "Unclassified" == name - || "Urban" == name - || "Unknown" == name - || "Vegetated Non-Fuel" == name) - { - logging::note("Fuel (%d, '%s') is treated like '%s' with internal code %d", - value, - name.c_str(), - fuel.c_str(), - fuel_obj->code()); - // logging::note("Fuel '%s' is treated like '%s'", name.c_str(), fuel.c_str()); - } - else - { - logging::debug("Fuel (%d, '%s') is treated like '%s' with internal code %d", - value, - name.c_str(), - fuel.c_str(), - fuel_obj->code()); - } - // fuel_grid_codes_[fuel_obj] = value; - auto emplaced = fuel_grid_codes_.try_emplace(fuel_obj, value); - if (!emplaced.second) - { - logging::debug("Fuel (%d, '%s') is treated like '%s' with internal code %d and tried to replace value %d for %d", - value, - name.c_str(), - fuel.c_str(), - fuel_obj->code(), - value, - emplaced.first->second); - } - fuel_good_values_[value].push_back(str); - } - else - { - logging::warning("Unknown fuel type '%s' in fuel lookup table", str.c_str()); - fuel_bad_values_[value].push_back(str); - } - read_ok = true; - } - } - in.close(); - } - if (!read_ok) - { - logging::fatal("Unable to read file %s", filename); - } - } - ~FuelLookupImpl() - { - delete fuel_types_; - } - /** - * \brief Put fuel into lookup table based on name - * \param fuel FuelType to put into lookup table based on its name - */ - void emplaceFuel(const FuelType* fuel) - { - emplaceFuel(FuelType::safeName(fuel), fuel); - } - /** - * \brief Put fuel into lookup table based on name - * \param name Name to use for FuelType in lookup table - * \param fuel FuelType to put into lookup table - */ - void emplaceFuel(const string& name, const FuelType* fuel) - { - fuel_by_name_.emplace(name, fuel); - const auto simple_name = simplify_fuel_name(fuel->name()); - logging::verbose( - "'%s' being registered as '%s' with simplified name '%s'", - fuel->name(), - name.c_str(), - simple_name.c_str()); - fuel_by_simplified_name_.emplace(simple_name, fuel); - } - /** - * \brief Create a set of all FuelTypes used in this lookup table - * \return Set of all FuelTypes used in this lookup table - */ - set usedFuels() const - { - set result{}; - for (const auto& kv : used_by_name_) - { - if (kv.second) - { - result.insert(fuel_by_name_.at(kv.first)); - } - } - return result; - } - FuelLookupImpl(const FuelLookupImpl& rhs) = delete; - FuelLookupImpl(FuelLookupImpl&& rhs) = delete; - FuelLookupImpl& operator=(const FuelLookupImpl& rhs) = delete; - FuelLookupImpl& operator=(FuelLookupImpl&& rhs) = delete; - /** - * \brief Look up a FuelType based on the given code - * \param value Value to use for lookup - * \param nodata Value that represents no data - * \return FuelType based on the given code - */ - const FuelType* codeToFuel(const FuelSize value, const FuelSize nodata) const - { - // NOTE: this should be looking things up based on the .lut codes - if (nodata == value) - { - return nullptr; - } - const auto result = fuel_types_->at(static_cast(value)); - if (nullptr != result) - { - // use [] for insert or change - used_by_name_[FuelType::safeName(result)] = true; - // listFuels(); - // logging::check_fatal(1 == result->code(), "Invalid fuel being converted"); - } - return result; - } - /** - * \brief List all fuels and their codes - */ - void listFuels() const - { - for (const auto& kv : fuel_good_values_) - { - for (const auto& name : kv.second) - { - logging::note("%ld => %s", kv.first, name.c_str()); - } - } - } - /** - * \brief Look up the original grid code for a FuelType - * \param value Value to use for lookup - * \return Original grid code for the FuelType - */ - FuelSize fuelToCode(const FuelType* const value) const - { - if (nullptr == value) - { - // HACK: for now assume 0 is always the invalid fuel type - return INVALID_FUEL_CODE; - } - const auto seek = fuel_grid_codes_.find(value); - if (seek != fuel_grid_codes_.end()) - { - return seek->second; - } - // listFuels(); - logging::warning("Invalid FuelType lookup: (%s, %ld) was never used in grid with %ld fuel codes defined", - value->name(), - value->code(), - fuel_grid_codes_.size()); - throw runtime_error("Converting fuel that wasn't in input grid to code"); - return 0; - } - /** - * \brief Look up a FuelType based on the given name - * \param name Name of the fuel to find - * \return FuelType based on the given name - */ - const FuelType* byName(const string& name) const - { - const auto seek = fuel_by_name_.find(name); - if (seek != fuel_by_name_.end()) - { - return seek->second; - } - return nullptr; - } - /** - * \brief Look up a FuelType based on the given simplified name - * \param name Simplified name of the fuel to find - * \return FuelType based on the given name - */ - const FuelType* bySimplifiedName(const string& name) const - { - const auto seek = fuel_by_simplified_name_.find(name); - if (seek != fuel_by_simplified_name_.end()) - { - return seek->second; - } - return nullptr; - } -private: - /** - * \brief Array of all possible fuel types - */ - array::max()>* fuel_types_; - /** - * \brief Map of FuelType to (first) original grid value - */ - unordered_map fuel_grid_codes_{}; - /** - * \brief Map of fuel name to FuelType - */ - unordered_map fuel_by_name_{}; - /** - * \brief Map of simplified fuel name to FuelType - */ - unordered_map fuel_by_simplified_name_{}; - /** - * \brief Map of fuel name to whether or not it is used in this simulation - */ - mutable unordered_map used_by_name_{}; - /** - * \brief Codes from input .lut that were for fuel types that are implemented - */ - unordered_map> fuel_good_values_{}; - /** - * \brief Codes from input .lut that were for fuel types that are not implemented - */ - unordered_map> fuel_bad_values_{}; -}; -const FuelType* FuelLookup::codeToFuel(const FuelSize value, const FuelSize nodata) const -{ - return impl_->codeToFuel(value, nodata); -} -void FuelLookup::listFuels() const -{ - impl_->listFuels(); -} -FuelSize FuelLookup::fuelToCode(const FuelType* const value) const -{ - return impl_->fuelToCode(value); -} -const FuelType* FuelLookup::operator()(const FuelSize value, const FuelSize nodata) const -{ - return codeToFuel(value, nodata); -} -set FuelLookup::usedFuels() const -{ - return impl_->usedFuels(); -} -const FuelType* FuelLookup::byName(const string& name) const -{ - return impl_->byName(name); -} -const FuelType* FuelLookup::bySimplifiedName(const string& name) const -{ - return impl_->bySimplifiedName(name); -} -FuelLookup::FuelLookup(const char* filename) - : impl_(make_shared(filename)) -{ -} -const array FuelLookup::Fuels{ - &NULL_FUEL, - &INVALID, - &C1, - &C2, - &C3, - &C4, - &C5, - &C6, - &C7, - &D1, - &D2, - &O1_A, - &O1_B, - &S1, - &S2, - &S3, - &D1_D2, - &M1_05, - &M1_10, - &M1_15, - &M1_20, - &M1_25, - &M1_30, - &M1_35, - &M1_40, - &M1_45, - &M1_50, - &M1_55, - &M1_60, - &M1_65, - &M1_70, - &M1_75, - &M1_80, - &M1_85, - &M1_90, - &M1_95, - &M2_05, - &M2_10, - &M2_15, - &M2_20, - &M2_25, - &M2_30, - &M2_35, - &M2_40, - &M2_45, - &M2_50, - &M2_55, - &M2_60, - &M2_65, - &M2_70, - &M2_75, - &M2_80, - &M2_85, - &M2_90, - &M2_95, - &M1_M2_05, - &M1_M2_10, - &M1_M2_15, - &M1_M2_20, - &M1_M2_25, - &M1_M2_30, - &M1_M2_35, - &M1_M2_40, - &M1_M2_45, - &M1_M2_50, - &M1_M2_55, - &M1_M2_60, - &M1_M2_65, - &M1_M2_70, - &M1_M2_75, - &M1_M2_80, - &M1_M2_85, - &M1_M2_90, - &M1_M2_95, - &M3_05, - &M3_10, - &M3_15, - &M3_20, - &M3_25, - &M3_30, - &M3_35, - &M3_40, - &M3_45, - &M3_50, - &M3_55, - &M3_60, - &M3_65, - &M3_70, - &M3_75, - &M3_80, - &M3_85, - &M3_90, - &M3_95, - &M3_100, - &M4_05, - &M4_10, - &M4_15, - &M4_20, - &M4_25, - &M4_30, - &M4_35, - &M4_40, - &M4_45, - &M4_50, - &M4_55, - &M4_60, - &M4_65, - &M4_70, - &M4_75, - &M4_80, - &M4_85, - &M4_90, - &M4_95, - &M4_100, - &M3_M4_00, - &M3_M4_05, - &M3_M4_10, - &M3_M4_15, - &M3_M4_20, - &M3_M4_25, - &M3_M4_30, - &M3_M4_35, - &M3_M4_40, - &M3_M4_45, - &M3_M4_50, - &M3_M4_55, - &M3_M4_60, - &M3_M4_65, - &M3_M4_70, - &M3_M4_75, - &M3_M4_80, - &M3_M4_85, - &M3_M4_90, - &M3_M4_95, - &M1_00, - &M2_00, - &M1_M2_00, - &M3_00, - &M4_00, - &M3_M4_100, - &O1, -}; -} diff --git a/firestarr/src/cpp/FuelLookup.h b/firestarr/src/cpp/FuelLookup.h deleted file mode 100644 index 73e6c8afa..000000000 --- a/firestarr/src/cpp/FuelLookup.h +++ /dev/null @@ -1,140 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include -#include "FuelType.h" -#include "Cell.h" -#include "Util.h" -namespace fs::fuel -{ -class FuelLookupImpl; - -string simplify_fuel_name(const string& fuel); - -/** - * \brief Provides ability to look up a fuel type based on name or code. - */ -class FuelLookup -{ -public: - ~FuelLookup() = default; - /** - * \brief Construct by reading from a file - * \param filename File to read from. Uses .lut format from Prometheus - */ - explicit FuelLookup(const char* filename); - /** - * \brief Copy constructor - * \param rhs FuelLookup to copy from - */ - FuelLookup(const FuelLookup& rhs) noexcept = default; - /** - * \brief Move constructor - * \param rhs FuelLookup to move from - */ - FuelLookup(FuelLookup&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs FuelLookup to copy from - * \return This, after assignment - */ - FuelLookup& operator=(const FuelLookup& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs FuelLookup to move from - * \return This, after assignment - */ - FuelLookup& operator=(FuelLookup&& rhs) noexcept = default; - /** - * \brief Look up a FuelType based on the given code - * \param value Value to use for lookup - * \param nodata Value that represents no data - * \return FuelType based on the given code - */ - [[nodiscard]] const FuelType* codeToFuel(FuelSize value, FuelSize nodata) const; - /** - * \brief List all fuels and their codes - */ - void listFuels() const; - /** - * \brief Look up the original code for the given FuelType - * \param value Value to use for lookup - * \return code for the given FuelType - */ - [[nodiscard]] FuelSize fuelToCode(const FuelType* fuel) const; - /** - * \brief Look up a FuelType ba1ed on the given code - * \param value Value to use for lookup - * \param nodata Value that represents no data - * \return FuelType based on the given code - */ - [[nodiscard]] const FuelType* operator()(FuelSize value, FuelSize nodata) const; - /** - * \brief Retrieve set of FuelTypes that are used in the lookup table - * \return set of FuelTypes that are used in the lookup table - */ - [[nodiscard]] set usedFuels() const; - /** - * \brief Look up a FuelType based on the given name - * \param name Name of the fuel to find - * \return FuelType based on the given name - */ - [[nodiscard]] const FuelType* byName(const string& name) const; - /** - * \brief Look up a FuelType based on the given simplified name - * \param name Simplified name of the fuel to find - * \return FuelType based on the given name - */ - [[nodiscard]] const FuelType* bySimplifiedName(const string& name) const; - /** - * \brief Array of all FuelTypes available to be used in simulations - */ - static const array Fuels; -private: - /** - * \brief Implementation class for FuelLookup - */ - shared_ptr impl_; -}; -/** - * \brief Look up a FuelType based on the given code - * \param code Value to use for lookup - * \return FuelType based on the given code - */ -[[nodiscard]] constexpr const FuelType* fuel_by_code(const FuelCodeSize& code) -{ - return FuelLookup::Fuels.at(code); -} -/** - * \brief Get FuelType based on the given cell - * \param cell Cell to retrieve FuelType for - * \return FuelType based on the given cell - */ -[[nodiscard]] constexpr const FuelType* check_fuel(const topo::Cell& cell) -{ - return fuel_by_code(cell.fuelCode()); -} -/** - * \brief Whether or not there is no fuel in the Cell - * \param cell Cell to check - * \return Whether or not there is no fuel in the Cell - */ -[[nodiscard]] constexpr bool is_null_fuel(const FuelType* fuel) -{ - return INVALID_FUEL_CODE == FuelType::safeCode(fuel); -} -/** - * \brief Whether or not there is no fuel in the Cell - * \param cell Cell to check - * \return Whether or not there is no fuel in the Cell - */ -[[nodiscard]] constexpr bool is_null_fuel(const topo::Cell& cell) -{ - return is_null_fuel(fuel_by_code(cell.fuelCode())); -} -} diff --git a/firestarr/src/cpp/FuelType.cpp b/firestarr/src/cpp/FuelType.cpp deleted file mode 100644 index b9c73f069..000000000 --- a/firestarr/src/cpp/FuelType.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "FireSpread.h" -#include "FuelType.h" -#include "FireWeather.h" -#include "Log.h" -namespace fs::fuel -{ -MathSize InvalidFuel::grass_curing(const int, const wx::FwiWeather&) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::cbh() const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::cfl() const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::buiEffect(MathSize) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::crownConsumption(MathSize) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::calculateRos(const int, const wx::FwiWeather&, MathSize) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::calculateIsf(const SpreadInfo&, MathSize) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::surfaceFuelConsumption(const SpreadInfo&) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::lengthToBreadth(MathSize) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::finalRos(const SpreadInfo&, MathSize, MathSize, MathSize) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::criticalSurfaceIntensity(const SpreadInfo&) const -{ - throw runtime_error("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::crownFractionBurned(MathSize, MathSize) const noexcept -{ - return logging::fatal("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::probabilityPeat(MathSize) const noexcept -{ - return logging::fatal("Invalid fuel type in fuel map"); -} -MathSize InvalidFuel::survivalProbability(const wx::FwiWeather&) const noexcept -{ - return logging::fatal("Invalid fuel type in fuel map"); -} -} diff --git a/firestarr/src/cpp/FuelType.h b/firestarr/src/cpp/FuelType.h deleted file mode 100644 index 0f6c98666..000000000 --- a/firestarr/src/cpp/FuelType.h +++ /dev/null @@ -1,532 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "Duff.h" -#include "FWI.h" -namespace fs -{ -namespace sim -{ -class SpreadInfo; -} -namespace data -{ -class LogValue; -} -using sim::SpreadInfo; -using data::LogValue; -namespace fuel -{ -constexpr FuelCodeSize INVALID_FUEL_CODE = 0; -// References -// Forestry Canada -// Development and Structure of the Canadian Forest Fire Behaviour Prediction System (ST-X-3) -// https://cfs.nrcan.gc.ca/pubwarehouse/pdfs/10068.pdf -// -// Wotton, B.M., Alexander, M.E., Taylor, S.W. -// Updates and revision to the 1992 Canadian Forest Fire Behavior Prediction System (GLC-X-10) -// https://cfs.nrcan.gc.ca/pubwarehouse/pdfs/31414.pdf -// -// Anderson, Kerry -// Incorporating Smoldering Into Fire Growth Modelling -// https://www.cfs.nrcan.gc.ca/pubwarehouse/pdfs/19950.pdf -// -// default grass fuel load (kg/m^2) -static constexpr MathSize DEFAULT_GRASS_FUEL_LOAD = 0.35; -// amount of duff to apply ffmc moisture to (cm) (1.2 cm is from Kerry's paper) -static constexpr MathSize DUFF_FFMC_DEPTH = 1.2; -/** - * \brief Fire Intensity (kW/m) [ST-X-3 eq 69] - * \param fc Fuel consumption (kg/m^2) - * \param ros Rate of spread (m/min) - * \return Fire Intensity (kW/m) [ST-X-3 eq 69] - */ -[[nodiscard]] constexpr MathSize fire_intensity(const MathSize fc, const MathSize ros) -{ - return 300.0 * fc * ros; -} -/** - * \brief An FBP fuel type. - */ -class FuelType -{ -public: - /** - * \brief Convert FuelType to its code, or 0 if nullptr - * \param fuel FuelType to convert - * \return Code for FuelType, or 0 if nullptr - */ - [[nodiscard]] static constexpr FuelCodeSize safeCode(const FuelType* fuel) - { - return nullptr == fuel ? static_cast(INVALID_FUEL_CODE) : fuel->code(); - } - /** - * \brief Convert FuelType to its name, or 0 if nullptr - * \param fuel FuelType to convert - * \return Name for FuelType, or "NULL" if nullptr - */ - [[nodiscard]] static constexpr const char* safeName(const FuelType* fuel) - { - return nullptr == fuel ? "NULL" : fuel->name(); - } - /** - * \brief Critical rate of spread (m/min) - * \param sfc Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 9-25] - * \param csi Critical Surface Fire Intensity (CSI) (kW/m) [ST-X-3 eq 56] - * \return Critical rate of spread (m/min) - */ - [[nodiscard]] static constexpr MathSize criticalRos(const MathSize sfc, const MathSize csi) - { - return sfc > 0 ? csi / (300.0 * sfc) : 0.0; - } - /** - * \brief Whether or not this is a crown fire - * \param csi Critical Surface Fire Intensity (CSI) (kW/m) [ST-X-3 eq 56] - * \param sfi Surface Fire Intensity (kW/m) - * \return Whether or not this is a crown fire - */ - [[nodiscard]] static constexpr bool isCrown(const MathSize csi, const MathSize sfi) - { - return sfi > csi; - } - /** - * \brief Crown fuel load (kg/m^2) [ST-X-3 table 8] - * \return Crown fuel load (kg/m^2) [ST-X-3 table 8] - */ - [[nodiscard]] virtual MathSize cfl() const = 0; - virtual ~FuelType() noexcept = default; - /** - * \brief Fuel type - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param can_crown Whether or not this fuel can have a crown fire - */ - constexpr FuelType(const FuelCodeSize& code, - const char* name, - const bool can_crown) noexcept - : name_(name), can_crown_(can_crown), code_(code) - { - } - FuelType(FuelType&& rhs) noexcept = delete; - FuelType(const FuelType& rhs) noexcept = delete; - FuelType& operator=(FuelType&& rhs) noexcept = delete; - FuelType& operator=(const FuelType& rhs) noexcept = delete; - /** - * \brief Whether or not this fuel can have a crown fire - * \return Whether or not this fuel can have a crown fire - */ - [[nodiscard]] constexpr bool canCrown() const - { - return can_crown_; - } - /** - * \brief Grass curing - * \return Grass curing (or -1 if invalid for this fuel type) - */ - [[nodiscard]] virtual MathSize grass_curing(const int, const wx::FwiWeather&) const - { - // NOTE: grass overrides this but everything else doesn't have curing - return INVALID_CURING; - } - - /** - * \brief Crown base height (m) [ST-X-3 table 8] - * \return Crown base height (m) [ST-X-3 table 8] - */ - [[nodiscard]] virtual MathSize cbh() const = 0; - /** - * \brief Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \param rss Surface Rate of spread (ROS) (m/min) [ST-X-3 eq 55] - * \param rso Critical surface fire spread rate (RSO) [ST-X-3 eq 57] - * \return Crown Fraction Burned (CFB) [ST-X-3 eq 58] - */ - [[nodiscard]] virtual MathSize crownFractionBurned(MathSize rss, MathSize rso) const noexcept = 0; - /** - * \brief Calculate probability of burning [Anderson eq 1] - * \param mc_fraction moisture content (% / 100) - * \return Calculate probability of burning [Anderson eq 1] - */ - [[nodiscard]] virtual MathSize probabilityPeat(MathSize mc_fraction) const noexcept = 0; - /** - * \brief Survival probability calculated using probability of ony survival based on multiple formulae - * \param wx FwiWeather to calculate survival probability for - * \return Chance of survival (% / 100) - */ - [[nodiscard]] virtual ThresholdSize survivalProbability( - const wx::FwiWeather& wx) const noexcept = 0; - /** - * \brief BUI Effect on surface fire rate of spread [ST-X-3 eq 54] - * \param bui Build-up Index - * \return BUI Effect on surface fire rate of spread [ST-X-3 eq 54] - */ - [[nodiscard]] virtual MathSize buiEffect(MathSize bui) const = 0; - /** - * \brief Crown Fuel Consumption (CFC) (kg/m^2) [ST-X-3 eq 66] - * \param cfb Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \return Crown Fuel Consumption (CFC) (kg/m^2) [ST-X-3 eq 66] - */ - [[nodiscard]] virtual MathSize crownConsumption(MathSize cfb) const = 0; - /** - * \brief Calculate rate of spread (m/min) - * \param nd Difference between date and the date of minimum foliar moisture content - * \param wx FwiWeather to use for calculation - * \param isi Initial Spread Index (may differ from wx because of slope) - * \return Rate of spread (m/min) - */ - [[nodiscard]] virtual MathSize calculateRos(int nd, - const wx::FwiWeather& wx, - MathSize isi) const = 0; - /** - * \brief Calculate ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41/42] - * \param spread SpreadInfo to use - * \param isi Initial Spread Index - * \return ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41/42] - */ - [[nodiscard]] virtual MathSize calculateIsf(const SpreadInfo& spread, - MathSize isi) const = 0; - /** - * \brief Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 9-25] - * \param spread SpreadInfo to use - * \return Surface fuel consumption (SFC) (kg/m^2) [ST-X-3 eq 9-25] - */ - [[nodiscard]] virtual MathSize surfaceFuelConsumption( - const SpreadInfo& spread) const = 0; - /** - * \brief Length to Breadth ratio [ST-X-3 eq 79] - * \param ws Wind Speed (km/h) - * \return Length to Breadth ratio [ST-X-3 eq 79] - */ - [[nodiscard]] virtual MathSize lengthToBreadth(MathSize ws) const = 0; - /** - * \brief Final rate of spread (m/min) - * \param spread SpreadInfo to use - * \param isi Initial Spread Index (may differ from wx because of slope) - * \param cfb Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \param rss Surface Rate of spread (ROS) (m/min) [ST-X-3 eq 55] - * \return Final rate of spread (m/min) - */ - [[nodiscard]] virtual MathSize finalRos(const SpreadInfo& spread, - MathSize isi, - MathSize cfb, - MathSize rss) const = 0; - /** - * \brief Critical Surface Fire Intensity (CSI) [ST-X-3 eq 56] - * \param spread SpreadInfo to use in calculation - * \return Critical Surface Fire Intensity (CSI) [ST-X-3 eq 56] - */ - [[nodiscard]] virtual MathSize criticalSurfaceIntensity( - const SpreadInfo& spread) const = 0; - /** - * \brief Name of the fuel - * \return Name of the fuel - */ - [[nodiscard]] constexpr const char* name() const - { - return name_; - } - /** - * \brief Code for this fuel type - * \return Code for this fuel type - */ - [[nodiscard]] constexpr FuelCodeSize code() const - { - return code_; - } -private: - /** - * \brief Name of the fuel - */ - const char* name_; - /** - * \brief Whether or not this fuel can have a crown fire - */ - const bool can_crown_; - /** - * \brief Code to identify fuel with - */ - FuelCodeSize code_; -}; -/** - * \brief Base class for all FuelTypes. - * \tparam BulkDensity Duff Bulk Density (kg/m^3) [Anderson table 1] * 1000 - * \tparam InorganicPercent Inorganic percent of Duff layer (%) [Anderson table 1] - * \tparam DuffDepth Depth of Duff layer (cm * 10) [Anderson table 1] - */ -template -class FuelBase - : public FuelType -{ -public: - ~FuelBase() override = default; - /** - * \brief Constructor - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param can_crown Whether or not this fuel type can have a crown fire - * \param duff_ffmc Type of duff near the surface - * \param duff_dmc Type of duff deeper underground - */ - constexpr FuelBase(const FuelCodeSize& code, - const char* name, - const bool can_crown, - const Duff* duff_ffmc, - const Duff* duff_dmc) - : FuelType(code, name, can_crown), - duff_ffmc_(duff_ffmc), - duff_dmc_(duff_dmc) - { - } - FuelBase(FuelBase&& rhs) noexcept = delete; - FuelBase(const FuelBase& rhs) = delete; - FuelBase& operator=(FuelBase&& rhs) noexcept = delete; - FuelBase& operator=(const FuelBase& rhs) = delete; - /** - * \brief Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \param rss Surface Rate of spread (ROS) (m/min) [ST-X-3 eq 55] - * \param rso Critical surface fire spread rate (RSO) [ST-X-3 eq 57] - * \return Crown Fraction Burned (CFB) [ST-X-3 eq 58] - */ - [[nodiscard]] MathSize crownFractionBurned(const MathSize rss, - const MathSize rso) const noexcept override - { - // can't burn crown if it doesn't exist - return cfl() > 0 ? max(0.0, 1.0 - exp(-0.230 * (rss - rso))) : 0.0; - } - /** - * \brief Calculate probability of burning [Anderson eq 1] - * \param mc_fraction moisture content (% / 100) - * \return Calculate probability of burning [Anderson eq 1] - */ - [[nodiscard]] ThresholdSize probabilityPeat(const MathSize mc_fraction) const noexcept override - { - // Anderson table 1 - constexpr auto pb = bulkDensity(); - // Anderson table 1 - constexpr auto fi = inorganicPercent(); - constexpr auto pi = fi * pb; - // Inorganic ratio - constexpr auto ri = fi / (1 - fi); - constexpr auto const_part = -19.329 + 1.7170 * ri + 23.059 * pi; - // Anderson eq 1 - return 1 / (1 + exp(17.047 * mc_fraction / (1 - fi) + const_part)); - } - /** - * \brief Survival probability calculated using probability of ony survival based on multiple formulae - * \param wx FwiWeather to calculate survival probability for - * \return Chance of survival (% / 100) - */ - [[nodiscard]] ThresholdSize survivalProbability(const wx::FwiWeather& wx) const noexcept - override - { - // divide by 100 since we need moisture ratio - // IFERROR(((1 / (1 + EXP($G$43 + $I$43 * - // (Q$44 * $O$43 + $N$43)))) - - // (1 / (1 + EXP($G$43 + $I$43 * (2.5 * $O$43 + $N$43))))) - // / (1 / (1 + EXP($G$43 + $I$43 * $N$43))), 0) - // HACK: use same constants for all fuels because they seem to work nicer than - // using the ratios, but they change anyway because of the other fuel attributes - static const auto WFfmc = 0.25; - static const auto WDmc = 1.0; - static const auto RatioHartford = 0.5; - static const auto RatioFrandsen = 1.0 - RatioHartford; - static const auto RatioAspen = 0.5; - static const auto RatioFuel = 1.0 - RatioAspen; - const auto mc_ffmc = wx.mcFfmc() * WFfmc + WDmc; - static const auto McFfmcSaturated = 2.5 * WFfmc + WDmc; - static const auto McDmc = WDmc; - const auto prob_ffmc_peat = probabilityPeat(mc_ffmc); - const auto prob_ffmc_peat_saturated = probabilityPeat(McFfmcSaturated); - const auto prob_ffmc_peat_zero = probabilityPeat(McDmc); - const auto prob_ffmc_peat_weighted = (prob_ffmc_peat - prob_ffmc_peat_saturated) / prob_ffmc_peat_zero; - const auto prob_ffmc = duffFfmcType()->probabilityOfSurvival(mc_ffmc * 100); - const auto prob_ffmc_saturated = duffFfmcType()->probabilityOfSurvival( - McFfmcSaturated * 100); - const auto prob_ffmc_zero = duffFfmcType()->probabilityOfSurvival(McDmc); - const auto prob_ffmc_weighted = (prob_ffmc - prob_ffmc_saturated) / prob_ffmc_zero; - const auto term_otway = exp(-3.11 + 0.12 * wx.dmc().asValue()); - const auto prob_otway = term_otway / (1 + term_otway); - const auto mc_pct = wx.mcDmcPct() * dmcRatio() + wx.mcFfmcPct() * ffmcRatio(); - const auto prob_weight_ffmc = duffFfmcType()->probabilityOfSurvival(mc_pct); - const auto prob_weight_ffmc_peat = probabilityPeat(mc_pct / 100); - const auto prob_weight_dmc = duffDmcType()->probabilityOfSurvival(wx.mcDmcPct()); - const auto prob_weight_dmc_peat = probabilityPeat(wx.mcDmc()); - // chance of survival is 1 - chance of it not surviving in every fuel - const auto tot_prob = 1 - (1 - prob_ffmc_peat_weighted) * (1 - prob_ffmc_weighted) * ((1 - prob_otway) * RatioAspen + ((1 - prob_weight_ffmc_peat) * RatioHartford + (1 - prob_weight_ffmc) * RatioFrandsen) * ((1 - prob_weight_dmc_peat) * RatioHartford + (1 - prob_weight_dmc) * RatioFrandsen) * RatioFuel); - return tot_prob; - } - /** - * \brief Duff Bulk Density (kg/m^3) [Anderson table 1] - * \return Duff Bulk Density (kg/m^3) [Anderson table 1] - */ - [[nodiscard]] static constexpr MathSize bulkDensity() - { - return BulkDensity / 1000.0; - } - /** - * \brief Inorganic Percent (% / 100) [Anderson table 1] - * \return Inorganic Percent (% / 100) [Anderson table 1] - */ - [[nodiscard]] static constexpr MathSize inorganicPercent() - { - return InorganicPercent / 100.0; - } - /** - * \brief DuffDepth Depth of Duff layer (cm) [Anderson table 1] - * \return DuffDepth Depth of Duff layer (cm) [Anderson table 1] - */ - [[nodiscard]] static constexpr MathSize duffDepth() - { - return DuffDepth / 10.0; - } - /** - * \brief Type of duff deeper underground - * \return Type of duff deeper underground - */ - [[nodiscard]] constexpr const Duff* duffDmcType() const - { - return duff_dmc_; - } - /** - * \brief Type of duff near the surface - * \return Type of duff near the surface - */ - [[nodiscard]] constexpr const Duff* duffFfmcType() const - { - return duff_ffmc_; - } - /** - * \brief What fraction of the duff layer should use FFMC to determine moisture - * \return What fraction of the duff layer should use FFMC to determine moisture - */ - [[nodiscard]] static constexpr MathSize ffmcRatio() - { - return 1 - dmcRatio(); - } - /** - * \brief What fraction of the duff layer should use DMC to determine moisture - * \return What fraction of the duff layer should use DMC to determine moisture - */ - [[nodiscard]] static constexpr MathSize dmcRatio() - { - return (duffDepth() - DUFF_FFMC_DEPTH) / duffDepth(); - } -private: - /** - * \brief Type of duff near the surface - */ - const Duff* duff_ffmc_; - /** - * \brief Type of duff deeper underground - */ - const Duff* duff_dmc_; -}; -/** - * \brief Placeholder fuel that throws exceptions if it ever gets used. - */ -class InvalidFuel final - : public FuelType -{ -public: - InvalidFuel() noexcept - : InvalidFuel(0, nullptr) - { - } - /** - * \brief Placeholder fuel that throws exceptions if it ever gets used. - * \param code Code to identify fuel with - * \param name Name of the fuel - */ - constexpr InvalidFuel(const FuelCodeSize& code, const char* name) noexcept - : FuelType(code, name, false) - { - } - ~InvalidFuel() override = default; - InvalidFuel(const InvalidFuel& rhs) noexcept = delete; - InvalidFuel(InvalidFuel&& rhs) noexcept = delete; - InvalidFuel& operator=(const InvalidFuel& rhs) noexcept = delete; - InvalidFuel& operator=(InvalidFuel&& rhs) noexcept = delete; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize grass_curing(const int nd, const wx::FwiWeather& wx) const override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize cbh() const override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize cfl() const override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize buiEffect(MathSize) const override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize crownConsumption(MathSize) const override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize calculateRos(int, const wx::FwiWeather&, MathSize) const - override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize calculateIsf(const SpreadInfo&, MathSize) const - override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize surfaceFuelConsumption(const SpreadInfo&) const - override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize lengthToBreadth(MathSize) const override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize finalRos(const SpreadInfo&, - MathSize, - MathSize, - MathSize) const override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize criticalSurfaceIntensity(const SpreadInfo&) const - override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] MathSize crownFractionBurned(MathSize, - MathSize) const noexcept override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] ThresholdSize probabilityPeat(MathSize) const noexcept override; - /** - * \brief Throw a runtime_error - * \return Throw a runtime_error - */ - [[nodiscard]] ThresholdSize survivalProbability(const wx::FwiWeather&) const noexcept - override; -}; -} -} diff --git a/firestarr/src/cpp/Grid.cpp b/firestarr/src/cpp/Grid.cpp deleted file mode 100644 index d23fd11fe..000000000 --- a/firestarr/src/cpp/Grid.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Grid.h" -#include "Settings.h" -#include "UTM.h" - -using fs::Idx; -namespace fs::data -{ -using fs::topo::try_fix_meridian; -using fs::topo::from_lat_long; -using fs::topo::to_lat_long; -string find_value(const string& key, const string& within) -{ - const auto c = within.find(key); - if (c != string::npos) - { - const string str = &within.c_str()[c + string(key).length()]; - return str.substr(0, str.find(' ')); - } - return ""; -} -int str_to_int(const string& str) -{ - return stoi(str); -} -MathSize str_to_value(const string& str) -{ - return static_cast(stod(str)); -} -template -bool find_value(const string& key, - const string& within, - T* result, - T (*convert)(const string& str)) -{ - const auto str = find_value(key, within); - if (!str.empty()) - { - *result = convert(str); - logging::extensive("%s '%s'\n", key.c_str(), str.c_str()); - return true; - } - return false; -} -GridBase::GridBase(const MathSize cell_size, - const MathSize xllcorner, - const MathSize yllcorner, - const MathSize xurcorner, - const MathSize yurcorner, - string&& proj4) noexcept - : proj4_(try_fix_meridian(std::forward(proj4))), - cell_size_(cell_size), - xllcorner_(xllcorner), - yllcorner_(yllcorner), - xurcorner_(xurcorner), - yurcorner_(yurcorner) -{ -} -GridBase::GridBase() noexcept - : cell_size_(-1), - xllcorner_(-1), - yllcorner_(-1), - xurcorner_(-1), - yurcorner_(-1) -{ -} -void GridBase::createPrj(const string& dir, const string& base_name) const -{ - const string filename = dir + base_name + ".prj"; - FILE* out = fopen(filename.c_str(), "w"); - logging::extensive(proj4_.c_str()); - // HACK: use what we know is true for the grids that were generated and - // hope it's correct otherwise - const auto proj = find_value("+proj=", proj4_); - // HACK: expect zone to already be converted to meridian in proj4 definition - const auto lon_0 = find_value("+lon_0=", proj4_); - const auto lat_0 = find_value("+lat_0=", proj4_); - const auto k = find_value("+k=", proj4_); - const auto x_0 = find_value("+x_0=", proj4_); - const auto y_0 = find_value("+y_0=", proj4_); - if ( - proj.empty() - || k.empty() - || lat_0.empty() - || lon_0.empty() - || x_0.empty() - || y_0.empty()) - { - logging::fatal( - "Cannot convert proj4 '%s' into .prj file", - proj4_.c_str()); - } - fprintf(out, "Projection TRANSVERSE\n"); - fprintf(out, "Datum AI_CSRS\n"); - fprintf(out, "Spheroid GRS80\n"); - fprintf(out, "Units METERS\n"); - fprintf(out, "Zunits NO\n"); - fprintf(out, "Xshift 0.0\n"); - fprintf(out, "Yshift 0.0\n"); - fprintf(out, "Parameters \n"); - // NOTE: it seems like comments in .prj files is no longer supported? - // these are all just strings since they're out of the proj4 string - // HACK: this is the order they were previously, so not just outputting in order they occur - fprintf(out, "%s /* scale factor at central meridian\n", k.c_str()); - fprintf(out, "%s 0 0.0 /* longitude of central meridian\n", lon_0.c_str()); - fprintf(out, "%s 0 0.0 /* latitude of origin\n", lat_0.c_str()); - fprintf(out, "%s /* false easting (meters)\n", x_0.c_str()); - fprintf(out, "%s /* false northing (meters)\n", y_0.c_str()); - fclose(out); -} -unique_ptr GridBase::findCoordinates(const topo::Point& point, - const bool flipped) const -{ - auto full = findFullCoordinates(point, flipped); - return make_unique(static_cast(std::get<0>(*full)), - static_cast(std::get<1>(*full)), - std::get<2>(*full), - std::get<3>(*full)); -} - -// Use pair instead of Location, so we can go above max columns & rows -unique_ptr GridBase::findFullCoordinates(const topo::Point& point, - const bool flipped) const -{ - MathSize x; - MathSize y; - from_lat_long(this->proj4_, point, &x, &y); - logging::debug("Coordinates (%f, %f) converted to (%f, %f)", - point.latitude(), - point.longitude(), - x, - y); - // check that north is the top of the raster at least along center - const auto x_mid = (xllcorner_ + xurcorner_) / 2.0; - topo::Point south = to_lat_long(proj4_, x_mid, yllcorner_); - topo::Point north = to_lat_long(proj4_, x_mid, yurcorner_); - auto x_s = static_cast(0.0); - auto y_s = static_cast(0.0); - from_lat_long(this->proj4_, south, &x_s, &y_s); - auto x_n = static_cast(0.0); - auto y_n = static_cast(0.0); - from_lat_long(this->proj4_, north, &x_n, &y_n); - // FIX: how different is too much? - constexpr MathSize MAX_DEVIATION = 0.001; - // if (x_n != x_s) - const auto deviation = x_n - x_s; - if (abs(deviation) > MAX_DEVIATION) - { - logging::note( - "Due north is not the top of the raster for (%f, %f) with proj4 '%s' - %f vs %f gives deviation of %f degrees which exceeds maximum of %f degrees", - point.latitude(), - point.longitude(), - this->proj4_.c_str(), - x_n, - x_s, - deviation, - MAX_DEVIATION); - return nullptr; - } - else if (abs(deviation * 10) > MAX_DEVIATION) - { - // if we're within an order of magnitude of an unacceptable deviation then warn about it - logging::warning( - "Due north deviates by %f degrees from South to North along the middle of the raster", - deviation); - } - logging::verbose("Lower left is (%f, %f)", this->xllcorner_, this->yllcorner_); - // convert coordinates into cell position - const auto actual_x = (x - this->xllcorner_) / this->cell_size_; - // these are already flipped across the y-axis on reading, so it's the same as for x now - auto actual_y = (!flipped) - ? (y - this->yllcorner_) / this->cell_size_ - : (yurcorner_ - y) / cell_size_; - const auto column = static_cast(actual_x); - const auto row = static_cast(round(actual_y - 0.5)); - if (0 > column || column >= calculateColumns() || 0 > row || row >= calculateRows()) - { - logging::verbose("Returning nullptr from findFullCoordinates() for (%f, %f) => (%d, %d)", - actual_x, - actual_y, - column, - row); - return nullptr; - } - const auto sub_x = static_cast((actual_x - column) * 1000); - const auto sub_y = static_cast((actual_y - row) * 1000); - return make_unique(static_cast(row), - static_cast(column), - sub_x, - sub_y); -} -void write_ascii_header(ofstream& out, - const MathSize num_columns, - const MathSize num_rows, - const MathSize xll, - const MathSize yll, - const MathSize cell_size, - const MathSize no_data) -{ - out << "ncols " << num_columns << "\n"; - out << "nrows " << num_rows << "\n"; - out << "xllcorner " << fixed << setprecision(6) << xll << "\n"; - out << "yllcorner " << fixed << setprecision(6) << yll << "\n"; - out << "cellsize " << cell_size << "\n"; - out << "NODATA_value " << no_data << "\n"; -} -[[nodiscard]] GridBase read_header(TIFF* tif, GTIF* gtif) -{ - GTIFDefn definition; - if (GTIFGetDefn(gtif, &definition)) - { - uint32_t columns; - uint32_t rows; - TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &columns); - // logging::check_fatal(columns > numeric_limits::max(), - // "Cannot use grids with more than %d columns", - // numeric_limits::max()); - TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &rows); - // logging::check_fatal(rows > numeric_limits::max(), - // "Cannot use grids with more than %d rows", - // numeric_limits::max()); - double x = 0.0; - double y = rows; - logging::check_fatal(!GTIFImageToPCS(gtif, &x, &y), - "Unable to translate image to PCS coordinates."); - const auto yllcorner = y; - const auto xllcorner = x; - logging::debug("Lower left for header is (%f, %f)", xllcorner, yllcorner); - double adf_coefficient[6] = {0}; - x = 0.5; - y = 0.5; - logging::check_fatal(!GTIFImageToPCS(gtif, &x, &y), - "Unable to translate image to PCS coordinates."); - adf_coefficient[4] = x; - adf_coefficient[5] = y; - x = 1.5; - y = 0.5; - logging::check_fatal(!GTIFImageToPCS(gtif, &x, &y), - "Unable to translate image to PCS coordinates."); - const auto cell_width = x - adf_coefficient[4]; - x = 0.5; - y = 1.5; - logging::check_fatal(!GTIFImageToPCS(gtif, &x, &y), - "Unable to translate image to PCS coordinates."); - const auto cell_height = y - adf_coefficient[5]; - logging::check_fatal(cell_width != -cell_height, - "Can only use grids with square pixels"); - logging::debug("Cell size is %f", cell_width); - const auto proj4_char = GTIFGetProj4Defn(&definition); - auto proj4 = string(proj4_char); - free(proj4_char); - const auto xurcorner = xllcorner + cell_width * columns; - const auto yurcorner = yllcorner + cell_width * rows; - return { - cell_width, - xllcorner, - yllcorner, - xurcorner, - yurcorner, - string(proj4)}; - } - throw runtime_error("Cannot read TIFF header"); -} -[[nodiscard]] GridBase read_header(const string& filename) -{ - return with_tiff(filename, [](TIFF* tif, GTIF* gtif) { return read_header(tif, gtif); }); -} -} diff --git a/firestarr/src/cpp/Grid.h b/firestarr/src/cpp/Grid.h deleted file mode 100644 index 476215de2..000000000 --- a/firestarr/src/cpp/Grid.h +++ /dev/null @@ -1,881 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include -#include -#include "Location.h" -#include "Cell.h" -#include "Log.h" -#include "Point.h" -#include "Settings.h" - -using fs::topo::Location; -using fs::topo::Position; -using NodataIntType = int64_t; -/** - * \brief Provides hash function for Location. - */ -template <> -struct std::hash -{ - /** - * \brief Get hash value for a Location - * \param location Location to get value for - * \return Hash value for a Location - */ - [[nodiscard]] constexpr fs::HashSize operator()( - const Location& location) const noexcept - { - return location.hash(); - } -}; -namespace fs::data -{ -/** - * \brief The base class with information for a grid of data with geographic coordinates. - */ -class GridBase -{ -public: - virtual ~GridBase() = default; - /** - * \brief Move constructor - * \param rhs GridBase to move from - */ - GridBase(GridBase&& rhs) noexcept = default; - /** - * \brief Copy constructor - * \param rhs GridBase to copy from - */ - GridBase(const GridBase& rhs) = default; - /** - * \brief Copy assignment - * \param rhs GridBase to copy from - * \return This, after assignment - */ - GridBase& operator=(const GridBase& rhs) = default; - /** - * \brief Move assignment - * \param rhs GridBase to move from - * \return This, after assignment - */ - GridBase& operator=(GridBase&& rhs) noexcept = default; - /** - * \brief Cell size used for GridBase. - * \return Cell height and width in meters. - */ - [[nodiscard]] constexpr MathSize cellSize() const noexcept - { - return cell_size_; - } - /** - * \brief Number of rows in the GridBase. - * \return Number of rows in the GridBase. - */ - [[nodiscard]] constexpr FullIdx calculateRows() const noexcept - { - return static_cast((yurcorner() - yllcorner()) / cellSize()) - 1; - // // HACK: just get rid of -1 for now because it seems weird - // return static_cast((yurcorner() - yllcorner()) / cellSize()); - } - /** - * \brief Number of columns in the GridBase. - * \return Number of columns in the GridBase. - */ - [[nodiscard]] constexpr FullIdx calculateColumns() const noexcept - { - return static_cast((xurcorner() - xllcorner()) / cellSize()) - 1; - // // HACK: just get rid of -1 for now because it seems weird - // return static_cast((xurcorner() - xllcorner()) / cellSize()); - } - /** - * \brief Lower left corner X coordinate in meters. - * \return Lower left corner X coordinate in meters. - */ - [[nodiscard]] constexpr MathSize xllcorner() const noexcept - { - return xllcorner_; - } - /** - * \brief Lower left corner Y coordinate in meters. - * \return Lower left corner Y coordinate in meters. - */ - [[nodiscard]] constexpr MathSize yllcorner() const noexcept - { - return yllcorner_; - } - /** - * \brief Upper right corner X coordinate in meters. - * \return Upper right corner X coordinate in meters. - */ - [[nodiscard]] constexpr MathSize xurcorner() const noexcept - { - return xurcorner_; - } - /** - * \brief Upper right corner Y coordinate in meters. - * \return Upper right corner Y coordinate in meters. - */ - [[nodiscard]] constexpr MathSize yurcorner() const noexcept - { - return yurcorner_; - } - /** - * \brief Proj4 string defining coordinate system for this grid. Must be a UTM projection. - * \return Proj4 string defining coordinate system for this grid. - */ - [[nodiscard]] constexpr const string& proj4() const noexcept - { - return proj4_; - } - /** - * \brief Constructor - * \param cell_size Cell width and height (m) - * \param xllcorner Lower left corner X coordinate (m) - * \param yllcorner Lower left corner Y coordinate (m) - * \param xurcorner Upper right corner X coordinate (m) - * \param yurcorner Upper right corner Y coordinate (m) - * \param proj4 Proj4 projection definition - */ - GridBase(MathSize cell_size, - MathSize xllcorner, - MathSize yllcorner, - MathSize xurcorner, - MathSize yurcorner, - string&& proj4) noexcept; - /** - * \brief Default constructor - */ - GridBase() noexcept; - /** - * \brief Create .prj file in directory with base name for file - * \param dir Directory to create in - * \param base_name base file name for .prj file - */ - void createPrj(const string& dir, const string& base_name) const; - /** - * \brief Find Coordinates for Point - * \param point Point to translate to Grid Coordinate - * \param flipped Whether or not Grid data is flipped along x axis - * \return Coordinates for Point translated to Grid - */ - [[nodiscard]] unique_ptr findCoordinates( - const topo::Point& point, - bool flipped) const; - /** - * \brief Find FullCoordinates for Point - * \param point Point to translate to Grid Coordinate - * \param flipped Whether or not Grid data is flipped along x axis - * \return Coordinates for Point translated to Grid - */ - [[nodiscard]] unique_ptr findFullCoordinates( - const topo::Point& point, - bool flipped) const; -private: - /** - * \brief Proj4 string defining projection. - */ - string proj4_; - /** - * \brief Cell height and width in meters. - */ - MathSize cell_size_; - /** - * \brief Lower left corner X coordinate in meters. - */ - MathSize xllcorner_; - /** - * \brief Lower left corner Y coordinate in meters. - */ - MathSize yllcorner_; - /** - * \brief Upper right corner X coordinate in meters. - */ - MathSize xurcorner_; - /** - * \brief Upper right corner Y coordinate in meters. - */ - MathSize yurcorner_; -}; -void write_ascii_header(ofstream& out, - MathSize num_columns, - MathSize num_rows, - MathSize xll, - MathSize yll, - MathSize cell_size, - MathSize no_data); -template -[[nodiscard]] R with_tiff(const string& filename, function fct) -{ - logging::debug("Reading file %s", filename.c_str()); - // suppress warnings about geotiff tags that aren't found - TIFFSetWarningHandler(nullptr); - auto tif = GeoTiffOpen(filename.c_str(), "r"); - logging::check_fatal(!tif, "Cannot open file %s as a TIF", filename.c_str()); - auto gtif = GTIFNew(tif); - logging::check_fatal(!gtif, "Cannot open file %s as a GEOTIFF", filename.c_str()); - // try - // { - R result = fct(tif, gtif); - if (tif) - { - XTIFFClose(tif); - } - if (gtif) - { - GTIFFree(gtif); - } - GTIFDeaccessCSV(); - return result; - // } - // catch (std::exception&) - // { - // return logging::fatal("Unable to process file %s", filename.c_str()); - // } -} -GridBase read_header(TIFF* tif, GTIF* gtif); -GridBase read_header(const string& filename); -/** - * \brief A GridBase with an associated type of data. - * \tparam T Type of data after conversion from initialization type. - * \tparam V Type of data used as an input when initializing. - */ -template -class Grid - : public GridBase -{ -public: - /** - * \brief Number of rows in the GridBase. - * \return Number of rows in the GridBase. - */ - [[nodiscard]] constexpr Idx rows() const noexcept - { - return rows_; - } - /** - * \brief Number of columns in the GridBase. - * \return Number of columns in the GridBase. - */ - [[nodiscard]] constexpr Idx columns() const noexcept - { - return columns_; - } - /** - * \brief Value used for grid locations that have no data. - * \return Value used for grid locations that have no data. - */ - [[nodiscard]] constexpr V nodataInput() const noexcept - { - return nodata_input_; - } - /** - * \brief Value representing no data - * \return Value representing no data - */ - constexpr T nodataValue() const noexcept - { - return nodata_value_; - } // NOTE: only use this for simple types because it's returning by value - /** - * \brief Value for grid at given Location. - * \param location Location to get value for. - * \return Value at grid Location. - */ - [[nodiscard]] virtual T at(const Location& location) const = 0; - template - [[nodiscard]] T at(const Position

& position) const - { - return at(Location{position.hash()}); - } - // NOTE: use set instead of at to avoid issues with bool - /** - * \brief Set value for grid at given Location. - * \param location Location to set value for. - * \param value Value to set at grid Location. - * \return None - */ - virtual void set(const Location& location, T value) = 0; - template - void set(const Position

& position, const T value) - { - set(Location{position.hash()}); - } -protected: - /** - * \brief Constructor - * \param cell_size Cell width and height (m) - * \param rows Number of rows - * \param columns Number of columns - * \param nodata_input Value that represents no data for type V - * \param nodata_value Value that represents no data for type T - * \param nodata Integer value that represents no data - * \param xllcorner Lower left corner X coordinate (m) - * \param yllcorner Lower left corner Y coordinate (m) - * \param proj4 Proj4 projection definition - */ - Grid(const MathSize cell_size, - const Idx rows, - const Idx columns, - const V nodata_input, - const T nodata_value, - const MathSize xllcorner, - const MathSize yllcorner, - const MathSize xurcorner, - const MathSize yurcorner, - string&& proj4) noexcept - : GridBase(cell_size, - xllcorner, - yllcorner, - xurcorner, - yurcorner, - std::forward(proj4)), - nodata_input_(nodata_input), - nodata_value_(nodata_value), - rows_(rows), - columns_(columns) - { -#ifdef DEBUG_GRIDS - logging::check_fatal(rows > MAX_ROWS, "Too many rows (%d > %d)", rows, MAX_ROWS); - logging::check_fatal(columns > MAX_COLUMNS, "Too many columns (%d > %d)", columns, MAX_COLUMNS); -#endif -#ifdef DEBUG_GRIDS - // enforce converting to an int and back produces same V - const auto n0 = this->nodata_input_; - const auto n1 = static_cast(n0); - const auto n2 = static_cast(n1); - const auto n3 = static_cast(n2); - logging::check_equal( - n1, - n3, - "nodata_input_ as int"); - logging::check_equal( - n0, - n2, - "nodata_input_ from int"); -#endif - } - /** - * \brief Construct based on GridBase and no data value - * \param grid_info GridBase defining Grid area - * \param no_data Value that represents no data - */ - Grid(const GridBase& grid_info, V no_data) noexcept - : Grid(grid_info.cellSize(), - static_cast(grid_info.calculateRows()), - static_cast(grid_info.calculateColumns()), - no_data, - to_string(no_data), - grid_info.xllcorner(), - grid_info.yllcorner(), - grid_info.xurcorner(), - grid_info.yurcorner(), - grid_info.proj4()) - { - } -private: - /** - * \brief Value used to represent no data at a Location. - */ - V nodata_input_; - /** - * \brief Value to use for representing no data at a Location. - */ - T nodata_value_; - /** - * \brief Number of rows in the grid. - */ - Idx rows_{}; - /** - * \brief Number of columns in the grid. - */ - Idx columns_{}; -}; -/** - * \brief A Grid that defines the data structure used for storing values. - * \tparam T Type of data after conversion from initialization type. - * \tparam V Type of data used as an input when initializing. - * \tparam D The data type that stores the values. - */ -template -class GridData - : public Grid -{ -public: - using Grid::Grid; - /** - * \brief Constructor - * \param cell_size Cell width and height (m) - * \param rows Number of rows - * \param columns Number of columns - * \param nodata_input Value that represents no data for type V - * \param nodata_value Value that represents no data for type T - * \param xllcorner Lower left corner X coordinate (m) - * \param yllcorner Lower left corner Y coordinate (m) - * \param xurcorner Upper right corner X coordinate (m) - * \param yurcorner Upper right corner Y coordinate (m) - * \param proj4 Proj4 projection definition - * \param data Data to populate GridData with - */ - GridData(const MathSize cell_size, - const Idx rows, - const Idx columns, - const V nodata_input, - const T nodata_value, - const MathSize xllcorner, - const MathSize yllcorner, - const MathSize xurcorner, - const MathSize yurcorner, - string&& proj4, - D&& data) - : Grid(cell_size, - rows, - columns, - nodata_input, - nodata_value, - xllcorner, - yllcorner, - xurcorner, - yurcorner, - std::forward(proj4)), - data(std::forward(data)) - { - } - ~GridData() = default; - /** - * \brief Copy constructor - * \param rhs GridData to copy from - */ - GridData(const GridData& rhs) - : Grid(rhs), data(rhs.data) - { - } - /** - * \brief Move constructor - * \param rhs GridData to move from - */ - GridData(GridData&& rhs) noexcept - : Grid(rhs), data(std::move(rhs.data)) - { - } - /** - * \brief Copy assignment - * \param rhs GridData to copy from - * \return This, after assignment - */ - GridData& operator=(const GridData& rhs) noexcept - { - if (this != &rhs) - { - Grid::operator=(rhs); - data = rhs.data; - } - return *this; - } - /** - * \brief Move assignment - * \param rhs GridData to copy from - * \return This, after assignment - */ - GridData& operator=(GridData&& rhs) noexcept - { - if (this != &rhs) - { - Grid::operator=(rhs); - data = std::move(rhs.data); - } - return *this; - } - /** - * \brief Size of data structure storing values - * \return Size of data structure storing values - */ - [[nodiscard]] size_t size() const - { - return data.size(); - } - // HACK: use public access so that we can get to the keys - /** - * \brief Structure that holds data represented by this GridData - */ - D data; -protected: - virtual tuple dataBounds() const = 0; - /** - * \brief Save GridMap contents to .asc file - * \tparam R Type to be written to .asc file - * \param dir Directory to save into - * \param base_name File base name to use - * \param convert Function to convert from V to R - * \param no_data Value to use as nodata in output - */ - template - string saveToAsciiFile(const string& dir, - const string& base_name, - std::function convert, - const R no_data) const - { - tuple bounds = dataBounds(); - auto min_column = std::get<0>(bounds); - auto min_row = std::get<1>(bounds); - auto max_column = std::get<2>(bounds); - auto max_row = std::get<3>(bounds); -#ifdef DEBUG_GRIDS - logging::debug( - "Bounds are (%d, %d), (%d, %d)", - min_column, - min_row, - max_column, - max_row); -#endif - logging::extensive("Lower left corner is (%d, %d)", min_column, min_row); - logging::extensive("Upper right corner is (%d, %d)", max_column, max_row); - const MathSize xll = this->xllcorner() + min_column * this->cellSize(); - // offset is different for y since it's flipped - const MathSize yll = this->yllcorner() + (min_row) * this->cellSize(); - logging::extensive("Lower left corner is (%f, %f)", xll, yll); - // HACK: make sure it's always at least 1 - const auto num_rows = static_cast(max_row) - min_row + 1; - const auto num_columns = static_cast(max_column) - min_column + 1; - ofstream out; - string filename = dir + base_name + ".asc"; - out.open(filename.c_str()); - write_ascii_header( - out, - num_columns, - num_rows, - xll, - yll, - this->cellSize(), - static_cast(no_data)); - for (Idx ro = 0; ro < num_rows; ++ro) - { - // HACK: do this so that we always get at least one pixel in output - // need to output in reverse order since (0,0) is bottom left - const Idx r = static_cast(max_row) - ro; - for (Idx co = 0; co < num_columns; ++co) - { - const Location idx(static_cast(r), static_cast(min_column + co)); - // HACK: use + here so that it gets promoted to a printable number - // prevents char type being output as characters - out << +(convert(this->at(idx))) - << " "; - } - out << "\n"; - } - out.close(); - this->createPrj(dir, base_name); - return filename; - } - /** - * \brief Save GridMap contents to .tif file - * \tparam R Type to be written to .tif file - * \param dir Directory to save into - * \param base_name File base name to use - * \param convert Function to convert from V to R - */ - template - string saveToTiffFile(const string& dir, - const string& base_name, - std::function convert, - const R no_data) const - { - uint32_t tileWidth = min((int)(this->columns()), 256); - uint32_t tileHeight = min((int)(this->rows()), 256); - tuple bounds = dataBounds(); - auto min_column = std::get<0>(bounds); - auto min_row = std::get<1>(bounds); - auto max_column = std::get<2>(bounds); - auto max_row = std::get<3>(bounds); - logging::check_fatal( - min_column > max_column, - "Invalid bounds for columns with %d => %d", - min_column, - max_column); - logging::check_fatal( - min_row > max_row, - "Invalid bounds for rows with %d => %d", - min_row, - max_row); -#ifdef DEBUG_GRIDS - logging::debug( - "Bounds are (%d, %d), (%d, %d) initially", - min_column, - min_row, - max_column, - max_row); -#endif - Idx c_min = 0; - while (c_min + static_cast(tileWidth) <= min_column) - { - c_min += static_cast(tileWidth); - } - Idx c_max = c_min + static_cast(tileWidth); - while (c_max < max_column) - { - c_max += static_cast(tileWidth); - } - min_column = c_min; - max_column = c_max; - Idx r_min = 0; - while (r_min + static_cast(tileHeight) <= min_row) - { - r_min += static_cast(tileHeight); - } - Idx r_max = r_min + static_cast(tileHeight); - while (r_max < max_row) - { - r_max += static_cast(tileHeight); - } - min_row = r_min; - max_row = r_max; - logging::check_fatal( - min_column >= max_column, - "Invalid bounds for columns with %d => %d", - min_column, - max_column); - logging::check_fatal( - min_row >= max_row, - "Invalid bounds for rows with %d => %d", - min_row, - max_row); -#ifdef DEBUG_GRIDS - logging::debug( - "Bounds are (%d, %d), (%d, %d) after correction", - min_column, - min_row, - max_column, - max_row); -#endif - logging::extensive("(%d, %d) => (%d, %d)", min_column, min_row, max_column, max_row); - logging::check_fatal((max_row - min_row) % tileHeight != 0, "Invalid start and end rows"); - logging::check_fatal((max_column - min_column) % tileHeight != 0, "Invalid start and end columns"); - logging::extensive("Lower left corner is (%d, %d)", min_column, min_row); - logging::extensive("Upper right corner is (%d, %d)", max_column, max_row); - const MathSize xll = this->xllcorner() + min_column * this->cellSize(); - // offset is different for y since it's flipped - const MathSize yll = this->yllcorner() + (min_row) * this->cellSize(); - logging::extensive("Lower left corner is (%f, %f)", xll, yll); - const auto num_rows = static_cast(max_row - min_row); - const auto num_columns = static_cast(max_column - min_column); - // ensure this is always divisible by tile size - logging::check_fatal(0 != (num_rows % tileWidth), "%d rows not divisible by tiles", num_rows); - logging::check_fatal(0 != (num_columns % tileHeight), "%d columns not divisible by tiles", num_columns); - string filename = dir + base_name + ".tif"; - TIFF* tif = GeoTiffOpen(filename.c_str(), "w"); - auto gtif = GTIFNew(tif); - logging::check_fatal(!gtif, "Cannot open file %s as a GEOTIFF", filename.c_str()); - const double xul = xll; - const double yul = this->yllcorner() + (this->cellSize() * max_row); - double tiePoints[6] = { - 0.0, - 0.0, - 0.0, - xul, - yul, - 0.0}; - double pixelScale[3] = { - this->cellSize(), - this->cellSize(), - 0.0}; - uint32_t bps = sizeof(R) * 8; - // make sure to use floating point if values are - if (std::is_floating_point::value) - { - logging::extensive("Writing %s with float data type for %s", - filename.c_str(), - typeid(R).name()); - TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); - } - else - { - logging::extensive("Writing %s with int data type for %s", - filename.c_str(), - typeid(R).name()); - } - // FIX: was using double, and that usually doesn't make sense, but sometime it might? - // use buffer big enought to fit any (V + '.000\0') + 1 - constexpr auto n = std::numeric_limits::digits10; - static_assert(n > 0); - char str[n + 6]{0}; - const auto nodata_as_int = static_cast(this->nodataInput()); - sxprintf(str, "%d.000", nodata_as_int); - logging::extensive( - "%s using nodata string '%s' for nodata value of (%d, %f)", - typeid(this).name(), - str, - nodata_as_int, - static_cast(no_data)); - TIFFSetField(tif, TIFFTAG_GDAL_NODATA, str); - logging::extensive("%s takes %d bits", base_name.c_str(), bps); - TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, num_columns); - TIFFSetField(tif, TIFFTAG_IMAGELENGTH, num_rows); - TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); - TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bps); - TIFFSetField(tif, TIFFTAG_TILEWIDTH, tileWidth); - TIFFSetField(tif, TIFFTAG_TILELENGTH, tileHeight); - TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); - TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); - TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_LZW); - GTIFSetFromProj4(gtif, this->proj4().c_str()); - TIFFSetField(tif, TIFFTAG_GEOTIEPOINTS, 6, tiePoints); - TIFFSetField(tif, TIFFTAG_GEOPIXELSCALE, 3, pixelScale); - size_t tileSize = tileWidth * tileHeight; - const auto buf_size = tileSize * sizeof(R); - logging::extensive("%s has buffer size %d", base_name.c_str(), buf_size); - R* buf = (R*)_TIFFmalloc(buf_size); - for (size_t co = 0; co < num_columns; co += tileWidth) - { - for (size_t ro = 0; ro < num_rows; ro += tileHeight) - { - std::fill_n( - &buf[0], - tileWidth * tileHeight, - no_data); - // NOTE: shouldn't need to check if writing outside of tile because we made bounds on tile edges above - // need to put data from grid into buffer, but flipped vertically - for (size_t x = 0; x < tileWidth; ++x) - { - for (size_t y = 0; y < tileHeight; ++y) - { - const Idx r = static_cast(max_row) - (ro + y + 1); - const Idx c = static_cast(min_column) + co + x; - const Location idx(r, c); - // might be out of bounds if not divisible by number of tiles - if (!(this->rows() <= r - || 0 > r - || this->columns() <= c - || 0 > c)) - { - // HACK: was getting invalid rasters if assigning directly into buf - const R value = convert(this->at(idx)); - buf[x + y * tileWidth] = value; - } - } - } - logging::check_fatal(TIFFWriteTile(tif, buf, co, ro, 0, 0) < 0, "Cannot write tile to %s", filename.c_str()); - } - } - GTIFWriteKeys(gtif); - if (gtif) - { - GTIFFree(gtif); - } - _TIFFfree(buf); - TIFFClose(tif); - return filename; - } -public: - /** - * \brief Save GridMap contents to file based on settings - * \tparam R Type to be written to file - * \param dir Directory to save into - * \param base_name File base name to use - * \param convert Function to convert from V to R - * \param no_data Value to use as nodata in output - */ - template - string saveToFileWithoutRetry(const string& dir, - const string& base_name, - std::function convert, - const R no_data) const - { - return this->template saveToTiffFile( - dir, - base_name, - convert, - no_data); - } - /** - * \brief Save GridMap contents to file based on settings - * \tparam R Type to be written to file - * \param dir Directory to save into - * \param base_name File base name to use - * \param convert Function to convert from V to R - * \param no_data Value to use as nodata in output - */ - template - string saveToFileWithRetry(const string& dir, - const string& base_name, - std::function convert, - const R no_data) const - { - // HACK: (hopefully) ensure that write works - try - { - // HACK: use different function name to prevent infinite recursion warning - return this->template saveToFileWithoutRetry(dir, base_name, convert, no_data); - } - catch (const std::exception& err) - { - logging::error("Error trying to write %s to %s so retrying\n%s", - base_name.c_str(), - dir.c_str(), - err.what()); - try - { - return this->template saveToFileWithoutRetry(dir, base_name, convert, no_data); - } - catch (const std::exception& err_fatal) - { - // will exit if not supposed to retry - return logging::fatal( - "Error trying to write %s to %s\n%s", - base_name.c_str(), - dir.c_str(), - err_fatal.what()); - } - } - } - /** - * \brief Save GridMap contents to file based on settings - * \tparam R Type to be written to file - * \param dir Directory to save into - * \param base_name File base name to use - * \param convert Function to convert from V to R - * \param no_data Value to use as nodata in output - */ - template - string saveToFile(const string& dir, - const string& base_name, - std::function convert, - const R no_data) const - { - return this->template saveToFileWithRetry(dir, base_name, convert, no_data); - } - /** - * \brief Save GridMap contents to file based on settings - * \tparam R Type to be written to file - * \param dir Directory to save into - * \param base_name File base name to use - * \param convert Function to convert from V to R - */ - template - string saveToFile(const string& dir, - const string& base_name, - std::function convert) const - { - return this->template saveToFile(dir, base_name, convert, static_cast(this->nodataInput())); - } - /** - * \brief Save GridMap contents to file based on settings - * \param dir Directory to save into - * \param base_name File base name to use - */ - template - string saveToFile(const string& dir, const string& base_name) const - { - return this->template saveToFile( - dir, - base_name, - [](T value) -> R { - return static_cast(value); - }); - } -}; -} diff --git a/firestarr/src/cpp/GridMap.h b/firestarr/src/cpp/GridMap.h deleted file mode 100644 index 228afca2f..000000000 --- a/firestarr/src/cpp/GridMap.h +++ /dev/null @@ -1,333 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "Util.h" -#include "Grid.h" -#include "ConstantGrid.h" -#include "Settings.h" -namespace fs::data -{ -using topo::Location; -using topo::Position; -/** - * \brief A GridData that uses an unordered_map for storage. - * \tparam T Type of data after conversion from initialization type. - * \tparam V Type of data used as an input when initializing. - */ -template -class GridMap - : public GridData> -{ -public: - /** - * \brief Determine if Location has a value - * \param location Location to determine if present in GridMap - * \return Whether or not a value is present for the Location - */ - [[nodiscard]] bool contains(const Location& location) const - { - return this->data.end() != this->data.find(location); - } - template - [[nodiscard]] bool contains(const Position

& position) const - { - return contains(Location{position.hash()}); - } - /** - * \brief Retrieve value at Location - * \param location Location to get value for - * \return Value at Location - */ - [[nodiscard]] T at(const Location& location) const override - { - const auto value = this->data.find(location); - if (value == this->data.end()) - { - return this->nodataValue(); - } - return get<1>(*value); - } - template - [[nodiscard]] T at(const Position

& position) const - { - return at(Location{position.hash()}); - } - /** - * \brief Set value at Location - * \param location Location to set value for - * \param value Value to set at Location - */ - void set(const Location& location, const T value) override - { - this->data[location] = value; - assert(at(location) == value); - } - template - void set(const Position

& position, const T value) - { - return set(Location{position.hash()}, value); - } - ~GridMap() = default; - /** - * \brief Constructor - * \param cell_size Cell width and height (m) - * \param rows Number of rows - * \param columns Number of columns - * \param no_data Value that represents no data - * \param nodata Integer value that represents no data - * \param xllcorner Lower left corner X coordinate (m) - * \param yllcorner Lower left corner Y coordinate (m) - * \param xllcorner Upper right corner X coordinate (m) - * \param yllcorner Upper right corner Y coordinate (m) - * \param proj4 Proj4 projection definition - */ - GridMap(const MathSize cell_size, - const Idx rows, - const Idx columns, - T no_data, - const int nodata, - const MathSize xllcorner, - const MathSize yllcorner, - const MathSize xurcorner, - const MathSize yurcorner, - string&& proj4) - : GridData>(cell_size, - rows, - columns, - no_data, - nodata, - xllcorner, - yllcorner, - xurcorner, - yurcorner, - std::forward(proj4), - map()) - { - constexpr auto max_hash = numeric_limits::max(); - // HACK: we don't want overflow errors, but we want to play with the hash size - const auto max_columns = static_cast(max_hash) / static_cast(this->rows()); - logging::check_fatal(this->columns() >= max_columns, - "Grid is too big for cells to be hashed - " - "recompile with a larger HashSize value"); -#ifdef DEBUG_GRIDS - // enforce converting to an int and back produces same V - const auto n0 = this->nodataInput(); - const auto n1 = static_cast(n0); - const auto n2 = static_cast(n1); - const auto n3 = static_cast(n2); - logging::check_equal( - n1, - n3, - "nodata_input_ as int"); - logging::check_equal( - n0, - n2, - "nodata_input_ from int"); -#endif - // HACK: reserve space for this based on how big our Idx is because that - // tells us how many cells there could be - // HACK: divide because we expect most perimeters to be fairly small, but we - // want it to be reasonably large - // this->data.reserve(static_cast(numeric_limits::max() / 4)); - // this->data.reserve(static_cast(MAX_ROWS) * MAX_COLUMNS); - } - /** - * \brief Construct empty GridMap with same extent as given Grid - * \param grid Grid to use extent from - */ - explicit GridMap(const Grid& grid) - : GridMap(grid.cellSize(), - grid.noData(), - grid.nodata(), - grid.xllcorner(), - grid.yllcorner(), - grid.xurcorner(), - grid.yurcorner(), - grid.proj4()) - { - } - /** - * \brief Construct empty GridMap with same extent as given Grid - * \param grid_info Grid to use extent from - * \param no_data Value to use for no data - */ - GridMap(const GridBase& grid_info, T no_data) - : GridMap(grid_info.cellSize(), - static_cast(grid_info.calculateRows()), - static_cast(grid_info.calculateColumns()), - no_data, - static_cast(no_data), - grid_info.xllcorner(), - grid_info.yllcorner(), - grid_info.xurcorner(), - grid_info.yurcorner(), - string(grid_info.proj4())) - { - } - /** - * \brief Move constructor - * \param rhs GridMap to move from - */ - GridMap(GridMap&& rhs) noexcept - : GridData>(std::move(rhs)) - { - this->data = std::move(rhs.data); - } - /** - * \brief Copy constructor - * \param rhs GridMap to copy from - */ - GridMap(const GridMap& rhs) - : GridData>(rhs) - { - this->data = rhs.data; - } - /** - * \brief Move assignment - * \param rhs GridMap to move from - * \return This, after assignment - */ - GridMap& operator=(GridMap&& rhs) noexcept - { - if (this != &rhs) - { - this->data = std::move(rhs.data); - } - return *this; - } - /** - * \brief Copy assignment - * \param rhs GridMap to copy from - * \return This, after assignment - */ - GridMap& operator=(const GridMap& rhs) - { - if (this != &rhs) - { - this->data = rhs.data; - } - return *this; - } - /** - * \brief Clear data from GridMap - */ - void clear() noexcept - { - // this->data.clear(); - this->data = {}; - // this->data.reserve(static_cast(numeric_limits::max() / 4)); - } -protected: - tuple dataBounds() const override - { - Idx min_row = this->rows(); - Idx max_row = 0; - Idx min_column = this->columns(); - Idx max_column = 0; - for (const auto& kv : this->data) - { - const Idx r = kv.first.row(); - const Idx c = kv.first.column(); - min_row = min(min_row, r); - max_row = max(max_row, r); - min_column = min(min_column, c); - max_column = max(max_column, c); - } - // do this so that we take the center point when there's no data since it should - // stay the same if the grid is centered on the fire - if (min_row > max_row) - { - min_row = max_row = this->rows() / 2; - } - if (min_column > max_column) - { - min_column = max_column = this->columns() / 2; - } - return tuple{ - min_column, - min_row, - max_column, - max_row}; - } -public: - /** - * \brief Save GridMap contents to .asc file as probability - * \param dir Directory to save into - * \param base_name File base name to use - * \param divisor Number of simulations to divide by to calculate probability per cell - */ - template - string saveToProbabilityFile(const string& dir, - const string& base_name, - const R divisor) const - { - auto div = [divisor](T value) -> R { - return static_cast(value / divisor); - }; - return this->template saveToFile(dir, base_name, div); - } - /** - * \brief Calculate area for cells that have a value (ha) - * \return Area for cells that have a value (ha) - */ - [[nodiscard]] MathSize fireSize() const noexcept - { - // we know that every cell is a key, so we convert that to hectares - const MathSize per_width = (this->cellSize() / 100.0); - // cells might have 0 as a value, but those shouldn't affect size - return static_cast(this->data.size()) * per_width * per_width; - } - /** - * \brief Make a list of all Locations that are on the edge of cells with a value - * \return A list of all Locations that are on the edge of cells with a value - */ - [[nodiscard]] list makeEdge() const - { - list edge{}; - for (const auto& kv : this->data) - { - auto loc = kv.first; - auto on_edge = false; - for (Idx r = -1; !on_edge && r <= 1; ++r) - { - const Idx row_index = loc.row() + r; - if (!(row_index < 0 || row_index >= this->rows())) - { - for (Idx c = -1; !on_edge && c <= 1; ++c) - { - const Idx col_index = loc.column() + c; - if (!(col_index < 0 || col_index >= this->columns()) - && this->data.find(Location(row_index, col_index)) == this->data.end()) - { - on_edge = true; - } - } - } - } - if (on_edge) - { - edge.push_back(loc); - } - } - logging::info("Created edge for perimeter with length %lu m", - static_cast(this->cellSize() * edge.size())); - return edge; - } - /** - * \brief Make a list of all Locations that have a value - * \return A list of all Locations that have a value - */ - [[nodiscard]] list makeList() const - { - list result{this->data.size()}; - std::transform(this->data.begin(), - this->data.end(), - result.begin(), - [](const pair& kv) { return kv.first; }); - return result; - } -}; -} diff --git a/firestarr/src/cpp/Index.h b/firestarr/src/cpp/Index.h deleted file mode 100644 index 2b08145a4..000000000 --- a/firestarr/src/cpp/Index.h +++ /dev/null @@ -1,196 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -namespace fs::data -{ -/** - * \brief A wrapper around a MathSize to ensure correct types are used. - * \tparam T The derived class that this Index represents. - */ -template -class Index -{ - /** - * \brief Value represented by this - */ - MathSize value_; -public: - /** - * \brief Destructor - */ - ~Index() = default; - /** - * \brief Construct with a value of 0 - */ - constexpr Index() noexcept - : value_(0) - { - } - /** - * \brief Construct with given value - * \param value Value to assign - */ - constexpr explicit Index(const MathSize value) noexcept - : value_(value) - { - } - /** - * \brief Move constructor - * \param rhs Index to move from - */ - constexpr Index(Index&& rhs) noexcept = default; - /** - * \brief Copy constructor - * \param rhs Index to copy from - */ - constexpr Index(const Index& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs Index to move from - * \return This, after assignment - */ - Index& operator=(Index&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs Index to copy from - * \return This, after assignment - */ - Index& operator=(const Index& rhs) noexcept = default; - /** - * \brief Equality operator - * \param rhs Index to compare to - * \return Whether or not these are equivalent - */ - [[nodiscard]] constexpr bool operator==(const Index& rhs) const noexcept - { - return value_ == rhs.value_; - } - /** - * \brief Not equals operator - * \param rhs Index to compare to - * \return Whether or not these are not equivalent - */ - [[nodiscard]] constexpr bool operator!=(const Index& rhs) const noexcept - { - return !(*this == rhs); - } - /** - * \brief Returns value as a MathSize - * \return MathSize value for Index - */ - [[nodiscard]] constexpr MathSize asValue() const noexcept - { - return value_; - } - /** - * \brief Less than operator - * \param rhs Index to compare to - * \return Whether or not this is less than the provided Index - */ - constexpr bool operator<(const Index rhs) const noexcept - { - return value_ < rhs.value_; - } - /** - * \brief Greater than operator - * \param rhs Index to compare to - * \return Whether or not this is greater than the provided Index - */ - [[nodiscard]] constexpr bool operator>(const Index rhs) const noexcept - { - return value_ > rhs.value_; - } - /** - * \brief Less than or equal to operator - * \param rhs Index to compare to - * \return Whether or not this is less than or equal to the provided Index - */ - [[nodiscard]] constexpr bool operator<=(const Index rhs) const noexcept - { - return value_ <= rhs.value_; - } - /** - * \brief Greater than or equal to operator - * \param rhs Index to compare to - * \return Whether or not this is greater than or equal to the provided Index - */ - [[nodiscard]] constexpr bool operator>=(const Index rhs) const noexcept - { - return value_ >= rhs.value_; - } - /** - * \brief Addition operator - * \param rhs Index to add value from - * \return The value of this plus the value of the provided index - */ - [[nodiscard]] constexpr Index operator+(const Index rhs) const noexcept - { - return Index(value_ + rhs.value_); - } - /** - * \brief Subtraction operator - * \param rhs Index to add value from - * \return The value of this minus the value of the provided index - */ - [[nodiscard]] constexpr Index operator-(const Index rhs) const noexcept - { - return Index(value_ - rhs.value_); - } - /** - * \brief Addition assignment operator - * \param rhs Index to add value from - * \return This, plus the value of the provided Index - */ - constexpr Index& operator+=(const Index rhs) noexcept - { - value_ += rhs.value_; - return *this; - } - /** - * \brief Subtraction assignment operator - * \param rhs Index to add value from - * \return This, minus the value of the provided Index - */ - constexpr Index& operator-=(const Index rhs) noexcept - { - value_ -= rhs.value_; - return *this; - } -}; -/** - * \brief A result of calling log(x) for some value of x, pre-calculated at compile time. - */ -class LogValue - : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond -}; -static constexpr LogValue LOG_0_70{-0.35667494393873245}; -static constexpr LogValue LOG_0_75{-0.2876820724517809}; -static constexpr LogValue LOG_0_80{-0.2231435513142097}; -static constexpr LogValue LOG_0_85{-0.16251892949777494}; -static constexpr LogValue LOG_0_90{-0.10536051565782628}; -static constexpr LogValue LOG_1_00{0.0}; -#ifndef _WIN32 -#if __cpp_constexpr >= 202211L // C++20 -// windows won't use constexpr with log but we can MathSize check numbers are right when compiling elsewhere -static constexpr LogValue LOG_0_70_CALC{log(0.7)}; -static constexpr LogValue LOG_0_75_CALC{log(0.75)}; -static constexpr LogValue LOG_0_80_CALC{log(0.8)}; -static constexpr LogValue LOG_0_85_CALC{log(0.85)}; -static constexpr LogValue LOG_0_90_CALC{log(0.9)}; -static constexpr LogValue LOG_1_00_CALC{log(1.0)}; -static_assert(abs((LOG_0_70 - LOG_0_70_CALC).asValue()) < numeric_limits::epsilon()); -static_assert(abs((LOG_0_75 - LOG_0_75_CALC).asValue()) < numeric_limits::epsilon()); -static_assert(abs((LOG_0_80 - LOG_0_80_CALC).asValue()) < numeric_limits::epsilon()); -static_assert(abs((LOG_0_90 - LOG_0_90_CALC).asValue()) < numeric_limits::epsilon()); -static_assert(abs((LOG_1_00 - LOG_1_00_CALC).asValue()) < numeric_limits::epsilon()); -#endif -#endif -} diff --git a/firestarr/src/cpp/InnerPos.cpp b/firestarr/src/cpp/InnerPos.cpp deleted file mode 100644 index 1921df805..000000000 --- a/firestarr/src/cpp/InnerPos.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "Location.h" -#include "MergeIterator.h" -#include "Cell.h" -namespace fs -{ -} diff --git a/firestarr/src/cpp/InnerPos.h b/firestarr/src/cpp/InnerPos.h deleted file mode 100644 index 175cd446c..000000000 --- a/firestarr/src/cpp/InnerPos.h +++ /dev/null @@ -1,90 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "stdafx.h" -#include "Cell.h" - -namespace fs -{ -using fs::wx::Direction; -using topo::Location; -template -class BoundedPoint - : public pair -{ -protected: - using class_type = BoundedPoint; - static constexpr auto INVALID_X = XMin - 1; - static constexpr auto INVALID_Y = YMin - 1; -public: - using pair::pair; - constexpr BoundedPoint() noexcept - : BoundedPoint(XMin - 1, YMin - 1) - { - } - /** - * \brief Add offset to position and return result - */ - template - [[nodiscard]] constexpr T add(const O& o) const noexcept - { - return static_cast(class_type(this->first + o.first, this->second + o.second)); - } -}; -/** - * \brief Offset from a position - */ -class Offset - : public BoundedPoint -{ -public: - /** - * \brief Collection of Offsets - */ - using BoundedPoint::BoundedPoint; -}; -using ROSOffset = std::tuple; -using OffsetSet = vector; -} -namespace fs::sim -{ -/** - * \brief The position within a Cell that a spreading point has. - */ -class InnerPos - : public BoundedPoint -{ - using BoundedPoint::BoundedPoint; -}; -/** - * \brief The position within the Environment that a spreading point has. - */ -class XYPos - : public BoundedPoint -{ -public: - using BoundedPoint::BoundedPoint; - CONSTEXPR Location location() const - { - // HACK: Location is (row, column) and this is (x, y) - return {static_cast(second), static_cast(first)}; - } -}; -/** - * \brief The position within the Environment that a spreading point has. - */ -class CellPos - : public BoundedPoint -{ -public: - using BoundedPoint::BoundedPoint; - CONSTEXPR Location location() const - { - // HACK: Location is (row, column) and this is (x, y) - return {static_cast(second), static_cast(first)}; - } -}; -} diff --git a/firestarr/src/cpp/IntensityMap.cpp b/firestarr/src/cpp/IntensityMap.cpp deleted file mode 100644 index 1221c458a..000000000 --- a/firestarr/src/cpp/IntensityMap.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "IntensityMap.h" -#include "Model.h" -#include "Perimeter.h" -#include "Weather.h" -namespace fs::sim -{ - -// FIX: maybe this can be more generic but just want to keep allocated objects and reuse them -template -class GridMapCache -{ -public: - GridMapCache(K nodata) - : nodata_(nodata) - { - } - // // use maximum value as nodata if not given - // // HACK: need to be able to convert to int, so don't use a value bigger than that can hold - // GridMapCache() - // : GridMapCache( - // static_cast( - // min(static_cast(std::numeric_limits::max()), - // static_cast(std::numeric_limits::max())))) - // { - // } - void release_map(unique_ptr> map) noexcept - { - map->clear(); - try - { - lock_guard lock(mutex_); - maps_.push_back(std::move(map)); - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } - } - unique_ptr> acquire_map(const Model& model) noexcept - { - try - { - lock_guard lock(mutex_); - if (!maps_.empty()) - { - auto result = std::move(maps_.at(maps_.size() - 1)); - maps_.pop_back(); - return result; - } - return model.environment().makeMap(nodata_); - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } - } -protected: - K nodata_; - vector>> maps_; - mutex mutex_; -}; - -static auto CacheIntensitySize = GridMapCache(NO_INTENSITY); -IntensityMap::IntensityMap(const Model& model) noexcept - : model_(model), - intensity_max_(CacheIntensitySize.acquire_map(model)), - is_burned_(model.getBurnedVector()) -{ -} - -IntensityMap::IntensityMap(const IntensityMap& rhs) - // : IntensityMap(rhs.model_, nullptr) - : IntensityMap(rhs.model_) -{ - *intensity_max_ = *rhs.intensity_max_; - is_burned_ = rhs.is_burned_; -} - -// IntensityMap::IntensityMap(IntensityMap&& rhs) -// : IntensityMap(rhs.model_) -// { -// *intensity_max_ = *rhs.intensity_max_; -// is_burned_ = rhs.is_burned_; -// } - -IntensityMap::~IntensityMap() noexcept -{ - model_.releaseBurnedVector(is_burned_); - CacheIntensitySize.release_map(std::move(intensity_max_)); -} -void IntensityMap::applyPerimeter(const topo::Perimeter& perimeter) noexcept -{ - // logging::verbose("Attaining lock"); - // lock_guard lock(mutex_); - logging::verbose("Applying burned cells"); - std::for_each( - std::execution::par_unseq, - perimeter.burned().begin(), - perimeter.burned().end(), - [this](const auto& location) { ignite(location); }); -} -// bool IntensityMap::canBurn(const HashSize hash) const -//{ -// return !hasBurned(hash); -// } -bool IntensityMap::canBurn(const Location& location) const -{ - return !hasBurned(location); -} -bool IntensityMap::hasBurned(const Location& location) const -{ - lock_guard lock(mutex_); - return (*is_burned_)[location.hash()]; - // return hasBurned(location.hash()); -} -// bool IntensityMap::hasBurned(const HashSize hash) const -//{ -// lock_guard lock(mutex_); -// return (*is_burned_)[hash]; -// } -bool IntensityMap::isSurrounded(const Location& location) const -{ - // implement here so we can just lock once - lock_guard lock(mutex_); - const auto x = location.column(); - const auto y = location.row(); - const auto min_row = static_cast(max(y - 1, 0)); - const auto max_row = min(y + 1, this->rows() - 1); - const auto min_column = static_cast(max(x - 1, 0)); - const auto max_column = min(x + 1, this->columns() - 1); - for (auto r = min_row; r <= max_row; ++r) - { - // auto h = static_cast(r) * MAX_COLUMNS + min_column; - for (auto c = min_column; c <= max_column; ++c) - { - // actually check x, y too since we care if the cell itself is burned - // if (!(*is_burned_)[h]) - if (!(*is_burned_)[Location(r, c).hash()]) - { - return false; - } - // ++h; - } - } - return true; -} -void IntensityMap::ignite(const Location& location) -{ - burn(location); -} -void IntensityMap::burn(const Location& location) -{ - lock_guard lock(mutex_); - // const auto is_new = !(*is_burned_)[location.hash()]; - // if (is_new || intensity_max_->at(location) < intensity) - // { - // intensity_max_->set(location, intensity); - // } - // // update ros and direction if higher ros - // if (is_new || rate_of_spread_at_max_->at(location) < ros) - // { - // rate_of_spread_at_max_->set(location, ros); - // direction_of_spread_at_max_->set(location, static_cast(raz.asDegrees())); - // } - // // just set anyway since it's probably faster than checking if we should - // (*is_burned_).set(location.hash()); - // if (check_valid) - // { - // // FIX: new fire uses intensity = 1, ros = 0 so this breaks - // logging::check_fatal(0 >= intensity, "Negative or 0 intensity given: %d", intensity); - // logging::check_fatal(0 >= ros, "Negative or 0 ros given: %f", ros); - // } - if (!(*is_burned_)[location.hash()]) - { - intensity_max_->set(location, - 1); - (*is_burned_).set(location.hash()); - } -} -MathSize IntensityMap::fireSize() const -{ - lock_guard lock(mutex_); - return intensity_max_->fireSize(); -} -map::const_iterator - IntensityMap::cend() const noexcept -{ - return intensity_max_->data.cend(); -} -map::const_iterator - IntensityMap::cbegin() const noexcept -{ - return intensity_max_->data.cbegin(); -} -} diff --git a/firestarr/src/cpp/IntensityMap.h b/firestarr/src/cpp/IntensityMap.h deleted file mode 100644 index 3afd7afcd..000000000 --- a/firestarr/src/cpp/IntensityMap.h +++ /dev/null @@ -1,158 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include -#include "GridMap.h" -#include "Location.h" -namespace fs -{ -namespace topo -{ -class Perimeter; -class Cell; -} -namespace sim -{ -using fs::topo::Position; -class ProbabilityMap; -class Model; -using BurnedData = std::bitset(MAX_ROWS) * MAX_COLUMNS>; -/** - * \brief Represents a map of intensities that cells have burned at for a single Scenario. - */ -class IntensityMap -{ - /** - * \brief Mutex for parallel access - */ - mutable mutex mutex_{}; -public: - /** - * \brief Constructor - * \param model Model to use extent from - */ - // IntensityMap(const Model& model, topo::Perimeter* perimeter) noexcept; - explicit IntensityMap(const Model& model) noexcept; - ~IntensityMap() noexcept; - IntensityMap(const IntensityMap& rhs); - IntensityMap(IntensityMap&& rhs) = delete; - // IntensityMap(IntensityMap&& rhs); - IntensityMap& operator=(const IntensityMap& rhs) = delete; - IntensityMap& operator=(IntensityMap&& rhs) noexcept = delete; - /** - * \brief Number of rows in this extent - * \return Number of rows in this extent - */ - [[nodiscard]] Idx rows() const - { - return intensity_max_->rows(); - } - /** - * \brief Number of columns in this extent - * \return Number of columns in this extent - */ - [[nodiscard]] Idx columns() const - { - return intensity_max_->columns(); - } - /** - * \brief Set cells in the map to be burned based on Perimeter - * \param perimeter Perimeter to burn cells based on - */ - void applyPerimeter(const topo::Perimeter& perimeter) noexcept; - /** - * \brief Whether or not the Cell with the given hash can burn - * \param hash Hash for Cell to check - * \return Whether or not the Cell with the given hash can burn - */ - [[nodiscard]] bool canBurn(const Location& location) const; - template - [[nodiscard]] bool canBurn(const Position

& position) const - { - return canBurn(Location{position.hash()}); - } - /** - * \brief Whether or not the Location with the given hash can burn - * \param hash Hash for Location to check - * \return Whether or not the Location with the given hash can burn - */ - [[nodiscard]] bool hasBurned(const Location& location) const; - template - [[nodiscard]] bool hasBurned(const Position

& position) const - { - return hasBurned(Location{position.hash()}); - } - /** - * \brief Whether or not all Locations surrounding the given Location are burned - * \param location Location to check - * \return Whether or not all Locations surrounding the given Location are burned - */ - [[nodiscard]] bool isSurrounded(const Location& location) const; - template - [[nodiscard]] bool isSurrounded(const Position

& position) const - { - return isSurrounded(Location{position.hash()}); - } - /** - * \brief Mark given location as burned - * \param location Location to burn - */ - void ignite(const Location& location); - template - void ignite(const Position

& position) - { - ignite(Location{position.hash()}); - } -public: - /** - * \brief Update Location with specified values - * \param location Location to burn - * \param intensity Intensity to burn with (kW/m) - * \param ros Rate of spread to check against maximu (m/min) - * \param raz Spread azimuth for ros - */ - void burn(const Location& location); - template - void burn(const Position

& position) - { - burn( - Location{position.hash()}); - } - /** - * \brief Size of the fire represented by this - * \return Size of the fire represented by this - */ - [[nodiscard]] MathSize fireSize() const; - /** - * \brief Iterator for underlying GridMap - * \return Iterator for underlying GridMap - */ - [[nodiscard]] map::const_iterator - cbegin() const noexcept; - /** - * \brief Iterator for underlying GridMap - * \return Iterator for underlying GridMap - */ - [[nodiscard]] map::const_iterator - cend() const noexcept; -private: - /** - * \brief Model map is for - */ - const Model& model_; - /** - * \brief Map of intensity that cells have burned at - */ - unique_ptr> intensity_max_; - /** - * \brief bitset denoting cells that can no longer burn - */ - BurnedData* is_burned_; -}; -} -} diff --git a/firestarr/src/cpp/Iteration.cpp b/firestarr/src/cpp/Iteration.cpp deleted file mode 100644 index 255086393..000000000 --- a/firestarr/src/cpp/Iteration.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Iteration.h" -#include "ProbabilityMap.h" -#include "Scenario.h" -namespace fs::sim -{ -Iteration::~Iteration() -{ - for (auto& s : scenarios_) - { - delete s; - } -} -Iteration::Iteration(vector scenarios) noexcept - : scenarios_(std::move(scenarios)) -{ -} -Iteration* Iteration::reset(mt19937* mt_extinction, mt19937* mt_spread) -{ - cancelled_ = false; - final_sizes_ = {}; - for (auto& scenario : scenarios_) - { - static_cast(scenario->reset(mt_extinction, mt_spread, &final_sizes_)); - } - return this; -} -// -// Iteration* Iteration::run(map* probabilities) -//{ -// // sort in run so that they still get the same extinction thresholds as when unsorted -// std::sort(scenarios_.begin(), -// scenarios_.end(), -// [](Scenario* lhs, Scenario* rhs) noexcept -// { -// // sort so that scenarios with highest DSRs are at the front -// //return lhs->weightedDsr() > rhs->weightedDsr(); -// }); -// if (Settings::runAsync()) -// { -// vector> results{}; -// // make a local copy so that we don't have mutex competition with other Iterations -// map local_probabilities{}; -// for (auto& kv : *probabilities) -// { -// local_probabilities[kv.first] = kv.second->copyEmpty(); -// } -// for (auto& scenario : scenarios_) -// { -// results.push_back(async(launch::async, -// &Scenario::run, -// scenario, -// &local_probabilities)); -// } -// for (auto& scenario : results) -// { -// auto s = scenario.get(); -// s->clear(); -// } -// for (auto& kv : *probabilities) -// { -// kv.second->addProbabilities(*local_probabilities[kv.first]); -// delete local_probabilities[kv.first]; -// } -// } -// else -// { -// for (auto& scenario : scenarios_) -// { -// scenario->run(probabilities); -// } -// } -// return this; -//} -vector Iteration::savePoints() const -{ - return scenarios_.at(0)->savePoints(); -} -DurationSize Iteration::startTime() const -{ - return scenarios_.at(0)->startTime(); -} -size_t Iteration::size() const noexcept -{ - return scenarios_.size(); -} -util::SafeVector Iteration::finalSizes() const -{ - return final_sizes_; -} -void Iteration::cancel(bool show_warning) noexcept -{ - cancelled_ = true; - for (auto& s : scenarios_) - { - s->cancel(show_warning); - } -} -} diff --git a/firestarr/src/cpp/Iteration.h b/firestarr/src/cpp/Iteration.h deleted file mode 100644 index fc2fc919e..000000000 --- a/firestarr/src/cpp/Iteration.h +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include "SafeVector.h" -#include "IntensityMap.h" -namespace fs::sim -{ -class ProbabilityMap; -class Scenario; -/** - * \brief Represents a full set of simulations using all available weather streams. - */ -class Iteration -{ -public: - ~Iteration(); - /** - * \brief Constructor - * \param scenarios List of Scenarios to wrap into Iteration - */ - explicit Iteration(vector scenarios) noexcept; - /** - * \brief Copy constructor - * \param rhs Iteration to copy form - */ - Iteration(const Iteration& rhs) = default; - /** - * \brief Move constructor - * \param rhs Iteration to move from - */ - Iteration(Iteration&& rhs) = default; - /** - * \brief Copy assignment - * \param rhs Iteration to copy from - * \return This, after assignment - */ - Iteration& operator=(const Iteration& rhs) = default; - /** - * \brief Move assignment - * \param rhs Iteration to move from - * \return This, after assignment - */ - Iteration& operator=(Iteration&& rhs) = default; - /** - * \brief Create new thresholds for use in each Scenario - * \param mt_extinction Extinction thresholds - * \param mt_spread Spread thresholds - * \return This - */ - Iteration* reset(mt19937* mt_extinction, - mt19937* mt_spread); - /** - * \brief List of Scenarios this Iteration contains - * \return List of Scenarios this Iteration contains - */ - [[nodiscard]] const vector& getScenarios() const noexcept - { - return scenarios_; - } - /** - * Mark as cancelled so it stops computing on next event. - * \param Whether to log a warning about this being cancelled - */ - void cancel(bool show_warning) noexcept; - /** - * \brief Points in time that ProbabilityMaps get saved for - * \return Points in time that ProbabilityMaps get saved for - */ - [[nodiscard]] vector savePoints() const; - /** - * \brief Time that simulations start - * \return Time that simulations start - */ - [[nodiscard]] DurationSize startTime() const; - /** - * \brief Number of Scenarios in this Iteration - * \return Number of Scenarios in this Iteration - */ - [[nodiscard]] size_t size() const noexcept; - /** - * \brief SafeVector of sizes that Scenarios have resulted in - * \return SafeVector of sizes that Scenarios have resulted in - */ - [[nodiscard]] util::SafeVector finalSizes() const; -private: - /** - * \brief List of Scenarios this Iteration contains - */ - vector scenarios_; - /** - * \brief SafeVector of sizes that Scenarios have resulted in - */ - util::SafeVector final_sizes_{}; - /** - * \brief Whether this has been cancelled and should stop computing. - */ - bool cancelled_ = false; -}; -} diff --git a/firestarr/src/cpp/Location.cpp b/firestarr/src/cpp/Location.cpp deleted file mode 100644 index 925915418..000000000 --- a/firestarr/src/cpp/Location.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "Location.h" -namespace fs::topo -{ -CellIndex relativeIndex(const Location& src, const Location& dst) noexcept -{ - static constexpr CellIndex DIRECTIONS[9] = - { - DIRECTION_SW, - DIRECTION_S, - DIRECTION_SE, - DIRECTION_W, - DIRECTION_NONE, - DIRECTION_E, - DIRECTION_NW, - DIRECTION_N, - DIRECTION_NE}; - return DIRECTIONS[((src.column() - dst.column()) + 1) - + 3 * ((src.row() - dst.row()) + 1)]; -} -} diff --git a/firestarr/src/cpp/Location.h b/firestarr/src/cpp/Location.h deleted file mode 100644 index 37742a067..000000000 --- a/firestarr/src/cpp/Location.h +++ /dev/null @@ -1,312 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "stdafx.h" -#include "Util.h" -#include "Log.h" -namespace fs::topo -{ -// have static versions of these outside Position so we can test with static_assert -/** - * \brief Create a hash from given values - * \param XYBits Number of bits to use for storing one coordinate of Position data - * \param row Row - * \param column Column - * \return Hash - */ -[[nodiscard]] static inline constexpr HashSize do_hash( - const uint32_t XYBits, - const Idx row, - const Idx column) noexcept -{ - return (static_cast(row) << XYBits) + static_cast(column); -} -/** - * \brief Row from hash - * \param XYBits Number of bits to use for storing one coordinate of Position data - * \param hash hash to extract row from - * \return Row from hash - */ -[[nodiscard]] static inline constexpr Idx unhash_row( - const uint32_t XYBits, - const Topo hash) noexcept -{ - // don't need to use mask since bits just get shifted out - return static_cast(hash >> XYBits); -} -/** - * \brief Column - * \param ColumnMask Hash mask for bits being used for Position data - * \param hash hash to extract column from - * \return Column - */ -[[nodiscard]] static inline constexpr Idx unhash_column( - const Topo ColumnMask, - const Topo hash) noexcept -{ - return static_cast(hash & ColumnMask); -} -/** - * \brief A Position with a row and column. - */ -template -class Position -{ -public: - Position() = default; - /** - * \brief Row - * \return Row - */ - [[nodiscard]] constexpr Idx row() const noexcept - { - return unhashRow(hash()); - } - /** - * \brief Column - * \return Column - */ - [[nodiscard]] constexpr Idx column() const noexcept - { - return unhashColumn(hash()); - } - /** - * \brief Hash derived from row and column - * \return Hash derived from row and column - */ - [[nodiscard]] constexpr HashSize hash() const noexcept - { -#ifdef DEBUG_POINTS - constexpr int num_bits = std::numeric_limits::digits; - constexpr Topo m = util::bit_mask(); - logging::check_equal( - static_cast(topo_data_), - static_cast(m & topo_data_), - "hash()"); -#endif - // can get away with just casting because all the other bits are outside this area - return static_cast(topo_data_); - } - /** - * \brief Equality operator - * \param rhs Position to compare to - * \return Whether or not these are equivalent - */ - [[nodiscard]] constexpr bool operator==(const Position& rhs) const noexcept - { - return hash() == rhs.hash(); - } - /** - * \brief Inequality operator - * \param rhs Position to compare to - * \return Whether or not these are not equivalent - */ - [[nodiscard]] constexpr bool operator!=(const Position& rhs) const noexcept - { - return !(*this == rhs); - } -protected: - /** - * \brief Stored hash that contains row and column data - */ - V topo_data_; - /** - * \brief Number of bits to use for storing one coordinate of Position data - */ - static constexpr uint32_t XYBits = std::bit_width(MAX_ROWS - 1); - static_assert(util::pow_int(2) == MAX_ROWS); - static_assert(util::pow_int(2) == MAX_COLUMNS); - /** - * \brief Number of bits to use for storing Position data - */ - static constexpr uint32_t PositionBits = XYBits * 2; - /** - * \brief Hash mask for bits being used for Position data - */ - static constexpr Topo ColumnMask = util::bit_mask(); - /** - * \brief Hash mask for bits being used for Position data - */ - static constexpr Topo HashMask = util::bit_mask(); - static_assert(HashMask >= static_cast(MAX_COLUMNS) * MAX_ROWS - 1); - static_assert(HashMask <= std::numeric_limits::max()); - /** - * \brief Construct with given hash that may contain data from subclasses - * \param topo Hash to store - */ - explicit constexpr Position(const Topo& topo) noexcept - : topo_data_(topo) - { - } - /** - * \brief Create a hash from given values - * \param row Row - * \param column Column - * \return Hash - */ - [[nodiscard]] static constexpr HashSize doHash( - const Idx row, - const Idx column) noexcept - { - return do_hash(XYBits, row, column); -// make sure hashing/unhashing works -#define ROW_MIN 0 -#define ROW_MAX (MAX_ROWS - 1) -#define COL_MIN 0 -#define COL_MAX (MAX_COLUMNS - 1) - static_assert(ROW_MIN == unhash_row(XYBits, do_hash(XYBits, ROW_MIN, COL_MIN))); - static_assert(COL_MIN == unhash_column(ColumnMask, do_hash(XYBits, ROW_MIN, COL_MIN))); - static_assert(ROW_MIN == unhash_row(XYBits, do_hash(XYBits, ROW_MIN, COL_MAX))); - static_assert(COL_MAX == unhash_column(ColumnMask, do_hash(XYBits, ROW_MIN, COL_MAX))); - static_assert(ROW_MAX == unhash_row(XYBits, do_hash(XYBits, ROW_MAX, COL_MIN))); - static_assert(COL_MIN == unhash_column(ColumnMask, do_hash(XYBits, ROW_MAX, COL_MIN))); - static_assert(ROW_MAX == unhash_row(XYBits, do_hash(XYBits, ROW_MAX, COL_MAX))); - static_assert(COL_MAX == unhash_column(ColumnMask, do_hash(XYBits, ROW_MAX, COL_MAX))); -#undef ROW_MIN -#undef ROW_MAX -#undef COL_MIN -#undef COL_MAX - } - /** - * \brief Row from hash - * \param hash hash to extract row from - * \return Row from hash - */ - [[nodiscard]] static constexpr Idx unhashRow(const Topo hash) noexcept - { - return unhash_row(XYBits, hash); - } - /** - * \brief Column - * \param hash hash to extract column from - * \return Column - */ - [[nodiscard]] static constexpr Idx unhashColumn(const Topo hash) noexcept - { - return unhash_column(ColumnMask, hash); - } -}; -template -inline bool operator<(const Position& lhs, const Position& rhs) -{ - return lhs.hash() < rhs.hash(); -} -template -inline bool operator>(const Position& lhs, const Position& rhs) -{ - return rhs < lhs; -} -template -inline bool operator<=(const Position& lhs, const Position& rhs) -{ - return !(lhs > rhs); -} -template -inline bool operator>=(const Position& lhs, const Position& rhs) -{ - return !(lhs < rhs); -} - -#ifdef DEBUG_DIRECTIONS -// FIX: seems like there must be something with enum type that would be better? -static const map DIRECTION_NAMES{ - {DIRECTION_NONE, "NONE"}, - {DIRECTION_W, "W"}, - {DIRECTION_E, "E"}, - {DIRECTION_S, "S"}, - {DIRECTION_N, "N"}, - {DIRECTION_SW, "SW"}, - {DIRECTION_NE, "NE"}, - {DIRECTION_NW, "NW"}, - {DIRECTION_SE, "SE"}}; -#endif - -class Location - : public Position -{ -public: - using Position::Position; - /** - * \brief Construct using hash of row and column - * \param hash HashSize derived form row and column - */ -// NOTE: do this so that we don't get warnings about unused variables in release mode -#ifdef NDEBUG - explicit constexpr Location(const Idx, const Idx, const HashSize hash) noexcept -#else - explicit Location(const Idx row, const Idx column, const HashSize hash) noexcept -#endif - : Location(hash & HashMask) - { -#ifdef DEBUG_GRIDS - logging::check_fatal( - row < 0 || row >= MAX_ROWS, - "Row %d is out of bounds (%d, %d)", - row, - 0, - MAX_ROWS); - logging::check_fatal( - column < 0 || column >= MAX_COLUMNS, - "Column %d is out of bounds (%d, %d)", - column, - 0, - MAX_COLUMNS); - logging::check_fatal( - (row != unhashRow(topo_data_)) - || column != unhashColumn(topo_data_), - "Hash is incorrect (%d, %d)", - row, - column); -#endif - } - /** - * \brief Constructor - * \param row Row - * \param column Column - */ -#ifdef NDEBUG - CONSTEXPR -#endif - Location(const Idx row, const Idx column) noexcept - : Location(row, column, doHash(row, column) & HashMask) - { -#ifdef DEBUG_GRIDS - logging::check_fatal(row >= MAX_ROWS || column >= MAX_COLUMNS, "Location out of bounds (%d, %d)", row, column); -#endif - } - Location(const Coordinates& coord) - : Location(std::get<0>(coord), std::get<1>(coord)) - { - } - /** - * \brief Construct with given hash that may contain data from subclasses - * \param hash_size Hash to store - */ - explicit constexpr Location(const HashSize& hash_size) noexcept - : Position(static_cast(hash_size)) - { - } - /** - * \brief Construct with given hash that may contain data from subclasses - * \param hash_size Hash to store - */ - template - explicit constexpr Location(const Position

& position) noexcept - : Position(static_cast(position.hash())) - { - } -}; -/** - * Determine the direction that a given cell is in from another cell. This is the - * same convention as wind (i.e. the direction it is coming from, not the direction - * it is going towards). - * @param src The cell to find directions relative to - * @param dst The cell to find the direction of - * @return Direction that you would have to go in to get to dst from src - */ -CellIndex - relativeIndex(const Location& src, const Location& dst) noexcept; -} diff --git a/firestarr/src/cpp/Log.cpp b/firestarr/src/cpp/Log.cpp deleted file mode 100644 index b6342801b..000000000 --- a/firestarr/src/cpp/Log.cpp +++ /dev/null @@ -1,364 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Log.h" -namespace fs::logging -{ -int Log::logging_level_ = LOG_DEBUG; -// do this in .cpp so that we don't get unused warnings including the .h -static const char* LOG_LABELS[] = - { - "EXTENSIVE: ", - "VERBOSE: ", - "DEBUG: ", - "INFO: ", - "NOTE: ", - "WARNING: ", - "ERROR: ", - "FATAL: ", - "SILENT: "}; -mutex mutex_; -void Log::setLogLevel(const int log_level) noexcept -{ - logging_level_ = log_level; -} -void Log::increaseLogLevel() noexcept -{ - // HACK: make sure we never go below 0 - logging_level_ = max(0, getLogLevel() - 1); -} -void Log::decreaseLogLevel() noexcept -{ - // HACK: make sure we never go above silent - logging_level_ = min(LOG_SILENT, getLogLevel() + 1); -} -int Log::getLogLevel() noexcept -{ - return logging_level_; -} -static FILE* out_; -int Log::openLogFile(const char* filename) noexcept -{ - out_ = fopen(filename, "w"); - if (nullptr != out_) - { - // turn off buffering so lines write to file immediately - setbuf(out_, nullptr); - return true; - } - return false; -} -int Log::closeLogFile() noexcept -{ - if (nullptr != out_) - { - return fclose(out_); - } - return 0; -} -string format_log_message(const char* prefix, const char* format, va_list* args) -{ - // do this separately from output() so we can redo it for fatal errors - // NOTE: create string first so that entire line writes - // (otherwise threads might mix lines) - const string tmp; - stringstream iss(tmp); -#ifdef NDEBUG - const time_t now = time(nullptr); - auto buf = localtime(&now); - iss << put_time(buf, "[%F %T] "); -#endif - // try to make output consistent if in debug mode - iss << prefix; - { - lock_guard lock(mutex_); - static char buffer[1024]{0}; - vsnprintf(buffer, std::size(buffer), format, *args); - iss << buffer; - return iss.str(); - } -} -void output(const int log_level, const char* format, va_list* args) -#ifdef NDEBUG - noexcept -#endif -{ - if (Log::getLogLevel() > log_level) - { - return; - } - try - { - auto msg = format_log_message(LOG_LABELS[log_level], format, args); - printf("%s\n", msg.c_str()); - if (nullptr != out_) - { - fprintf(out_, "%s\n", msg.c_str()); - fflush(out_); - } - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } -} -void output(const int log_level, const char* format, ...) -#ifdef NDEBUG - noexcept -#endif -{ - va_list args; - va_start(args, format); - output(log_level, format, &args); - va_end(args); -} -void extensive(const char* format, ...) noexcept -{ - if (Log::getLogLevel() <= LOG_EXTENSIVE) - { - va_list args; - va_start(args, format); - output(LOG_EXTENSIVE, format, &args); - va_end(args); - } -} -void verbose(const char* format, ...) noexcept -{ - if (Log::getLogLevel() <= LOG_VERBOSE) - { - va_list args; - va_start(args, format); - output(LOG_VERBOSE, format, &args); - va_end(args); - } -} -void debug(const char* format, ...) noexcept -{ - if (Log::getLogLevel() <= LOG_DEBUG) - { - va_list args; - va_start(args, format); - output(LOG_DEBUG, format, &args); - va_end(args); - } -} -void info(const char* format, ...) noexcept -{ - if (Log::getLogLevel() <= LOG_INFO) - { - va_list args; - va_start(args, format); - output(LOG_INFO, format, &args); - va_end(args); - } -} -void note(const char* format, ...) noexcept -{ - if (Log::getLogLevel() <= LOG_NOTE) - { - va_list args; - va_start(args, format); - output(LOG_NOTE, format, &args); - va_end(args); - } -} -void warning(const char* format, ...) noexcept -{ - if (Log::getLogLevel() <= LOG_WARNING) - { - va_list args; - va_start(args, format); - output(LOG_WARNING, format, &args); - va_end(args); - } -} -void error(const char* format, ...) noexcept -{ - if (Log::getLogLevel() <= LOG_ERROR) - { - va_list args; - va_start(args, format); - output(LOG_ERROR, format, &args); - va_end(args); - } -} -void fatal(const char* format, va_list* args) -#ifdef NDEBUG - noexcept -#endif -{ - // HACK: call the other version - fatal(format, args); -} -void fatal(const char* format, ...) -#ifdef NDEBUG - noexcept -#endif -{ - va_list args; - va_start(args, format); - fatal(format, &args); - // cppcheck-suppress va_end_missing - // va_end(args); -} -void fatal(const std::exception& ex) -{ - output(LOG_FATAL, "%s", ex.what()); - Log::closeLogFile(); -#ifdef NDEBUG - exit(EXIT_FAILURE); -#endif -} -void fatal(const std::exception& ex, const char* format, ...) -{ - va_list args; - va_start(args, format); - output(LOG_FATAL, format, &args); - // cppcheck-suppress va_end_missing - // va_end(args); - fatal(ex); -} -void check_fatal(const bool condition, const char* format, va_list* args) -#ifdef NDEBUG - noexcept -#endif -{ - if (condition) - { - fatal(format, args); - } -} -inline void check_fatal(const bool condition, const char* format, ...) -#ifdef NDEBUG - noexcept -#endif -{ - if (condition) - { - va_list args; - va_start(args, format); - fatal(format, &args); - // cppcheck-suppress va_end_missing - // va_end(args); - } -} -void check_equal(const MathSize lhs, const MathSize rhs, const char* name) -#ifdef NDEBUG - noexcept -#endif -{ - logging::check_fatal(lhs != rhs, - "Expected %s to be %f but got %f", - name, - rhs, - lhs); -} -void check_equal(const char* lhs, const char* rhs, const char* name) -#ifdef NDEBUG - noexcept -#endif -{ - logging::check_fatal(0 != strcmp(lhs, rhs), - "Expected %s to be %s got %s", - name, - rhs, - lhs); -} -void SelfLogger::log_output(const int level, const char* format, ...) const noexcept -{ - // FIX: better/any way to call this from other level-specific functions? - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::output(level, fmt.c_str(), &args); - va_end(args); -} -void SelfLogger::log_extensive(const char* format, ...) const noexcept -{ - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::output(LOG_EXTENSIVE, fmt.c_str(), &args); - va_end(args); -} -void SelfLogger::log_verbose(const char* format, ...) const noexcept -{ - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::output(LOG_VERBOSE, fmt.c_str(), &args); - va_end(args); -} -void SelfLogger::log_debug(const char* format, ...) const noexcept -{ - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::output(LOG_DEBUG, fmt.c_str(), &args); - va_end(args); -} -void SelfLogger::log_info(const char* format, ...) const noexcept -{ - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::output(LOG_INFO, fmt.c_str(), &args); - va_end(args); -} -void SelfLogger::log_note(const char* format, ...) const noexcept -{ - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::output(LOG_NOTE, fmt.c_str(), &args); - va_end(args); -} - -void SelfLogger::log_warning(const char* format, ...) const noexcept -{ - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::output(LOG_WARNING, fmt.c_str(), &args); - va_end(args); -} - -void SelfLogger::log_error(const char* format, ...) const noexcept -{ - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::output(LOG_ERROR, fmt.c_str(), &args); - va_end(args); -} -void SelfLogger::log_check_fatal(bool condition, const char* format, ...) const -#ifdef NDEBUG - noexcept -#endif -{ - if (condition) - { - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::fatal(fmt.c_str(), &args); - va_end(args); - } -} - -void SelfLogger::log_fatal(const char* format, ...) const -#ifdef NDEBUG - noexcept -#endif -{ - va_list args; - va_start(args, format); - const auto fmt = add_log(format); - logging::fatal(fmt.c_str(), &args); - va_end(args); -} -} diff --git a/firestarr/src/cpp/Log.h b/firestarr/src/cpp/Log.h deleted file mode 100644 index da78c626f..000000000 --- a/firestarr/src/cpp/Log.h +++ /dev/null @@ -1,269 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -namespace fs::logging -{ -static const int LOG_EXTENSIVE = 0; -static const int LOG_VERBOSE = 1; -static const int LOG_DEBUG = 2; -static const int LOG_INFO = 3; -static const int LOG_NOTE = 4; -static const int LOG_WARNING = 5; -static const int LOG_ERROR = 6; -static const int LOG_FATAL = 7; -static const int LOG_SILENT = 8; - -/** - * \brief Provides logging functionality. - */ -class Log -{ - /** - * \brief Current logging level - */ - static int logging_level_; -public: - /** - * \brief Set logging level to a specific level - * \param log_level Log level to use - * \return None - */ - static void setLogLevel(int log_level) noexcept; - /** - * \brief Increase amount of logging output by one level - * \return None - */ - static void increaseLogLevel() noexcept; - /** - * \brief Decrease amount of logging output by one level - * \return None - */ - static void decreaseLogLevel() noexcept; - /** - * \brief Get current logging level - * \return Current logging level - */ - static int getLogLevel() noexcept; - /** - * \brief Set output log file - * \return Return value of open() - */ - static int openLogFile(const char* filename) noexcept; - /** - * \brief Set output log file - * \return Return value of close() - */ - static int closeLogFile() noexcept; -}; -string format_log_message(const char* prefix, const char* format, va_list* args); -/** - * \brief Output a message to the log - * \param log_level Log level to use for label - * \param format Format string for message - * \param args Arguments to use in format string - * \return None - */ -void output(int log_level, const char* format, va_list* args) -#ifdef NDEBUG - noexcept -#endif - ; -/** - * \brief Output a message to the log - * \param log_level Log level to use for label - * \param format Format string for message - * \param ... Arguments to format message with - * \return None - */ -void output(int log_level, const char* format, ...) -#ifdef NDEBUG - noexcept -#endif - ; -/** - * \brief Log with EXTENSIVE level - * \param format Format string for message - * \param ... Arguments to format message with - */ -void extensive(const char* format, ...) noexcept; -/** - * \brief Log with VERBOSE level - * \param format Format string for message - * \param ... Arguments to format message with - */ -void verbose(const char* format, ...) noexcept; -/** - * \brief Log with DEBUG level - * \param format Format string for message - * \param ... Arguments to format message with - */ -void debug(const char* format, ...) noexcept; -/** - * \brief Log with INFO level - * \param format Format string for message - * \param ... Arguments to format message with - */ -void info(const char* format, ...) noexcept; -/** - * \brief Log with NOTE level - * \param format Format string for message - * \param ... Arguments to format message with - */ -void note(const char* format, ...) noexcept; -/** - * \brief Log with WARNING level - * \param format Format string for message - * \param ... Arguments to format message with - */ -void warning(const char* format, ...) noexcept; -/** - * \brief Log with ERROR level - * \param format Format string for message - * \param ... Arguments to format message with - */ -void error(const char* format, ...) noexcept; -/** - * \brief Check condition and log and exit if true - * \param condition Condition to check (true ends program after logging) - * \param format Format string for message - * \param ... Arguments to format message with - */ -void check_fatal(bool condition, const char* format, ...) -#ifdef NDEBUG - noexcept -#endif - ; -/** - * \brief Check if items are not equal and log and exit if true - * \param lhs first value - * \param rhs second value - * \param name String for message describing what's being compared - */ -void check_equal(const MathSize lhs, const MathSize rhs, const char* name) -#ifdef NDEBUG - noexcept -#endif - ; -/** - * \brief Check if items are not equal and log and exit if true - * \param lhs first value - * \param rhs second value - * \param name String for message describing what's being compared - */ -void check_equal(const char* lhs, const char* rhs, const char* name) -#ifdef NDEBUG - noexcept -#endif - ; -/** - * \brief Check if items are not equal and log and exit if true - * \param lhs first value - * \param rhs second value - * \param name String for message describing what's being compared - */ -template -void check_equal(const V& lhs, const V& rhs, const char* name) -#ifdef NDEBUG - noexcept -#endif -{ - logging::check_fatal(lhs != rhs, - "Expected %s to be %d but got %d", - name, - rhs, - lhs); -} -/** - * \brief Log with FATAL level and exit - * \param format Format string for message - * \param ... Arguments to format message with - */ -void fatal(const char* format, ...) -#ifdef NDEBUG - noexcept -#endif - ; -/** - * \brief Log with FATAL level and exit - * \param ex Exception that is causing fatal error - */ -void fatal(const std::exception& ex); -/** - * \brief Log with FATAL level and exit - * \param ex Exception that is causing fatal error - * \param format Format string for message - * \param ... Arguments to format message with - */ -void fatal(const std::exception& ex, const char* format, ...); -// templated so we can return it from any function and not get an error -// about not returning on all paths -/** - * \brief Log a fatal error and quit - * \tparam T Type to return (so that it can be used to avoid no return value warning) - * \param format Format string for message - * \param args Arguments to format message with - * \return Nothing, because this ends the program - */ -template -T fatal(const char* format, va_list* args) -#ifdef NDEBUG - noexcept -#endif -{ - // format message and then output so we don't parse args twice and can use for error - auto msg = format_log_message("", format, args); - output(LOG_FATAL, msg.c_str()); - Log::closeLogFile(); -#ifdef NDEBUG - exit(EXIT_FAILURE); -#else - // HACK: just throw the format for a start - just want to see stack traces when debugging - throw std::runtime_error(msg); -#endif -} -/** - * \brief Log a fatal error and quit - * \tparam T Type to return (so that it can be used to avoid no return value warning) - * \param format Format string for message - * \param ... Arguments to format message with - * \return Nothing, because this ends the program - */ -template -T fatal(const char* format, ...) -#ifdef NDEBUG - noexcept -#endif -{ - va_list args; - va_start(args, format); - // cppcheck-suppress va_end_missing - return fatal(format, &args); - // va_end(args); -} -class SelfLogger -{ -protected: - virtual string add_log(const char* format) const noexcept = 0; - void log_output(const int level, const char* format, ...) const noexcept; - void log_extensive(const char* format, ...) const noexcept; - void log_verbose(const char* format, ...) const noexcept; - void log_debug(const char* format, ...) const noexcept; - void log_info(const char* format, ...) const noexcept; - void log_note(const char* format, ...) const noexcept; - void log_warning(const char* format, ...) const noexcept; - void log_error(const char* format, ...) const noexcept; - void log_check_fatal(bool condition, const char* format, ...) const -#ifdef NDEBUG - noexcept -#endif - ; - void log_fatal(const char* format, ...) const -#ifdef NDEBUG - noexcept -#endif - ; -}; -} diff --git a/firestarr/src/cpp/LogPoints.cpp b/firestarr/src/cpp/LogPoints.cpp deleted file mode 100644 index 9c08ada86..000000000 --- a/firestarr/src/cpp/LogPoints.cpp +++ /dev/null @@ -1,9 +0,0 @@ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "LogPoints.h" - -namespace fs::sim -{ -} diff --git a/firestarr/src/cpp/LogPoints.h b/firestarr/src/cpp/LogPoints.h deleted file mode 100644 index f1ef31ffb..000000000 --- a/firestarr/src/cpp/LogPoints.h +++ /dev/null @@ -1,9 +0,0 @@ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ -#include "stdafx.h" -#include "CellPoints.h" - -namespace fs::sim -{ -}; \ No newline at end of file diff --git a/firestarr/src/cpp/LookupTable.h b/firestarr/src/cpp/LookupTable.h deleted file mode 100644 index e2b1416ea..000000000 --- a/firestarr/src/cpp/LookupTable.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "Util.h" -#define LOOKUP_TABLES_OFF 1 -#undef LOOKUP_TABLES_OFF -namespace fs::util -{ -/** - * \brief A table initialized using the given function ranging over the number of digits and precision. - * \tparam Fct Function to apply over the range of values - * \tparam IndexDigits Number of digits to use for range of values - * \tparam Precision Precision in decimal places to use for range of values - */ -template -class LookupTable -{ -#ifndef LOOKUP_TABLES_OFF - /** - * \brief Array with enough space for function called with specific number of digits and precision - */ - using ValuesArray = array(10) * pow_int(10)>; - /** - * \brief Array of values from calling function - */ - const ValuesArray values_; - /** - * \brief Call function with range of values with given precision - * \return Results of function with range of values with given precision - */ - [[nodiscard]] constexpr ValuesArray makeValues() - { - // FIX: would prefer consteval but c++26 or external library is required for cmath functions - ValuesArray values{}; - for (size_t i = 0; i < values.size(); ++i) - { - const auto value = i / static_cast(pow_int(10)); - values[i] = Fct(value); - } - return values; - } -#endif -public: - constexpr explicit LookupTable() noexcept -#ifndef LOOKUP_TABLES_OFF - : values_(makeValues()) -#endif - { - } - ~LookupTable() = default; - LookupTable(LookupTable&& rhs) noexcept = delete; - LookupTable(const LookupTable& rhs) noexcept = delete; - LookupTable& operator=(LookupTable&& rhs) noexcept = delete; - LookupTable& operator=(const LookupTable& rhs) noexcept = delete; - /** - * \brief Get result of function lookup table was initialized with for given value - * \param value value to get lookup result for - * \return result of lookup for function at value - */ - [[nodiscard]] constexpr MathSize operator()(const MathSize value) const - { -#ifndef LOOKUP_TABLES_OFF - return values_.at(static_cast(value * pow_int(10))); -#else - return Fct(value); -#endif - } -}; -} diff --git a/firestarr/src/cpp/Main.cpp b/firestarr/src/cpp/Main.cpp deleted file mode 100644 index 3c2dd6b05..000000000 --- a/firestarr/src/cpp/Main.cpp +++ /dev/null @@ -1,578 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -/*! \mainpage FireSTARR Documentation - * - * \section intro_sec Introduction - * - * FireSTARR is a probabilistic fire growth model. - */ -#include "stdafx.h" -#include -#include "Model.h" -#include "Scenario.h" -#include "Test.h" -#include "TimeUtil.h" -#include "Log.h" -#include "version.h" -#include "SpreadAlgorithm.h" -#include "Util.h" -#include "FireWeather.h" -using fs::logging::Log; -using fs::sim::Settings; -using fs::AspectSize; -using fs::SlopeSize; -using fs::INVALID_TIME; -using fs::INVALID_SLOPE; -using fs::INVALID_ASPECT; -using fs::wx::Ffmc; -using fs::wx::Dmc; -using fs::wx::Dc; -using fs::wx::Precipitation; -using fs::wx::Temperature; -using fs::wx::RelativeHumidity; -using fs::wx::Direction; -using fs::wx::Wind; -using fs::wx::Speed; -using fs::ThresholdSize; -using fs::wx::FwiWeather; -using fs::topo::StartPoint; -static const char* BIN_NAME = nullptr; -static map> PARSE_FCT{}; -static vector> PARSE_HELP{}; -static map PARSE_REQUIRED{}; -static map PARSE_HAVE{}; -static int ARGC = 0; -static const char* const* ARGV = nullptr; -static int CUR_ARG = 0; -enum MODE -{ - SIMULATION, - TEST, - SURFACE -}; -string get_args() -{ - std::string args(ARGV[0]); - for (auto i = 1; i < ARGC; ++i) - { - args.append(" "); - args.append(ARGV[i]); - } - return args; -} -void show_args() -{ - auto args = get_args(); - printf("Arguments are:\n%s\n", args.c_str()); -} -void log_args() -{ - auto args = get_args(); - fs::logging::note("Arguments are:\n%s\n", args.c_str()); -} -void show_usage_and_exit(int exit_code) -{ - printf("Usage: %s [options]\n\n", BIN_NAME); - printf("Run simulations and save output in the specified directory\n\n\n"); - printf("Usage: %s surface [options]\n\n", BIN_NAME); - printf("Calculate probability surface and save output in the specified directory\n\n\n"); - printf("Usage: %s test [options]\n\n", BIN_NAME); - printf(" Run test cases and save output in the specified directory\n\n"); - printf(" Input Options\n"); - // FIX: this should show arguments specific to mode, but it doesn't indicate that on the outputs - for (auto& kv : PARSE_HELP) - { - printf(" %-25s %s\n", kv.first.c_str(), kv.second.c_str()); - } - exit(exit_code); -} -void show_usage_and_exit() -{ - show_args(); - show_usage_and_exit(-1); -} -void show_help_and_exit() -{ - // showing help isn't an error - show_usage_and_exit(0); -} -const char* get_arg() noexcept -{ - // check if we don't have any more arguments - fs::logging::check_fatal(CUR_ARG + 1 >= ARGC, "Missing argument to --%s", ARGV[CUR_ARG]); - // NOTE: doing this breaks negative numbers, so don't check for '-' at start - // // check if we have another flag right after - // fs::logging::check_fatal('-' == ARGV[CUR_ARG + 1][0], - // "Missing argument to %s", - // ARGV[CUR_ARG]); - return ARGV[++CUR_ARG]; -} -template -T parse(std::function fct) -{ - PARSE_HAVE.emplace(ARGV[CUR_ARG], true); - return fct(); -} -template -T parse_once(std::function fct) -{ - if (PARSE_HAVE.contains(ARGV[CUR_ARG])) - { - printf("\nArgument %s already specified\n\n", ARGV[CUR_ARG]); - show_usage_and_exit(); - } - return parse(fct); -} -bool parse_flag(bool not_inverse) -{ - return parse_once([not_inverse] { return not_inverse; }); -} -template -T parse_value() -{ - return parse_once([] { return stod(get_arg()); }); -} -size_t parse_size_t() -{ - return parse_once([] { return static_cast(stoi(get_arg())); }); -} -const char* parse_raw() -{ - return parse_once(&get_arg); -} -string parse_string() -{ - return string(parse_raw()); -} -template -T parse_index() -{ - // return T(parse_value()); - return parse_once([] { return T(stod(get_arg())); }); -} -// template -// T parse_int_index() -// { -// return T(static_cast(parse_size_t())); -// // return parse_once([] { return T(stoi(get_arg())); }); -// } -void register_argument(string v, string help, bool required, std::function fct) -{ - PARSE_FCT.emplace(v, fct); - PARSE_HELP.emplace_back(v, help); - PARSE_REQUIRED.emplace(v, required); -} -template -void register_setter(std::function fct_set, string v, string help, bool required, std::function fct) -{ - register_argument(v, help, required, [fct_set, fct] { fct_set(fct()); }); -} -template -void register_setter(T& variable, string v, string help, bool required, std::function fct) -{ - register_argument(v, help, required, [&variable, fct] { variable = fct(); }); -} -void register_flag(std::function fct, bool not_inverse, string v, string help) -{ - register_argument(v, help, false, [not_inverse, fct] { fct(parse_flag(not_inverse)); }); -} -void register_flag(bool& variable, bool not_inverse, string v, string help) -{ - register_argument(v, help, false, [not_inverse, &variable] { variable = parse_flag(not_inverse); }); -} -template -void register_index(T& index, string v, string help, bool required) -{ - register_argument(v, help, required, [&index] { index = parse_index(); }); -} -// template -// void register_int_index(T& index, string v, string help, bool required) -// { -// register_argument(v, help, required, [&index] { index = parse_int_index(); }); -// } -int main(const int argc, const char* const argv[]) -{ - // FILE* out_adj = fopen("horizontal_adjustment.csv", "w"); - // fprintf( - // out_adj, - // "slope,aspect,theta,horizontal_adjustment\n"); - // const size_t aspect = 0; - // // for (size_t aspect = 0; aspect < 360; aspect += 5) - // { - // for (size_t slope = 0; slope < 500; ++slope) - // { - // auto adj = fs::horizontal_adjustment(aspect, slope); - // for (size_t theta = 0; theta < 360; ++theta) - // { - // // aspect is in degrees and theta is radians - // const MathSize f = adj(fs::util::to_radians(theta)); - // // printf( - // // "Adjustment for slope %ld with aspect %ld at angle %ld is %f\n", - // // slope, - // // aspect, - // // theta, - // // f); - // fprintf( - // out_adj, - // "%ld,%ld,%ld,%f\n", - // slope, - // aspect, - // theta, - // f); - // } - // } - // } - // fclose(out_adj); - // exit(0); -#ifdef _WIN32 - printf("FireSTARR windows-testing\n\n"); -#else - printf("FireSTARR %s <%s>\n\n", VERSION, COMPILE_DATE); -#endif - fs::debug::show_debug_settings(); - ARGC = argc; - ARGV = argv; - auto bin = string(ARGV[CUR_ARG++]); - replace(bin.begin(), bin.end(), '\\', '/'); - const auto end = max(static_cast(0), bin.rfind('/') + 1); - const auto bin_dir = bin.substr(0, end); - const auto bin_name = bin.substr(end, bin.size() - end); - // printf("Binary is %s in directory %s\n", bin_name.c_str(), bin_dir.c_str()); - BIN_NAME = bin.c_str(); - Settings::setRoot(bin_dir.c_str()); - // _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); - Log::setLogLevel(fs::logging::LOG_NOTE); - register_argument("-h", "Show help", false, &show_help_and_exit); - // auto start_time = fs::Clock::now(); - // auto time = fs::Clock::now(); - // constexpr size_t n_test = 100000000; - // for (size_t i = 0; i < n_test; ++i) - // { - // time = fs::Clock::now(); - // } - // const auto run_time = time - start_time; - // const auto run_time_seconds = std::chrono::duration_cast(run_time); - // printf("Calling Clock::now() %ld times took %ld seconds", - // n_test, run_time_seconds.count()); - // Calling Clock::now() 100000000 times took 2 seconds - // real 0m2.737s - // user 0m2.660s - // sys 0m0.011s - // return 0; - string wx_file_name; - string log_file_name = "firestarr.log"; - string fuel_name; - string perim; - bool test_all = false; - MathSize hours = INVALID_TIME; - size_t size = 0; - // ffmc, dmc, dc are required for simulation & surface mode, so no indication of it not being provided - Ffmc ffmc = Ffmc::Invalid; - Dmc dmc = Dmc::Invalid; - Dc dc = Dc::Invalid; - auto wind_direction = Direction::Invalid.asValue(); - auto wind_speed = Speed::Invalid.asValue(); - auto slope = static_cast(INVALID_SLOPE); - auto aspect = static_cast(INVALID_ASPECT); - - size_t SKIPPED_ARGS = 0; - // FIX: need to get rain since noon yesterday to start of this hourly weather - Precipitation apcp_prev; - // can be used multiple times - register_argument("-v", "Increase output level", false, &Log::increaseLogLevel); - // if they want to specify -v and -q then that's fine - register_argument("-q", "Decrease output level", false, &Log::decreaseLogLevel); - auto result = -1; - MODE mode = SIMULATION; - if (ARGC > 1 && 0 == strcmp(ARGV[1], "test")) - { - fs::logging::note("Running in test mode"); - mode = TEST; - CUR_ARG += 1; - SKIPPED_ARGS = 1; - // if we have a directory and nothing else then use defaults for single run - // if we have 'all' then overrride specified indices, but then filter down to the subset that matches what was specified - register_setter(hours, "--hours", "Duration in hours", false, &parse_value); - register_setter(fuel_name, "--fuel", "FBP fuel type", false, &parse_string); - register_index(ffmc, "--ffmc", "Constant Fine Fuel Moisture Code", false); - register_index(dmc, "--dmc", "Constant Duff Moisture Code", false); - register_index(dc, "--dc", "Constant Drought Code", false); - register_setter(wind_direction, "--wd", "Constant wind direction", false, &parse_value); - register_setter(wind_speed, "--ws", "Constant wind speed", false, &parse_value); - register_setter(slope, "--slope", "Constant slope", false, &parse_value); - register_setter(aspect, "--aspect", "Constant slope aspect/azimuth", false, &parse_value); - } - else - { - register_flag(&Settings::setSaveProbability, false, "--no-probability", "Do not output probability grids"); - register_setter(&Settings::setRasterRoot, "--raster-root", "Use specified directory as raster root", false, &parse_raw); - register_setter(&Settings::setFuelLookupTable, "--fuel-lut", "Use specified fuel lookup table", false, &parse_raw); - register_setter(log_file_name, "--log", "Output log file", false, &parse_string); - - register_setter(wx_file_name, "--wx", "Input weather file", true, &parse_string); - register_setter(&Settings::setConfidenceLevel, "--confidence", "Use specified confidence level", false, &parse_value); - register_setter(perim, "--perim", "Start from perimeter", false, &parse_string); - register_setter(size, "--size", "Start from size", false, &parse_size_t); - // HACK: want different text for same flag so define here too - register_index(ffmc, "--ffmc", "Startup Fine Fuel Moisture Code", true); - register_index(dmc, "--dmc", "Startup Duff Moisture Code", true); - register_index(dc, "--dc", "Startup Drought Code", true); - register_index(apcp_prev, "--apcp_prev", "Startup precipitation between 1200 yesterday and start of hourly weather", false); - register_setter(&Settings::setOutputDateOffsets, "--output_date_offsets", "Override output date offsets", false, &parse_raw); - if (2 == ARGC && 0 == strcmp(ARGV[CUR_ARG], "-h")) - { - // HACK: just do this for now - show_help_and_exit(); - } - else if (3 > (ARGC - SKIPPED_ARGS)) - { - show_usage_and_exit(); - } - } -#ifdef NDEBUG - try - { -#endif - vector positional_args{}; - while (CUR_ARG < ARGC) - { - const string arg = ARGV[CUR_ARG]; - bool is_positional = !arg.starts_with("-"); - if (!is_positional) - { - // check for single letter flags or '--' - if (PARSE_FCT.find(arg) != PARSE_FCT.end()) - { - fs::logging::debug("Found option for argument '%s'", arg.c_str()); - try - { - PARSE_FCT[arg](); - } - catch (std::exception&) - { - // CUR_ARG would be incremented while trying to parse at this point, so -1 is 'arg' - printf("\n'%s' is not a valid value for argument %s\n\n", ARGV[CUR_ARG], arg.c_str()); - show_usage_and_exit(); - } - } - else - { - if (arg.starts_with("--")) - { - // anything starting with '--' should be a flag, but it's not a valid one so complain - printf("\n'%s' is not a valid option\n\n", arg.c_str()); - show_usage_and_exit(); - } - // it wasn't a flag, so treat it as a positional argument - // show_usage_and_exit(); - is_positional = true; - } - } - if (is_positional) - { - // this is a positional argument so add to that list - positional_args.emplace_back(arg); - fs::logging::debug("Found positional argument '%s'", arg.c_str()); - } - ++CUR_ARG; - } - for (auto& kv : PARSE_REQUIRED) - { - if (kv.second && PARSE_HAVE.end() == PARSE_HAVE.find(kv.first)) - { - fs::logging::fatal("%s must be specified", kv.first.c_str()); - } - } - if (0 == positional_args.size()) - { - // always require at least some positional argument - show_usage_and_exit(); - } - // parse positional arguments - // output directory is always the first thing - size_t cur_arg = 0; - auto has_positional = [&cur_arg, &positional_args]() { - return (cur_arg < positional_args.size()); - }; - auto get_positional = [&cur_arg, &positional_args, &has_positional]() { - if (!has_positional()) - { - fs::logging::error("Not enough positional arguments"); - show_usage_and_exit(); - } - // return from front and advance to next - return positional_args[cur_arg++]; - }; - auto done_positional = [&cur_arg, &positional_args]() { - // should be exactly at size since increments after getting argument - if (positional_args.size() != cur_arg) - { - fs::logging::error("Too many positional arguments"); - show_usage_and_exit(); - } - }; - // positional arguments all start with after mode (if applicable) - // " [surface] [options] [-v | -q]" - string output_directory(get_positional()); - replace(output_directory.begin(), output_directory.end(), '\\', '/'); - if ('/' != output_directory[output_directory.length() - 1]) - { - output_directory += '/'; - } - const char* dir_out = output_directory.c_str(); - struct stat info{}; - if (stat(dir_out, &info) != 0 || !(info.st_mode & S_IFDIR)) - { - fs::util::make_directory_recursive(dir_out); - } - // if name starts with "/" then it's an absolute path, otherwise append to working directory - const string log_file = log_file_name.starts_with("/") ? log_file_name : (output_directory + log_file_name); - fs::logging::check_fatal(!Log::openLogFile(log_file.c_str()), - "Can't open log file %s", - log_file.c_str()); - fs::logging::note("Output directory is %s", dir_out); - fs::logging::note("Output log is %s", log_file.c_str()); - if (mode != TEST) - { - // handle surface/simulation positional arguments - // positional arguments should be: - // " [surface] [options] [-v | -q]" - string date(get_positional()); - tm start_date{}; - start_date.tm_year = stoi(date.substr(0, 4)) - 1900; - start_date.tm_mon = stoi(date.substr(5, 2)) - 1; - start_date.tm_mday = stoi(date.substr(8, 2)); - const auto latitude = stod(get_positional()); - const auto longitude = stod(get_positional()); - const StartPoint start_point(latitude, longitude); - size_t num_days = 0; - string arg(get_positional()); - tm start{}; - if (5 == arg.size() && ':' == arg[2]) - { - try - { - // if this is a time then we aren't just running the weather - start_date.tm_hour = stoi(arg.substr(0, 2)); - fs::logging::check_fatal(start_date.tm_hour < 0 || start_date.tm_hour > 23, - "Simulation start time has an invalid hour (%d)", - start_date.tm_hour); - start_date.tm_min = stoi(arg.substr(3, 2)); - fs::logging::check_fatal(start_date.tm_min < 0 || start_date.tm_min > 59, - "Simulation start time has an invalid minute (%d)", - start_date.tm_min); - fs::logging::note("Simulation start time before fix_tm() is %d-%02d-%02d %02d:%02d", - start_date.tm_year + 1900, - start_date.tm_mon + 1, - start_date.tm_mday, - start_date.tm_hour, - start_date.tm_min); - fs::util::fix_tm(&start_date); - fs::logging::note("Simulation start time after fix_tm() is %d-%02d-%02d %02d:%02d", - start_date.tm_year + 1900, - start_date.tm_mon + 1, - start_date.tm_mday, - start_date.tm_hour, - start_date.tm_min); - // we were given a time, so number of days is until end of year - start = start_date; - const auto start_t = mktime(&start); - auto year_end = start; - year_end.tm_mon = 11; - year_end.tm_mday = 31; - const auto seconds = difftime(mktime(&year_end), start_t); - // start day counts too, so +1 - // HACK: but we don't want to go to Jan 1 so don't add 1 - num_days = static_cast(seconds / fs::DAY_SECONDS); - fs::logging::debug("Calculated number of days until end of year: %d", - num_days); - // +1 because day 1 counts too - // +2 so that results don't change when we change number of days - num_days = min(num_days, static_cast(Settings::maxDateOffset()) + 2); - } - catch (std::exception&) - { - show_usage_and_exit(); - } - } - done_positional(); - // at this point we've parsed positional args and know we're not in test mode - if (!PARSE_HAVE.contains("--apcp_prev")) - { - fs::logging::warning("Assuming 0 precipitation between noon yesterday and weather start for startup indices"); - apcp_prev = Precipitation::Zero; - } - // HACK: ISI for yesterday really doesn't matter so just use any wind - // HACK: it's basically wrong to assign this precip to yesterday's object, - // but don't want to add another argument right now - const auto yesterday = FwiWeather(Temperature::Zero, - RelativeHumidity::Zero, - Wind(Direction(wind_direction, false), Speed(wind_speed)), - Precipitation(apcp_prev), - ffmc, - dmc, - dc); - fs::util::fix_tm(&start_date); - fs::logging::note("Simulation start time after fix_tm() again is %d-%02d-%02d %02d:%02d", - start_date.tm_year + 1900, - start_date.tm_mon + 1, - start_date.tm_mday, - start_date.tm_hour, - start_date.tm_min); - start = start_date; - log_args(); - result = fs::sim::Model::runScenarios(output_directory, - wx_file_name.c_str(), - yesterday, - Settings::rasterRoot(), - start_point, - start, - perim, - size); - Log::closeLogFile(); - } - else - { - // test mode - if (has_positional()) - { - const auto arg = get_positional(); - if (0 != strcmp(arg.c_str(), "all")) - { - fs::logging::error("Only positional argument allowed for test mode aside from output directory is 'all' but got '%s'", arg.c_str()); - show_usage_and_exit(); - } - test_all = true; - } - done_positional(); - const auto wx = FwiWeather( - Temperature::Zero, - RelativeHumidity::Zero, - Wind(Direction(wind_direction, false), Speed(wind_speed)), - Precipitation::Zero, - ffmc, - dmc, - dc); - show_args(); - result = fs::sim::test( - output_directory, - hours, - &wx, - fuel_name, - slope, - aspect, - test_all); - } -#ifdef NDEBUG - } - catch (const std::exception& ex) - { - fs::logging::fatal(ex); - std::terminate(); - } -#endif - return result; -} diff --git a/firestarr/src/cpp/MergeIterator.cpp b/firestarr/src/cpp/MergeIterator.cpp deleted file mode 100644 index 1c288675f..000000000 --- a/firestarr/src/cpp/MergeIterator.cpp +++ /dev/null @@ -1,9 +0,0 @@ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "MergeIterator.h" - -namespace fs::sim -{ -} diff --git a/firestarr/src/cpp/MergeIterator.h b/firestarr/src/cpp/MergeIterator.h deleted file mode 100644 index d693e50f2..000000000 --- a/firestarr/src/cpp/MergeIterator.h +++ /dev/null @@ -1,99 +0,0 @@ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Location.h" -#include "Cell.h" -#include "InnerPos.h" -#include "CellPoints.h" - -namespace fs::sim -{ -using topo::Location; -using topo::SpreadKey; - -// mangled version of std::transform_reduce() that calls .begin() and .end() -template -_Tp do_transform_reduce( - _ForwardIteratorSource&& container, - _Tp __init, - _BinaryOperation __binary_op, - _UnaryOperation __unary_op) -{ - // to help compiler determine type - using _ForwardIterator = decltype(container.begin()); - return std::transform_reduce( - std::execution::par_unseq, - static_cast<_ForwardIterator>(container.begin()), - static_cast<_ForwardIterator>(container.end()), - __init, - __binary_op, - __unary_op); -} - -template -const M merge_maps_generic( - const M& lhs, - const M& rhs, - F f) -{ - using const_iterator = typename M::const_iterator; - using value_type = typename M::value_type; - using key_type = typename M::key_type; - using mapped_type = typename M::mapped_type; - std::function fct_merge = f; - M out{}; - const_iterator it_lhs = lhs.begin(); - const_iterator it_rhs = rhs.begin(); - const_iterator end_lhs = lhs.end(); - const_iterator end_rhs = rhs.end(); - while (true) - { - if (end_lhs == it_lhs) - { - while (end_rhs != it_rhs) - { - out.emplace(*it_rhs); - ++it_rhs; - } - break; - } - if (end_rhs == it_rhs) - { - while (end_lhs != it_lhs) - { - out.emplace(*it_lhs); - ++it_lhs; - } - break; - } - // at end of neither so pick lower value - const value_type& pair0 = *it_lhs; - const value_type& pair1 = *it_rhs; - const key_type& k0 = pair0.first; - const key_type& k1 = pair1.first; - const mapped_type& m0 = pair0.second; - const mapped_type& m1 = pair1.second; - if (k0 < k1) - { - out.emplace(pair0); - ++it_lhs; - } - else if (k0 > k1) - { - out.emplace(pair1); - ++it_rhs; - } - else - { - assert(k0 == k1); - const mapped_type merged = fct_merge(m0, m1); - out.emplace(pair(k0, merged)); - ++it_lhs; - ++it_rhs; - } - } - return out; -} -} diff --git a/firestarr/src/cpp/Model.cpp b/firestarr/src/cpp/Model.cpp deleted file mode 100644 index fbca6fada..000000000 --- a/firestarr/src/cpp/Model.cpp +++ /dev/null @@ -1,1285 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include -#include "Model.h" -#include "Scenario.h" -#include "FBP45.h" -#include "Observer.h" -#include "Perimeter.h" -#include "ProbabilityMap.h" -#include "FireWeatherDaily.h" -#include "ConstantWeather.h" -namespace fs::sim -{ -#ifdef DEBUG_WEATHER -constexpr auto FMT_OUT = "%ld,%d-%02d-%02d %02d:%02d:%02d,%1.6f,%1.6f,%1.6f,%1.6f,%1.6f,%1.6f,%1.6f,%1.6f,%1.6f,%1.6f,%1.6f%s"; -#endif -// constexpr MathSize PCT_CPU = 0.8; -// HACK: assume using half the CPUs probably means that faster cores are being used? -constexpr MathSize PCT_CPU = 0.5; -Semaphore Model::task_limiter{static_cast(std::thread::hardware_concurrency())}; -BurnedData* Model::getBurnedVector() const noexcept -{ - try - { - lock_guard lock(vector_mutex_); - if (!vectors_.empty()) - { - // check again once we have the mutex - if (!vectors_.empty()) - { - const auto v = std::move(vectors_.back()).release(); - vectors_.pop_back(); - // this is already reset before it was given back - return v; - } - } - auto result = environment().makeBurnedData().release(); - // environment().resetBurnedData(result); - return result; - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } -} -void Model::releaseBurnedVector(BurnedData* has_burned) const noexcept -{ - if (nullptr == has_burned) - { - return; - } - try - { - environment().resetBurnedData(has_burned); - lock_guard lock(vector_mutex_); - vectors_.push_back(unique_ptr(has_burned)); - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } -} -Model::Model(const string dir_out, - const topo::StartPoint& start_point, - topo::Environment* env) - : dir_out_(dir_out), - start_time_(tm()), - running_since_(Clock::now()), - time_limit_(Settings::maximumTimeSeconds()), - no_interim_save_since_(Clock::now()), - interim_save_interval_(Settings::interimOutputIntervalSeconds()), - env_(env), - latitude_(start_point.latitude()), - longitude_(start_point.longitude()) -{ - logging::debug("Calculating for (%f, %f)", start_point.latitude(), start_point.longitude()); - const auto nd_for_point = - calculate_nd_ref_for_point(env->elevation(), start_point); - for (auto day = 0; day < MAX_DAYS; ++day) - { - nd_.at(static_cast(day)) = static_cast(day - nd_for_point); - logging::debug("Day %d has nd %d, is%s green, %d%% curing", - day, - nd_.at(static_cast(day)), - fuel::calculate_is_green(nd_.at(static_cast(day))) - ? "" - : " not", - fuel::calculate_grass_curing(nd_.at(static_cast(day)))); - } -} -void Model::setWeather(const wx::FwiWeather& weather, const Day start_day) -{ - yesterday_ = weather; - const auto fuel_lookup = sim::Settings::fuelLookup(); - const auto& f = fuel_lookup.usedFuels(); - auto wx_const = make_shared(f, start_day - 1, weather.dc(), weather.dmc(), weather.ffmc(), weather.wind()); - wx_.emplace(0, wx_const); - wx_daily_.emplace(0, wx_const); -} -void Model::readWeather(const wx::FwiWeather& yesterday, - const MathSize latitude, - const string& filename) -{ - map*> wx{}; - map> wx_daily{}; - map dates{}; - Day min_date = numeric_limits::max(); - Day max_date = numeric_limits::min(); - time_t prev_time = numeric_limits::min(); - ifstream in; - in.open(filename); - logging::check_fatal(!in.is_open(), - "Could not open input weather file %s", - filename.c_str()); - if (in.is_open()) - { -#ifndef NDEBUG - const auto file_out = string(dir_out_) + "/wx_hourly_out_read.csv"; - FILE* out = fopen(file_out.c_str(), "w"); - logging::check_fatal(nullptr == out, "Cannot open file %s for output", file_out.c_str()); - fprintf(out, "Scenario,Date,PREC,TEMP,RH,WS,WD,FFMC,DMC,DC,ISI,BUI,FWI\r\n"); -#endif - string str; - logging::info("Reading scenarios from '%s'", filename.c_str()); - // read header line - getline(in, str); - // get rid of whitespace - str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); - str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); - str.erase(std::remove(str.begin(), str.end(), '\r'), str.end()); - constexpr auto expected_header = - "Scenario,Date,PREC,TEMP,RH,WS,WD,FFMC,DMC,DC,ISI,BUI,FWI"; - logging::check_fatal(expected_header != str, - "Input CSV must have columns in this order:\n'%s'\n but got:\n'%s'", - expected_header, - str.c_str()); - auto prev = &yesterday; - // HACK: adding to original object if we don't do this? - auto apcp_24h = yesterday.prec().asValue(); - while (getline(in, str)) - { - istringstream iss(str); - if (getline(iss, str, ',') && !str.empty()) - { - // HACK: ignore date and just worry about relative order?? - // Scenario - logging::verbose("Scenario is %s", str.c_str()); - size_t cur = 0; - try - { - cur = static_cast(stoi(str)); - } - catch (const std::exception& ex) - { - // HACK: somehow stoi() is still getting empty strings - logging::fatal(ex, "Error reading weather file %s: %s is not a valid integer", filename.c_str(), str.c_str()); - } - if (wx.find(cur) == wx.end()) - { - logging::debug("Loading scenario %d...", cur); - wx.emplace(cur, new vector()); - prev_time = std::numeric_limits::min(); - logging::check_fatal(wx_daily.find(cur) != wx_daily.end(), - "Somehow have daily weather for scenario %ld before hourly weather", - cur); - wx_daily.emplace(cur, map()); - prev = &yesterday; - logging::extensive("Resetting new scenario precip to %f from %f", - yesterday.prec().asValue(), - apcp_24h); - apcp_24h = yesterday.prec().asValue(); - } - auto& s = wx.at(cur); - struct tm t{}; - util::read_date(&iss, &str, &t); - year_ = t.tm_year + 1900; - const auto ticks = mktime(&t); - if (1 == cur) - { - logging::debug("Date '%s' is %ld and calculated jd is %d", - str.c_str(), - ticks, - t.tm_yday); - if (!s->empty() && t.tm_yday < min_date) - { - logging::fatal( - "Weather input file crosses year boundary or dates are not sequential"); - } - } - min_date = min(min_date, static_cast(t.tm_yday)); - max_date = max(max_date, static_cast(t.tm_yday)); - time_t cur_time = mktime(&t); - if (prev_time != std::numeric_limits::min()) - { - auto seconds_diff = (cur_time - prev_time); - logging::check_fatal( - seconds_diff != HOUR_SECONDS, - "Expected sequential hours in weather input but rows are %f hours away from each other", - seconds_diff / static_cast(HOUR_SECONDS)); - } - prev_time = cur_time; - const auto for_time = (t.tm_yday - min_date) * DAY_HOURS + t.tm_hour; - // HACK: can be up until rest of year since start date - const size_t new_size = (max_date - min_date + 1) * DAY_HOURS; - const auto old_size = s->size(); - if (old_size != new_size) - { - s->resize(new_size); - for (auto i = old_size; i < new_size; ++i) - { - s->at(i) = nullptr; - } - } - logging::verbose("for_time == %d", for_time); - const wx::FwiWeather* w = new wx::FwiWeather(&iss, - &str); - s->at(for_time) = w; - logging::check_fatal(0 > w->prec().asValue(), - "Hourly weather precip %f is negative", - w->prec().asValue()); - apcp_24h += w->prec().asValue(); - logging::extensive("Adding %f to precip results in accumulation of %f", - w->prec().asValue(), - apcp_24h); - if (12 == t.tm_hour) - { - // we just hit noon on a new day, so add the daily value - auto& s_daily = wx_daily.at(cur); - const auto day = static_cast(t.tm_yday); - logging::check_fatal(s_daily.find(day) != s_daily.end(), - "Day already exists"); - const auto month = t.tm_mon + 1; - s_daily.emplace(day, - wx::FwiWeather(*prev, - month, - latitude, - w->temp(), - w->rh(), - w->wind(), - wx::Precipitation(apcp_24h))); - // new 24 hour period - logging::extensive("Resetting daily precip to %f from %f", 0.0, apcp_24h); - apcp_24h = 0; - prev = &s_daily.at(static_cast(t.tm_yday)); - } -#ifdef DEBUG_WEATHER - const auto month = t.tm_mon + 1; - logging::debug(FMT_OUT, - cur, - year_, - month, - t.tm_mday, - t.tm_hour, - t.tm_min, - t.tm_sec, - w->prec().asValue(), - w->temp().asValue(), - w->rh().asValue(), - w->wind().speed().asValue(), - w->wind().direction().asValue(), - w->ffmc().asValue(), - w->dmc().asValue(), - w->dc().asValue(), - w->isi().asValue(), - w->bui().asValue(), - w->fwi().asValue(), - ""); - fprintf(out, - FMT_OUT, - cur, - year_, - month, - t.tm_mday, - t.tm_hour, - t.tm_min, - t.tm_sec, - w->prec().asValue(), - w->temp().asValue(), - w->rh().asValue(), - w->wind().speed().asValue(), - w->wind().direction().asValue(), - w->ffmc().asValue(), - w->dmc().asValue(), - w->dc().asValue(), - w->isi().asValue(), - w->bui().asValue(), - w->fwi().asValue(), - "\r\n"); -#endif - } - } -#ifndef NDEBUG - logging::check_fatal(0 != fclose(out), "Could not close file %s", file_out.c_str()); -#endif - in.close(); - } - // for (auto& kv : wx) - // { - // kv.second.emplace(static_cast(min_date - 1), yesterday); - // } - // const auto file_out = string(dir_out_) + "/wx_out.csv"; - // FILE* out = fopen(file_out.c_str(), "w"); - // logging::check_fatal(nullptr == out, "Cannot open file %s for output", file_out.c_str()); - // fprintf(out, "Scenario,Day,PREC,TEMP,RH,WS,WD,FFMC,DMC,DC,ISI,BUI,FWI\r\n"); - // size_t i = 1; - // for (auto& kv : wx) - // { - // auto& s = kv.second; - // for (auto& kv2 : s) - // { - // auto& day = kv2.first; - // auto& w = kv2.second; - // fprintf(out, - // "%ld,%d,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g\r\n, - // i, - // day, - // w.prec().asValue(), - // w.temp().asValue(), - // w.rh().asValue(), - // w.wind().speed().asValue(), - // w.wind().direction().asValue(), - // w.ffmc().asValue(), - // w.dmc().asValue(), - // w.dc().asValue(), - // w.isi().asValue(), - // w.bui().asValue(), - // w.fwi().asValue()); - // } - // ++i; - // } - // logging::check_fatal(0 != fclose(out), "Could not close file %s", file_out.c_str()); - const auto fuel_lookup = sim::Settings::fuelLookup(); - const auto& f = fuel_lookup.usedFuels(); - // loop through and try to find duplicates - for (const auto& kv : wx) - { - const auto k = kv.first; - const auto s = kv.second; - // FIX: this is just looking for duplicate scenario ids, not weather? - if (wx_.find(k) == wx_.end()) - { - const auto w = make_shared(f, min_date, max_date, s); - wx_.emplace(k, w); - // calculate daily indices - auto& s_daily = wx_daily.at(k); - // HACK: set yesterday to match today - s_daily.emplace(min_date - 1, s_daily.at(min_date)); - const auto w_daily = make_shared(f, s_daily); - wx_daily_.emplace(k, w_daily); - } - } -} -void Model::findStarts(const Location location) -{ - logging::error("Trying to start a fire in non-fuel"); - Idx range = 1; - // HACK: should always be centered in the grid - while (starts_.empty() && (range < (MAX_COLUMNS / 2))) - { - for (Idx x = -range; x <= range; ++x) - { - for (Idx y = -range; y <= range; ++y) - { - // make sure we only look at the outside of the box - if (1 == range || abs(x) == range || abs(y) == range) - { - // const auto loc = env_->cell(location.hash() + (y * MAX_COLUMNS) + x); - const auto loc = env_->cell(Location(location.row() + y, location.column() + x)); - if (!fuel::is_null_fuel(loc)) - { - starts_.push_back(make_shared(cell(loc))); - } - } - } - } - ++range; - } - logging::check_fatal(starts_.empty(), "Fuel grid is empty"); - logging::info("Using %d start locations:", ignitionScenarios()); - for (const auto& s : starts_) - { - logging::info("\t%d, %d", s->row(), s->column()); - } -} -void Model::makeStarts(Coordinates coordinates, - const topo::Point& point, - string perim, - size_t size) -{ - Location location(std::get<0>(coordinates), std::get<1>(coordinates)); - if (!perim.empty()) - { - logging::note("Initializing from perimeter %s", perim.c_str()); - perimeter_ = make_shared(perim, point, *env_); - // HACK: if perimeter is only one cell then use position not perimeter so it can bounce if non-fuel - const auto burned = perimeter_->burned(); - const auto s = burned.size(); - if (1 >= s) - { - logging::note("Converting perimeter into point since size is %ld", s); - // use whatever the one cell is instead of the lat/long - if (1 == s) - { - location = *(burned.begin()); - } - // HACK: use 0 for 0 or 1 so it'll assign by point - size = 0; - perim = ""; - } - } - // use if instead of else if in case perimeter was a single point and got switched - if (size > 0) - { - logging::note("Initializing from size %d ha", size); - perimeter_ = make_shared( - cell(location), - size, - *env_); - } - // figure out where the fire can exist - if (nullptr != perimeter_ && !perimeter_->burned().empty()) - { - logging::check_fatal(size != 0 && !perim.empty(), "Can't specify size and perimeter"); - // we have a perimeter to start from - // HACK: make sure this isn't empty - starts_.push_back(make_shared(cell(location))); - logging::note("Fire starting with size %0.1f ha", - perimeter_->burned().size() * env_->cellSize() / 100.0); - } - else - { - if (nullptr != perimeter_) - { - logging::check_fatal(!perimeter_->burned().empty(), - "Not using perimeter so it should be empty"); - logging::note("Using fire perimeter results in empty fire - changing to use point"); - perimeter_ = nullptr; - } - logging::note("Fire starting with size %0.1f ha", env_->cellSize() / 100.0); - // if (0 == size && fuel::is_null_fuel(cell(location.hash()))) - if (0 == size && fuel::is_null_fuel(cell(location))) - { - findStarts(location); - } - else - { - starts_.push_back(make_shared(cell(location))); - } - } - // if (nullptr != perimeter_) - // { - // initial_intensity_ = make_shared(*this, &(*perimeter_)); - // } - logging::note("Creating %ld streams x %ld location%s = %ld scenarios", - wx_.size(), - ignitionScenarios(), - ignitionScenarios() > 1 ? "s" : "", - wx_.size() * ignitionScenarios()); -} -Iteration Model::readScenarios(const topo::StartPoint& start_point, - const DurationSize start, - const Day start_day, - const Day last_date) -{ - // FIX: this is going to do a lot of work to set up each scenario if we're making a surface - vector result{}; - auto saves = Settings::outputDateOffsets(); - const auto setup_scenario = [&result, - &saves](Scenario* scenario) { - // FIX: this should be relative to the start date, not the weather start date - for (const auto& i : saves) - { - scenario->addSaveByOffset(i); - } - // logging::note("Ended up with %ld save points initially", scenario->savePoints().size()); - result.push_back(scenario); - }; - for (const auto& kv : wx_) - { - const auto id = kv.first; - const auto cur_wx = kv.second.get(); - const auto cur_daily = wx_daily_.at(id).get(); - if (nullptr != perimeter_) - { - setup_scenario(new Scenario(this, - id, - cur_wx, - cur_daily, - start, - // initial_intensity_, - perimeter_, - start_point, - start_day, - last_date)); - } - else - { - for (const auto& cur_start : starts_) - { - // should always have at least the day before the fire in the weather stream - setup_scenario(new Scenario(this, - id, - cur_wx, - cur_daily, - start, - cur_start, - start_point, - start_day, - last_date)); - } - } - } - return Iteration(result); -} -[[nodiscard]] std::chrono::seconds Model::runTime() const -{ - const auto run_time = last_checked_ - runningSince(); - const auto run_time_seconds = std::chrono::duration_cast(run_time); - return run_time_seconds; -} -[[nodiscard]] std::chrono::seconds Model::timeSinceLastSave() const -{ - return std::chrono::duration_cast( - last_checked_ - no_interim_save_since_); -} -bool Model::shouldStop() const noexcept -{ - return (isOutOfTime() || isOverSimulationCountLimit()); -} -bool Model::isOutOfTime() const noexcept -{ - // return is_out_of_time_ || runTime() > timeLimit(); - // return runTime() > timeLimit(); - // return ((last_checked_ - runningSince()) > timeLimit()); - // return (is_out_of_time_ || ((last_checked_ - runningSince()) > timeLimit())); - // return (Clock::now() - runningSince()) > timeLimit(); - return is_out_of_time_; -} -bool Model::isOverSimulationCountLimit() const noexcept -{ - return is_over_simulation_count_; -} -ProbabilityMap* Model::makeProbabilityMap(const DurationSize time, - const DurationSize start_time) const -{ - return env_->makeProbabilityMap(time, - start_time); -} -static void show_probabilities(const map& probabilities) -{ - for (const auto& kv : probabilities) - { - kv.second->show(); - } -} -map make_prob_map(const Model& model, - const vector& saves, - const DurationSize started) -{ - map result{}; - for (const auto& time : saves) - { - result.emplace( - time, - model.makeProbabilityMap(time, - started)); - } - return result; -} -map make_size_map(const vector& saves) -{ - map result{}; - for (const auto& time : saves) - { - result.emplace(time, new util::SafeVector()); - } - return result; -} -void Model::add_statistics(vector* all_sizes, - vector* means, - vector* pct, - const util::SafeVector& sizes) -{ - const auto cur_sizes = sizes.getValues(); - logging::check_fatal(cur_sizes.empty(), "No sizes at end of simulation"); - const util::Statistics s{cur_sizes}; - static_cast(util::insert_sorted(pct, s.percentile(95))); - static_cast(util::insert_sorted(means, s.mean())); - // NOTE: Used to just look at mean and percentile of each iteration, but should probably look at all the sizes together? - for (const auto& size : cur_sizes) - { - static_cast(util::insert_sorted(all_sizes, size)); - } - is_over_simulation_count_ = all_sizes->size() >= Settings::maximumCountSimulations(); -} -/*! - * \page ending Simulation stop conditions - * - * Simulations will continue to run until a stop condition is reached. - * - * 1) the program has reached the time defined in the settings file as the maximum - * run duration. - * - * 2) the amount of variability in the output statistics has decreased to a point - * that is less than the confidence level defined in the settings file - */ -size_t runs_required(const size_t i, - const vector* all_sizes, - const vector* means, - const vector* pct, - const Model& model) -{ - if (model.isOverSimulationCountLimit()) - { - logging::note( - "Stopping after %d iterations. Simulation limit of %d simulations has been reached.", - all_sizes->size(), - Settings::maximumCountSimulations()); - return 0; - } - if (model.isOutOfTime()) - { - logging::note( - "Stopping after %d iterations. Time limit of %d seconds has been reached.", - i, - Settings::maximumTimeSeconds()); - return 0; - } - const auto for_sizes = util::Statistics{*all_sizes}; - const auto for_means = util::Statistics{*means}; - const auto for_pct = util::Statistics{*pct}; - if (!(!for_means.isConfident(Settings::confidenceLevel()) - || !for_pct.isConfident(Settings::confidenceLevel()) - || !for_sizes.isConfident(Settings::confidenceLevel()))) - { - return 0; - } - // const auto left = max( - // max(max(for_means.runsRequired(i, Settings::confidenceLevel()), - // for_pct.runsRequired(i, Settings::confidenceLevel())), - // for_sizes.runsRequired(); - const auto runs_for_means = for_means.runsRequired(Settings::confidenceLevel()); - const auto runs_for_pct = for_pct.runsRequired(Settings::confidenceLevel()); - const auto runs_for_sizes = for_sizes.runsRequired(Settings::confidenceLevel()); - logging::debug("Runs required based on criteria: { means: %ld, pct: %ld, sizes: %ld}", - runs_for_means, - runs_for_pct, - runs_for_sizes); - logging::debug("Number of values based on criteria: { means: %ld, pct: %ld, sizes: %ld}", - for_means.n(), - for_pct.n(), - for_sizes.n()); - const auto left = max( - max( - runs_for_means, - runs_for_pct), - runs_for_sizes); - return left; -} -DurationSize Model::saveProbabilities(map& probabilities, const Day start_day, const bool is_interim) -{ - lock_guard lock(mutex_); - auto final_time = numeric_limits::min(); - const ProcessingStatus processing_status = - !is_interim - ? processed - : ( - 0 == scenarios_done_ - ? unprocessed - : processing); - if ( - (processing_status == processing) - && (scenarios_last_save_ == scenarios_done_)) - { - logging::error("No change since last call to saveProbabilities"); - } - else if ( - (processing_status != processing) || should_output_interim_) - { - // HACK: use max as "never saved" and replace with 0 on first save - if (is_being_cancelled_) - { - logging::info( - "Saving%s results for (%ld of %ld) required scenarios%s", - is_interim ? " interim" : "", - scenarios_required_done_, - scenarios_per_iteration_, - is_being_cancelled_ ? " because cancelling" : ""); - } - else - { - logging::info( - "Saving%s results for %ld scenarios (%ld new in %lds since last save)", - is_interim ? " interim" : "", - scenarios_done_, - scenarios_done_ - scenarios_last_save_, - timeSinceLastSave().count()); - } - for (const auto& by_time : probabilities) - { - const auto time = by_time.first; - final_time = max(final_time, time); - const auto prob = by_time.second; - logging::debug("Setting perimeter"); - prob->setPerimeter(this->perimeter_.get()); - prob->saveAll(this->start_time_, time, processing_status); - if (processing_status == processed) - { - const auto day = static_cast(round(time)); - const auto n = nd(day); - logging::note("Fuels for day %d are %s green-up and grass has %d%% curing", - day - static_cast(start_day), - fuel::calculate_is_green(n) ? "after" : "before", - fuel::calculate_grass_curing(n)); - } - } - logging::debug("Done saving proabability grids"); - interim_changed_ = false; - should_output_interim_ = false; - scenarios_last_save_ = scenarios_done_; - if (!is_interim) - { - ProbabilityMap::deleteInterim(); - } - else - { - no_interim_save_since_ = Clock::now(); - } - } - else - { - interim_changed_ = true; - } - return final_time; -} -map Model::runIterations(const topo::StartPoint& start_point, - const DurationSize start, - const Day start_day) -{ - auto last_date = start_day; - for (const auto& i : Settings::outputDateOffsets()) - { - last_date = max(static_cast(start_day + i), last_date); - } - // use independent seeds so that if we remove one threshold it doesn't affect the other - // HACK: seed_seq takes a list of integers now, so multiply and convert to get more digits - const auto lat = static_cast(start_point.latitude() * pow(10, std::numeric_limits::digits10 - 4)); - const auto lon = static_cast(start_point.longitude() * pow(10, std::numeric_limits::digits10 - 4)); - logging::debug("lat/long (%f, %f) converted to (%ld, %ld)", start_point.latitude(), start_point.longitude(), lat, lon); - std::seed_seq seed_spread{static_cast(0), static_cast(start_day), lat, lon}; - std::seed_seq seed_extinction{static_cast(1), static_cast(start_day), lat, lon}; - mt19937 mt_spread(seed_spread); - mt19937 mt_extinction(seed_extinction); - vector all_sizes{}; - vector means{}; - vector pct{}; - vector all_iterations{}; - logging::verbose("Reading scenarios"); - all_iterations.push_back(readScenarios(start_point, - start, - start_day, - last_date)); - // HACK: reference from vector so timer can cancel everything in vector - auto& iteration = all_iterations[0]; - scenarios_per_iteration_ = iteration.size(); - // put probability maps into map - logging::verbose("Setting save points"); - const auto saves = iteration.savePoints(); - const auto started = iteration.startTime(); - auto probabilities = make_prob_map(*this, - saves, - started); - vector> all_probabilities{}; - all_probabilities.push_back(make_prob_map(*this, - saves, - started)); - logging::verbose("Setting up initial intensity map with perimeter"); - auto runs_left = 1; - // // set up a timer to mark when simulation is out of time - // auto t = Settings::maximumTimeSeconds(); - // // set up a timer to check the clock to see when simulation is out of time - // logging::verbose("Starting timer for %ld seconds", t); - // auto timer = std::thread([t, this] (){ - // printf("Starting timer for %ld seconds", t); - // std::this_thread::sleep_for(std::chrono::seconds(t)); - // printf("out of time after %ld seconds", t); - // is_out_of_time_ = true; - // }); - // typedef std::chrono::duration s; - is_being_cancelled_ = false; - // HACK: use initial value for type - auto timer = std::thread([this, &all_probabilities, &runs_left, &all_sizes, &all_iterations, &probabilities, &start_day]() { - constexpr auto CHECK_INTERVAL = std::chrono::seconds(1); - // const auto SLEEP_INTERVAL = std::chrono::seconds(Settings::maximumTimeSeconds()); - do - { - this->last_checked_ = Clock::now(); - // think we need to check regularly instead of just sleeping so that we can see - // if we've done enough runs and need to stop for that reason - std::this_thread::sleep_for(CHECK_INTERVAL); - // set bool so other things don't need to check clock - is_out_of_time_ = runTime().count() >= timeLimit().count(); - should_output_interim_ = timeSinceLastSave().count() >= interimTimeLimit().count(); - if (should_output_interim_ && interim_changed_) - { - saveProbabilities(all_probabilities[0], start_day, true); - } - logging::verbose("Checking clock [%ld of %ld]", runTime(), timeLimit()); - } - while (runs_left > 0 && !shouldStop()); - if (isOutOfTime()) - { - logging::warning("Ran out of time - cancelling simulations"); - } - if (0 == iterations_done_) - { - logging::warning("Ran out of time, but haven't finished any iterations, so cancelling all but first"); - } - size_t i = 0; - for (auto& iter : all_iterations) - { - // don't cancel first iteration if no iterations are done - if (0 != iterations_done_ || 0 != i) - { - // if not over limit then just did all the runs so no warning - iter.cancel(shouldStop()); - } - ++i; - } - // is_being_cancelled_ = (0 == iterations_done_); - if (0 == iterations_done_) - { - is_being_cancelled_ = true; - if (scenarios_required_done_ > 0) - { - saveProbabilities(all_probabilities[0], start_day, true); - } - } - const auto run_time_seconds = runTime().count(); - // const auto run_time = last_checked_ - runningSince(); - // const auto run_time_seconds = std::chrono::duration_cast(run_time); - // const auto time_left = Settings::maximumTimeSeconds() - run_time_seconds.count(); - const auto time_left = Settings::maximumTimeSeconds() - run_time_seconds; - logging::debug("Ending timer after %ld seconds with %ld seconds left", - run_time_seconds, - time_left); - }); - // HACK: save immediately to get "unprocessed" version of grids - saveProbabilities(all_probabilities[0], start_day, true); - auto threads = list{}; - // const auto finalize_probabilities = [&threads, &timer, &probabilities](bool do_cancel) { - const auto finalize_probabilities = [this, &start_day, &all_sizes, &threads, &timer, &probabilities]() { - // assume timer is cancelling everything - for (auto& t : threads) - { - if (t.joinable()) - { - t.join(); - } - } - if (timer.joinable()) - { - timer.join(); - } - return probabilities; - }; - // if using surface just run each start through in a loop here - size_t cur_start = 0; - // HACK: just do this here so that we know it happened - // iterations.reset(&mt_extinction, &mt_spread); - auto reset_iter = [&cur_start, this, &mt_extinction, &mt_spread](Iteration& iter) { - { - iter.reset(&mt_extinction, &mt_spread); - } - return true; - }; - // FIX: I think we can just have 2 Iteration objects and roll through starting - // threads in the second one as the first one finishes? - // const auto MAX_THREADS = static_cast(std::thread::hardware_concurrency() * PCT_CPU); - // const auto MAX_THREADS = static_cast(std::thread::hardware_concurrency() / 4); - // const auto MAX_THREADS = std::thread::hardware_concurrency() - 1; - const auto HARDWARE_THREADS = static_cast(std::thread::hardware_concurrency()); - // maybe a bit slower but prefer to run all scenarios at the same time - const auto MAX_THREADS = max(HARDWARE_THREADS, scenarios_per_iteration_); - if (MAX_THREADS > HARDWARE_THREADS) - { - logging::note("Increasing to use at least one thread for each of %ld scenarios", scenarios_per_iteration_); - Model::task_limiter.set_limit(MAX_THREADS); - } - const auto MAX_CONCURRENT = std::max(MAX_THREADS, 1); - constexpr auto MIN_CONCURRENT = 2; - const auto concurrent_iterations = std::max( - MAX_CONCURRENT / all_iterations[0].getScenarios().size(), - MIN_CONCURRENT); - // HACK: just set max of 4 for now - // constexpr auto MIN_ITERATIONS_BEFORE_CHECK = 4; - // const auto concurrent_iterations = std::min( - // static_cast(MIN_ITERATIONS_BEFORE_CHECK), - // MAX_THREADS); - // no point in running multiple iterations if deterministic - // const auto concurrent_iterations = 1; - // const auto concurrent_iterations = MAX_THREADS; - for (size_t x = 1; x < concurrent_iterations; ++x) - { - all_iterations.push_back(readScenarios(start_point, - start, - start_day, - last_date)); - all_probabilities.push_back(make_prob_map(*this, - saves, - started)); - } - auto run_scenario = [this, &all_probabilities, &all_iterations, &start_day](Scenario* s, size_t i, bool is_required) { - auto result = s->run(&all_probabilities[i]); - ++scenarios_done_; - logging::extensive("Done %ld scenarios in iteration %ld which %s required", scenarios_done_, i, (is_required ? "is" : "is not")); - if (is_required) - { - logging::verbose("Done %ld scenarios in iteration %ld which %s required", scenarios_done_, i, (is_required ? "is" : "is not")); - ++scenarios_required_done_; - } - logging::debug("Have (%ld of %ld) scenarios and %s being cancelled", - scenarios_required_done_, - scenarios_per_iteration_, - (is_being_cancelled_ ? "is" : "not")); - // no point in saving interim if final is done - if (!(is_being_cancelled_ - && scenarios_per_iteration_ == scenarios_required_done_)) - { - saveProbabilities(all_probabilities[0], start_day, true); - } - return result; - }; - logging::debug("Created %d iterations to run concurrently", all_iterations.size()); - size_t cur_iter = 0; - for (auto& iter : all_iterations) - { - if (reset_iter(iter)) - { - auto& scenarios = iter.getScenarios(); - for (auto s : scenarios) - { - threads.emplace_back(run_scenario, - s, - cur_iter, - 0 == cur_iter); - } - ++cur_iter; - } - } - cur_iter = 0; - while (runs_left > 0) - { - // should have completed one iteration, so add it - auto& iteration = all_iterations[cur_iter]; - // so now try to loop through and add iterations as they finish - // FIX: look at converting so that new threads get started as others complete - // - would have to have multiple Iterations so we keep the data from them separate? - size_t k = 0; - while (k < scenarios_per_iteration_) - { - threads.front().join(); - threads.pop_front(); - ++k; - } - auto final_sizes = iteration.finalSizes(); - ++iterations_done_; - for (auto& kv : all_probabilities[cur_iter]) - { - probabilities[kv.first]->addProbabilities(*kv.second); - // clear so we don't double count - kv.second->reset(); - } - add_statistics(&all_sizes, &means, &pct, final_sizes); - runs_left = runs_required(iterations_done_, &all_sizes, &means, &pct, *this); - // runs_left = runs_required(iterations_done_, &means, &pct, *this); - logging::note("Need another %d iterations", runs_left); - if (runs_left > 0) - { - if (reset_iter(iteration)) - { - auto& scenarios = iteration.getScenarios(); - for (auto s : scenarios) - { - threads.emplace_back(run_scenario, - s, - cur_iter, - false); - } - ++cur_iter; - // loop around to start if required - cur_iter %= all_iterations.size(); - } - } - } - // everything should be done when this section ends - return finalize_probabilities(); -} -int Model::runScenarios(const string dir_out, - const char* const weather_input, - const wx::FwiWeather& yesterday, - const char* const raster_root, - const topo::StartPoint& start_point, - const tm& start_time, - const string& perimeter, - const size_t size) -{ - fs::logging::note("Simulation start time at start of runScenarios() is %d-%02d-%02d %02d:%02d", - start_time.tm_year + 1900, - start_time.tm_mon + 1, - start_time.tm_mday, - start_time.tm_hour, - start_time.tm_min); - auto env = topo::Environment::loadEnvironment(dir_out, - raster_root, - start_point, - perimeter, - start_time.tm_year); - logging::debug("Environment loaded"); - // don't flip for Environment because that already happened - const auto position = env.findCoordinates(start_point, false); -#ifndef NDEBUG - logging::check_fatal( - std::get<0>(*position) > MAX_ROWS || std::get<1>(*position) > MAX_COLUMNS, - "Location loaded outside of grid at position (%d, %d)", - std::get<0>(*position), - std::get<1>(*position)); -#endif - logging::info("Position is (%d, %d)", std::get<0>(*position), std::get<1>(*position)); - const Location location{std::get<0>(*position), std::get<1>(*position)}; - Model model(dir_out, start_point, &env); - // HACK: set after constructor so Test doesn't need to set - model.start_time_ = start_time; - // auto x = static_cast(0.0); - // auto y = static_cast(0.0); - // const auto zone = lat_lon_to_utm(start_point, &x, &y); - // logging::note("UTM coordinates are: %d %d %d", - // zone, - // static_cast(x), - // static_cast(y)); - logging::note("Grid has size (%d, %d)", env.rows(), env.columns()); - logging::note("Fire start position is cell (%d, %d)", - location.row(), - location.column()); - auto start_hour = ((start_time.tm_hour + (static_cast(start_time.tm_min) / 60)) - / DAY_HOURS); - logging::note("Simulation start time is %d-%02d-%02d %02d:%02d", - start_time.tm_year + 1900, - start_time.tm_mon + 1, - start_time.tm_mday, - start_time.tm_hour, - start_time.tm_min); - const auto start = start_time.tm_yday + start_hour; - const auto start_day = static_cast(start); - model.readWeather(yesterday, start_point.latitude(), weather_input); - if (model.wx_.empty()) - { - logging::fatal("No weather provided"); - } - const auto w = model.wx_.begin()->second; - logging::debug("Have weather from day %d to %d", w->minDate(), w->maxDate()); - const auto numDays = (w->maxDate() - w->minDate() + 1); - const auto needDays = Settings::maxDateOffset(); - if (numDays < needDays) - { - logging::fatal("Not enough weather to proceed - have %d days but looking for %d", numDays, needDays); - } - // want to output internal representation of weather to file - // want to check that start time is in the range of the weather data we have - logging::check_fatal(start < w->minDate(), "Start time is before weather streams start"); - logging::check_fatal(start > w->maxDate(), "Start time is after weather streams end"); - logging::note("Simulation start time of %f is %s", - start, - make_timestamp(model.year(), start).c_str()); - model.makeStarts(*position, start_point, perimeter, size); - auto probabilities = - model.runIterations(start_point, start, start_day); - logging::note("Ran %d simulations", Scenario::completed()); - const auto run_time_seconds = model.runTime(); - const auto time_left = Settings::maximumTimeSeconds() - run_time_seconds.count(); - logging::debug("Finished successfully after %ld seconds with %ld seconds left", - run_time_seconds.count(), - time_left); - logging::debug("Processed %ld spread events between all scenarios", Scenario::total_steps()); - show_probabilities(probabilities); - // auto final_time = - model.saveProbabilities(probabilities, start_day, false); - // HACK: update last checked time to use in calculation - model.last_checked_ = Clock::now(); - logging::note("Total simulation time was %ld seconds", model.runTime()); - for (const auto& kv : probabilities) - { - delete kv.second; - } - return 0; -} -#ifdef DEBUG_WEATHER -void Model::outputWeather() -{ - outputWeather(wx_, "wx_hourly_out.csv"); - outputWeather(wx_daily_, "wx_daily_out.csv"); -} -void Model::outputWeather( - map>& weather, - const char* file_name) -{ - const auto file_out = string(dir_out_) + file_name; - const auto file_out_fbp = string(dir_out_) + string("fbp_") + file_name; - FILE* out = fopen(file_out.c_str(), "w"); - FILE* out_fbp = fopen(file_out_fbp.c_str(), "w"); - logging::check_fatal(nullptr == out, "Cannot open file %s for output", file_out.c_str()); - constexpr auto HEADER_FWI = "Scenario,Date,PREC,TEMP,RH,WS,WD,FFMC,DMC,DC,ISI,BUI,FWI"; - constexpr auto HEADER_FBP_PRIMARY = "CFB,CFC,FD,HFI,RAZ,ROS,SFC,TFC"; - // constexpr auto HEADER_FBP_SECONDARY = "BE,SF,ISI,FFMC,FMC,D0,RSO,CSI,FROS,BROS,HROSt,FROSt,BROSt,FCFB,BCFB,FFI,BFI,FTFC,BTFC,TI,FTI,BTI,LB,LBt,WSV,DH,DB,DF,TROS,TROSt,TCFB,TFI,TTFC,TTI"; - fprintf(out, "%s\r\n", HEADER_FWI); - fprintf(out_fbp, "%s,%s\r\n", HEADER_FWI, HEADER_FBP_PRIMARY); - size_t i = 0; - for (auto& kv : weather) - { - auto& s = kv.second; - // do we need to index this by hour and day? - // was assuming it started at 0 for first hour and day - auto wx = s->getWeather(); - size_t min_hour = s->minDate() * DAY_HOURS; - size_t wx_size = wx->size(); - size_t hour = min_hour; - for (size_t j = 0; j < wx_size; ++j) - { - size_t day = hour / 24; - auto w = wx->at(hour - min_hour); - size_t month; - size_t day_of_month; - month_and_day(year_, day, &month, &day_of_month); - if (nullptr != w) - { - fprintf(out, - FMT_OUT, - i, - year_, - static_cast(month), - static_cast(day_of_month), - static_cast(hour - day * DAY_HOURS), - 0, - 0, - w->prec().asValue(), - w->temp().asValue(), - w->rh().asValue(), - w->wind().speed().asValue(), - w->wind().direction().asValue(), - w->ffmc().asValue(), - w->dmc().asValue(), - w->dc().asValue(), - w->isi().asValue(), - w->bui().asValue(), - w->fwi().asValue(), - "\r\n"); - // printf(FMT_OUT, - // i, - // year_, - // static_cast(month), - // static_cast(day_of_month), - // static_cast(hour - day * DAY_HOURS), - // 0, - // 0, - // w->prec().asValue(), - // w->temp().asValue(), - // w->rh().asValue(), - // w->wind().speed().asValue(), - // w->wind().direction().asValue(), - // w->ffmc().asValue(), - // w->dmc().asValue(), - // w->dc().asValue(), - // w->isi().asValue(), - // w->bui().asValue(), - // w->fwi().asValue(), - // "\r\n"); - // SlopeSize SLOPE_MAX = 300; - SlopeSize SLOPE_MAX = MAX_SLOPE_FOR_DISTANCE; - SlopeSize SLOPE_INCREMENT = 200; - AspectSize ASPECT_MAX = 360; - AspectSize ASPECT_INCREMENT = 450; - const auto lookup = fs::sim::Settings::fuelLookup(); - const auto fuel = lookup.byName("C-2"); - for (SlopeSize slope = 0; slope < SLOPE_MAX; slope += SLOPE_INCREMENT) - { - for (AspectSize aspect = 0; aspect < ASPECT_MAX; aspect += ASPECT_INCREMENT) - { - // for (auto f : lookup.usedFuels()) - // const auto FUELS = {"C-1", "C-2", "C-3", "C-4", "C-5", "C-6", "C-7"}; - // for (auto fuel_name : FUELS) - { - const auto fuel_name = fuel->name(); - - // calculate and output fbp - // const auto spread = fs::sim::SpreadInfo(year_, - const fs::sim::SpreadInfo spread(year_, - month, - day_of_month, - hour, - 0, - latitude_, - longitude_, - env_->elevation(), - slope, - aspect, - fuel_name, - w); - // constexpr auto HEADER_FBP_PRIMARY = "CFB,CFC,FD,HFI,RAZ,ROS,SFC,TFC"; - constexpr auto FMT_FBP_OUT = "%ld,%d-%02d-%02d %02d:%02d:%02d,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g,%c,%1.6g,%1.6g,%1.6g,%1.6g,%1.6g%s"; - printf(FMT_FBP_OUT, - i, - year_, - static_cast(month), - static_cast(day_of_month), - static_cast(hour - day * DAY_HOURS), - 0, - 0, - w->prec().asValue(), - w->temp().asValue(), - w->rh().asValue(), - w->wind().speed().asValue(), - w->wind().direction().asValue(), - w->ffmc().asValue(), - w->dmc().asValue(), - w->dc().asValue(), - w->isi().asValue(), - w->bui().asValue(), - w->fwi().asValue(), - spread.crownFractionBurned(), - spread.crownFuelConsumption(), - spread.fireDescription(), - spread.maxIntensity(), - spread.headDirection().asDegrees(), - spread.headRos(), - spread.surfaceFuelConsumption(), - spread.totalFuelConsumption(), - "\r\n"); - fprintf(out_fbp, - FMT_FBP_OUT, - i, - year_, - static_cast(month), - static_cast(day_of_month), - static_cast(hour - day * DAY_HOURS), - 0, - 0, - w->prec().asValue(), - w->temp().asValue(), - w->rh().asValue(), - w->wind().speed().asValue(), - w->wind().direction().asValue(), - w->ffmc().asValue(), - w->dmc().asValue(), - w->dc().asValue(), - w->isi().asValue(), - w->bui().asValue(), - w->fwi().asValue(), - spread.crownFractionBurned(), - spread.crownFuelConsumption(), - spread.fireDescription(), - spread.maxIntensity(), - spread.headDirection().asDegrees(), - spread.headRos(), - spread.surfaceFuelConsumption(), - spread.totalFuelConsumption(), - "\r\n"); - } - } - } - } - ++hour; - } - ++i; - } - logging::check_fatal(0 != fclose(out), "Could not close file %s", file_out.c_str()); - logging::check_fatal(0 != fclose(out_fbp), "Could not close file %s", file_out_fbp.c_str()); -} -#endif -} diff --git a/firestarr/src/cpp/Model.h b/firestarr/src/cpp/Model.h deleted file mode 100644 index 1b69ee928..000000000 --- a/firestarr/src/cpp/Model.h +++ /dev/null @@ -1,558 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "Environment.h" -#include "Iteration.h" -#include "FireWeather.h" -namespace fs -{ -namespace topo -{ -class StartPoint; -} -namespace sim -{ -class Event; -class Scenario; -/** - * \brief Provides the ability to limit number of threads running at once. - */ -class Semaphore -{ -public: - /** - * \brief Create a Semaphore that limits number of concurrent things running - * \param n Number of concurrent things running - */ - explicit Semaphore(const int n) - : used_{0}, - limit_{n} - { - } - Semaphore(const Semaphore& rhs) = delete; - Semaphore(Semaphore&& rhs) = delete; - Semaphore& operator=(const Semaphore& rhs) = delete; - Semaphore& operator=(Semaphore&& rhs) = delete; - void set_limit(size_t limit) - { - logging::debug("Changing Semaphore limit from %d to %d", limit_, limit); - // NOTE: won't drop threads if set lower but won't give out more until below limit - limit_ = limit; - } - size_t limit() - { - return limit_; - } - /** - * \brief Notify something that's waiting so it can run - */ - void notify() - { - std::unique_lock l(mutex_); - --used_; - cv_.notify_one(); - } - /** - * \brief Wait until allowed to run - */ - void wait() - { - std::unique_lock l(mutex_); - cv_.wait(l, [this] { return used_ <= limit_; }); - ++used_; - } -private: - /** - * \brief Mutex for parallel access - */ - std::mutex mutex_; - /** - * \brief Condition variable to use for checking count - */ - std::condition_variable cv_; - /** - * \brief Variable to keep count of threads in use - */ - int used_; - /** - * \brief Limit for number of threads - */ - int limit_; -}; -/** - * \brief Indicates a section of code that is limited to a certain number of threads running at once. - */ -class CriticalSection -{ - /** - * \brief Semaphore that this keeps track of access for - */ - Semaphore& s_; -public: - /** - * \brief Constructor - * \param ss Semaphore to wait on - */ - explicit CriticalSection(Semaphore& ss) - : s_{ss} - { - s_.wait(); - } - CriticalSection(const CriticalSection& rhs) = delete; - CriticalSection(CriticalSection&& rhs) = delete; - CriticalSection& operator=(const CriticalSection& rhs) = delete; - CriticalSection& operator=(CriticalSection&& rhs) = delete; - ~CriticalSection() noexcept - { - try - { - s_.notify(); - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } - } -}; -/** - * \brief Contains all the immutable information regarding a simulation that is common between Scenarios. - */ -class Model -{ -public: - /** - * \brief Run Scenarios initialized from given inputs - * \param dir_out Folder to save outputs to - * \param weather_input Name of file to read weather from - * \param yesterday FwiWeather yesterday used for startup indices - * \param raster_root Directory to read raster inputs from - * \param start_point StartPoint to use for sunrise/sunset - * \param start_time Start time for simulation - * \param perimeter Perimeter to initialize fire from, if there is one - * \param size Size to start fire at if no Perimeter - * \return - */ - [[nodiscard]] static int runScenarios(const string dir_out, - const char* weather_input, - const wx::FwiWeather& yesterday, - const char* raster_root, - const topo::StartPoint& start_point, - const tm& start_time, - const string& perimeter, - size_t size); - /** - * \brief Cell at the given row and column - * \param row Row - * \param column Column - * \return Cell at the given row and column - */ - [[nodiscard]] -#ifdef NDEBUG - CONSTEXPR -#endif - topo::Cell - cell(const Idx row, const Idx column) const - { - return env_->cell(row, column); - } - /** - * \brief Cell at the given Location - * \param location Location to get Cell for - * \return Cell at the given Location - */ - template - [[nodiscard]] constexpr topo::Cell cell(const Position

& position) const - { - return env_->cell(position); - } - /** - * \brief Cell at the Location represented by the given hash - * \param hash_size Hash size for Location to get Cell for - * \return Cell at the Location represented by the given hash - */ - // [[nodiscard]] constexpr topo::Cell cell(const HashSize hash_size) const - // { - // return env_->cell(hash_size); - // } - /** - * \brief Number of rows in extent - * \return Number of rows in extent - */ - [[nodiscard]] constexpr Idx rows() const - { - return env_->rows(); - } - /** - * \brief Number of columns in extent - * \return Number of columns in extent - */ - [[nodiscard]] constexpr Idx columns() const - { - return env_->columns(); - } - /** - * \brief Cell width and height (m) - * \return Cell width and height (m) - */ - [[nodiscard]] constexpr MathSize cellSize() const - { - return env_->cellSize(); - } - /** - * \brief Environment simulation is occurring in - * \return Environment simulation is occurring in - */ - [[nodiscard]] constexpr const topo::Environment& environment() const - { - return *env_; - } - /** - * \brief Time that execution started - * \return Time that execution started - */ - [[nodiscard]] constexpr Clock::time_point runningSince() const - { - return running_since_; - } - /** - * \brief Maximum amount of time simulation can run for before being stopped - * \return Maximum amount of time simulation can run for before being stopped - */ - [[nodiscard]] constexpr Clock::duration timeLimit() const - { - return time_limit_; - } - /** - * \brief Time between generating interim outputs (s) - * \return Time between generating interim outputs (s) - */ - [[nodiscard]] constexpr Clock::duration interimTimeLimit() const - { - return interim_save_interval_; - } - /** - * \brief Whether or not simulation has exceeded any limits that mean it should stop - * \return Whether or not simulation has exceeded any limits that mean it should stop - */ - [[nodiscard]] bool shouldStop() const noexcept; - /** - * \brief Whether or not simulation has been running longer than maximum duration - * \return Whether or not simulation has been running longer than maximum duration - */ - [[nodiscard]] bool isOutOfTime() const noexcept; - /** - * \brief Whether or not simulation is over max simulation count - * \return Whether or not simulation is over max simulation count - */ - [[nodiscard]] bool isOverSimulationCountLimit() const noexcept; - /** - * \brief What year the weather is for - * \return What year the weather is for - */ - [[nodiscard]] int year() const noexcept - { - return year_; - } - /** - * \brief How many ignition scenarios are being used - * \return How many ignition scenarios are being used - */ - [[nodiscard]] int ignitionScenarios() const noexcept - { - return starts_.size(); - } - /** - * \brief How many Scenarios are in each Iteration - * \return How many Scenarios are in each Iteration - */ - [[nodiscard]] int scenarioCount() const noexcept - { - return wx_.size() * ignitionScenarios(); - } - /** - * \brief Difference between date and the date of minimum foliar moisture content - * \param time Date to get value for - * \return Difference between date and the date of minimum foliar moisture content - */ - [[nodiscard]] constexpr int nd(const DurationSize time) const - { - return nd_.at(static_cast(time)); - } - [[nodiscard]] const char* outputDirectory() const - { - return dir_out_.c_str(); - } - /** - * \brief Duration that model has run for - * \return std::chrono::seconds Duration model has been running for - */ - [[nodiscard]] std::chrono::seconds runTime() const; - /** - * \brief Time since last interim save - * \return std::chrono::seconds Time since last interim save - */ - [[nodiscard]] std::chrono::seconds timeSinceLastSave() const; - /** - * \brief Create a ProbabilityMap with the same extent as this - * \param time Time in simulation this ProbabilityMap represents - * \param start_time Start time of simulation - * \param min_value Lower bound of 'low' intensity range - * \param low_max Upper bound of 'low' intensity range - * \param med_max Upper bound of 'moderate' intensity range - * \param max_value Upper bound of 'high' intensity range - * \return ProbabilityMap with the same extent as this - */ - [[nodiscard]] ProbabilityMap* makeProbabilityMap(DurationSize time, - DurationSize start_time) const; - ~Model() = default; - /** - * \brief Constructor - * \param start_point StartPoint to use for sunrise/sunset times - * \param env Environment to run simulations in - */ - Model(const string dir_out, - const topo::StartPoint& start_point, - topo::Environment* env); - Model(Model&& rhs) noexcept = delete; - Model(const Model& rhs) = delete; - Model& operator=(Model&& rhs) noexcept = delete; - Model& operator=(const Model& rhs) = delete; - /** - * \brief Set constant weather - * \param weather FwiWeather to use as constant weather - */ - void setWeather(const wx::FwiWeather& weather, const Day start_day); - /** - * \brief Read weather used for Scenarios - * \param yesterday FwiWeather for yesterday - * \param latitude Latitude to calculate for - * \param filename Weather file to read - */ - void readWeather(const wx::FwiWeather& yesterday, - const MathSize latitude, - const string& filename); - /** - * \brief Make starts based on desired point and where nearest combustible cells are - * \param coordinates Coordinates in the Environment to try starting at - * \param point Point Coordinates represent - * \param perim Perimeter to start from, if there is one - * \param size Size of fire to create if no input Perimeter - */ - void makeStarts(Coordinates coordinates, - const topo::Point& point, - string perim, - size_t size); - /** - * \brief Create an Iteration by initializing Scenarios - * \param start_point StartPoint to use for sunrise/sunset - * \param start Start time for simulation - * \param start_day Start date for simulation - * \param last_date End date for simulation - * \return Iteration containing initialized Scenarios - */ - [[nodiscard]] Iteration readScenarios(const topo::StartPoint& start_point, - DurationSize start, - Day start_day, - Day last_date); - /** - * \brief Acquire a BurnedData that has already burnt cells set - * \return A BurnedData that has already burnt cells set - */ - [[nodiscard]] BurnedData* getBurnedVector() const noexcept; - /** - * \brief Return a BurnedData so it can be used in the future - * \param has_burned BurnedData to return to pool - */ - void releaseBurnedVector(BurnedData* has_burned) const noexcept; - /** - * \brief Semaphore used to limit how many things run at once - */ - static Semaphore task_limiter; - /** - * Conditions for yesterday (or constant weather) - */ - const wx::FwiWeather* yesterday() const noexcept - { - return &yesterday_; - } -private: - const string dir_out_; - /** - * \brief Add statistics for completed iterations - * \param all_sizes All sizes that have simulations have produced - * \param means Mean sizes per iteration - * \param pct 95th percentile sizes per iteration - * \param cur_sizes Sizes to add to statistics - */ - void add_statistics(vector* all_sizes, - vector* means, - vector* pct, - const util::SafeVector& sizes); - /** - * \brief Mutex for parallel access - */ - mutable mutex vector_mutex_; - /** - * \brief Start time of simulation - */ - tm start_time_; - /** - * \brief Pool of BurnedData that can be reused - */ - mutable vector> vectors_{}; - /** - * \brief Run Iterations until confidence is reached - * \param start_point StartPoint to use for sunrise/sunset - * \param start Start time for simulation - * \param start_day Start day for simulation - * \return Map of times to ProbabilityMap for that time - */ - map runIterations(const topo::StartPoint& start_point, - DurationSize start, - Day start_day); - /** - * Save probability rasters - */ - DurationSize saveProbabilities(map& probabilities, const Day start_day, const bool is_interim); - /** - * \brief Find Cell(s) that can burn closest to Location - * \param location Location to look for start Cells - */ - void findStarts(Location location); - /** - * \brief Differences between date and the date of minimum foliar moisture content - */ - array nd_{}; - /** - * \brief Map of scenario number to weather stream - */ - map> wx_{}; - /** - * \brief Map of scenario number to weather stream - */ - map> wx_daily_{}; - /** - * \brief Cell(s) that can burn closest to start Location - */ - vector> starts_{}; - /** - * \brief Time to use for simulation start - */ - Clock::time_point running_since_; - /** - * \brief Maximum amount of time simulation can run for before being stopped - */ - Clock::duration time_limit_; - /** - * \brief Time of last interim save - */ - Clock::time_point no_interim_save_since_; - /** - * \briefTime between generating interim outputs - */ - Clock::duration interim_save_interval_; - // /** - // * @brief Initial intensity map based off perimeter - // */ - // shared_ptr initial_intensity_ = nullptr; - /** - * \brief Perimeter to use for initializing simulations - */ - shared_ptr perimeter_ = nullptr; - /** - * \brief Environment to use for Model - */ - topo::Environment* env_; -#ifdef DEBUG_WEATHER - /** - * \brief Write weather that was loaded to an output file - */ - void outputWeather(); - /** - * \brief Write weather that was loaded to an output file - * \param weather Weather to write - * \param file_name Name of file to write to - */ - void outputWeather( - map>& weather, - const char* file_name); -#endif - /** - * \brief What year the weather is for - */ - int year_; - /** - * \brief If simulation is out of time and should stop - */ - bool is_out_of_time_ = false; - /** - * \brief If simulation is past the time interval between interim outputs - */ - bool should_output_interim_ = false; - /** - * \brief If simulation is being cancelled - */ - bool is_being_cancelled_ = false; - /** - * \brief How many scenarios have been completed - */ - size_t scenarios_done_ = 0; - /** - * \brief How many scenarios out of first iteration have been completed - */ - size_t scenarios_required_done_ = 0; - /** - * \brief Scenarios completed at time of last interim save - */ - atomic scenarios_last_save_ = 0; - /** - * \brief Number of scenarios per iteration - */ - size_t scenarios_per_iteration_ = 0; - /** - * \brief How many iterations have been completed - */ - size_t iterations_done_ = 0; - /** - * \brief If interim outputs are different than last time they were saved - */ - bool interim_changed_ = true; - /** - * \brief If simulation is over max simulation count - */ - bool is_over_simulation_count_ = false; - /** - * Conditions for yesterday (or constant weather) - */ - wx::FwiWeather yesterday_; - // /** - // * @brief Time when we last checked if simulation should end - // * - // */ - std::chrono::steady_clock::time_point last_checked_; - /** - * \brief Latitude to use for any calcualtions - */ - MathSize latitude_; - /** - * \brief Longitude to use for any calcualtions - */ - MathSize longitude_; -private: - mutex mutex_; -}; -} -} diff --git a/firestarr/src/cpp/Observer.cpp b/firestarr/src/cpp/Observer.cpp deleted file mode 100644 index 2b325ae4c..000000000 --- a/firestarr/src/cpp/Observer.cpp +++ /dev/null @@ -1,10 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Observer.h" -namespace fs::sim -{ -} diff --git a/firestarr/src/cpp/Observer.h b/firestarr/src/cpp/Observer.h deleted file mode 100644 index d76c3c60b..000000000 --- a/firestarr/src/cpp/Observer.h +++ /dev/null @@ -1,14 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include "Event.h" -#include "GridMap.h" -#include "Scenario.h" -namespace fs::sim -{ -} diff --git a/firestarr/src/cpp/Perimeter.cpp b/firestarr/src/cpp/Perimeter.cpp deleted file mode 100644 index 92c00f58b..000000000 --- a/firestarr/src/cpp/Perimeter.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Perimeter.h" -#include "Environment.h" -namespace fs::topo -{ -/** - * \brief A map of locations which have burned in a Scenario. - */ -class BurnedMap final - : public data::GridMap -{ -public: - // implement this class so that we can filter by fuel cells but not expose the members - /** - * \brief Constructor - * \param perim_grid Grid representing Perimeter to initialize from - * \param env Environment to use as base - */ - BurnedMap(const Grid& perim_grid, const Environment& env) - : GridMap( - *env.makeMap(static_cast(0))) - { - // HACK: fix offset if the perimeter raster is different from this one - logging::check_fatal(0 != strcmp(perim_grid.proj4().c_str(), this->proj4().c_str()), - "Invalid projection for input perimeter raster - %s instead of %s", - perim_grid.proj4().c_str(), - this->proj4().c_str()); - logging::check_fatal(perim_grid.cellSize() != this->cellSize(), - "Invalid cell size for input perimeter raster - %f instead of %f", - perim_grid.cellSize(), - this->cellSize()); - const auto offset_x = static_cast((this->xllcorner() - perim_grid.xllcorner()) / this->cellSize()); - const auto perim_origin = static_cast(perim_grid.rows() + perim_grid.yllcorner() / this->cellSize()); - const auto this_origin = static_cast(this->rows() + this->yllcorner() / this->cellSize()); - const auto offset_y = static_cast((perim_origin - this_origin)); - // make sure we don't go out of bounds on grid - const auto min_column = static_cast(offset_x < 0 ? abs(offset_x) : 0); - const auto max_columns = min(this->columns(), perim_grid.columns()); - const auto min_row = static_cast(offset_y < 0 ? abs(offset_y) : 0); - const auto max_rows = min(this->rows(), - static_cast(perim_grid.rows() - abs(offset_y))); - logging::note("Correcting perimeter raster offset by %dx%d cells", - offset_x, - offset_y); - size_t count = 0; - // since it was read in as a vector we need to check all the cells - for (auto r = min_row; r < max_rows; ++r) - { - for (auto c = min_column; c < max_columns; ++c) - { - const auto loc = env.cell(r, c); - const Location fixed_loc(static_cast(r + offset_y), - static_cast(c + offset_x)); - const auto value = perim_grid.at(fixed_loc); - if (value != perim_grid.nodataValue() && !fuel::is_null_fuel(loc)) - { - this->GridMap::set(loc, value); - // logging::debug("(%d, %d) = (%d)", fixed_loc.column(), fixed_loc.row(), value); - ++count; - } - } - } -#ifdef DEBUG_GRIDS - for (auto& kv : data) - { - logging::check_fatal(fuel::is_null_fuel(env.cell(kv.first)), - "Null fuel in BurnedData"); - } -#endif - logging::info("Loaded burned area of size %lu ha", - static_cast(this->cellSize() / 100.0 * count)); - } -}; -Perimeter::Perimeter(const string& perim, const Point& point, const Environment& env) -{ - const auto perim_grid = unique_ptr>( - data::ConstantGrid::readTiff(perim, point)); - const BurnedMap burned(*perim_grid, env); - burned_ = burned.makeList(); - edge_ = burned.makeEdge(); -#ifdef DEBUG_GRIDS - for (auto c : edge_) - { - logging::check_fatal(fuel::is_null_fuel(env.cell(c)), "Null fuel in perimeter edge"); - } -#endif -} -Perimeter::Perimeter(const Location& location, - const size_t size, - const Environment& env) -{ - // NOTE: FwiWeather is unused but could change this to try doing length to breadth ratio - auto perim_grid = env.makeMap(0); - // want to find cells in the area that fill up the size we're looking for - size_t count = 0; - // convert into number of cells - const auto num_cells = size / (100.0 * 100.0 / (perim_grid->cellSize() * perim_grid->cellSize())); - auto max_distance = sqrt(num_cells / M_PI); - perim_grid->set(location, 1); - ++count; - // HACK: assume fuel for origin matches the rest of the fire - while (num_cells > count) - { - const auto range = static_cast(ceil(max_distance)); - for (auto x = -range; x <= range && num_cells > count; ++x) - { - for (auto y = -range; y <= range && num_cells > count; ++y) - { - // look at any cell that's within the range - if (sqrt(util::pow_int<2>(x) + util::pow_int<2>(y)) < max_distance) - { - const auto r = static_cast(location.row() + x); - const auto c = static_cast(location.column() + y); - const auto loc = env.cell(r, c); - if (1 != perim_grid->at(loc) && !fuel::is_null_fuel(loc)) - { - perim_grid->set(loc, 1); - ++count; - } - } - } - } - max_distance += 0.1; - } - // burned_map_ = BurnedMap(*perim_grid, env); - auto burned = BurnedMap(*perim_grid, env); - burned_ = burned.makeList(); - edge_ = burned.makeEdge(); -} -// [[nodiscard]] const BurnedMap& Perimeter::burned_map() const noexcept -// { -// return burned_map_; -// } -[[nodiscard]] const list& Perimeter::burned() const noexcept -{ - return burned_; -} -[[nodiscard]] const list& Perimeter::edge() const noexcept -{ - return edge_; -} -} diff --git a/firestarr/src/cpp/Perimeter.h b/firestarr/src/cpp/Perimeter.h deleted file mode 100644 index 528d77f07..000000000 --- a/firestarr/src/cpp/Perimeter.h +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include "Location.h" -#include "Point.h" -namespace fs -{ -namespace wx -{ -class FwiWeather; -} -namespace topo -{ -class Environment; -/** - * \brief Perimeter for an existing fire to initialize a simulation with. - */ -class Perimeter -{ -public: - /** - * \brief Initialize perimeter from a file - * \param perim File to read from - * \param point Origin of fire - * \param env Environment to apply Perimeter to - */ - Perimeter(const string& perim, const Point& point, const Environment& env); - /** - * \brief Create a Perimeter of the given size at the given Location - * \param location Location to center Perimeter on - * \param size Size of Perimeter to create - * \param env Environment to apply Perimeter to - */ - Perimeter(const Location& location, - size_t size, - const Environment& env); - template - Perimeter(const Position

& position, - size_t size, - const Environment& env) - : Perimeter( - Location{position.hash()}, - size, - env) - { - } - // /** - // * \brief Map of all burned Locations - // * \return All Locations burned by this Perimeter - // */ - // [[nodiscard]] const BurnedMap& burned_map() const noexcept; - /** - * \brief List of all burned Locations - * \return All Locations burned by this Perimeter - */ - [[nodiscard]] const list& burned() const noexcept; - /** - * \brief List of all Locations along the edge of this Perimeter - * \return All Locations along the edge of this Perimeter - */ - [[nodiscard]] const list& edge() const noexcept; -private: - // /** - // * @brief Map of burned cells - // * - // */ - // const BurnedMap burned_map_; - // /** - // * \brief List of all burned Locations - // */ - list burned_; - /** - * \brief List of all Locations along the edge of this Perimeter - */ - list edge_; -}; -} -} diff --git a/firestarr/src/cpp/Point.h b/firestarr/src/cpp/Point.h deleted file mode 100644 index a47cdc99f..000000000 --- a/firestarr/src/cpp/Point.h +++ /dev/null @@ -1,73 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -namespace fs::topo -{ -/** - * \brief A geographic location in lat/long coordinates. - */ -class Point -{ -public: - /** - * \brief Constructor - * \param latitude Latitude (decimal degrees) - * \param longitude Longitude (decimal degrees) - */ - constexpr Point(const MathSize latitude, const MathSize longitude) noexcept - : latitude_(latitude), longitude_(longitude) - { - } - ~Point() noexcept = default; - /** - * \brief Copy constructor - * \param rhs Point to copy from - */ - Point(const Point& rhs) noexcept = default; - /** - * \brief Move constructor - * \param rhs Point to move from - */ - Point(Point&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs Point to copy from - * \return This, after assignment - */ - Point& operator=(const Point& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs Point to move from - * \return This, after assignment - */ - Point& operator=(Point&& rhs) noexcept = default; - /** - * \brief Latitude (decimal degrees) - * \return Latitude (degrees) - */ - [[nodiscard]] constexpr MathSize latitude() const noexcept - { - return latitude_; - } - /** - * \brief Longitude (decimal degrees) - * \return Longitude (decimal degrees) - */ - [[nodiscard]] constexpr MathSize longitude() const noexcept - { - return longitude_; - } -private: - /** - * \brief Latitude (decimal degrees) - */ - MathSize latitude_; - /** - * \brief Longitude (decimal degrees) - */ - MathSize longitude_; -}; -} diff --git a/firestarr/src/cpp/ProbabilityMap.cpp b/firestarr/src/cpp/ProbabilityMap.cpp deleted file mode 100644 index 32bc97154..000000000 --- a/firestarr/src/cpp/ProbabilityMap.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "ProbabilityMap.h" -#include "FBP45.h" -#include "IntensityMap.h" -#include "Model.h" -#include "GridMap.h" -namespace fs::sim -{ -/** - * \brief List of interim files that were saved - */ -static set PATHS_INTERIM{}; -static mutex PATHS_INTERIM_MUTEX{}; - -ProbabilityMap::~ProbabilityMap() -{ - // make sure we don't delete if still locked - lock_guard lock(mutex_); -} - -ProbabilityMap::ProbabilityMap(const string dir_out, - const DurationSize time, - const DurationSize start_time, - const data::GridBase& grid_info) - : dir_out_(dir_out), - all_(data::GridMap(grid_info, 0)), - time_(time), - start_time_(start_time), - perimeter_(nullptr) -{ -} -ProbabilityMap* ProbabilityMap::copyEmpty() const -{ - return new ProbabilityMap(dir_out_, - time_, - start_time_, - all_); -} -void ProbabilityMap::setPerimeter(const topo::Perimeter* const perimeter) -{ - lock_guard lock(mutex_); - perimeter_ = perimeter; -} -void ProbabilityMap::addProbabilities(const ProbabilityMap& rhs) -{ -#ifndef DEBUG_PROBABILITY - logging::check_fatal(rhs.time_ != time_, "Wrong time"); - logging::check_fatal(rhs.start_time_ != start_time_, "Wrong start time"); -#endif - lock_guard lock(mutex_); - // need to lock both maps - lock_guard lock_rhs(rhs.mutex_); - for (auto&& kv : rhs.all_.data) - { - all_.data[kv.first] += kv.second; - } - for (auto size : rhs.sizes_) - { - static_cast(util::insert_sorted(&sizes_, size)); - } -} -void ProbabilityMap::addProbability(const IntensityMap& for_time) -{ - lock_guard lock(mutex_); - std::for_each( - for_time.cbegin(), - for_time.cend(), - [this](auto&& kv) { - const auto k = kv.first; - all_.data[k] += 1; - }); - const auto size = for_time.fireSize(); - static_cast(util::insert_sorted(&sizes_, size)); -} -vector ProbabilityMap::getSizes() const -{ - return sizes_; -} -util::Statistics ProbabilityMap::getStatistics() const -{ - return util::Statistics{getSizes()}; -} -size_t ProbabilityMap::numSizes() const noexcept -{ - return sizes_.size(); -} -void ProbabilityMap::show() const -{ - lock_guard lock(mutex_); - // even if we only ran the actuals we'll still have multiple scenarios - // with different randomThreshold values - const auto day = static_cast(time_ - floor(start_time_)); - const auto s = getStatistics(); - logging::note( - "Fire size at end of day %d: %0.1f ha - %0.1f ha (mean %0.1f ha, median %0.1f ha)", - day, - s.min(), - s.max(), - s.mean(), - s.median()); -} -bool ProbabilityMap::record_if_interim(const char* filename) const -{ - lock_guard lock_interim(PATHS_INTERIM_MUTEX); - logging::verbose("Checking if %s is interim", filename); - if (nullptr != strstr(filename, "interim_")) - { - logging::verbose("Recording %s as interim", filename); - // is an interim file, so keep path for later deleting - PATHS_INTERIM.emplace(string(filename)); - logging::check_fatal(!PATHS_INTERIM.contains(filename), - "Expected %s to be in interim files list", - filename); - return true; - } - return false; -} -void ProbabilityMap::saveSizes(const string& base_name) const -{ - ofstream out; - string filename = dir_out_ + base_name + ".csv"; - record_if_interim(filename.c_str()); - out.open(filename.c_str()); - auto sizes = getSizes(); - if (!sizes.empty()) - { - // don't want to modify original array so that we can still lookup in correct order - sort(sizes.begin(), sizes.end()); - } - for (const auto& s : sizes) - { - out << s << "\n"; - } - out.close(); -} -string make_string(const char* name, const tm& t, const int day) -{ - constexpr auto mask = "%s_%03d_%04d-%02d-%02d"; - char tmp[100]; - sxprintf(tmp, - mask, - name, - day, - t.tm_year + 1900, - t.tm_mon + 1, - t.tm_mday); - return string(tmp); -}; -void ProbabilityMap::deleteInterim() -{ - lock_guard lock_interim(PATHS_INTERIM_MUTEX); - for (const auto& path : PATHS_INTERIM) - { - logging::debug("Removing interim file %s", path.c_str()); - if (util::file_exists(path.c_str())) - { - try - { -#ifdef _WIN32 - _unlink(path.c_str()); -#else - unlink(path.c_str()); -#endif - } - catch (const std::exception& err) - { - logging::error("Error trying to remove %s", - path.c_str()); - logging::error(err.what()); - } - } - } -} -void ProbabilityMap::saveAll(const tm& start_time, - const DurationSize time, - const ProcessingStatus processing_status) const -{ - lock_guard lock(mutex_); - const auto is_interim = processed != processing_status; - auto t = start_time; - auto ticks = mktime(&t); - const auto day = static_cast(round(time)); - ticks += (static_cast(day) - t.tm_yday - 1) * DAY_SECONDS; - t = *localtime(&ticks); - auto fix_string = [&t, &day, &is_interim](string prefix) { - auto text = (is_interim ? "interim_" : "") + prefix; - return make_string(text.c_str(), t, day); - }; - vector> results{}; - if (Settings::saveProbability()) - { - results.push_back(async(launch::async, - &ProbabilityMap::saveTotal, - this, - fix_string("probability"), - processing_status)); - } - results.push_back(async(launch::async, - &ProbabilityMap::saveSizes, - this, - fix_string("sizes"))); - for (auto& result : results) - { - result.wait(); - } -} -void ProbabilityMap::saveTotal(const string& base_name, const ProcessingStatus processing_status) const -{ - // FIX: do this for other outputs too - auto with_perim = all_; - if (nullptr != perimeter_) - { - for (auto loc : perimeter_->burned()) - { - // multiply initial perimeter cells so that probability shows processing status - // HACK: value is 0 if nothing is saved yet - with_perim.data[loc] = max(static_cast(1), with_perim.data[loc]) * processing_status; - } - } - // HACK: numSize() is going to be 0 if nothing saved yet - const auto divisor = max(static_cast(1.0), static_cast(numSizes())); - saveToProbabilityFile( - with_perim, - dir_out_, - base_name, - divisor); -} -void ProbabilityMap::saveTotalCount(const string& base_name) const -{ - saveToProbabilityFile(all_, dir_out_, base_name, 1); -} -void ProbabilityMap::reset() -{ - lock_guard lock(mutex_); - all_.clear(); - sizes_.clear(); -} -} diff --git a/firestarr/src/cpp/ProbabilityMap.h b/firestarr/src/cpp/ProbabilityMap.h deleted file mode 100644 index 444b31ba2..000000000 --- a/firestarr/src/cpp/ProbabilityMap.h +++ /dev/null @@ -1,175 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include "GridMap.h" -#include "Statistics.h" -#include "Perimeter.h" -namespace fs -{ -namespace sim -{ -/** - * \brief The code to burn into probability maps where the perimeter is to represent the status of the sims - */ -enum ProcessingStatus : size_t -{ - unprocessed = 2, - processing = 3, - processed = 4, -}; -class Model; -class IntensityMap; -/** - * \brief Map of the percentage of simulations in which a Cell burned in each intensity category. - */ -class ProbabilityMap -{ -public: - ProbabilityMap() = delete; - ~ProbabilityMap(); - ProbabilityMap(const ProbabilityMap& rhs) noexcept = delete; - ProbabilityMap(ProbabilityMap&& rhs) noexcept = delete; - ProbabilityMap& operator=(const ProbabilityMap& rhs) noexcept = delete; - ProbabilityMap& operator=(ProbabilityMap&& rhs) noexcept = delete; - /** - * \brief Constructor - * \param dir_out Directory to save outputs to - * \param time Time in simulation this ProbabilityMap represents - * \param start_time Start time of simulation - * \param min_value Lower bound of 'low' intensity range - * \param low_max Upper bound of 'low' intensity range - * \param med_max Upper bound of 'moderate' intensity range - * \param max_value Upper bound of 'high' intensity range - * \param grid_info GridBase to use for extent of this - */ - ProbabilityMap(const string dir_out, - DurationSize time, - DurationSize start_time, - const data::GridBase& grid_info); - /** - * \brief Assign perimeter to use for marking cells as initial perimeter - * \param perimeter Ignition grid to store for marking in outputs - */ - void setPerimeter(const topo::Perimeter* const perimeter); - /** - * \brief Combine results from another ProbabilityMap into this one - * \param rhs ProbabilityMap to combine from - */ - void addProbabilities(const ProbabilityMap& rhs); - /** - * \brief Add in an IntensityMap to the appropriate probability grid based on each cell burn intensity - * \param for_time IntensityMap to add results from - */ - void addProbability(const IntensityMap& for_time); - /** - * \brief Output Statistics to log - */ - void show() const; - /** - * \brief Save total, low, moderate, and high maps, and output information to log - * \param start_time Start time of simulation - * \param time Time for these maps - * \param processing_status Stage in processing for simulations - */ - void saveAll(const tm& start_time, - DurationSize time, - const ProcessingStatus processing_status) const; - /** - * \brief Clear maps and return to initial state - */ - void reset(); - /** - * Delete interim output files - */ - static void deleteInterim(); -private: - /** - * \brief Create a copy of this that is empty - * \return New empty Probability with same range bounds and times - */ - ProbabilityMap* copyEmpty() const; - /** - * \brief List of sizes of IntensityMaps that have been added - * \return List of sizes of IntensityMaps that have been added - */ - [[nodiscard]] vector getSizes() const; - /** - * \brief Generate Statistics on sizes of IntensityMaps that have been added - * \return Generate Statistics on sizes of IntensityMaps that have been added - */ - [[nodiscard]] util::Statistics getStatistics() const; - /** - * \brief Number of sizes that have been added - * \return Number of sizes that have been added - */ - [[nodiscard]] size_t numSizes() const noexcept; - /** - * \brief Save list of sizes - * \param base_name Base name of file to save into - */ - void saveSizes(const string& base_name) const; - /** - * \brief Save map representing all intensities - * \param base_name Base file name to save to - * \param processing_status Stage in processing for simulations - */ - void saveTotal(const string& base_name, const ProcessingStatus processing_status) const; - /** - * \brief Save map representing all intensities occurrence - * \param base_name Base file name to save to - */ - void saveTotalCount(const string& base_name) const; - /** - * \brief Make note of any interim files for later deletion - */ - bool record_if_interim(const char* filename) const; - /** - * \brief Save probability file and record filename if interim - * \return Path for file that was written - */ - template - string saveToProbabilityFile(const data::GridMap& grid, - const string& dir, - const string& base_name, - const R divisor) const - { - const string filename = grid.saveToProbabilityFile(dir, base_name, divisor); - record_if_interim(filename.c_str()); - return filename; - }; - /** - * \brief Directory to write outputs to - */ - const string dir_out_; - /** - * \brief Map representing all intensities - */ - data::GridMap all_; - /** - * \brief List of sizes for perimeters that have been added - */ - vector sizes_{}; - /** - * \brief Time in simulation this ProbabilityMap represents - */ - const DurationSize time_; - /** - * \brief Start time of simulation - */ - const DurationSize start_time_; - /** - * \brief Mutex for parallel access - */ - mutable mutex mutex_; - /** - * \brief Initial ignition grid to apply to outputs - */ - const topo::Perimeter* perimeter_; -}; -} -} diff --git a/firestarr/src/cpp/SafeVector.cpp b/firestarr/src/cpp/SafeVector.cpp deleted file mode 100644 index 85187d3a7..000000000 --- a/firestarr/src/cpp/SafeVector.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "SafeVector.h" -#include "Statistics.h" -namespace fs::util -{ -SafeVector::SafeVector(const SafeVector& rhs) - : values_(rhs.values_) -{ -} -SafeVector::SafeVector(SafeVector&& rhs) noexcept - : values_(std::move(rhs.values_)) -{ -} -SafeVector& SafeVector::operator=(const SafeVector& rhs) noexcept -{ - try - { - lock_guard lock(mutex_); - values_ = rhs.values_; - return *this; - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } -} -SafeVector& SafeVector::operator=(SafeVector&& rhs) noexcept -{ - try - { - lock_guard lock(mutex_); - values_ = std::move(rhs.values_); - return *this; - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } -} -void SafeVector::addValue(const MathSize value) -{ - lock_guard lock(mutex_); - static_cast(insert_sorted(&values_, value)); -} -vector SafeVector::getValues() const -{ - lock_guard lock(mutex_); - return values_; -} -Statistics SafeVector::getStatistics() const -{ - return Statistics{getValues()}; -} -size_t SafeVector::size() const noexcept -{ - return values_.size(); -} -} diff --git a/firestarr/src/cpp/SafeVector.h b/firestarr/src/cpp/SafeVector.h deleted file mode 100644 index a7d4e20fc..000000000 --- a/firestarr/src/cpp/SafeVector.h +++ /dev/null @@ -1,76 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -namespace fs::util -{ -class Statistics; -/** - * \brief A vector with added thread safety. - */ -class SafeVector -{ - /** - * \brief Vector of stored values - */ - std::vector values_{}; - /** - * \brief Mutex for parallel access - */ - mutable mutex mutex_{}; -public: - /** - * \brief Destructor - */ - ~SafeVector() = default; - /** - * \brief Construct empty SafeVector - */ - SafeVector() = default; - /** - * \brief Copy constructor - * \param rhs SafeVector to copy from - */ - SafeVector(const SafeVector& rhs); - /** - * \brief Move constructor - * \param rhs SafeVector to move from - */ - SafeVector(SafeVector&& rhs) noexcept; - /** - * \brief Copy assignment operator - * \param rhs SafeVector to copy from - * \return This, after assignment - */ - SafeVector& operator=(const SafeVector& rhs) noexcept; - /** - * \brief Move assignment operator - * \param rhs SafeVector to move from - * \return This, after assignment - */ - SafeVector& operator=(SafeVector&& rhs) noexcept; - /** - * \brief Add a value to the SafeVector - * \param value Value to add - */ - void addValue(MathSize value); - /** - * \brief Get a vector with the stored values - * \return A vector with the stored values - */ - [[nodiscard]] std::vector getValues() const; - /** - * \brief Calculate Statistics for values in this SafeVector - * \return Statistics for values in this SafeVector - */ - [[nodiscard]] Statistics getStatistics() const; - /** - * \brief Number of values in the SafeVector - * \return Size of the SafeVector - */ - [[nodiscard]] size_t size() const noexcept; -}; -} diff --git a/firestarr/src/cpp/Scenario.cpp b/firestarr/src/cpp/Scenario.cpp deleted file mode 100644 index 40de8b375..000000000 --- a/firestarr/src/cpp/Scenario.cpp +++ /dev/null @@ -1,1034 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#define LOG_POINTS_RELATIVE -// #undef LOG_POINTS_RELATIVE -#define LOG_POINTS_CELL -#undef LOG_POINTS_CELL - -#include "Scenario.h" -#include "Observer.h" -#include "Perimeter.h" -#include "ProbabilityMap.h" -#include "IntensityMap.h" -#include "FuelType.h" -#include "MergeIterator.h" -#include "CellPoints.h" -#include "FuelLookup.h" -#include "Location.h" -#include "Cell.h" -#include "LogPoints.h" - -namespace fs::sim -{ -using topo::Position; -using topo::Cell; -using topo::Perimeter; -using topo::SpreadKey; -using topo::StartPoint; - -constexpr auto CELL_CENTER = static_cast(0.5); -constexpr auto PRECISION = static_cast(0.001); -static atomic COUNT = 0; -static atomic COMPLETED = 0; -static atomic TOTAL_STEPS = 0; -static std::mutex MUTEX_SIM_COUNTS; -static map SIM_COUNTS{}; - -template -void do_each(T& for_list, F fct) -{ - std::for_each( - std::execution::seq, - for_list.begin(), - for_list.end(), - fct); -} - -template -void do_par(T& for_list, F fct) -{ - std::for_each( - std::execution::par_unseq, - for_list.begin(), - for_list.end(), - fct); -} -void Scenario::clear() noexcept -{ - // scheduler_.clear(); - scheduler_ = set(); - // arrival_.clear(); - arrival_ = {}; - // points_.clear(); - points_ = {}; - spread_info_ = {}; - extinction_thresholds_.clear(); - spread_thresholds_by_ros_.clear(); - max_ros_ = 0; -#ifdef DEBUG_SIMULATION - log_check_fatal(!scheduler_.empty(), "Scheduler isn't empty after clear()"); -#endif - model_->releaseBurnedVector(unburnable_); - unburnable_ = nullptr; - step_ = 0; - oob_spread_ = 0; -} -size_t Scenario::completed() noexcept -{ - return COMPLETED; -} -size_t Scenario::count() noexcept -{ - return COUNT; -} -size_t Scenario::total_steps() noexcept -{ - return TOTAL_STEPS; -} -Scenario::~Scenario() -{ - clear(); -} -/*! - * \page probability Probability of events - * - * Probability throughout the simulations is handled using pre-rolled random numbers - * based on a fixed seed, so that simulation results are reproducible. - * - * Probability is stored as 'thresholds' for a certain event on a day-by-day and hour-by-hour - * basis. If the calculated probability of that type of event matches or exceeds the threshold - * then the event will occur. - * - * Each iteration of a scenario will have its own thresholds, and thus different behaviour - * can occur with the same input indices. - * - * Thresholds are used to determine: - * - extinction - * - spread events - */ -static void make_threshold(vector* thresholds, - mt19937* mt, - const Day start_day, - const Day last_date, - ThresholdSize (*convert)(double value)) -{ - const auto total_weight = Settings::thresholdScenarioWeight() + Settings::thresholdDailyWeight() + Settings::thresholdHourlyWeight(); - uniform_real_distribution rand(0.0, 1.0); - const auto general = rand(*mt); - for (size_t i = start_day; i < MAX_DAYS; ++i) - { - const auto daily = rand(*mt); - for (auto h = 0; h < DAY_HOURS; ++h) - { - // generate no matter what so if we extend the time period the results - // for the first days don't change - const auto hourly = rand(*mt); - // only save if we're going to use it - // HACK: +1 so if it's exactly at the end time there's something there - if (i <= static_cast(last_date + 1)) - { - // subtract from 1.0 because we want weight to make things more likely not less - // ensure we stay between 0 and 1 - thresholds->at((i - start_day) * DAY_HOURS + h) = - convert( - max(0.0, - min(1.0, - 1.0 - (Settings::thresholdScenarioWeight() * general + Settings::thresholdDailyWeight() * daily + Settings::thresholdHourlyWeight() * hourly) / total_weight))); - // thresholds->at((i - start_day) * DAY_HOURS + h) = 0.0; - } - } - } -} -template -constexpr V same(const V value) noexcept -{ - return value; -} -static void make_threshold(vector* thresholds, - mt19937* mt, - const Day start_day, - const Day last_date) -{ - make_threshold(thresholds, mt, start_day, last_date, &same); -} -Scenario::Scenario(Model* model, - const size_t id, - wx::FireWeather* weather, - wx::FireWeather* weather_daily, - const DurationSize start_time, - // const shared_ptr& initial_intensity, - const shared_ptr& perimeter, - const StartPoint& start_point, - const Day start_day, - const Day last_date) - : Scenario(model, - id, - weather, - weather_daily, - start_time, - // initial_intensity, - perimeter, - nullptr, - start_point, - start_day, - last_date) -{ -} -Scenario::Scenario(Model* model, - const size_t id, - wx::FireWeather* weather, - wx::FireWeather* weather_daily, - const DurationSize start_time, - const shared_ptr& start_cell, - const StartPoint& start_point, - const Day start_day, - const Day last_date) - : Scenario(model, - id, - weather, - weather_daily, - start_time, - // make_unique(*model, nullptr), - nullptr, - start_cell, - start_point, - start_day, - last_date) -{ -} -// HACK: just set next start point here for surface right now -Scenario* Scenario::reset_with_new_start(const shared_ptr& start_cell, - util::SafeVector* final_sizes) -{ - start_cell_ = start_cell; - // FIX: remove duplicated code - // logging::extensive("Set cell; resetting"); - // return reset(nullptr, nullptr, final_sizes); - cancelled_ = false; - model_->releaseBurnedVector(unburnable_); - unburnable_ = nullptr; - current_time_ = start_time_; - intensity_ = nullptr; - probabilities_ = nullptr; - final_sizes_ = final_sizes; - ran_ = false; - clear(); - current_time_ = start_time_ - 1; - points_ = {}; - intensity_ = make_unique(model()); - // HACK: never reset these if using a surface - // if (!Settings::surface()) - { - // these are reset in clear() - // spread_info_ = {}; - // max_ros_ = 0; - // surrounded_ = POOL_BURNED_DATA.acquire(); - current_time_index_ = numeric_limits::max(); - } - ++COUNT; - { - // want a global count of how many times this scenario ran - std::lock_guard lk(MUTEX_SIM_COUNTS); - simulation_ = ++SIM_COUNTS[id_]; - } - return this; -} -Scenario* Scenario::reset(mt19937* mt_extinction, - mt19937* mt_spread, - util::SafeVector* final_sizes) -{ - cancelled_ = false; - model_->releaseBurnedVector(unburnable_); - unburnable_ = nullptr; - current_time_ = start_time_; - intensity_ = nullptr; - // weather_(weather); - // model_(model); - probabilities_ = nullptr; - final_sizes_ = final_sizes; - // start_point_(std::move(start_point)); - // id_(id); - // start_time_(start_time); - // simulation_(-1); - // start_day_(start_day); - // last_date_(last_date); - ran_ = false; - // track this here because reset is always called before use - // HACK: +2 so there's something there if we land exactly on the end date - const auto num = (static_cast(last_date_) - start_day_ + 2) * DAY_HOURS; - clear(); - extinction_thresholds_.resize(num); - spread_thresholds_by_ros_.resize(num); - // if these are null then all probability thresholds remain 0 - if (nullptr != mt_extinction) - { - make_threshold(&extinction_thresholds_, mt_extinction, start_day_, last_date_); - } - if (nullptr != mt_spread) - { - make_threshold(&spread_thresholds_by_ros_, - mt_spread, - start_day_, - last_date_, - &SpreadInfo::calculateRosFromThreshold); - } - // std::fill(extinction_thresholds_.begin(), extinction_thresholds_.end(), 1.0 - abs(1.0 / (10 * id_))); - // std::fill(spread_thresholds_by_ros_.begin(), spread_thresholds_by_ros_.end(), 1.0 - abs(1.0 / (10 * id_))); - // std::fill(extinction_thresholds_.begin(), extinction_thresholds_.end(), 0.5); - // std::fill(spread_thresholds_by_ros_.begin(), spread_thresholds_by_ros_.end(), SpreadInfo::calculateRosFromThreshold(0.5)); - current_time_ = start_time_ - 1; - points_ = {}; - // don't do this until we run so that we don't allocate memory too soon - // log_verbose("Applying initial intensity map"); - // // HACK: if initial_intensity is null then perimeter must be too? - // intensity_ = (nullptr == initial_intensity_) - // ? make_unique(model(), nullptr) - // : make_unique(*initial_intensity_); - intensity_ = make_unique(model()); - spread_info_ = {}; - arrival_ = {}; - max_ros_ = 0; - // surrounded_ = POOL_BURNED_DATA.acquire(); - current_time_index_ = numeric_limits::max(); - ++COUNT; - { - // want a global count of how many times this scenario ran - std::lock_guard lk(MUTEX_SIM_COUNTS); - simulation_ = ++SIM_COUNTS[id_]; - } - return this; -} -void Scenario::evaluate(const Event& event) -{ -#ifdef DEBUG_SIMULATION - log_check_fatal(event.time() < current_time_, - "Expected time to be > %f but got %f", - current_time_, - event.time()); -#endif - const auto& p = event.cell(); - const auto x = p.column() + CELL_CENTER; - const auto y = p.row() + CELL_CENTER; - const XYPos p0{x, y}; - switch (event.type()) - { - case Event::FIRE_SPREAD: - ++step_; -#ifdef DEBUG_POINTS - // if (fs::logging::Log::getLogLevel() >= fs::logging::LOG_VERBOSE) - { - const auto ymd = fs::make_timestamp(model().year(), event.time()); - // log_note("Handling spread event for time %f representing %s with %ld points", event.time(), ymd.c_str(), points_.size()); - } -#endif - scheduleFireSpread(event); - break; - case Event::SAVE: - saveStats(event.time()); - break; - case Event::NEW_FIRE: - // HACK: don't do this in constructor because scenario creates this in its constructor - // HACK: insert point as originating from itself - points_.insert( - x, - y); - if (fuel::is_null_fuel(event.cell())) - { - log_fatal("Trying to start a fire in non-fuel"); - } - log_verbose("Starting fire at point (%f, %f) in fuel type %s at time %f", - x, - y, - fuel::FuelType::safeName(fuel::check_fuel(event.cell())), - event.time()); - if (!survives(event.time(), event.cell(), event.timeAtLocation())) - { - // const auto wx = weather(event.time()); - // HACK: show daily values since that's what survival uses - const auto wx = weather_daily(event.time()); - log_info("Didn't survive ignition in %s with weather %f, %f", - fuel::FuelType::safeName(fuel::check_fuel(event.cell())), - wx->ffmc(), - wx->dmc()); - // HACK: we still want the fire to have existed, so set the intensity of the origin - } - // fires start with intensity of 1 - burn(event); - scheduleFireSpread(event); - break; - case Event::END_SIMULATION: - log_verbose("End simulation event reached at %f", event.time()); - endSimulation(); - break; - default: - throw runtime_error("Invalid event type"); - } -} -Scenario::Scenario(Model* model, - const size_t id, - wx::FireWeather* weather, - wx::FireWeather* weather_daily, - const DurationSize start_time, - // const shared_ptr& initial_intensity, - const shared_ptr& perimeter, - const shared_ptr& start_cell, - StartPoint start_point, - const Day start_day, - const Day last_date) - : current_time_(start_time), - unburnable_(nullptr), - intensity_(nullptr), - // initial_intensity_(initial_intensity), - perimeter_(perimeter), - // surrounded_(nullptr), - max_ros_(0), - start_cell_(start_cell), - weather_(weather), - weather_daily_(weather_daily), - model_(model), - probabilities_(nullptr), - final_sizes_(nullptr), - start_point_(std::move(start_point)), - id_(id), - start_time_(start_time), - simulation_(-1), - start_day_(start_day), - last_date_(last_date), - ran_(false), - step_(0), - oob_spread_(0) -{ - last_save_ = weather_->minDate(); - const auto wx = weather_->at(start_time_); - logging::check_fatal(nullptr == wx, - "No weather for start time %s", - make_timestamp(model->year(), start_time_).c_str()); - const auto saves = Settings::outputDateOffsets(); - const auto last_save = start_day_ + saves[saves.size() - 1]; - logging::check_fatal(last_save > weather_->maxDate(), - "No weather for last save time %s", - make_timestamp(model->year(), last_save).c_str()); -} -void Scenario::saveStats(const DurationSize time) const -{ - probabilities_->at(time)->addProbability(*intensity_); - if (time == last_save_) - { - final_sizes_->addValue(intensity_->fireSize()); - } -} -bool Scenario::ran() const noexcept -{ - return ran_; -} -Scenario::Scenario(Scenario&& rhs) noexcept - : save_points_(std::move(rhs.save_points_)), - extinction_thresholds_(std::move(rhs.extinction_thresholds_)), - spread_thresholds_by_ros_(std::move(rhs.spread_thresholds_by_ros_)), - current_time_(rhs.current_time_), - points_(std::move(rhs.points_)), - unburnable_(std::move(rhs.unburnable_)), - scheduler_(std::move(rhs.scheduler_)), - intensity_(std::move(rhs.intensity_)), - // initial_intensity_(std::move(rhs.initial_intensity_)), - perimeter_(std::move(rhs.perimeter_)), - spread_info_(std::move(rhs.spread_info_)), - arrival_(std::move(rhs.arrival_)), - max_ros_(rhs.max_ros_), - start_cell_(std::move(rhs.start_cell_)), - weather_(rhs.weather_), - weather_daily_(rhs.weather_daily_), - model_(rhs.model_), - probabilities_(rhs.probabilities_), - final_sizes_(rhs.final_sizes_), - start_point_(std::move(rhs.start_point_)), - id_(rhs.id_), - start_time_(rhs.start_time_), - last_save_(rhs.last_save_), - simulation_(rhs.simulation_), - start_day_(rhs.start_day_), - last_date_(rhs.last_date_), - ran_(rhs.ran_) -{ -} -Scenario& Scenario::operator=(Scenario&& rhs) noexcept -{ - if (this != &rhs) - { - save_points_ = std::move(rhs.save_points_); - extinction_thresholds_ = std::move(rhs.extinction_thresholds_); - spread_thresholds_by_ros_ = std::move(rhs.spread_thresholds_by_ros_); - points_ = std::move(rhs.points_); - current_time_ = rhs.current_time_; - scheduler_ = std::move(rhs.scheduler_); - intensity_ = std::move(rhs.intensity_); - // initial_intensity_ = std::move(rhs.initial_intensity_); - perimeter_ = std::move(rhs.perimeter_); - // surrounded_ = rhs.surrounded_; - start_cell_ = std::move(rhs.start_cell_); - weather_ = rhs.weather_; - weather_daily_ = rhs.weather_daily_; - model_ = rhs.model_; - probabilities_ = rhs.probabilities_; - final_sizes_ = rhs.final_sizes_; - start_point_ = std::move(rhs.start_point_); - id_ = rhs.id_; - start_time_ = rhs.start_time_; - last_save_ = rhs.last_save_; - simulation_ = rhs.simulation_; - start_day_ = rhs.start_day_; - last_date_ = rhs.last_date_; - ran_ = rhs.ran_; - } - return *this; -} -void Scenario::burn(const Event& event) -{ -#ifdef DEBUG_SIMULATION - log_check_fatal( - intensity_->hasBurned(event.cell()), - "Re-burning cell (%d, %d)", - event.cell().column(), - event.cell().row()); -#endif -#ifdef DEBUG_POINTS - log_check_fatal( - (*unburnable_)[event.cell().hash()], - "Burning unburnable cell (%d, %d)", - event.cell().column(), - event.cell().row()); -#endif - // Observers only care about cells burning so do it here - intensity_->burn(event.cell()); -#ifdef DEBUG_GRIDS - log_check_fatal( - !intensity_->hasBurned(event.cell()), - "Wasn't marked as burned after burn"); -#endif - arrival_[event.cell()] = event.time(); - // scheduleFireSpread(event); -} -bool Scenario::isSurrounded(const Location& location) const -{ - return intensity_->isSurrounded(location); -} -Cell Scenario::cell(const InnerPos& p) const noexcept -{ - return cell(p.second, p.first); -} -string Scenario::add_log(const char* format) const noexcept -{ - const string tmp; - stringstream iss(tmp); - static char buffer[1024]{0}; - sxprintf(buffer, "Scenario %4ld.%04ld (%3f): ", id(), simulation(), current_time_); - iss << buffer << format; - return iss.str(); -} -#ifdef DEBUG_PROBABILITY -void saveProbabilities(const string& dir, - const string& base_name, - vector& thresholds) -{ - ofstream out; - out.open(dir + base_name + ".csv"); - for (auto v : thresholds) - { - out << v << '\n'; - } - out.close(); -} -#endif -Scenario* Scenario::run(map* probabilities) -{ -#ifdef DEBUG_SIMULATION - log_check_fatal(ran(), "Scenario has already run"); -#endif - log_verbose("Starting"); - CriticalSection _(Model::task_limiter); - logging::debug("Concurrent Scenario limit is %d", Model::task_limiter.limit()); - unburnable_ = model_->getBurnedVector(); - probabilities_ = probabilities; - log_verbose("Setting save points"); - for (auto time : save_points_) - { - // NOTE: these happen in this order because of the way they sort based on type - addEvent(Event::makeSave(static_cast(time))); - } - if (nullptr == perimeter_) - { - addEvent(Event::makeNewFire(start_time_, cell(*start_cell_))); - } - else - { - log_verbose("Applying perimeter"); - intensity_->applyPerimeter(*perimeter_); - log_verbose("Perimeter applied"); - const auto& env = model().environment(); - log_verbose("Igniting points"); - for (const auto& location : perimeter_->edge()) - { - // const auto cell = env.cell(location.hash()); - const auto cell = env.cell(location); -#ifdef DEBUG_SIMULATION - log_check_fatal(fuel::is_null_fuel(cell), "Null fuel in perimeter"); -#endif - const auto x = cell.column() + CELL_CENTER; - const auto y = cell.row() + CELL_CENTER; - const XYPos p0{x, y}; - // log_verbose("Adding point (%d, %d)", - log_verbose("Adding point (%f, %f)", - x, - y); - points_.insert( - x, - y); - // auto e = points_.try_emplace(cell, cell.column() + CELL_CENTER, cell.row() + CELL_CENTER); - // log_check_fatal(!e.second, - // "Excepted to add point to new cell but (%ld, %ld) is already in map", - // cell.column(), - // cell.row()); - } - addEvent(Event::makeFireSpread(start_time_)); - } - // HACK: make a copy of the event so that it still exists after it gets processed - // NOTE: sorted so that EventSaveASCII is always just before this - // Only run until last time we asked for a save for - log_verbose("Creating simulation end event for %f", last_save_); - addEvent(Event::makeEnd(last_save_)); - // mark all original points as burned at start - for (auto& kv : points_.map_) - { - const auto& location = cell(kv.first); - // const auto& location = kv.first; - // would be burned already if perimeter applied - if (canBurn(location)) - { - const auto fake_event = Event::makeFireSpread( - start_time_, - location); - burn(fake_event); - } - } - while (!cancelled_ && !scheduler_.empty()) - { - evaluateNextEvent(); - // // FIX: the timer thread can cancel these instead of having this check - // if (!evaluateNextEvent()) - // { - // cancel(true); - // } - } - ++TOTAL_STEPS; - model_->releaseBurnedVector(unburnable_); - unburnable_ = nullptr; - if (cancelled_) - { - return nullptr; - } - const auto completed = ++COMPLETED; - // const auto count = Settings::surface() ? model_->ignitionScenarios() : (+COUNT); - // HACK: use + to pull value out of atomic - const auto count = - (+COUNT); - const auto log_level = (0 == (completed % 1000)) ? logging::LOG_NOTE : logging::LOG_INFO; - { -#ifdef NDEBUG - log_output(log_level, - "[% d of % d] Completed with final size % 0.1f ha", - completed, - count, - currentFireSize()); -#else - // try to make output consistent if in debug mode - log_output(log_level, "Completed with final size %0.1f ha", currentFireSize()); -#endif - } - ran_ = true; -#ifdef DEBUG_PROBABILITY - // nice to have this get output when debugging, but only need it in extreme cases - if (logging::Log::getLogLevel() <= logging::LOG_EXTENSIVE) - { - char buffer[64]{0}; - sxprintf(buffer, - "%03zu_%06ld_extinction", - id(), - simulation()); - saveProbabilities(model().outputDirectory(), string(buffer), extinction_thresholds_); - sxprintf(buffer, - "%03zu_%06ld_spread", - id(), - simulation()); - saveProbabilities(model().outputDirectory(), string(buffer), spread_thresholds_by_ros_); - } -#endif - if (oob_spread_ > 0) - { - log_warning("Tried to spread out of bounds %ld times", oob_spread_); - } - return this; -} -CellPointsMap apply_offsets_spreadkey( - const DurationSize& arrival_time, - const DurationSize& duration, - const OffsetSet& offsets, - spreading_points::mapped_type& cell_pts_map) -{ - // NOTE: really tried to do this in parallel, but not enough points - // in a cell for it to work well - CellPointsMap r1{}; - OffsetSet offsets_after_duration{}; - logging::verbose("Applying %ld offsets", offsets.size()); - // // offsets_after_duration.resize(offsets.size()); - // std::transform( - // offsets.cbegin(), - // offsets.cend(), - // std::back_inserter(offsets_after_duration), - offsets_after_duration.resize(offsets.size()); - std::transform( - offsets.cbegin(), - offsets.cend(), - offsets_after_duration.begin(), - [&duration, &arrival_time](const ROSOffset& r_p) { - const auto& p = std::get<0>(r_p); - // logging::verbose("ros %f; x %f; y %f; duration %f;", - // ros, - // p.first, - // p.second, - // duration); - return ROSOffset( - Offset(p.first * duration, p.second * duration)); - }); - logging::verbose("Calculated %ld offsets after duration %f", offsets_after_duration.size(), duration); - logging::verbose("cell_pts_map has %ld items", cell_pts_map.size()); - for (auto& pts_for_cell : cell_pts_map) - { - CellPoints& cell_pts = std::get<1>(pts_for_cell); -#ifdef DEBUG_CELLPOINTS - logging::note( - "cell_pts for (%d, %d) has %ld items", - src.column(), - src.row(), - cell_pts.size()); -#endif - if (cell_pts.empty()) - { -#ifdef DEBUG_CELLPOINTS - logging::note( - "Cell (%d, %d) ignored because empty", - src.column(), - src.row()); -#endif - continue; - } - auto& pts = cell_pts.pts_.points(); - // combine point and direction that lead to it so we can get unique values - // c++23 is where zip() gets implemented - // auto pt_dirs = std::ranges::views::zip(pts, dirs); - for (const auto& pt : pts) - { - const auto& cell_x = cell_pts.cell_x_y_.first; - const auto& cell_y = cell_pts.cell_x_y_.second; - // // FIX: HACK: recompose into XYPos - // const XYPos src{location.column() + cell_x, location.row() + cell_y}; - const XYPos src{pt.first + cell_x, pt.second + cell_y}; - // apply offsets to point - // should be quicker to loop over offsets in inner loop - for (const ROSOffset& r_p : offsets_after_duration) - { - const auto& out = std::get<0>(r_p); - const auto& x_o = out.first; - const auto& y_o = out.second; - { - const auto new_x = x_o + pt.first + cell_x; - const auto new_y = y_o + pt.second + cell_y; - r1.insert( - new_x, - new_y); -#ifdef DEBUG_CELLPOINTS - logging::note("r1 is now %ld items", r1.size()); -#endif - } - } - } - } - return r1; -} -void Scenario::scheduleFireSpread(const Event& event) -{ - const auto time = event.time(); - // HACK: if a surface then always use 1600 weather - // keeps a bunch of things we don't need in it if we don't reset? - // const auto this_time = Settings::surface() ? util::time_index(static_cast(time), 16) : util::time_index(time); - const auto this_time = util::time_index(time); - // const auto wx_time = Settings::surface() ? util::to_time(util::time_index(static_cast(time), 16)) : util::to_time(this_time); - // const auto wx_time = util::to_time(this_time); - const auto wx = - weather(time); - const auto wx_daily = - weather_daily(time); - // note("time is %f", time); - current_time_ = time; - logging::check_fatal(nullptr == wx, "No weather available for time %f", time); - // log_note("%d points", points_->size()); - const auto next_time = static_cast(this_time + 1) / DAY_HOURS; - // should be in minutes? - const auto max_duration = (next_time - time) * DAY_MINUTES; - // log_verbose("time is %f, next_time is %f, max_duration is %f", - // time, - // next_time, - // max_duration); - const auto max_time = time + max_duration / DAY_MINUTES; - // if (wx->ffmc().asValue() < minimumFfmcForSpread(time)) - // HACK: use the old ffmc for this check to be consistent with previous version - if (wx_daily->ffmc().asValue() < minimumFfmcForSpread(time)) - { - addEvent(Event::makeFireSpread(max_time)); - log_extensive("Waiting until %f because of FFMC", max_time); - return; - } - // log_note("There are %ld spread offsets calculated", spread_info_.size()); - if (current_time_index_ != this_time) - { - // logging::check_fatal(Settings::surface() && current_time_index_ != numeric_limits::max(), - // "Expected to only pick weather time once"); - current_time_index_ = this_time; - // seemed like it would be good to keep offsets but max_ros_ needs to reset or things slow to a crawl? - { - spread_info_ = {}; - } - max_ros_ = 0.0; - } - // get once and keep - const auto ros_min = Settings::minimumRos(); - spreading_points to_spread{}; - // make block to prevent it being visible beyond use - { - // if we use an iterator this way we don't need to copy keys to erase things - auto it = points_.map_.begin(); - while (it != points_.map_.end()) - { - const Location& loc = it->first; - const Cell for_cell = cell(loc); - const auto key = for_cell.key(); - // HACK: need to lookup before emplace since might try to create Cell without fuel - // if (!fuel::is_null_fuel(loc)) - // const auto h = for_cell.hash(); - // if (!(*unburnable_)[h]) - // if (canBurn(for_cell)) - { - const auto& origin_inserted = spread_info_.try_emplace(key, *this, time, key, nd(time), wx); - // any cell that has the same fuel, slope, and aspect has the same spread - const auto& origin = origin_inserted.first->second; - // filter out things not spreading fast enough here so they get copied if they aren't - // isNotSpreading() had better be true if ros is lower than minimum - const auto ros = origin.headRos(); - if (ros >= ros_min) - { - max_ros_ = max(max_ros_, ros); - // NOTE: shouldn't be Cell if we're looking up by just Location later - to_spread[key].emplace_back(loc, std::move(it->second)); - it = points_.map_.erase(it); -#ifdef DEBUG_CELLPOINTS - auto& v = to_spread[key]; - const auto n = v.size(); - const auto& p = v[n - 1].second; - logging::note("added %ld items to to_spread[%d][(%d, %d)]", - p.size(), - key, - loc.column(), - loc.row()); -#endif - } - else - { - ++it; - } - } - } - } - // if nothing in to_spread then nothing is spreading - if (to_spread.empty()) - { - // if no spread then we left everything back in points_ still - log_verbose("Waiting until %f", max_time); - addEvent(Event::makeFireSpread(max_time)); - return; - } - // note("Max spread is %f, max_ros is %f", - // Settings::maximumSpreadDistance() * cellSize(), - // max_ros_); - const auto duration = ((max_ros_ > 0) - ? min(max_duration, - Settings::maximumSpreadDistance() * cellSize() / max_ros_) - : max_duration); - // note("Spreading for %f minutes", duration); - const auto new_time = time + duration / DAY_MINUTES; - CellPointsMap cell_pts{}; - auto spread = std::views::transform( - to_spread, - [this, &duration, &new_time]( - spreading_points::value_type& kv0) -> CellPointsMap { - auto& key = kv0.first; - const auto& offsets = spread_info_[key].offsets(); - spreading_points::mapped_type& cell_pts = kv0.second; - auto r = apply_offsets_spreadkey(new_time, duration, offsets, cell_pts); - return r; - }); - auto it = spread.begin(); - while (spread.end() != it) - { - const CellPointsMap& cell_pts_cur = *it; - // // HACK: keep old behaviour until we can figure out whey removing isn't the same as not adding - // const auto h = cell_pts.location().hash(); - // if (!unburnable[h]) - // { - cell_pts.merge(*unburnable_, cell_pts_cur); - // } - ++it; - } -#ifdef DEBUG_CELLPOINTS - const auto n_c = cell_pts.size(); -#endif - cell_pts.remove_if( - [this]( - const pair& kv) { - const auto& location = kv.first; - const auto h = location.hash(); - // clear out if unburnable - const auto do_clear = (*unburnable_)[h]; - return do_clear; - }); -#ifdef DEBUG_CELLPOINTS - logging::note("%ld cell_pts before remove_if() and %ld after", n_c, cell_pts.size()); -#endif - // need to merge new points back into cells that didn't spread - points_.merge( - *unburnable_, - cell_pts); - // if we move everything out of points_ we can parallelize this check? - do_each( - points_.map_, - [this, &new_time](pair& kv) { - const auto for_cell = cell(kv.first); - CellPoints& pts = kv.second; - // logging::check_fatal(pts.empty(), "Empty points for some reason"); - // ******************* CHECK THIS BECAUSE IF SOMETHING IS IN HERE SHOULD IT ALWAYS HAVE SPREAD????? *****************8 - const auto& seek_spread = spread_info_.find(for_cell.key()); - const auto max_intensity = (spread_info_.end() == seek_spread) ? 0 : seek_spread->second.maxIntensity(); - // // if we don't have empty cells anymore then intensity should always be >0? - // logging::check_fatal(max_intensity <= 0, - // "Expected max_intensity to be > 0 but got %f", - // max_intensity); - // HACK: just use side-effect to log and check bounds - if (canBurn(for_cell) && max_intensity > 0) - { - // // HACK: make sure it can't round down to 0 - // const auto intensity = static_cast(max( - // 1.0, - // max_intensity)); - // HACK: just use the first cell as the source - // FIX: HACK: only output spread within for now - // const auto& spread = pts.spread_internal_; - const auto fake_event = Event::makeFireSpread( - new_time, - for_cell); - burn(fake_event); - } - if (!(*unburnable_)[for_cell.hash()] - // && canBurn(for_cell) - && ((survives(new_time, for_cell, new_time - arrival_[for_cell]) - && !isSurrounded(for_cell)))) - { - const auto r = for_cell.row(); - const auto c = for_cell.column(); - const Location loc{r, c}; - std::swap(points_.map_[loc], pts); - } - else - { - // just inserted false, so make sure unburnable gets updated - // whether it went out or is surrounded just mark it as unburnable - (*unburnable_)[for_cell.hash()] = true; - // not swapping means these points get dropped - } - }); - log_extensive("Spreading %d cells until %f", points_.map_.size(), new_time); - addEvent(Event::makeFireSpread(new_time)); -} -MathSize - Scenario::currentFireSize() const -{ - return intensity_->fireSize(); -} -bool Scenario::canBurn(const Cell& location) const -{ - return intensity_->canBurn(location); -} -// bool Scenario::canBurn(const HashSize hash) const -//{ -// return intensity_->canBurn(hash); -// } -bool Scenario::hasBurned(const Location& location) const -{ - return intensity_->hasBurned(location); -} -// bool Scenario::hasBurned(const HashSize hash) const -//{ -// return intensity_->hasBurned(hash); -// } -void Scenario::endSimulation() noexcept -{ - log_verbose("Ending simulation"); - // scheduler_.clear(); - scheduler_ = set(); -} -void Scenario::addSaveByOffset(const int offset) -{ - // offset is from begging of the day the simulation starts - // e.g. 1 is midnight, 2 is tomorrow at midnight - addSave(static_cast(startTime()) + offset); -} -vector Scenario::savePoints() const -{ - return save_points_; -} -template -void Scenario::addSave(V time) -{ - last_save_ = max(last_save_, static_cast(time)); - save_points_.push_back(time); -} -void Scenario::addEvent(Event&& event) -{ - scheduler_.insert(std::move(event)); -} -// bool Scenario::evaluateNextEvent() -void Scenario::evaluateNextEvent() -{ - // make sure to actually copy it before we erase it - const auto& event = *scheduler_.begin(); - evaluate(event); - if (!scheduler_.empty()) - { - scheduler_.erase(event); - } - // return !model_->isOutOfTime(); - // return cancelled_; -} -void Scenario::cancel(bool show_warning) noexcept -{ - // ignore if already cancelled - if (!cancelled_) - { - cancelled_ = true; - if (show_warning) - { - log_warning("Simulation cancelled"); - } - } -} -} diff --git a/firestarr/src/cpp/Scenario.h b/firestarr/src/cpp/Scenario.h deleted file mode 100644 index 2dcaabe0b..000000000 --- a/firestarr/src/cpp/Scenario.h +++ /dev/null @@ -1,616 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "stdafx.h" -#include "EventCompare.h" -#include "FireWeather.h" -#include "IntensityMap.h" -#include "Model.h" -#include "Settings.h" -#include "StartPoint.h" -#include "InnerPos.h" -#include "FireSpread.h" -#include "CellPoints.h" - -namespace fs::sim -{ -class LogPoints; -class IObserver; -class Event; -using topo::Location; -using topo::Position; -using PointSet = vector; -/** - * \brief A single Scenario in an Iteration using a specific FireWeather stream. - */ -class Scenario - : public logging::SelfLogger -{ -public: - /** - * \brief Number of Scenarios that have completed running - * \return Number of Scenarios that have completed running - */ - [[nodiscard]] static size_t completed() noexcept; - /** - * \brief Number of Scenarios that have been initialized - * \return Number of Scenarios that have been initialized - */ - [[nodiscard]] static size_t count() noexcept; - /** - * \brief Total number of spread events for all Scenarios - * \return Total number of spread events for all Scenarios - */ - [[nodiscard]] static size_t total_steps() noexcept; - /** - * \brief Weighted Danger Severity Rating - * \return Weighted Danger Severity Rating - */ - [[nodiscard]] constexpr size_t weightedDsr() const noexcept - { - return weather_->weightedDsr(); - } - virtual ~Scenario(); - /** - * \brief Constructor - * \param model Model running this Scenario - * \param id Identifier - * \param weather Hourly weather stream to use - * \param weather Weather stream to use for spread and extinction probability - * \param start_time Start time for simulation - // * \param initial_intensity Intensity grid to start from - * \param perimeter Perimeter to initialize with - * \param start_point StartPoint to use sunrise/sunset times from - * \param start_day First day of simulation - * \param last_date Last day of simulation - */ - Scenario(Model* model, - size_t id, - wx::FireWeather* weather, - wx::FireWeather* weather_daily, - DurationSize start_time, - // const shared_ptr& initial_intensity, - const shared_ptr& perimeter, - const topo::StartPoint& start_point, - Day start_day, - Day last_date); - /** - * \brief Constructor - * \param model Model running this Scenario - * \param id Identifier - * \param weather Hourly weather stream to use - * \param weather Weather stream to use for spread and extinction probability - * \param start_time Start time for simulation - * \param start_cell Cell to start ignition in - * \param start_point StartPoint to use sunrise/sunset times from - * \param start_day First day of simulation - * \param last_date Last day of simulation - */ - Scenario(Model* model, - size_t id, - wx::FireWeather* weather, - wx::FireWeather* weather_daily, - DurationSize start_time, - const shared_ptr& start_cell, - const topo::StartPoint& start_point, - Day start_day, - Day last_date); - /** - * \brief Move constructor - * \param rhs Scenario to move from - */ - Scenario(Scenario&& rhs) noexcept; - Scenario(const Scenario& rhs) = delete; - /** - * \brief Move assignment - * \param rhs Scenario to move from - * \return This, after assignment - */ - Scenario& operator=(Scenario&& rhs) noexcept; - Scenario& operator=(const Scenario& rhs) const = delete; - // HACK: use for surface right now - /** - * \brief Assign start Cell, reset thresholds and set SafeVector to output results to - * \param start_cell Cell to start ignition in - * \param final_sizes SafeVector to output results to - * \return This - */ - [[nodiscard]] Scenario* reset_with_new_start(const shared_ptr& start_cell, - util::SafeVector* final_sizes); - /** - * \brief Reset thresholds and set SafeVector to output results to - * \param mt_extinction Used for extinction random numbers - * \param mt_spread Used for spread random numbers - * \param final_sizes SafeVector to output results to - * \return This - */ - [[nodiscard]] Scenario* reset(mt19937* mt_extinction, - mt19937* mt_spread, - util::SafeVector* final_sizes); - /** - * \brief Burn cell that Event takes place in - * \param event Event with cell location - */ - void burn(const Event& event); - /** - * Mark as cancelled so it stops computing on next event. - * \param Whether to log a warning about this being cancelled - */ - void cancel(bool show_warning) noexcept; - /** - * \brief Get Cell for given row and column - * \param row Row - * \param column Column - * \return Cell for given row and column - */ - [[nodiscard]] -#ifdef NDEBUG - CONSTEXPR -#endif - topo::Cell - cell(const Idx row, const Idx column) const - { - return model_->cell(row, column); - } - /** - * \brief Get Cell for given Location - * \param location Location - * \return Cell for given Location - */ - template - [[nodiscard]] constexpr topo::Cell cell(const Position

& position) const - { - return model_->cell(position); - } - /** - * \brief Number of rows - * \return Number of rows - */ - [[nodiscard]] constexpr Idx rows() const - { - return model_->rows(); - } - /** - * \brief Number of columns - * \return Number of columns - */ - [[nodiscard]] constexpr Idx columns() const - { - return model_->columns(); - } - /** - * \brief Cell width and height (m) - * \return Cell width and height (m) - */ - [[nodiscard]] constexpr MathSize cellSize() const - { - return model_->cellSize(); - } - /** - * \brief Simulation number - * \return Simulation number - */ - [[nodiscard]] constexpr int64_t simulation() const - { - return simulation_; - } - /** - * \brief StartPoint that provides sunrise/sunset times - * \return StartPoint - */ - [[nodiscard]] constexpr const topo::StartPoint& startPoint() const - { - return start_point_; - } - /** - * \brief Simulation start time - * \return Simulation start time - */ - [[nodiscard]] constexpr DurationSize startTime() const - { - return start_time_; - } - /** - * \brief Identifier - * \return Identifier - */ - [[nodiscard]] constexpr size_t id() const - { - return id_; - } - /** - * \brief Model this Scenario is running in - * \return Model this Scenario is running in - */ - [[nodiscard]] constexpr const Model& model() const - { - return *model_; - } - /** - * \brief Sunrise time for given day - * \param for_day Day to get sunrise time for - * \return Sunrise time for given day - */ - [[nodiscard]] constexpr DurationSize dayStart(const size_t for_day) const - { - return start_point_.dayStart(for_day); - } - /** - * \brief Sunset time for given day - * \param for_day Day to get sunset time for - * \return Sunset time for given day - */ - [[nodiscard]] constexpr DurationSize dayEnd(const size_t for_day) const - { - return start_point_.dayEnd(for_day); - } - /** - * \brief FwiWeather for given time - * \param time Time to get weather for (decimal days) - * \return FwiWeather for given time - */ - [[nodiscard]] const wx::FwiWeather* weather(const DurationSize time) const - { - return weather_->at(time); - } - [[nodiscard]] const wx::FwiWeather* weather_daily(const DurationSize time) const - { - return weather_daily_->at(time); - } - /** - * \brief Difference between date and the date of minimum foliar moisture content - * \param time Time to get value for - * \return Difference between date and the date of minimum foliar moisture content - */ - [[nodiscard]] constexpr int nd(const DurationSize time) const - { - return model().nd(time); - } - /** - * \brief Get extinction threshold for given time - * \param time Time to get value for - * \return Extinction threshold for given time - */ - [[nodiscard]] ThresholdSize extinctionThreshold(const DurationSize time) const - { - return extinction_thresholds_.at(util::time_index(time - start_day_)); - } - /** - * \brief Get spread threshold for given time - * \param time Time to get value for - * \return Spread threshold for given time - */ - [[nodiscard]] ThresholdSize spreadThresholdByRos(const DurationSize time) const - { - return spread_thresholds_by_ros_.at(util::time_index(time - start_day_)); - } - /** - * \brief Whether or not time is after sunrise and before sunset - * \param time Time to determine for - * \return Whether or not time is after sunrise and before sunset - */ - [[nodiscard]] constexpr bool isAtNight(const DurationSize time) const - { - const auto day = static_cast(time); - const auto hour_part = 24 * (time - day); - return hour_part < dayStart(day) || hour_part > dayEnd(day); - } - /** - * \brief Minimum Fine Fuel Moisture Code for spread to be possible - * \param time Time to determine for - * \return Minimum Fine Fuel Moisture Code for spread to be possible - */ - [[nodiscard]] MathSize minimumFfmcForSpread(const DurationSize time) const noexcept - { - return isAtNight(time) ? Settings::minimumFfmcAtNight() : Settings::minimumFfmc(); - } - /** - * \brief Whether or not the given Location is surrounded by cells that are burnt - * \param location Location to check if is surrounded - * \return Whether or not the given Location is surrounded by cells that are burnt - */ - [[nodiscard]] bool isSurrounded(const Location& location) const; - template - [[nodiscard]] bool isSurrounded(const Position

& position) const - { - return isSurrounded(Location{position.hash()}); - } - /** - * \brief Cell that InnerPos falls within - * \param p InnerPos - * \return Cell that InnerPos falls within - */ - [[nodiscard]] topo::Cell cell(const InnerPos& p) const noexcept; - /** - * \brief Run the Scenario - * \param probabilities map to update ProbabilityMap for times base on Scenario results - * \return This - */ - Scenario* run(map* probabilities); - /** - * \brief Schedule a fire spread Event - * \param event Event to schedule - */ - void scheduleFireSpread(const Event& event); - /** - * \brief Current fire size (ha) - * \return Current fire size (ha) - */ - [[nodiscard]] MathSize currentFireSize() const; - /** - * \brief Whether or not a Cell can burn - * \param location Cell - * \return Whether or not a Cell can burn - */ - [[nodiscard]] bool canBurn(const topo::Cell& location) const; - /** - * \brief Whether or not Cell with the given hash can burn - * \param hash Hash for Cell to check - * \return Whether or not Cell with the given hash can burn - */ - // [[nodiscard]] bool canBurn(HashSize hash) const; - /** - * \brief Whether or not Location has burned already - * \param location Location to check - * \return Whether or not Location has burned already - */ - [[nodiscard]] bool hasBurned(const Location& location) const; - template - [[nodiscard]] bool hasBurned(const Position

& position) const - { - return hasBurned(Location{position.hash()}); - } - /** - * \brief Whether or not Location with given hash has burned already - * \param hash Hash of Location to check - * \return Whether or not Location with given hash has burned already - */ - // [[nodiscard]] bool hasBurned(HashSize hash) const; - /** - * \brief Add an Event to the queue - * \param event Event to add - */ - void addEvent(Event&& event); - /** - * \brief Evaluate next Event in the queue - // * \return Whether to continue simulation - */ - // bool evaluateNextEvent(); - void evaluateNextEvent(); - /** - * \brief End the simulation - */ - void endSimulation() noexcept; - /** - * \brief Add a save point for simulation data at the given offset - * \param offset Offset from start of simulation (days) - */ - void addSaveByOffset(int offset); - /** - * \brief Add a save point for simulation data at given time - * \tparam V Type to use for time - * \param time Time to add save point at - */ - template - void addSave(V time); - /** - * \brief Whether or not this Scenario has run already - * \return Whether or not this Scenario has run already - */ - [[nodiscard]] bool ran() const noexcept; - /** - * \brief Whether or not the fire survives the conditions - * \param time Time to use weather from - * \param cell Cell to use - * \param time_at_location How long the fire has been in that Cell - * \return Whether or not the fire survives the conditions - */ - [[nodiscard]] bool survives(const DurationSize time, - const topo::Cell& cell, - const DurationSize time_at_location) const - { - try - { - const auto fire_wx = weather_; - // // NOTE: Does using daily makes sense if we're looking at moisture? - // // HACK: use daily with diurnal curves to be consistent with pre-hourly wx version - // const auto fire_wx = weather_daily_; - const auto wx = fire_wx->at(time); - // use Mike's table - const auto mc = wx->mcDmcPct(); - if (100 > mc - || (109 >= mc && 5 > time_at_location) - || (119 >= mc && 4 > time_at_location) - || (131 >= mc && 3 > time_at_location) - || (145 >= mc && 2 > time_at_location) - || (218 >= mc && 1 > time_at_location)) - { - return true; - } - // we can look by fuel type because the entire landscape shares the weather - return extinctionThreshold(time) < fire_wx->survivalProbability( - time, - cell.fuelCode()); - } - catch (const std::out_of_range& e) - { - // FIX: just ignore for now - // std::cerr << e.what() << '\n'; - // logging::warning("Survival is checking for weather that doesn't exist at %f", time); - // no weather, so don't survive - return false; - } - } - /** - * \brief List of what times the simulation will save - * \return List of what times the simulation will save - */ - [[nodiscard]] vector savePoints() const; - /** - * \brief Save state of Scenario at given time - * \param time - */ - void saveStats(DurationSize time) const; - /** - * \brief Take whatever steps are necessary to process the given Event - * \param event Event to process - */ - void evaluate(const Event& event); - /** - * \brief Clear the Event list and all other data - */ - void clear() noexcept; -protected: - string add_log(const char* format) const noexcept override; - /** - * \brief Constructor - * \param model Model running this Scenario - * \param id Identifier - * \param weather Hourly weather stream to use - * \param weather Weather stream to use for spread and extinction probability - * \param start_time Start time for simulation - * \param start_point StartPoint to use sunrise/sunset times from - * \param start_day First day of simulation - * \param last_date Last day of simulation - */ - Scenario(Model* model, - size_t id, - wx::FireWeather* weather, - wx::FireWeather* weather_daily, - DurationSize start_time, - // const shared_ptr& initial_intensity, - const shared_ptr& perimeter, - const shared_ptr& start_cell, - topo::StartPoint start_point, - Day start_day, - Day last_date); - /** - * \brief List of times to save simulation - */ - vector save_points_; - /** - * \brief Thresholds used to determine if extinction occurs - */ - vector extinction_thresholds_{}; - /** - * \brief Thresholds used to determine if spread occurs - */ - vector spread_thresholds_by_ros_{}; - /** - * \brief Current time for this Scenario - */ - DurationSize current_time_; - /** - * \brief Map of Cells to the PointSets within them - */ - CellPointsMap points_; - /** - * \brief Contains information on cells that are not burnable - */ - BurnedData* unburnable_; - /** - * \brief Event scheduler used for ordering events - */ - set scheduler_; - /** - * \brief Map of what intensity each cell has burned at - */ - unique_ptr intensity_; - // /** - // * @brief Initial intensity map based off perimeter - // */ - // shared_ptr initial_intensity_; - /** - * \brief Perimeter used to start Scenario from - */ - shared_ptr perimeter_; - /** - * \brief Calculated SpreadInfo for SpreadKey for current time - */ - map spread_info_{}; - /** - * \brief Map of when Cell had first Point arrive in it - */ - map arrival_{}; - /** - * \brief Maximum rate of spread for current time - */ - MathSize max_ros_; - /** - * \brief Cell that the Scenario starts from if no Perimeter - */ - shared_ptr start_cell_; - /** - * \brief Hourly weather to use for this Scenario - */ - wx::FireWeather* weather_; - /** - * \brief Weather stream to use for spread and extinction probability - */ - wx::FireWeather* weather_daily_; - /** - * \brief Model this Scenario is being run in - */ - Model* model_; - /** - * \brief Map of ProbabilityMaps by time snapshot for them was taken - */ - map* probabilities_; - /** - * \brief Where to append the final size of this Scenario when run is complete - */ - util::SafeVector* final_sizes_; - /** - * \brief Origin of fire - */ - topo::StartPoint start_point_; - /** - * \brief Identifier - */ - size_t id_; - /** - * \brief Start time (decimal days) - */ - DurationSize start_time_; - /** - * \brief Which save point is the last one - */ - DurationSize last_save_; - /** - * \brief Time index for current time - */ - size_t current_time_index_ = numeric_limits::max(); - /** - * \brief Simulation number - */ - int64_t simulation_; - /** - * \brief First day of simulation - */ - Day start_day_; - /** - * \brief Last day of simulation - */ - Day last_date_; - /** - * \brief Whether or not this Scenario has completed running - */ - bool ran_; - /** - * \brief Whether this has been cancelled. - */ - bool cancelled_ = false; - shared_ptr log_points_; - /** - * \brief How many times point spread event has happened - */ - size_t step_; - /** - * \brief How many times this scenario tried to spread out of bounds - */ - size_t oob_spread_; -}; -} diff --git a/firestarr/src/cpp/Settings.cpp b/firestarr/src/cpp/Settings.cpp deleted file mode 100644 index 6fefda36c..000000000 --- a/firestarr/src/cpp/Settings.cpp +++ /dev/null @@ -1,683 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include -#include "Settings.h" -#include "Trim.h" -namespace fs::sim -{ -template -static vector parse_list(string str, T (*convert)(const string& s)) -{ - vector result{}; - // want format without spaces to work - // OUTPUT_DATE_OFFSETS = [1,2,3,7,14] - logging::check_fatal(str[0] != '[', "Expected list starting with '["); - istringstream iss(str.substr(1)); - while (getline(iss, str, ',')) - { - // need to make sure this isn't an empty list - if (0 != strcmp("]", str.c_str())) - { - result.push_back(convert(str)); - } - } - return result; -} -/** - * \brief Settings implementation class - */ -class SettingsImplementation -{ -public: - ~SettingsImplementation() = default; - SettingsImplementation(const SettingsImplementation& rhs) = delete; - SettingsImplementation(SettingsImplementation&& rhs) = delete; - SettingsImplementation& operator=(const SettingsImplementation& rhs) = delete; - SettingsImplementation& operator=(SettingsImplementation&& rhs) = delete; - static SettingsImplementation& instance() noexcept; - static SettingsImplementation& instance(bool check_loaded) noexcept; - /** - * \brief Set root directory and read settings from file - * \param dirname Directory to use for settings and relative paths - */ - void setRoot(const char* dirname) noexcept; - /** - * \brief Root directory that raster inputs are stored in - * \return Root directory that raster inputs are stored in - */ - [[nodiscard]] const char* rasterRoot() const noexcept - { - return raster_root_.c_str(); - } - /** - * \brief Fuel lookup table - * \return Fuel lookup table - */ - [[nodiscard]] const fuel::FuelLookup& fuelLookup() noexcept - { - if (nullptr == fuel_lookup_) - { - // do this here because it relies on instance being created already - fuel_lookup_ = std::make_unique(fuel_lookup_table_file_.c_str()); - logging::check_fatal(nullptr == fuel_lookup_, "Fuel lookup table has not been loaded"); - } - return *fuel_lookup_; - } - /** - * \brief Minimum rate of spread before fire is considered to be spreading (m/min) - * \return Minimum rate of spread before fire is considered to be spreading (m/min) - */ - [[nodiscard]] MathSize minimumRos() const noexcept - { - return minimum_ros_; - } - void setMinimumRos(const MathSize value) noexcept - { - minimum_ros_ = value; - } - /** - * \brief Maximum distance that the fire is allowed to spread in one step (# of cells) - * \return Maximum distance that the fire is allowed to spread in one step (# of cells) - */ - [[nodiscard]] constexpr MathSize maximumSpreadDistance() const noexcept - { - return maximum_spread_distance_; - } - /** - * \brief Minimum Fine Fuel Moisture Code required for spread during the day - * \return Minimum Fine Fuel Moisture Code required for spread during the day - */ - [[nodiscard]] constexpr MathSize minimumFfmc() const noexcept - { - return minimum_ffmc_; - } - /** - * \brief Minimum Fine Fuel Moisture Code required for spread during the night - * \return Minimum Fine Fuel Moisture Code required for spread during the night - */ - [[nodiscard]] constexpr MathSize minimumFfmcAtNight() const noexcept - { - return minimum_ffmc_at_night_; - } - /** - * \brief Offset from sunrise at which the day is considered to start (hours) - * \return Offset from sunrise at which the day is considered to start (hours) - */ - [[nodiscard]] constexpr DurationSize offsetSunrise() const noexcept - { - return offset_sunrise_; - } - /** - * \brief Offset from sunrise at which the day is considered to end (hours) - * \return Offset from sunrise at which the day is considered to end (hours) - */ - [[nodiscard]] constexpr DurationSize offsetSunset() const noexcept - { - return offset_sunset_; - } - /** - * \brief Default Percent Conifer to use for M1/M2 fuels where none is specified (%) - * \return Percent of the stand that is composed of conifer (%) - */ - [[nodiscard]] constexpr int defaultPercentConifer() const noexcept - { - return default_percent_conifer_; - } - /** - * \brief Default Percent Dead Fir to use for M3/M4 fuels where none is specified (%) - * \return Percent of the stand that is composed of dead fir (NOT percent of the fir that is dead) (%) - */ - [[nodiscard]] constexpr int defaultPercentDeadFir() const noexcept - { - return default_percent_dead_fir_; - } - /** - * \brief Confidence required before simulation stops (% / 100) - * \return Confidence required before simulation stops (% / 100) - */ - [[nodiscard]] ThresholdSize confidenceLevel() const noexcept - { - return confidence_level_; - } - /** - * \brief Set confidence required before simulation stops (% / 100) - * \return Set confidence required before simulation stops (% / 100) - */ - void setConfidenceLevel(const ThresholdSize value) noexcept - { - confidence_level_ = value; - } - void setRasterRoot(const char* dirname) noexcept - { - raster_root_ = dirname; - } - void setFuelLookupTable(const char* filename) noexcept - { - fuel_lookup_table_file_ = filename; - } - /** - * \brief Maximum time simulation can run before it is ended and whatever results it has are used (s) - * \return Maximum time simulation can run before it is ended and whatever results it has are used (s) - */ - [[nodiscard]] size_t maximumTimeSeconds() const noexcept - { - return maximum_time_seconds_; - } - /** - * \brief Set maximum time simulation can run before it is ended and whatever results it has are used (s) - * \return Set maximum time simulation can run before it is ended and whatever results it has are used (s) - */ - void setMaximumTimeSeconds(const size_t value) noexcept - { - maximum_time_seconds_ = value; - } - /** - * \brief Time between generating interim outputs (s) - * \return Time between generating interim outputs (s) - */ - [[nodiscard]] size_t interimOutputIntervalSeconds() const noexcept - { - return interim_output_interval_seconds_; - } - /** - * \brief Set time between generating interim outputs (s) - * \return Set time between generating interim outputs (s) - */ - void setInterimOutputIntervalSeconds(const size_t value) noexcept - { - interim_output_interval_seconds_ = value; - } - /** - * \brief Maximum number of simulations that can run before it is ended and whatever results it has are used - * \return Maximum number of simulations that can run before it is ended and whatever results it has are used - */ - [[nodiscard]] constexpr size_t maximumCountSimulations() const noexcept - { - return maximum_count_simulations_; - } - /** - * \brief Weight to give to Scenario part of thresholds - * \return Weight to give to Scenario part of thresholds - */ - [[nodiscard]] constexpr ThresholdSize thresholdScenarioWeight() const noexcept - { - return threshold_scenario_weight_; - } - /** - * \brief Weight to give to daily part of thresholds - * \return Weight to give to daily part of thresholds - */ - [[nodiscard]] constexpr ThresholdSize thresholdDailyWeight() const noexcept - { - return threshold_daily_weight_; - } - /** - * \brief Weight to give to hourly part of thresholds - * \return Weight to give to hourly part of thresholds - */ - [[nodiscard]] constexpr ThresholdSize thresholdHourlyWeight() const noexcept - { - return threshold_hourly_weight_; - } - /** - * \brief Days to output probability contours for (1 is start date, 2 is day after, etc.) - * \return Days to output probability contours for (1 is start date, 2 is day after, etc.) - */ - [[nodiscard]] vector outputDateOffsets() const - { - return output_date_offsets_; - } - /** - * \brief Set days to output probability contours for (1 is start date, 2 is day after, etc.) - * \return None - */ - void setOutputDateOffsets(const char* value) - { - output_date_offsets_ = parse_list(value, [](const string& s) { return stoi(s); }); - max_date_offset_ = *std::max_element(output_date_offsets_.begin(), output_date_offsets_.end()); - } - /** - * \brief Whatever the maximum value in the date offsets is - * \return Whatever the maximum value in the date offsets is - */ - [[nodiscard]] constexpr int maxDateOffset() const noexcept - { - return max_date_offset_; - } -private: - /** - * \brief Initialize object but don't load settings from file - */ - explicit SettingsImplementation() noexcept; - /** - * \brief Directory used for settings and relative paths - */ - string dir_root_; - /** - * \brief Mutex for parallel access - */ - mutex mutex_; - /** - * \brief Root directory that raster inputs are stored in - */ - string raster_root_; - /** - * \brief Name of file that defines fuel lookup table - */ - string fuel_lookup_table_file_; - /** - * \brief fuel lookup table - */ - unique_ptr fuel_lookup_ = nullptr; - /** - * \brief Minimum rate of spread before fire is considered to be spreading (m/min) - */ - atomic minimum_ros_; - /** - * \brief Maximum distance that the fire is allowed to spread in one step (# of cells) - */ - MathSize maximum_spread_distance_; - /** - * \brief Minimum Fine Fuel Moisture Code required for spread during the day - */ - MathSize minimum_ffmc_; - /** - * \brief Minimum Fine Fuel Moisture Code required for spread during the night - */ - MathSize minimum_ffmc_at_night_; - /** - * \brief Offset from sunrise at which the day is considered to start (hours) - */ - DurationSize offset_sunrise_; - /** - * \brief Offset from sunrise at which the day is considered to end (hours) - */ - DurationSize offset_sunset_; - /** - * \brief Confidence required before simulation stops (% / 100) - */ - atomic confidence_level_; - /** - * \brief Ignition position row - */ - atomic ign_row_ = 1; - /** - * \brief Ignition position col - */ - atomic ign_col_ = 1; - /** - * \brief Static curing value - */ - atomic static_curing_ = 75; - /** - * \brief Maximum time simulation can run before it is ended and whatever results it has are used (s) - */ - atomic maximum_time_seconds_; - /** - * \brief Time between generating interim outputs (s) - */ - atomic interim_output_interval_seconds_; - /** - * @brief Maximum number of simulations that can run before it is ended and whatever results it has are used - */ - size_t maximum_count_simulations_; - /** - * \brief Weight to give to Scenario part of thresholds - */ - ThresholdSize threshold_scenario_weight_; - /** - * \brief Weight to give to daily part of thresholds - */ - ThresholdSize threshold_daily_weight_; - /** - * \brief Weight to give to hourly part of thresholds - */ - ThresholdSize threshold_hourly_weight_; - /** - * \brief Days to output probability contours for (1 is start date, 2 is day after, etc.) - */ - vector output_date_offsets_; - /** - * \brief Default Percent Conifer to use for M1/M2 fuels where none is specified (%) - */ - int default_percent_conifer_; - /** - * \brief Default Percent Dead Fir to use for M3/M4 fuels where none is specified (%) - */ - int default_percent_dead_fir_; - /** - * \brief Whatever the maximum value in the date offsets is - */ - int max_date_offset_; - /** - * \brief The maximum fire intensity for the 'low' range of intensity (kW/m) - */ - int intensity_max_low_; - /** - * \brief The maximum fire intensity for the 'moderate' range of intensity (kW/m) - */ - int intensity_max_moderate_; -public: - /** - * \brief Whether or not to save individual grids - * \return Whether or not to save individual grids - */ - atomic save_individual = false; - /** - * \brief Whether or not to run things asynchronously where possible - * \return Whether or not to run things asynchronously where possible - */ - atomic run_async = true; - /** - * \brief Whether or not to run deterministically (100% chance of spread & survival) - * \return Whether or not to run deterministically (100% chance of spread & survival) - */ - atomic deterministic = false; - /** - * \brief Whether or not to create a probability surface - * \return Whether or not to create a probability surface - */ - atomic surface = false; - /** - * \brief Whether or not to save grids as .asc - * \return Whether or not to save grids as .asc - */ - atomic save_as_ascii = false; - /** - * \brief Whether or not to save points used for spread - * \return Whether or not to save points used for spread - */ - atomic save_points = false; - /** - * \brief Whether or not to save intensity grids - * \return Whether or not to save intensity grids - */ - atomic save_intensity = true; - /** - * \brief Whether or not to save probability grids - * \return Whether or not to save probability grids - */ - atomic save_probability = true; - /** - * \brief Whether or not to save occurrence grids - * \return Whether or not to save occurrence grids - */ - atomic save_occurrence = false; - /** - * \brief Whether or not to save simulation area grids - * \return Whether or not to save simulation area grids - */ - atomic save_simulation_area = false; - /** - * \brief Whether or not to force greenup for all fires - * \return Whether or not to force greenup for all fires - */ - atomic force_greenup = false; - /** - * \brief Whether or not to force no greenup for all fires - * \return Whether or not to force no greenup for all fires - */ - atomic force_no_greenup = false; - /** - * \brief Whether or not to force static curing value for all fires - * \return Whether or not to force static curing value for all fires - */ - atomic force_curing = false; - /** - * \brief Whether or not the start point is specified by row and column id of a forced fuel grid - * \return Whether or not the start point is specified by row and column id of a forced fuel grid - */ - atomic rowcol_ignition = false; -}; -/** - * \brief The singleton instance for this class - * \param check_loaded Whether to ensure a file has been loaded already - * \return The singleton instance for this class - */ -SettingsImplementation& SettingsImplementation::instance(bool check_loaded) noexcept -{ - static SettingsImplementation instance_{}; - if (check_loaded) - { - logging::check_fatal(instance_.dir_root_.empty(), "Expected settings to be loaded, but no root directory specified yet"); - } - return instance_; -} -/** - * \brief The singleton instance for this class - * \return The singleton instance for this class - */ -SettingsImplementation& SettingsImplementation::instance() noexcept -{ - return instance(true); -} -string get_value(unordered_map& settings, const string& key) -{ - const auto found = settings.find(key); - if (found != settings.end()) - { - auto result = found->second; - settings.erase(found); - return result; - } - logging::fatal("Missing setting for %s", key.c_str()); - // HACK: use return to avoid compiler warning - static const string Invalid = "INVALID"; - return Invalid; -} -string get_path(const char* const dir_root, unordered_map& settings, const string& key) -{ - auto path = get_value(settings, key); - if (!path.starts_with("/")) - { - // not an absolute path - // if binary path starts with ./ then ignore it - std::filesystem::path p = (0 == strcmp("./", dir_root) - || 0 == strcmp(".\\", dir_root)) - ? path - : (dir_root + path); -#ifdef _WIN32 - path = std::filesystem::canonical(p).generic_string(); -#else - path = std::filesystem::canonical(p).c_str(); -#endif - logging::info("Converted relative path to absolute path %s", path.c_str()); - } - return path; -} -SettingsImplementation::SettingsImplementation() noexcept -{ - dir_root_ = ""; -} - -void SettingsImplementation::setRoot(const char* dirname) noexcept -{ - try - { - dir_root_ = dirname; - const auto filename = dir_root_ + "settings.ini"; - unordered_map settings{}; - ifstream in; - in.open(filename.c_str()); - if (in.is_open()) - { - string str; - logging::info("Reading settings from '%s'", filename.c_str()); - while (getline(in, str)) - { - istringstream iss(str); - if (getline(iss, str, '#')) - { - iss = istringstream(str); - } - if (getline(iss, str, '=')) - { - const auto key = util::trim_copy(str); - getline(iss, str, '\n'); - const auto value = util::trim_copy(str); - settings.emplace(key, value); - logging::debug("%s: %s", key.c_str(), value.c_str()); - } - } - in.close(); - } - raster_root_ = get_path(dir_root_.c_str(), settings, "RASTER_ROOT"); - fuel_lookup_table_file_ = get_path(dir_root_.c_str(), settings, "FUEL_LOOKUP_TABLE"); - // HACK: run into fuel consumption being too low if we don't have a minimum ros - static const auto MinRos = 0.05; - // HACK: make sure this is always > 0 so that we don't have to check - // specifically for 0 to avoid div error - minimum_ros_ = max(stod(get_value(settings, "MINIMUM_ROS")), MinRos); - maximum_spread_distance_ = stod(get_value(settings, "MAX_SPREAD_DISTANCE")); - minimum_ffmc_ = stod(get_value(settings, "MINIMUM_FFMC")); - minimum_ffmc_at_night_ = stod(get_value(settings, "MINIMUM_FFMC_AT_NIGHT")); - offset_sunrise_ = stod(get_value(settings, "OFFSET_SUNRISE")); - offset_sunset_ = stod(get_value(settings, "OFFSET_SUNSET")); - confidence_level_ = stod(get_value(settings, "CONFIDENCE_LEVEL")); - maximum_time_seconds_ = stol(get_value(settings, "MAXIMUM_TIME")); - interim_output_interval_seconds_ = stol(get_value(settings, "INTERIM_OUTPUT_INTERVAL")); - maximum_count_simulations_ = stol(get_value(settings, "MAXIMUM_SIMULATIONS")); - threshold_scenario_weight_ = stod(get_value(settings, "THRESHOLD_SCENARIO_WEIGHT")); - threshold_daily_weight_ = stod(get_value(settings, "THRESHOLD_DAILY_WEIGHT")); - threshold_hourly_weight_ = stod(get_value(settings, "THRESHOLD_HOURLY_WEIGHT")); - setOutputDateOffsets(get_value(settings, "OUTPUT_DATE_OFFSETS").c_str()); - default_percent_conifer_ = stoi(get_value(settings, "DEFAULT_PERCENT_CONIFER")); - default_percent_dead_fir_ = stoi(get_value(settings, "DEFAULT_PERCENT_DEAD_FIR")); - intensity_max_low_ = stoi(get_value(settings, "INTENSITY_MAX_LOW")); - intensity_max_moderate_ = stoi(get_value(settings, "INTENSITY_MAX_MODERATE")); - if (!settings.empty()) - { - logging::warning("Unused settings in settings file %s", filename.c_str()); - for (const auto& kv : settings) - { - logging::warning("%s = %s", kv.first.c_str(), kv.second.c_str()); - } - } - } - catch (const std::exception& ex) - { - logging::fatal(ex); - std::terminate(); - } -} -void Settings::setRoot(const char* dirname) noexcept -{ - return SettingsImplementation::instance(false).setRoot(dirname); -} -void Settings::setRasterRoot(const char* dirname) noexcept -{ - return SettingsImplementation::instance().setRasterRoot(dirname); -} -const char* Settings::rasterRoot() noexcept -{ - return SettingsImplementation::instance().rasterRoot(); -} -void Settings::setFuelLookupTable(const char* filename) noexcept -{ - return SettingsImplementation::instance().setFuelLookupTable(filename); -} -const fuel::FuelLookup& Settings::fuelLookup() noexcept -{ - return SettingsImplementation::instance().fuelLookup(); -} -bool Settings::saveProbability() noexcept -{ - return SettingsImplementation::instance().save_probability; -} -void Settings::setSaveProbability(const bool value) noexcept -{ - SettingsImplementation::instance().save_probability = value; -} -MathSize Settings::minimumRos() noexcept -{ - return SettingsImplementation::instance().minimumRos(); -} -void Settings::setMinimumRos(const MathSize value) noexcept -{ - SettingsImplementation::instance().setMinimumRos(value); -} -MathSize Settings::maximumSpreadDistance() noexcept -{ - return SettingsImplementation::instance().maximumSpreadDistance(); -} -MathSize Settings::minimumFfmc() noexcept -{ - return SettingsImplementation::instance().minimumFfmc(); -} -MathSize Settings::minimumFfmcAtNight() noexcept -{ - return SettingsImplementation::instance().minimumFfmcAtNight(); -} -DurationSize Settings::offsetSunrise() noexcept -{ - return SettingsImplementation::instance().offsetSunrise(); -} -DurationSize Settings::offsetSunset() noexcept -{ - return SettingsImplementation::instance().offsetSunset(); -} -int Settings::defaultPercentConifer() noexcept -{ - return SettingsImplementation::instance().defaultPercentConifer(); -} -int Settings::defaultPercentDeadFir() noexcept -{ - return SettingsImplementation::instance().defaultPercentDeadFir(); -} -ThresholdSize Settings::confidenceLevel() noexcept -{ - return SettingsImplementation::instance().confidenceLevel(); -} -void Settings::setConfidenceLevel(const ThresholdSize value) noexcept -{ - SettingsImplementation::instance().setConfidenceLevel(value); -} -size_t Settings::maximumTimeSeconds() noexcept -{ - return SettingsImplementation::instance().maximumTimeSeconds(); -} -void Settings::setMaximumTimeSeconds(const size_t value) noexcept -{ - return SettingsImplementation::instance().setMaximumTimeSeconds(value); -} -size_t Settings::interimOutputIntervalSeconds() noexcept -{ - return SettingsImplementation::instance().interimOutputIntervalSeconds(); -} -void Settings::setInterimOutputIntervalSeconds(const size_t value) noexcept -{ - return SettingsImplementation::instance().setInterimOutputIntervalSeconds(value); -} -size_t Settings::maximumCountSimulations() noexcept -{ - return SettingsImplementation::instance().maximumCountSimulations(); -} -ThresholdSize Settings::thresholdScenarioWeight() noexcept -{ - return SettingsImplementation::instance().thresholdScenarioWeight(); -} -ThresholdSize Settings::thresholdDailyWeight() noexcept -{ - return SettingsImplementation::instance().thresholdDailyWeight(); -} -ThresholdSize Settings::thresholdHourlyWeight() noexcept -{ - return SettingsImplementation::instance().thresholdHourlyWeight(); -} -vector Settings::outputDateOffsets() -{ - return SettingsImplementation::instance().outputDateOffsets(); -} -void Settings::setOutputDateOffsets(const char* value) -{ - SettingsImplementation::instance().setOutputDateOffsets(value); -} -int Settings::maxDateOffset() noexcept -{ - return SettingsImplementation::instance().maxDateOffset(); -} -} diff --git a/firestarr/src/cpp/Settings.h b/firestarr/src/cpp/Settings.h deleted file mode 100644 index 2e97f0d4d..000000000 --- a/firestarr/src/cpp/Settings.h +++ /dev/null @@ -1,169 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include "FuelLookup.h" - -namespace fs -{ -namespace sim -{ -/** - * \brief Difference minimum for MathSizes to be considered the same - */ -static const MathSize COMPARE_LIMIT = 1.0E-20f; -/** - * \brief Reads and provides access to settings for the simulation. - */ -class Settings -{ -public: - /** - * \brief Set root directory and read settings from file - * \param dirname Directory to use for settings and relative paths - */ - static void setRoot(const char* dirname) noexcept; - /** - * \brief Set raster root directory - * \param dirname Directory to use for rasters - */ - static void setRasterRoot(const char* dirname) noexcept; - /** - * \brief Root directory that raster inputs are stored in - * \return Root directory that raster inputs are stored in - */ - [[nodiscard]] static const char* rasterRoot() noexcept; - /** - * \brief Set fuel lookup table file - * \param dirname Directory to use for rasters - */ - static void setFuelLookupTable(const char* filename) noexcept; - /** - * \brief Fuel lookup table - * \return Fuel lookup table - */ - [[nodiscard]] static const fuel::FuelLookup& fuelLookup() noexcept; - /** - * \brief Whether or not to save probability grids - * \return Whether or not to save probability grids - */ - [[nodiscard]] static bool saveProbability() noexcept; - /** - * \brief Set whether or not to save probability grids - * \param value Whether or not to save probability grids - * \return None - */ - static void setSaveProbability(bool value) noexcept; - /** - * \brief Minimum rate of spread before fire is considered to be spreading (m/min) - * \return Minimum rate of spread before fire is considered to be spreading (m/min) - */ - [[nodiscard]] static MathSize minimumRos() noexcept; - static void setMinimumRos(MathSize value) noexcept; - /** - * \brief Maximum distance that the fire is allowed to spread in one step (# of cells) - * \return Maximum distance that the fire is allowed to spread in one step (# of cells) - */ - [[nodiscard]] static MathSize maximumSpreadDistance() noexcept; - /** - * \brief Minimum Fine Fuel Moisture Code required for spread during the day - * \return Minimum Fine Fuel Moisture Code required for spread during the day - */ - [[nodiscard]] static MathSize minimumFfmc() noexcept; - /** - * \brief Minimum Fine Fuel Moisture Code required for spread during the night - * \return Minimum Fine Fuel Moisture Code required for spread during the night - */ - [[nodiscard]] static MathSize minimumFfmcAtNight() noexcept; - /** - * \brief Offset from sunrise at which the day is considered to start (hours) - * \return Offset from sunrise at which the day is considered to start (hours) - */ - [[nodiscard]] static DurationSize offsetSunrise() noexcept; - /** - * \brief Offset from sunrise at which the day is considered to end (hours) - * \return Offset from sunrise at which the day is considered to end (hours) - */ - [[nodiscard]] static DurationSize offsetSunset() noexcept; - /** - * \brief Default Percent Conifer to use for M1/M2 fuels where none is specified (%) - * \return Percent of the stand that is composed of conifer (%) - */ - [[nodiscard]] static int defaultPercentConifer() noexcept; - /** - * \brief Default Percent Dead Fir to use for M3/M4 fuels where none is specified (%) - * \return Percent of the stand that is composed of dead fir (NOT percent of the fir that is dead) (%) - */ - [[nodiscard]] static int defaultPercentDeadFir() noexcept; - /** - * \brief Confidence required before simulation stops (% / 100) - * \return Confidence required before simulation stops (% / 100) - */ - [[nodiscard]] static ThresholdSize confidenceLevel() noexcept; - /** - * \brief Set confidence required before simulation stops (% / 100) - * \return Set confidence required before simulation stops (% / 100) - */ - static void setConfidenceLevel(const ThresholdSize value) noexcept; - /** - * \brief Maximum time simulation can run before it is ended and whatever results it has are used (s) - * \return Maximum time simulation can run before it is ended and whatever results it has are used (s) - */ - [[nodiscard]] static size_t maximumTimeSeconds() noexcept; - /** - * \brief Set maximum time simulation can run before it is ended and whatever results it has are used (s) - * \return Set maximum time simulation can run before it is ended and whatever results it has are used (s) - */ - static void setMaximumTimeSeconds(const size_t value) noexcept; - /** - * \brief Time between generating interim outputs (s) - * \return Time between generating interim outputs (s) - */ - [[nodiscard]] static size_t interimOutputIntervalSeconds() noexcept; - /** - * \brief Set time between generating interim outputs (s) - * \return Set time between generating interim outputs (s) - */ - static void setInterimOutputIntervalSeconds(const size_t value) noexcept; - /** - * \brief Maximum number of simulations that can run before it is ended and whatever results it has are used - * \return Maximum number of simulations that can run before it is ended and whatever results it has are used - */ - [[nodiscard]] static size_t maximumCountSimulations() noexcept; - /** - * \brief Weight to give to Scenario part of thresholds - * \return Weight to give to Scenario part of thresholds - */ - [[nodiscard]] static ThresholdSize thresholdScenarioWeight() noexcept; - /** - * \brief Weight to give to daily part of thresholds - * \return Weight to give to daily part of thresholds - */ - [[nodiscard]] static ThresholdSize thresholdDailyWeight() noexcept; - /** - * \brief Weight to give to hourly part of thresholds - * \return Weight to give to hourly part of thresholds - */ - [[nodiscard]] static ThresholdSize thresholdHourlyWeight() noexcept; - /** - * \brief Days to output probability contours for (1 is start date, 2 is day after, etc.) - * \return Days to output probability contours for (1 is start date, 2 is day after, etc.) - */ - [[nodiscard]] static vector outputDateOffsets(); - /** - * \brief Set days to output probability contours for (1 is start date, 2 is day after, etc.) - * \return None - */ - static void setOutputDateOffsets(const char* value); - /** - * \brief Whatever the maximum value in the date offsets is - * \return Whatever the maximum value in the date offsets is - */ - [[nodiscard]] static int maxDateOffset() noexcept; - Settings() = delete; -}; -} -} diff --git a/firestarr/src/cpp/SpreadAlgorithm.cpp b/firestarr/src/cpp/SpreadAlgorithm.cpp deleted file mode 100644 index 93da5cf25..000000000 --- a/firestarr/src/cpp/SpreadAlgorithm.cpp +++ /dev/null @@ -1,360 +0,0 @@ -/* Copyright (c) 2020, Queen's Printer for Ontario */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "SpreadAlgorithm.h" -#include "Util.h" -#include "unstable.h" -#include "CellPoints.h" -namespace fs -{ -HorizontalAdjustment horizontal_adjustment( - const AspectSize slope_azimuth, - const SlopeSize slope) -{ - // do everything we can to avoid calling trig functions unnecessarily - constexpr auto no_correction = [](const MathSize) noexcept { return 1.0; }; - if (0 == slope) - { - // do check once and make function just return 1.0 if no slope - return no_correction; - } - const auto b_semi = _cos(atan(slope / 100.0)); - const auto slope_radians = util::to_radians(slope_azimuth); - const auto do_correction = [b_semi, slope_radians](const MathSize theta) noexcept { - // never gets called if isInvalid() so don't check - // figure out how far the ground distance is in map distance horizontally - auto angle_unrotated = theta - slope_radians; - if (util::to_degrees(angle_unrotated) == 270 || util::to_degrees(angle_unrotated) == 90) - { - // CHECK: if we're going directly across the slope then horizontal distance is same as spread distance - return 1.0; - } - const auto tan_u = tan(angle_unrotated); - const auto y = b_semi / sqrt(b_semi * tan_u * (b_semi * tan_u) + 1.0); - const auto x = y * tan_u; - // CHECK: Pretty sure you can't spread farther horizontally than the spread distance, regardless of angle? - return min(1.0, sqrt(x * x + y * y)); - }; - return do_correction; -} -[[nodiscard]] OffsetSet OriginalSpreadAlgorithm::calculate_offsets( - HorizontalAdjustment correction_factor, - MathSize tfc, - MathSize head_raz, - MathSize head_ros, - MathSize back_ros, - MathSize length_to_breadth) const noexcept -{ - OffsetSet offsets{}; - const auto add_offset = - [this, &offsets, tfc]( - const MathSize direction, - const MathSize ros) { - if (ros < min_ros_) - { - return false; - } - const auto ros_cell = ros / cell_size_; - // spreading, so figure out offset from current point - offsets.emplace_back( - Offset{ - static_cast(ros_cell * _sin(direction)), - static_cast(ros_cell * _cos(direction))}); - return true; - }; - // if not over spread threshold then don't spread - // HACK: set ros in boolean if we get that far so that we don't have to repeat the if body - if (!add_offset(head_raz, head_ros * correction_factor(head_raz))) - { - return offsets; - } - const auto a = (head_ros + back_ros) / 2.0; - const auto c = a - back_ros; - const auto flank_ros = a / length_to_breadth; - const auto a_sq = a * a; - const auto flank_ros_sq = flank_ros * flank_ros; - const auto a_sq_sub_c_sq = a_sq - (c * c); - const auto ac = a * c; - const auto calculate_ros = - [a, c, ac, flank_ros, a_sq, flank_ros_sq, a_sq_sub_c_sq](const MathSize theta) noexcept { - const auto cos_t = _cos(theta); - const auto cos_t_sq = cos_t * cos_t; - const auto f_sq_cos_t_sq = flank_ros_sq * cos_t_sq; - // 1.0 = cos^2 + sin^2 - // const auto sin_t_sq = 1.0 - cos_t_sq; - const auto sin_t = _sin(theta); - const auto sin_t_sq = sin_t * sin_t; - return abs((a * ((flank_ros * cos_t * sqrt(f_sq_cos_t_sq + a_sq_sub_c_sq * sin_t_sq) - ac * sin_t_sq) / (f_sq_cos_t_sq + a_sq * sin_t_sq)) + c) / cos_t); - }; - const auto add_offsets = - [this, &correction_factor, &add_offset, head_raz]( - const MathSize angle_radians, - const MathSize ros_flat) { - if (ros_flat < min_ros_) - { - return false; - } - auto direction = util::fix_radians(angle_radians + head_raz); - // spread is symmetrical across the center axis, but needs to be adjusted if on a slope - // intentionally don't use || because we want both of these to happen all the time - auto added = add_offset(direction, ros_flat * correction_factor(direction)); - direction = util::fix_radians(head_raz - angle_radians); - added |= add_offset(direction, ros_flat * correction_factor(direction)); - return added; - }; - const auto add_offsets_calc_ros = - [&add_offsets, &calculate_ros](const MathSize angle_radians) { return add_offsets(angle_radians, calculate_ros(angle_radians)); }; - // bool added = add_offset(head_raz, head_ros); - bool added = add_offset(head_raz, head_ros); - MathSize i = max_angle_; - while (added && i < 90) - { - added = add_offsets_calc_ros(util::to_radians(i)); - i += max_angle_; - } - if (added) - { - added = add_offsets(util::to_radians(90), flank_ros * sqrt(a_sq_sub_c_sq) / a); - i = 90 + max_angle_; - while (added && i < 180) - { - added = add_offsets_calc_ros(util::to_radians(i)); - i += max_angle_; - } - if (added) - { - // only use back ros if every other angle is spreading since this should be lowest - // 180 - if (back_ros >= min_ros_) - { - const auto direction = util::fix_radians(util::RAD_180 + head_raz); - static_cast(!add_offset(direction, back_ros * correction_factor(direction))); - } - } - } - return offsets; -} -[[nodiscard]] OffsetSet WidestEllipseAlgorithm::calculate_offsets( - const HorizontalAdjustment correction_factor, - const MathSize tfc, - const MathSize head_raz, - const MathSize head_ros, - const MathSize back_ros, - const MathSize length_to_breadth) const noexcept -{ - OffsetSet offsets{}; - const auto add_offset = - [this, &offsets, tfc]( - const MathSize direction, - const MathSize ros) { -#ifdef DEBUG_POINTS - const auto s0 = offsets.size(); -#endif - if (ros < min_ros_) - { - // might not be correct depending on slope angle correction - // #ifdef DEBUG_POINTS - // // should never be empty since head_ros must have been high enough - // logging::check_fatal(offsets.empty(), "offsets.empty()"); - // #endif - return false; - } - const auto ros_cell = ros / cell_size_; - // spreading, so figure out offset from current point - offsets.emplace_back( - Offset{ - static_cast(ros_cell * _sin(direction)), - static_cast(ros_cell * _cos(direction))}); - // // HACK: avoid bounds check - // offsets.emplace_back(ros_cell * _sin(direction), ros_cell * _cos(direction), false); -#ifdef DEBUG_POINTS - const auto s1 = offsets.size(); - logging::check_equal(s0 + 1, s1, "offsets.size()"); - logging::check_fatal(offsets.empty(), "offsets.empty()"); -#endif - return true; - }; - // if not over spread threshold then don't spread - // HACK: set ros in boolean if we get that far so that we don't have to repeat the if body - if (!add_offset(head_raz, head_ros * correction_factor(head_raz))) - { - // might not be correct depending on slope angle correction - // #ifdef DEBUG_POINTS - // // if (head_ros >= min_ros_) - // { - // logging::check_fatal( - // offsets.empty(), - // "Empty when ros of %f >= %f", - // head_ros, - // min_ros_); - // } - // #endif - return offsets; - } -#ifdef DEBUG_POINTS - logging::check_fatal(offsets.empty(), "offsets.empty()"); -#endif - const auto a = (head_ros + back_ros) / 2.0; - const auto c = a - back_ros; - const auto flank_ros = a / length_to_breadth; - const auto a_sq = a * a; - const auto flank_ros_sq = flank_ros * flank_ros; - const auto a_sq_sub_c_sq = a_sq - (c * c); - const auto ac = a * c; - const auto calculate_ros = - [a, c, ac, flank_ros, a_sq, flank_ros_sq, a_sq_sub_c_sq](const MathSize theta) noexcept { - const auto cos_t = _cos(theta); - const auto cos_t_sq = cos_t * cos_t; - const auto f_sq_cos_t_sq = flank_ros_sq * cos_t_sq; - // 1.0 = cos^2 + sin^2 - // const auto sin_t_sq = 1.0 - cos_t_sq; - const auto sin_t = _sin(theta); - const auto sin_t_sq = sin_t * sin_t; - return abs((a * ((flank_ros * cos_t * sqrt(f_sq_cos_t_sq + a_sq_sub_c_sq * sin_t_sq) - ac * sin_t_sq) / (f_sq_cos_t_sq + a_sq * sin_t_sq)) + c) / cos_t); - }; - const auto add_offsets = - [this, &correction_factor, &add_offset, head_raz]( - const MathSize angle_radians, - const MathSize ros_flat) { - if (ros_flat < min_ros_) - { - return false; - } - auto direction = util::fix_radians(angle_radians + head_raz); - // spread is symmetrical across the center axis, but needs to be adjusted if on a slope - // intentionally don't use || because we want both of these to happen all the time - auto added = add_offset(direction, ros_flat * correction_factor(direction)); - direction = util::fix_radians(head_raz - angle_radians); - added |= add_offset(direction, ros_flat * correction_factor(direction)); - return added; - }; - const auto add_offsets_calc_ros = - [&add_offsets, &calculate_ros](const MathSize angle_radians) { return add_offsets(angle_radians, calculate_ros(angle_radians)); }; - // bool added = add_offset(head_raz, head_ros); - bool added = true; -#define STEP_X 0.2 -#define STEP_MAX util::to_radians(max_angle_) - // MathSize step_x = STEP_X; - // MathSize step_x = STEP_X / length_to_breadth; - MathSize step_x = STEP_X / pow(length_to_breadth, 0.5); - MathSize theta = 0; - MathSize angle = 0; - MathSize last_theta = 0; - MathSize cur_x = 1.0; - // MathSize last_angle = 0; - // widest point should be at origin, which is 'c' away from origin - MathSize widest = atan2(flank_ros, c); - // printf("head_ros = %f, back_ros = %f, flank_ros = %f, c = %f, widest = %f\n", - // head_ros, - // back_ros, - // flank_ros, - // c, - // util::to_degrees(widest)); - // MathSize step = 1; - // MathSize last_step = 0; - size_t num_angles = 0; - MathSize widest_x = _cos(widest); - MathSize step_max = STEP_MAX / pow(length_to_breadth, 0.5); - while (added && cur_x > (STEP_MAX / 4.0)) - { - ++num_angles; - theta = min(acos(cur_x), last_theta + step_max); - angle = ellipse_angle(length_to_breadth, theta); - added = add_offsets_calc_ros(angle); - cur_x = _cos(theta); - // printf("cur_x = %f, theta = %f, angle = %f, last_theta = %f, last_angle = %f\n", - // cur_x, - // util::to_degrees(theta), - // util::to_degrees(angle), - // util::to_degrees(last_theta), - // util::to_degrees(last_angle)); - last_theta = theta; - // last_angle = angle; - if (theta > (STEP_MAX / 2.0)) - { - step_max = STEP_MAX; - } - cur_x -= step_x; - if (cur_x > widest_x && abs(cur_x - widest_x) < step_x) - { - cur_x = widest_x; - } - } - if (added) - { - angle = ellipse_angle(length_to_breadth, (util::RAD_090 + theta) / 2.0); - added = add_offsets_calc_ros(angle); - // always just do one between the last angle and 90 - theta = util::RAD_090; - ++num_angles; - angle = ellipse_angle(length_to_breadth, theta); - added = add_offsets(util::RAD_090, flank_ros * sqrt(a_sq_sub_c_sq) / a); - cur_x = _cos(theta); - // printf("cur_x = %f, theta = %f, angle = %f, last_theta = %f, last_angle = %f\n", - // cur_x, - // util::to_degrees(theta), - // util::to_degrees(angle), - // util::to_degrees(last_theta), - // util::to_degrees(last_angle)); - last_theta = theta; - // last_angle = angle; - } - // just because 5 seems good for the front and 10 for the back - // step_max = 2.0 * STEP_MAX; - cur_x -= (step_x / 2.0); - // trying to pick less rear points - // step_x *= length_to_breadth; - step_x *= length_to_breadth; - // just trying random things now - // MathSize max_angle = util::RAD_180 - (pow(length_to_breadth, 1.5) * STEP_MAX); - MathSize max_angle = util::RAD_180 - (length_to_breadth * step_max); - MathSize min_x = _cos(max_angle); - while (added && cur_x >= min_x) - { - ++num_angles; - theta = max(acos(cur_x), last_theta + step_max); - angle = ellipse_angle(length_to_breadth, theta); - if (angle > max_angle) - { - break; - // // compromise and put a point in the middle - // theta = (theta + last_theta) / 2.0; - // angle = ellipse_angle(length_to_breadth, theta); - } - added = add_offsets_calc_ros(angle); - cur_x = _cos(theta); - // printf("cur_x = %f, theta = %f, angle = %f, last_theta = %f, last_angle = %f\n", - // cur_x, - // util::to_degrees(theta), - // util::to_degrees(angle), - // util::to_degrees(last_theta), - // util::to_degrees(last_angle)); - last_theta = theta; - // last_angle = angle; - cur_x -= step_x; - } - if (added) - { - // only use back ros if every other angle is spreading since this should be lowest - // 180 - if (back_ros >= min_ros_) - { - const auto direction = util::fix_radians(util::RAD_180 + head_raz); - static_cast(!add_offset(direction, back_ros * correction_factor(direction))); - } - } -#ifdef DEBUG_POINTS - if (head_ros >= min_ros_) - { - logging::check_fatal( - offsets.empty(), - "Empty when ros of %f >= %f", - head_ros, - min_ros_); - } -#endif - return offsets; -} -} diff --git a/firestarr/src/cpp/SpreadAlgorithm.h b/firestarr/src/cpp/SpreadAlgorithm.h deleted file mode 100644 index 284700794..000000000 --- a/firestarr/src/cpp/SpreadAlgorithm.h +++ /dev/null @@ -1,73 +0,0 @@ -/* Copyright (c) 2020, Queen's Printer for Ontario */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "InnerPos.h" - -namespace fs -{ -using HorizontalAdjustment = std::function; - -HorizontalAdjustment horizontal_adjustment( - const AspectSize slope_azimuth, - const SlopeSize slope); - -class SpreadAlgorithm -{ -public: - [[nodiscard]] virtual OffsetSet calculate_offsets( - HorizontalAdjustment correction_factor, - MathSize tfc, - MathSize head_raz, - MathSize head_ros, - MathSize back_ros, - MathSize length_to_breadth) const - noexcept = 0; -}; - -class BaseSpreadAlgorithm - : public SpreadAlgorithm -{ -public: - BaseSpreadAlgorithm(const MathSize max_angle, - const MathSize cell_size, - const MathSize min_ros) - : max_angle_(max_angle), cell_size_(cell_size), min_ros_(min_ros) - { - } -protected: - MathSize max_angle_; - MathSize cell_size_; - MathSize min_ros_; -}; - -class OriginalSpreadAlgorithm - : public BaseSpreadAlgorithm -{ -public: - using BaseSpreadAlgorithm::BaseSpreadAlgorithm; - [[nodiscard]] OffsetSet calculate_offsets( - HorizontalAdjustment correction_factor, - MathSize tfc, - MathSize head_raz, - MathSize head_ros, - MathSize back_ros, - MathSize length_to_breadth) const noexcept override; -}; - -class WidestEllipseAlgorithm - : public BaseSpreadAlgorithm -{ -public: - using BaseSpreadAlgorithm::BaseSpreadAlgorithm; - [[nodiscard]] OffsetSet calculate_offsets( - HorizontalAdjustment correction_factor, - MathSize tfc, - MathSize head_raz, - MathSize head_ros, - MathSize back_ros, - MathSize length_to_breadth) const noexcept override; -}; -} diff --git a/firestarr/src/cpp/StandardFuel.cpp b/firestarr/src/cpp/StandardFuel.cpp deleted file mode 100644 index 80732b054..000000000 --- a/firestarr/src/cpp/StandardFuel.cpp +++ /dev/null @@ -1,4 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ diff --git a/firestarr/src/cpp/StandardFuel.h b/firestarr/src/cpp/StandardFuel.h deleted file mode 100644 index ac7c04efb..000000000 --- a/firestarr/src/cpp/StandardFuel.h +++ /dev/null @@ -1,278 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "FireSpread.h" -#include "FuelType.h" -#include "LookupTable.h" -namespace fs -{ -using sim::SpreadInfo; -namespace fuel -{ -/** - * \brief Limit to slope when calculating ISI - */ -static constexpr MathSize SLOPE_LIMIT_ISI = 0.01; -/** - * \brief Calculate standard foliar moisture effect (FME) based on FMC [ST-X-3 eq 61] - * \param fmc Foliar Moisture Content (FMC) - * \return Standard foliar moisture effect (FME) based on FMC [ST-X-3 eq 61] - */ -[[nodiscard]] static constexpr MathSize - calculate_standard_foliar_moisture_fmc( - const MathSize fmc) noexcept -{ - return util::pow_int<4>(1.5 - 0.00275 * fmc) / (460.0 + 25.9 * fmc) / 0.778 * 1000.0; -} -/** - * \brief Standard foliar moisture effect (FME) based on FMC [ST-X-3 eq 61] - */ -static const util::LookupTable<&calculate_standard_foliar_moisture_fmc> - STANDARD_FOLIAR_MOISTURE_FMC{}; -/** - * \brief Crown fire spread rate (m/min) / Foliar Moisture Effect (RSC / (FME / FME_avg)) [ST-X-3 eq 64] - * \param isi Initial Spread Index - * \return RSC / (FME / FME_avg) [ST-X-3 eq 64] - */ -[[nodiscard]] static MathSize - calculate_standard_foliar_moisture_isi( - const MathSize isi) noexcept -{ - return 60.0 * (1.0 - exp(-0.0497 * isi)); -} -/** - * \brief Crown fire spread rate (m/min) / Foliar Moisture Effect (RSC / (FME / FME_avg)) [ST-X-3 eq 64] - * \return RSC / (FME / FME_avg) [ST-X-3 eq 64] - */ -static const util::LookupTable<&calculate_standard_foliar_moisture_isi> - STANDARD_FOLIAR_MOISTURE_ISI{}; -/** - * \brief Length to Breadth ratio [ST-X-3 eq 79] - * \param ws Wind Speed (km/h) - * \return Length to Breadth ratio [ST-X-3 eq 79] - */ -[[nodiscard]] static MathSize - calculate_standard_length_to_breadth(const MathSize ws) noexcept -{ - return 1.0 + 8.729 * pow(1.0 - exp(-0.030 * ws), 2.155); -} -/** - * \brief Length to Breadth ratio [ST-X-3 eq 79] - * \return Length to Breadth ratio [ST-X-3 eq 79] - */ -static const util::LookupTable<&calculate_standard_length_to_breadth> - STANDARD_LENGTH_TO_BREADTH{}; -/** - * \brief A FuelBase made of a standard fuel type. - * \tparam A Rate of spread parameter a [ST-X-3 table 6] - * \tparam B Rate of spread parameter b * 10000 [ST-X-3 table 6] - * \tparam C Rate of spread parameter c * 100 [ST-X-3 table 6] - * \tparam Bui0 Average Build-up Index for the fuel type [ST-X-3 table 7] - * \tparam Cbh Crown base height (m) [ST-X-3 table 8] - * \tparam Cfl Crown fuel load (kg/m^2) [ST-X-3 table 8] - * \tparam BulkDensity Duff Bulk Density (kg/m^3) [Anderson table 1] * 1000 - * \tparam InorganicPercent Inorganic percent of Duff layer (%) [Anderson table 1] - * \tparam DuffDepth Depth of Duff layer (cm * 10) [Anderson table 1] - */ -template -class StandardFuel - : public FuelBase -{ -public: - /** - * \brief Constructor - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param can_crown Whether or not this fuel type can have a crown fire - * \param log_q Log value of q [ST-X-3 table 7] - * \param duff_ffmc Type of duff near the surface - * \param duff_dmc Type of duff deeper underground - */ - constexpr StandardFuel(const FuelCodeSize& code, - const char* name, - const bool can_crown, - const LogValue log_q, - const Duff* duff_ffmc, - const Duff* duff_dmc) noexcept - : FuelBase(code, - name, - can_crown, - duff_ffmc, - duff_dmc), - log_q_(log_q) - { - } - /** - * \brief Constructor - * \param code Code to identify fuel with - * \param name Name of the fuel - * \param can_crown Whether or not this fuel type can have a crown fire - * \param log_q Log value of q [ST-X-3 table 7] - * \param duff Type of duff near the surface and deeper underground - */ - constexpr StandardFuel(const FuelCodeSize& code, - const char* name, - const bool can_crown, - const LogValue log_q, - const Duff* duff) noexcept - : StandardFuel(code, - name, - can_crown, - log_q, - duff, - duff) - { - } - StandardFuel(StandardFuel&& rhs) noexcept = delete; - StandardFuel(const StandardFuel& rhs) noexcept = delete; - StandardFuel& operator=(StandardFuel&& rhs) noexcept = delete; - StandardFuel& operator=(const StandardFuel& rhs) = delete; - /** - * \brief Initial rate of spread (m/min) [ST-X-3 eq 26] - * \param isi Initial Spread Index - * \return Initial rate of spread (m/min) [ST-X-3 eq 26] - */ - [[nodiscard]] MathSize rosBasic(const MathSize isi) const noexcept - { - return a() * pow(1.0 - exp(negB() * isi), c()); - } - virtual /** - * \brief Crown Fuel Consumption (CFC) (kg/m^2) [ST-X-3 eq 66] - * \param cfb Crown Fraction Burned (CFB) [ST-X-3 eq 58] - * \return Crown Fuel Consumption (CFC) (kg/m^2) [ST-X-3 eq 66] - */ - MathSize - crownConsumption(const MathSize cfb) const noexcept override - { - return cfl() * cfb; - } - /** - * \brief ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41] - * \param mu Multiplier - * \param rsf Slope-adjusted zero wind rate of spread (RSF) [ST-X-3 eq 40] - * \return ISI with slope influence and zero wind (ISF) [ST-X-3 eq 41] - */ - [[nodiscard]] MathSize limitIsf(const MathSize mu, const MathSize rsf) const noexcept - { - return (1.0 / negB()) * log(max(SLOPE_LIMIT_ISI, (rsf > 0.0) ? (1.0 - pow((rsf / (mu * a())), (1.0 / c()))) : 1.0)); - } - /** - * \brief Critical Surface Fire Intensity (CSI) [ST-X-3 eq 56] - * \param spread SpreadInfo to use in calculation - * \return Critical Surface Fire Intensity (CSI) [ST-X-3 eq 56] - */ - [[nodiscard]] MathSize criticalSurfaceIntensity(const SpreadInfo& spread) const noexcept - override - { - return 0.001 * pow(cbh(), 1.5) * pow(460.0 + 25.9 * spread.foliarMoisture(), 1.5); - } - /** - * \brief Length to Breadth ratio [ST-X-3 eq 79] - * \param ws Wind Speed (km/h) - * \return Length to Breadth ratio [ST-X-3 eq 79] - */ - [[nodiscard]] MathSize lengthToBreadth(const MathSize ws) const noexcept override - { - return STANDARD_LENGTH_TO_BREADTH(ws); - } - /** - * \brief Final rate of spread (m/min) - * \param rss Surface Rate of spread (ROS) (m/min) [ST-X-3 eq 55] - * \return Final rate of spread (m/min) - */ - MathSize finalRos(const SpreadInfo&, - MathSize, - MathSize, - const MathSize rss) const noexcept override - { - return rss; - } - /** - * \brief BUI Effect on surface fire rate of spread [ST-X-3 eq 54] - * \param bui Build-up Index - * \return BUI Effect on surface fire rate of spread [ST-X-3 eq 54] - */ - [[nodiscard]] MathSize buiEffect(const MathSize bui) const noexcept override - { - return (0 < bui) - ? exp( - 50.0 - * log_q_.asValue() - * ((1.0 / bui) - (1.0 / bui0()))) - : 1.0; - } -protected: - ~StandardFuel() = default; - /** - * \brief Average Build-up Index for the fuel type [ST-X-3 table 7] - * \return Average Build-up Index for the fuel type [ST-X-3 table 7] - */ - [[nodiscard]] static constexpr MathSize bui0() noexcept - { - return Bui0; - } - /** - * \brief Crown base height (m) [ST-X-3 table 8] - * \return Crown base height (m) [ST-X-3 table 8] - */ - [[nodiscard]] MathSize cbh() const override - { - return Cbh; - } - /** - * \brief Crown fuel load (kg/m^2) [ST-X-3 table 8] - * \return Crown fuel load (kg/m^2) [ST-X-3 table 8] - */ - [[nodiscard]] MathSize cfl() const override - { - return Cfl / 100.0; - } - /** - * \brief Rate of spread parameter a [ST-X-3 table 6] - * \return Rate of spread parameter a [ST-X-3 table 6] - */ - [[nodiscard]] static constexpr MathSize a() noexcept - { - return A; - } - /** - * \brief Negative of rate of spread parameter b [ST-X-3 table 6] - * \return Negative of rate of spread parameter b [ST-X-3 table 6] - */ - [[nodiscard]] static constexpr MathSize negB() noexcept - { - // the only places this gets used it gets negated so just store it that way - return -B / 10000.0; - } - /** - * \brief Rate of spread parameter c [ST-X-3 table 6] - * \return Rate of spread parameter c [ST-X-3 table 6] - */ - [[nodiscard]] static constexpr MathSize c() noexcept - { - return C / 100.0; - } - /** - * \brief Crown fire spread rate (RSC) (m/min) [ST-X-3 eq 64] - * \param isi Initial Spread Index - * \param fmc Foliar Moisture Content - * \return Crown fire spread rate (RSC) (m/min) [ST-X-3 eq 64] - */ - [[nodiscard]] static constexpr MathSize crownRateOfSpread(const MathSize isi, - const MathSize fmc) noexcept - { - return STANDARD_FOLIAR_MOISTURE_ISI(isi) * STANDARD_FOLIAR_MOISTURE_FMC(fmc); - } -private: - /** - * \brief Log value of q [ST-X-3 table 7] - */ - LogValue log_q_; - static_assert(-negB() < 1); - static_assert(c() < 10 && c() > 1); -}; -} -} diff --git a/firestarr/src/cpp/StartPoint.cpp b/firestarr/src/cpp/StartPoint.cpp deleted file mode 100644 index 2dfb07cda..000000000 --- a/firestarr/src/cpp/StartPoint.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "StartPoint.h" -#include "Settings.h" -#include "unstable.h" -namespace fs::topo -{ -template -static T fix_range(T value, T min_value, T max_value) noexcept -{ - while (value < min_value) - { - value += max_value; - } - while (value >= max_value) - { - value -= max_value; - } - return value; -} -template -static T fix_degrees(T value) noexcept -{ - return fix_range(value, 0.0, 360.0); -} -template -static T fix_hours(T value) noexcept -{ - return fix_range(value, 0.0, 24.0); -} -static DurationSize sunrise_sunset(const int jd, - const MathSize latitude, - const MathSize longitude, - const bool for_sunrise) noexcept -{ - static const auto Zenith = util::to_radians(96); - static const auto LocalOffset = -5; - const auto t_hour = for_sunrise ? 6 : 18; - // http://edwilliams.org/sunrise_sunset_algorithm.htm - const auto lng_hour = longitude / 15; - const auto t = jd + (t_hour - lng_hour) / 24; - const auto m = 0.9856 * t - 3.289; - const auto l = fix_degrees( - m + 1.916 * _sin(util::to_radians(m)) + 0.020 * _sin(util::to_radians(2 * m)) + 282.634); - auto ra = fix_degrees(util::to_degrees(atan(0.91764 * tan(util::to_radians(l))))); - const auto l_quadrant = floor(l / 90) * 90; - const auto ra_quadrant = floor(ra / 90) * 90; - ra += l_quadrant - ra_quadrant; - ra /= 15; - const auto sin_dec = 0.39782 * _sin(util::to_radians(l)); - const auto cos_dec = _cos(asin(sin_dec)); - const auto cos_h = (_cos(Zenith) - sin_dec * _sin(util::to_radians(latitude))) / (cos_dec * _cos(util::to_radians(latitude))); - if (cos_h > 1) - { - // sun never rises - return for_sunrise ? -1 : 25; - } - if (cos_h < -1) - { - // sun never sets - return for_sunrise ? 25 : -1; - } - auto h = util::to_degrees(acos(cos_h)); - if (for_sunrise) - { - h = 360 - h; - } - h /= 15; - const auto mean_t = h + ra - 0.06571 * t - 6.622; - const auto ut = mean_t - lng_hour; - return fix_hours(ut + LocalOffset); -} -static DurationSize sunrise(const int jd, - const MathSize latitude, - const MathSize longitude) noexcept -{ - return sunrise_sunset(jd, latitude, longitude, true); -} -static DurationSize sunset(const int jd, const MathSize latitude, const MathSize longitude) noexcept -{ - return sunrise_sunset(jd, latitude, longitude, false); -} -static array, MAX_DAYS> make_days( - const MathSize latitude, - const MathSize longitude) noexcept -{ - array, MAX_DAYS> days{}; - array day_length_hours{}; - for (size_t i = 0; i < day_length_hours.size(); ++i) - { - days[i] = make_tuple( - fix_hours( - sunrise(static_cast(i), latitude, longitude) + sim::Settings::offsetSunrise()), - fix_hours( - sunset(static_cast(i), - latitude, - longitude) - - sim::Settings::offsetSunset())); - day_length_hours[i] = get<1>(days[i]) - get<0>(days[i]); - } - return days; -} -StartPoint::StartPoint(const MathSize latitude, const MathSize longitude) noexcept - : Point(latitude, longitude), days_(make_days(latitude, longitude)) -{ -} -StartPoint& StartPoint::operator=(StartPoint&& rhs) noexcept -{ - if (this != &rhs) - { - Point::operator=(rhs); - for (size_t i = 0; i < days_.size(); ++i) - { - days_[i] = rhs.days_[i]; - } - } - return *this; -} -} diff --git a/firestarr/src/cpp/StartPoint.h b/firestarr/src/cpp/StartPoint.h deleted file mode 100644 index 0dbf45969..000000000 --- a/firestarr/src/cpp/StartPoint.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include "Point.h" -namespace fs -{ -namespace topo -{ -/** - * \brief A Point that has sunrise and sunset times for each day. - */ -class StartPoint : public Point -{ - /** - * \brief Array of tuple for sunrise/sunset times by day - */ - array, MAX_DAYS> days_; -public: - /** - * \brief Constructor - * \param latitude Latitude (decimal degrees) - * \param longitude Longitude (decimal degrees) - */ - StartPoint(MathSize latitude, MathSize longitude) noexcept; - ~StartPoint() noexcept = default; - /** - * \brief Copy constructor - * \param rhs StartPoint to copy from - */ - StartPoint(const StartPoint& rhs) noexcept = default; - /** - * \brief Move constructor - * \param rhs StartPoint to move from - */ - StartPoint(StartPoint&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs StartPoint to copy from - * \return This, after assignment - */ - StartPoint& operator=(const StartPoint& rhs) = default; - /** - * \brief Move assignment - * \param rhs StartPoint to move from - * \return This, after assignment - */ - StartPoint& operator=(StartPoint&& rhs) noexcept; - /** - * \brief Sunrise time - * \param day Day - * \return Sunrise time on give day - */ - [[nodiscard]] constexpr DurationSize dayStart(const size_t day) const - { - return get<0>(days_.at(day)); - } - /** - * \brief Sunset time - * \param day Day - * \return Sunset time on give day - */ - [[nodiscard]] constexpr DurationSize dayEnd(const size_t day) const - { - return get<1>(days_.at(day)); - } -}; -} -} diff --git a/firestarr/src/cpp/Startup.cpp b/firestarr/src/cpp/Startup.cpp deleted file mode 100644 index 876a92e0a..000000000 --- a/firestarr/src/cpp/Startup.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Startup.h" -namespace fs::wx -{ -Startup::Startup(string station, - const tm& generated, - const topo::Point& point, - const double distance_from, - const Ffmc& ffmc, - const Dmc& dmc, - const Dc& dc, - const Precipitation& apcp_prev, - const bool overridden) noexcept - : station_(std::move(station)), - generated_(generated), - point_(point), - distance_from_(distance_from), - ffmc_(ffmc), - dmc_(dmc), - dc_(dc), - apcp_prev_(apcp_prev), - is_overridden_(overridden) -{ -} -} diff --git a/firestarr/src/cpp/Startup.h b/firestarr/src/cpp/Startup.h deleted file mode 100644 index 2a1431775..000000000 --- a/firestarr/src/cpp/Startup.h +++ /dev/null @@ -1,179 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "stdafx.h" -#include "FWI.h" -#include "Point.h" -#include "Weather.h" -namespace fs -{ -namespace wx -{ -/** - * \brief Startup values to initialize a weather stream calculation with. - */ -class Startup -{ -public: - /** - * \brief Station providing the Startup values - * \return Station providing the Startup values - */ - [[nodiscard]] constexpr const string& station() const noexcept - { - return station_; - } - /** - * \brief Time the Startup value is for - * \return Time the Startup value is for - */ - [[nodiscard]] constexpr const tm& generated() const noexcept - { - return generated_; - } - /** - * \brief Point the Startup value is for - * \return Point the Startup value is for - */ - [[nodiscard]] constexpr const topo::Point& point() const noexcept - { - return point_; - } - /** - * \brief Distance Startup value location is from the requested location (m) - * \return Distance Startup value location is from the requested location (m) - */ - [[nodiscard]] constexpr MathSize distanceFrom() const noexcept - { - return distance_from_; - } - /** - * \brief Fine Fuel Moisture Code - * \return Fine Fuel Moisture Code - */ - [[nodiscard]] constexpr const Ffmc& ffmc() const noexcept - { - return ffmc_; - } - /** - * \brief Duff Moisture Code - * \return Duff Moisture Code - */ - [[nodiscard]] constexpr const Dmc& dmc() const noexcept - { - return dmc_; - } - /** - * \brief Drought Code - * \return Drought Code - */ - [[nodiscard]] constexpr const Dc& dc() const noexcept - { - return dc_; - } - /** - * \brief Accumulated Precipitation from noon yesterday to start of hourly weather (mm) - * \return Accumulated Precipitation from noon yesterday to start of hourly weather (mm) - */ - [[nodiscard]] constexpr const Precipitation& apcpPrev() const noexcept - { - return apcp_prev_; - } - /** - * \brief Whether or not any Startup values were overridden - * \return Whether or not any Startup values were overridden - */ - [[nodiscard]] constexpr bool isOverridden() const noexcept - { - return is_overridden_; - } - /** - * \brief Constructor - * \param station Station indices are from - * \param generated Date/Time indices are from - * \param point Point indices were requested for - * \param distance_from Distance from requested point the weather station is (m) - * \param ffmc Fine Fuel Moisture Code - * \param dmc Duff Moisture Code - * \param dc Drought Code - * \param apcp_prev Accumulated Precipitation from noon yesterday to start of hourly weather (mm) - * \param overridden whether or not any Startup values were overridden - */ - Startup(string station, - const tm& generated, - const topo::Point& point, - MathSize distance_from, - const Ffmc& ffmc, - const Dmc& dmc, - const Dc& dc, - const Precipitation& apcp_prev, - bool overridden) noexcept; - /** - * \brief Move constructor - * \param rhs Startup to move from - */ - Startup(Startup&& rhs) noexcept = default; - /** - * \brief Copy constructor - * \param rhs Startup to copy from - */ - Startup(const Startup& rhs) = default; - /** - * \brief Move assignment operator - * \param rhs Startup to move from - * \return This, after assignment - */ - Startup& operator=(Startup&& rhs) noexcept = default; - /** - * \brief Copy assignment operator - * \param rhs Startup to copy from - * \return This, after assignment - */ - Startup& operator=(const Startup& rhs) = default; - /** - * \brief Destructor - */ - ~Startup() = default; -private: - /** - * \brief Station indices are from - */ - string station_; - /** - * \brief When these indices were observed - */ - tm generated_; - /** - * \brief Point this represents - */ - topo::Point point_; - /** - * \brief Distance actual point for this is from represented Point (m) - */ - MathSize distance_from_; - /** - * \brief Fine Fuel Moisture Code - */ - Ffmc ffmc_; - /** - * \brief Duff Moisture Code - */ - Dmc dmc_; - /** - * \brief Drought code - */ - Dc dc_; - /** - * \brief Accumulated Precipitation from noon yesterday to start of hourly weather (mm) - */ - Precipitation apcp_prev_; - /** - * \brief Whether or not any of the indices have been overridden - */ - bool is_overridden_; -}; -} -} diff --git a/firestarr/src/cpp/Statistics.h b/firestarr/src/cpp/Statistics.h deleted file mode 100644 index 6f5648767..000000000 --- a/firestarr/src/cpp/Statistics.h +++ /dev/null @@ -1,359 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -#include -#include -#include "Log.h" -#include "Settings.h" -#include "Util.h" -namespace fs -{ -namespace util -{ -/** - * \brief Student's T critical values - */ -static constexpr array T_VALUES{ - 3.078, - 1.886, - 1.638, - 1.533, - 1.476, - 1.440, - 1.415, - 1.397, - 1.383, - 1.372, - 1.363, - 1.356, - 1.350, - 1.345, - 1.341, - 1.337, - 1.333, - 1.330, - 1.328, - 1.325, - 1.323, - 1.321, - 1.319, - 1.318, - 1.316, - 1.315, - 1.314, - 1.313, - 1.311, - 1.310, - 1.309, - 1.309, - 1.308, - 1.307, - 1.306, - 1.306, - 1.305, - 1.304, - 1.304, - 1.303, - 1.303, - 1.302, - 1.302, - 1.301, - 1.301, - 1.300, - 1.300, - 1.299, - 1.299, - 1.299, - 1.298, - 1.298, - 1.298, - 1.297, - 1.297, - 1.297, - 1.297, - 1.296, - 1.296, - 1.296, - 1.296, - 1.295, - 1.295, - 1.295, - 1.295, - 1.295, - 1.294, - 1.294, - 1.294, - 1.294, - 1.294, - 1.293, - 1.293, - 1.293, - 1.293, - 1.293, - 1.293, - 1.292, - 1.292, - 1.292, - 1.292, - 1.292, - 1.292, - 1.292, - 1.292, - 1.291, - 1.291, - 1.291, - 1.291, - 1.291, - 1.291, - 1.291, - 1.291, - 1.291, - 1.291, - 1.290, - 1.290, - 1.290, - 1.290, - 1.290}; -/** - * \brief Provides statistics calculation for vectors of values. - */ -class Statistics -{ -public: - /** - * \brief Minimum value - * \return Minimum value - */ - [[nodiscard]] MathSize min() const noexcept - { - return percentiles_[0]; - } - /** - * \brief Maximum value - * \return Maximum value - */ - [[nodiscard]] MathSize max() const noexcept - { - return percentiles_[100]; - } - /** - * \brief Median value - * \return Median value - */ - [[nodiscard]] MathSize median() const noexcept - { - return percentiles_[50]; - } - /** - * \brief Mean (average) value - * \return Mean (average) value - */ - [[nodiscard]] MathSize mean() const noexcept - { - return mean_; - } - /** - * \brief Standard Deviation - * \return Standard Deviation - */ - [[nodiscard]] MathSize standardDeviation() const noexcept - { - return standard_deviation_; - } - /** - * \brief Sample Variance - * \return Sample Variance - */ - [[nodiscard]] MathSize sampleVariance() const noexcept - { - return sample_variance_; - } - /** - * \brief Number of data points in the set - * \return Number of data points in the set - */ - [[nodiscard]] size_t n() const noexcept - { - return n_; - } - /** - * \brief Value for given percentile - * \param i Percentile to retrieve value for - * \return Value for given percentile - */ - [[nodiscard]] MathSize percentile(const uint8_t i) const noexcept - { -#ifdef DEBUG_STATISTICS - logging::check_fatal(static_cast(i) >= percentiles_.size(), - "Invalid percentile %d requested", - i); -#endif - return percentiles_.at(i); - } - /** - * \brief 80% Confidence Interval - * \return 80% Confidence Interval - */ - [[nodiscard]] MathSize confidenceInterval80() const - { - return confidenceInterval(1.28); - } - /** - * \brief 90% Confidence Interval - * \return 90% Confidence Interval - */ - [[nodiscard]] MathSize confidenceInterval90() const - { - return confidenceInterval(1.645); - } - /** - * \brief 95% Confidence Interval - * \return 95% Confidence Interval - */ - [[nodiscard]] MathSize confidenceInterval95() const - { - return confidenceInterval(1.96); - } - /** - * \brief 98% Confidence Interval - * \return 98% Confidence Interval - */ - [[nodiscard]] MathSize confidenceInterval98() const - { - return confidenceInterval(2.33); - } - /** - * \brief 99% Confidence Interval - * \return 99% Confidence Interval - */ - [[nodiscard]] MathSize confidenceInterval99() const - { - return confidenceInterval(2.58); - } - /** - * \brief Calculates statistics on a vector of values - * \param values Values to use for calculation - */ - explicit Statistics(vector values) - { - // values should already be sorted - // std::sort(values.begin(), values.end()); - n_ = values.size(); - min_ = values[0]; - max_ = values[n_ - 1]; - median_ = values[n_ / 2]; - const auto total_sum = std::accumulate(values.begin(), - values.end(), - 0.0, - [](const MathSize t, const MathSize x) { return t + x; }); - mean_ = total_sum / n_; - for (size_t i = 0; i < percentiles_.size(); ++i) - { - const auto pos = std::min(n_ - 1, - static_cast(truncl( - (static_cast(i) / (percentiles_.size() - 1)) * n_))); - // note("For %d values %dth percentile is at %d", n_, i, pos); - percentiles_[i] = values[pos]; - } - const auto total = std::accumulate(values.begin(), - values.end(), - 0.0, - [this](const MathSize t, const MathSize x) { return t + pow_int<2>(x - mean_); }); - standard_deviation_ = sqrt(total / n_); - sample_variance_ = total / (n_ - 1); -#ifdef DEBUG_STATISTICS - logging::check_equal(min_, percentiles_[0], "min"); - logging::check_equal(max_, percentiles_[100], "max"); - logging::check_equal(median_, percentiles_[50], "median"); -#endif - } - /** - * \brief Calculate Student's T value - * \return Student's T value - */ - [[nodiscard]] MathSize studentsT() const noexcept - { - const auto result = T_VALUES[std::min(T_VALUES.size(), n()) - 1] - * sqrt(sampleVariance() / n()) / abs(mean()); - // printf("%ld %f %f %f\n", n(), mean(), sampleVariance(), result); - return result; - } - /** - * \brief Whether or not we have less than the relative error and can be confident in the results - * \param relative_error Relative Error that is required - * \return If Student's T value is less than the relative error - */ - [[nodiscard]] bool isConfident(const MathSize relative_error) const noexcept - { - const auto st = studentsT(); - const auto re = relative_error / (1 + relative_error); - // printf("%f <= %f is %s\n", st, re, ((st <= re) ? "true" : "false")); - return st <= re; - } - /** - * \brief Estimate how many more runs are required to achieve desired confidence - * \param cur_runs Current number of runs completed - * \param relative_error Relative Error to achieve to be confident - * \return Number of runs still required - */ - [[nodiscard]] size_t runsRequired( - // const size_t cur_runs, - const MathSize relative_error) const - { - const auto re = relative_error / (1 + relative_error); - const std::function fct = [this](const size_t i) noexcept { - return T_VALUES[std::min(T_VALUES.size(), i) - 1] - * sqrt(sampleVariance() / i) / abs(mean()); - }; - const auto cur_runs = n(); - return binary_find_checked(cur_runs, 10 * cur_runs, re, fct) - cur_runs; - } -private: - /** - * \brief Calculate Confidence Interval for given z value - * \param z Z value to calculate for - * \return Confidence Interval - */ - [[nodiscard]] MathSize confidenceInterval(const MathSize z) const - { - return z * mean_ / sqrt(n_); - } - /** - * \brief Number of values - */ - size_t n_; - /** - * \brief Minimum value - */ - MathSize min_; - /** - * \brief Maximum value - */ - MathSize max_; - /** - * \brief Mean (average) value - */ - MathSize mean_; - /** - * \brief Median value - */ - MathSize median_; - /** - * \brief Standard Deviation - */ - MathSize standard_deviation_; - /** - * \brief Sample variance - */ - MathSize sample_variance_; - /** - * \brief Array of all integer percentile values - */ - array percentiles_{}; -}; -} -} diff --git a/firestarr/src/cpp/Test.cpp b/firestarr/src/cpp/Test.cpp deleted file mode 100644 index 4c5ee41a6..000000000 --- a/firestarr/src/cpp/Test.cpp +++ /dev/null @@ -1,537 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Test.h" -#include "FireSpread.h" -#include "Model.h" -#include "Observer.h" -#include "Util.h" -#include "ConstantWeather.h" - -namespace fs::sim -{ -using fs::fuel::simplify_fuel_name; -/** - * \brief An Environment with no elevation and the same value in every Cell. - */ -class TestEnvironment - : public topo::Environment -{ -public: - /** - * \brief Environment with the same data in every cell - * \param dir_out Folder to save outputs to - * \param cells Constant cells - */ - explicit TestEnvironment(const string dir_out, - topo::CellGrid* cells) noexcept - : Environment(dir_out, cells, 0) - { - } -}; -/** - * \brief A Scenario run with constant fuel, weather, and topography. - */ -class TestScenario final - : public Scenario -{ -public: - ~TestScenario() override = default; - TestScenario(const TestScenario& rhs) = delete; - TestScenario(TestScenario&& rhs) = delete; - TestScenario& operator=(const TestScenario& rhs) = delete; - TestScenario& operator=(TestScenario&& rhs) = delete; - /** - * \brief Constructor - * \param model Model running this Scenario - * \param start_cell Cell to start ignition in - * \param start_point StartPoint represented by start_cell - * \param start_date Start date of simulation - * \param end_date End data of simulation - * \param weather Constant weather to use for duration of simulation - */ - TestScenario(Model* model, - const shared_ptr& start_cell, - const topo::StartPoint& start_point, - const int start_date, - const DurationSize end_date, - wx::FireWeather* weather) - : Scenario(model, - 1, - weather, - weather, - start_date, - start_cell, - start_point, - static_cast(start_date), - static_cast(end_date)) - { - addEvent(Event::makeEnd(end_date)); - last_save_ = end_date; - final_sizes_ = {}; - // cast to avoid warning - static_cast(reset(nullptr, nullptr, reinterpret_cast(&final_sizes_))); - } -}; -void showSpread(const SpreadInfo& spread, const wx::FwiWeather* w, const fuel::FuelType* fuel) -{ - // column, (width, format) - static const map> FMT{ - {"PREC", {5, "%*.2f"}}, - {"TEMP", {5, "%*.1f"}}, - {"RH", {3, "%*g"}}, - {"WS", {5, "%*.1f"}}, - {"WD", {3, "%*g"}}, - {"FFMC", {5, "%*.1f"}}, - {"DMC", {5, "%*.1f"}}, - {"DC", {5, "%*g"}}, - {"ISI", {5, "%*.1f"}}, - {"BUI", {5, "%*.1f"}}, - {"FWI", {5, "%*.1f"}}, - {"GS", {3, "%*d"}}, - {"SAZ", {3, "%*d"}}, - {"FUEL", {7, "%*s"}}, - {"GC", {3, "%*g"}}, - {"L:B", {5, "%*.2f"}}, - {"CBH", {4, "%*.1f"}}, - {"CFB", {6, "%*.3f"}}, - {"CFC", {6, "%*.3f"}}, - {"FD", {2, "%*c"}}, - {"HFI", {6, "%*ld"}}, - {"RAZ", {3, "%*d"}}, - {"ROS", {6, "%*.4g"}}, - {"SFC", {6, "%*.4g"}}, - {"TFC", {6, "%*.4g"}}, - }; - printf("Calculated spread is:\n"); - // print header row - for (const auto& h_f : FMT) - { - // HACK: need to format string of the same length - const auto col = h_f.first; - const auto width = h_f.second.first; - // column width + 1 space - printf("%*s", width + 1, col); - } - printf("\n"); - auto print_col = [](const char* col, auto value) { - const auto width_fmt = FMT.at(col); - const auto width = width_fmt.first + 1; - const auto fmt = width_fmt.second; - printf(fmt, width, value); - }; - // HACK: just do individual calls for now - // can we assign them to a lookup table if they're not all numbers? - print_col("PREC", w->prec().asValue()); - print_col("TEMP", w->temp().asValue()); - print_col("RH", w->rh().asValue()); - print_col("WS", w->wind().speed().asValue()); - print_col("WD", w->wind().direction().asValue()); - print_col("FFMC", w->ffmc().asValue()); - print_col("DMC", w->dmc().asValue()); - print_col("DC", w->dc().asValue()); - print_col("ISI", w->isi().asValue()); - print_col("BUI", w->bui().asValue()); - print_col("FWI", w->fwi().asValue()); - print_col("GS", spread.percentSlope()); - print_col("SAZ", spread.slopeAzimuth()); - const auto simple_fuel = simplify_fuel_name(fuel->name()); - print_col("FUEL", simple_fuel.c_str()); - print_col("GC", fuel->grass_curing(spread.nd(), *w)); - print_col("L:B", spread.lengthToBreadth()); - print_col("CBH", fuel->cbh()); - print_col("CFB", spread.crownFractionBurned()); - print_col("CFC", spread.crownFuelConsumption()); - print_col("FD", spread.fireDescription()); - print_col("HFI", static_cast(spread.maxIntensity())); - print_col("RAZ", static_cast(spread.headDirection().asDegrees())); - print_col("ROS", spread.headRos()); - print_col("SFC", spread.surfaceFuelConsumption()); - print_col("TFC", spread.totalFuelConsumption()); - printf("\r\n"); -} -static Semaphore num_concurrent{static_cast(std::thread::hardware_concurrency())}; -string generate_test_name( - const auto& fuel, - const SlopeSize slope, - const AspectSize aspect, - const fs::wx::Wind& wind) -{ - // wind speed & direction can be decimal values, but slope and aspect are int - constexpr auto mask = "%s_S%03d_A%03d_WD%05.1f_WS%05.1f"; - auto simple_fuel_name = simplify_fuel_name(fuel); - const size_t out_length = simple_fuel_name.length() + 27 + 1; - vector out{}; - out.resize(out_length); - sxprintf(&(out[0]), - out_length, - mask, - simple_fuel_name.c_str(), - slope, - aspect, - wind.direction().asDegrees(), - wind.speed().asValue()); - return string(&(out[0])); -}; -string run_test(const string base_directory, - const string& fuel_name, - const SlopeSize slope, - const AspectSize aspect, - const DurationSize num_hours, - const wx::Dc& dc, - const wx::Dmc& dmc, - const wx::Ffmc& ffmc, - const wx::Wind& wind, - const bool ignore_existing) -{ - string test_name = generate_test_name( - fuel_name, - slope, - aspect, - wind); - logging::verbose("Queueing test for %s", test_name); - const string output_directory = base_directory + "/" + test_name + "/"; - if (ignore_existing && util::directory_exists(output_directory.c_str())) - { - // skip if directory exists - logging::warning("Skipping existing directory %s", output_directory.c_str()); - return output_directory; - } - // delay instantiation so things only get made when executed - CriticalSection _(num_concurrent); - logging::debug("Concurrent test limit is %d", num_concurrent.limit()); - logging::note("Running test for %s", output_directory.c_str()); - const auto year = 2020; - const auto month = 6; - const auto day = 15; - const auto hour = 12; - const auto minute = 0; - const auto t = util::to_tm(year, month, day, hour, minute); - logging::verbose("DJ = %d\n", t.tm_yday); - static const auto Latitude = 49.3911; - static const auto Longitude = -84.7395; - static const topo::StartPoint ForPoint(Latitude, Longitude); - const auto start_date = t.tm_yday; - const auto end_date = start_date + static_cast(num_hours) / DAY_HOURS; - util::make_directory_recursive(output_directory.c_str()); - const auto fuel = Settings::fuelLookup().bySimplifiedName(simplify_fuel_name(fuel_name)); - auto values = vector(); - // values.reserve(static_cast(MAX_ROWS) * MAX_COLUMNS); - for (Idx r = 0; r < MAX_ROWS; ++r) - { - for (Idx c = 0; c < MAX_COLUMNS; ++c) - { - values.emplace_back(r, c, slope, aspect, fuel::FuelType::safeCode(fuel)); - } - } - const topo::Cell cell_nodata{}; - const auto cells = new topo::CellGrid{ - TEST_GRID_SIZE, - MAX_ROWS, - MAX_COLUMNS, - cell_nodata.fullHash(), - cell_nodata, - TEST_XLLCORNER, - TEST_YLLCORNER, - TEST_XLLCORNER + TEST_GRID_SIZE * MAX_COLUMNS, - TEST_YLLCORNER + TEST_GRID_SIZE * MAX_ROWS, - TEST_PROJ4, - std::move(values)}; - TestEnvironment env(output_directory, cells); - const Location start_location(static_cast(MAX_ROWS / 2), - static_cast(MAX_COLUMNS / 2)); - Model model(output_directory, ForPoint, &env); - const auto start_cell = make_shared(model.cell(start_location)); - ConstantWeather weather(fuel, start_date, dc, dmc, ffmc, wind); - TestScenario scenario(&model, start_cell, ForPoint, start_date, end_date, &weather); - const auto w = weather.at(start_date); - auto info = SpreadInfo(scenario, - start_date, - start_cell->key(), - model.nd(start_date), - w); - showSpread(info, w, fuel); - map probabilities{}; - logging::debug("Starting simulation"); - // NOTE: don't want to reset first because TestScenabuirio handles what that does - scenario.run(&probabilities); - logging::note( - "Saving results for %s in %s", - test_name.c_str(), - output_directory.c_str()); - logging::note("Final Size: %0.0f, ROS: %0.2f", - scenario.currentFireSize(), - info.headRos()); - return output_directory; -} -string run_test_ignore_existing( - const string output_directory, - const string& fuel_name, - const SlopeSize slope, - const AspectSize aspect, - const DurationSize num_hours, - const wx::Dc& dc, - const wx::Dmc& dmc, - const wx::Ffmc& ffmc, - const wx::Wind& wind) -{ - return run_test(output_directory, fuel_name, slope, aspect, num_hours, dc, dmc, ffmc, wind, true); -} -template -void show_options(const char* name, - const vector& values, - const char* fmt, - std::function convert) -{ - printf("\t%ld %s: ", values.size(), name); - // HACK: always print something before but avoid extra comma - const char* prefix_open = "["; - const char* prefix_comma = ", "; - const char** p = &prefix_open; - for (auto v : values) - { - printf(*p); - printf(fmt, convert(v)); - p = &prefix_comma; - } - printf("]\n"); -}; -template -void show_options(const char* name, const vector& values) -{ - return show_options(name, - values, - "%d", - [](V& value) { return value; }); -}; -void show_options(const char* name, const vector& values) -{ - return show_options(name, - values, - "%s", - [](string& value) { - return value.c_str(); - }); -}; - -const AspectSize ASPECT_INCREMENT = 90; -const SlopeSize SLOPE_INCREMENT = 60; -const int WS_INCREMENT = 5; -const int WD_INCREMENT = 45; -const int MAX_WIND = 50; -const DurationSize DEFAULT_HOURS = 10.0; -const SlopeSize DEFAULT_SLOPE = 0; -const AspectSize DEFAULT_ASPECT = 0; -const wx::Speed DEFAULT_WIND_SPEED(20); -const wx::Direction DEFAULT_WIND_DIRECTION(180, false); -const wx::Wind DEFAULT_WIND(DEFAULT_WIND_DIRECTION, DEFAULT_WIND_SPEED); -const wx::Ffmc DEFAULT_FFMC(90); -const wx::Dmc DEFAULT_DMC(35.5); -const wx::Dc DEFAULT_DC(275); -// these weather variables change nothing? -static const wx::Temperature TEMP(20.0); -static const wx::RelativeHumidity RH(30.0); -static const wx::Precipitation PREC(0.0); -const vector FUEL_NAMES{"C-2", "O-1a", "M-1/M-2 (25 PC)", "S-1", "C-3"}; -const auto DEFAULT_FUEL_NAME = simplify_fuel_name(FUEL_NAMES[0]); - -int test( - const string& output_directory, - const DurationSize num_hours, - const fs::wx::FwiWeather* wx, - const string& constant_fuel_name, - const SlopeSize constant_slope, - const AspectSize constant_aspect, - const bool test_all) -{ - // FIX: I think this does a lot of the same things as the test code is doing because it was - // derived from this code - Settings::setMinimumRos(0.0); - // make sure all tests run regardless of how long it takes - Settings::setMaximumTimeSeconds(numeric_limits::max()); - const auto hours = INVALID_TIME == num_hours ? DEFAULT_HOURS : num_hours; - const auto ffmc = (fs::wx::Ffmc::Invalid == wx->ffmc()) ? DEFAULT_FFMC : wx->ffmc(); - const auto dmc = (fs::wx::Dmc::Invalid == wx->dmc()) ? DEFAULT_DMC : wx->dmc(); - const auto dc = (fs::wx::Dc::Invalid == wx->dc()) ? DEFAULT_DC : wx->dc(); - // HACK: need to compare value and not object - const auto wind_direction = (fs::wx::Direction::Invalid.asValue() == wx->wind().direction().asValue()) ? DEFAULT_WIND_DIRECTION : wx->wind().direction(); - const auto wind_speed = (fs::wx::Speed::Invalid.asValue() == wx->wind().speed().asValue()) ? DEFAULT_WIND_SPEED : wx->wind().speed(); - const auto wind = fs::wx::Wind(wind_direction, wind_speed); - static const wx::Temperature TEMP(20.0); - static const wx::RelativeHumidity RH(30.0); - static const wx::Precipitation PREC(0.0); - const auto slope = (INVALID_SLOPE == constant_slope) ? DEFAULT_SLOPE : constant_slope; - const auto aspect = (INVALID_ASPECT == constant_aspect) ? DEFAULT_ASPECT : constant_aspect; - const auto fixed_fuel_name = simplify_fuel_name(constant_fuel_name); - const auto fuel = (fixed_fuel_name.empty() ? DEFAULT_FUEL_NAME : fixed_fuel_name); - try - { - if (test_all) - { - size_t result = 0; - // generate all options first so we can say how many there are at start - auto fuel_names = vector(); - if (fixed_fuel_name.empty()) - { - for (auto f : FUEL_NAMES) - { - fuel_names.emplace_back(f); - } - } - else - { - fuel_names.emplace_back(fuel); - } - auto slopes = vector(); - if (INVALID_SLOPE == constant_slope) - { - for (SlopeSize slope = 0; slope <= 100; slope += SLOPE_INCREMENT) - { - slopes.emplace_back(slope); - } - } - else - { - slopes.emplace_back(constant_slope); - } - auto aspects = vector(); - if (INVALID_ASPECT == constant_aspect) - { - for (AspectSize aspect = 0; aspect < 360; aspect += ASPECT_INCREMENT) - { - aspects.emplace_back(aspect); - } - } - else - { - aspects.emplace_back(constant_aspect); - } - auto wind_directions = vector(); - if (fs::wx::Direction::Invalid == wx->wind().direction()) - { - for (auto wind_direction = 0; wind_direction < 360; wind_direction += WD_INCREMENT) - { - wind_directions.emplace_back(wind_direction); - } - } - else - { - wind_directions.emplace_back(static_cast(wx->wind().direction().asDegrees())); - } - auto wind_speeds = vector(); - if (fs::wx::Speed::Invalid == wx->wind().speed()) - { - for (auto wind_speed = 0; wind_speed <= MAX_WIND; wind_speed += WS_INCREMENT) - { - wind_speeds.emplace_back(wind_speed); - } - } - else - { - wind_speeds.emplace_back(static_cast(wx->wind().speed().asValue())); - } - size_t values = 1; - values *= fuel_names.size(); - values *= slopes.size(); - values *= aspects.size(); - values *= wind_directions.size(); - values *= wind_speeds.size(); - printf("There are %ld options to try based on:\n", values); - show_options("fuels", fuel_names); - show_options("slopes", slopes); - show_options("aspects", aspects); - show_options("wind directions", wind_directions); - show_options("wind speeds", wind_speeds); - // do everything in parallel but not all at once because it uses too much memory for most computers - vector> results{}; - for (const auto& fuel : fuel_names) - { - // do everything in parallel but not all at once because it uses too much memory for most computers - for (auto slope : slopes) - { - for (auto aspect : aspects) - { - for (auto wind_direction : wind_directions) - { - const wx::Direction direction(wind_direction, false); - for (auto wind_speed : wind_speeds) - { - const wx::Wind wind(direction, wx::Speed(wind_speed)); - // need to make string now because it'll be another value if we wait - results.push_back(async(launch::async, - run_test_ignore_existing, - output_directory, - fuel, - slope, - aspect, - hours, - dc, - dmc, - ffmc, - wind)); - } - } - } - } - } - for (auto& r : results) - { - r.wait(); - auto dir_out = r.get(); - logging::check_fatal(!util::directory_exists(dir_out.c_str()), - "Directory for test is missing: %s\n", - dir_out.c_str()); - ++result; - } - vector directories{}; - util::read_directory(false, output_directory, &directories); - logging::check_fatal(directories.size() != result, - "Expected %ld directories but have %ld", - result, - directories.size()); - logging::note("Successfully ran %ld tests", result); - } - else - { - logging::note( - "Running tests with constant inputs for %f hours:\n" - "\tFFMC:\t\t\t%f\n" - "\tDMC:\t\t\t%f\n" - "\tDC:\t\t\t%f\n" - "\tWind Speed:\t\t%f\n" - "\tWind Direction:\t\t%f\n" - "\tSlope:\t\t\t%d\n" - "\tAspect:\t\t\t%d\n", - hours, - ffmc.asValue(), - dmc.asValue(), - dc.asValue(), - wind_speed, - wind_direction, - slope, - aspect); - auto dir_out = run_test(output_directory.c_str(), - fuel, - slope, - aspect, - hours, - dc, - dmc, - ffmc, - wind, - false); - logging::check_fatal(!util::directory_exists(dir_out.c_str()), - "Directory for test is missing: %s\n", - dir_out.c_str()); - } - } - catch (const runtime_error& err) - { - logging::fatal(err); - } - return 0; -} -} diff --git a/firestarr/src/cpp/Test.h b/firestarr/src/cpp/Test.h deleted file mode 100644 index 5fd3210a7..000000000 --- a/firestarr/src/cpp/Test.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -namespace fs::wx -{ -class FwiWeather; -}; -namespace fs::sim -{ -#include "stdafx.h" - -static const double TEST_GRID_SIZE = 100.0; -static const char TEST_PROJ4[] = - "+proj=tmerc +lat_0=0.000000000 +lon_0=-90.000000000" - " +k=0.999600 +x_0=500000.000 +y_0=0.000 +a=6378137.000 +b=6356752.314 +units=m"; -static const double TEST_XLLCORNER = 324203.990666; -static const double TEST_YLLCORNER = 12646355.311160; -/** - * \brief Runs test cases for constant weather and fuel based on given arguments. - * \param dir_out root directory for outputs - * \param fuel_name FBP fuel to use or empty string for default - * \param wx FwiWeather to use for constant indices, where anything Invalid uses default - * \param test_all whether to run all combinations of test outputs filtered by criteria (true), or to just run the default single value modified by whatever has been specified (false) - * \return - */ -int test( - const string& output_directory, - const DurationSize num_hours, - const fs::wx::FwiWeather* wx, - const string& fuel_name, - const SlopeSize slope, - const AspectSize aspect, - const bool test_all); -} diff --git a/firestarr/src/cpp/TimeUtil.cpp b/firestarr/src/cpp/TimeUtil.cpp deleted file mode 100644 index 56dae43b6..000000000 --- a/firestarr/src/cpp/TimeUtil.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "TimeUtil.h" -namespace fs::util -{ -void fix_tm(tm* t) -{ - const time_t t_t = mktime(t); - t = localtime(&t_t); -} -} diff --git a/firestarr/src/cpp/TimeUtil.h b/firestarr/src/cpp/TimeUtil.h deleted file mode 100644 index fd7d393ad..000000000 --- a/firestarr/src/cpp/TimeUtil.h +++ /dev/null @@ -1,14 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -namespace fs::util -{ -/** - * \brief Calculate tm fields from values already there - * @param t tm object to update - */ -void fix_tm(tm* t); -} diff --git a/firestarr/src/cpp/Trim.cpp b/firestarr/src/cpp/Trim.cpp deleted file mode 100644 index 5c6cad7b5..000000000 --- a/firestarr/src/cpp/Trim.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Trim.h" -// from https://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring -namespace fs::util -{ -// trim from start (in place) -void trim_left(string* s) -{ - s->erase(s->begin(), find_if(s->begin(), s->end(), [](const int ch) noexcept { return !isspace(ch); })); -} -// trim from end (in place) -void trim_right(string* s) -{ - s->erase(find_if(s->rbegin(), s->rend(), [](const int ch) noexcept { return !isspace(ch); }).base(), s->end()); -} -// trim from both ends (in place) -void trim(string* s) -{ - trim_left(s); - trim_right(s); -} -// trim from start (copying) -string trim_left_copy(string s) -{ - trim_left(&s); - return s; -} -// trim from end (copying) -string trim_right_copy(string s) -{ - trim_right(&s); - return s; -} -// trim from both ends (copying) -string trim_copy(string s) -{ - trim(&s); - return s; -} -} diff --git a/firestarr/src/cpp/Trim.h b/firestarr/src/cpp/Trim.h deleted file mode 100644 index 6744e197b..000000000 --- a/firestarr/src/cpp/Trim.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include -// from https://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring -namespace fs::util -{ -/** - * \brief Remove whitespace from left side of string - * \param s string to trim - */ -void trim_left(string* s); -/** - * \brief Remove whitespace from right side of string - * \param s string to trim - */ -void trim_right(string* s); -/** - * \brief Remove whitespace from both sides of string - * \param s string to trim - */ -void trim(string* s); -/** - * \brief Return new string with whitespace removed from left side - * \param s string to trim - * \return new string with whitespace removed from left side - */ -[[nodiscard]] string trim_left_copy(string s); -/** - * \brief Return new string with whitespace removed from right side - * \param s string to trim - * \return new string with whitespace removed from right side - */ -[[nodiscard]] string trim_right_copy(string s); -/** - * \brief Return new string with whitespace removed from both sides - * \param s string to trim - * \return new string with whitespace removed from both sides - */ -[[nodiscard]] string trim_copy(string s); -} diff --git a/firestarr/src/cpp/UTM.cpp b/firestarr/src/cpp/UTM.cpp deleted file mode 100644 index 53a0697f8..000000000 --- a/firestarr/src/cpp/UTM.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "UTM.h" -#include "Point.h" -#include "Log.h" -#include "Util.h" -#include "unstable.h" - -#include -#include "stdafx.h" -#include "Util.h" -namespace fs::topo -{ -class Point; -PJ* normalized_context(PJ_CONTEXT* C, const string& proj4_from, const string& proj4_to) -{ - // FIX: this is WGS84, but do we need to support more than that for lat/long - PJ* P = proj_create_crs_to_crs( - C, - proj4_from.c_str(), - proj4_to.c_str(), - nullptr); - fs::logging::check_fatal(0 == P, "Failed to create transformation object"); - // This will ensure that the order of coordinates for the input CRS - // will be longitude, latitude, whereas EPSG:4326 mandates latitude, - // longitude - PJ* P_norm = proj_normalize_for_visualization(C, P); - fs::logging::check_fatal(0 == P_norm, "Failed to normalize transformation object"); - proj_destroy(P); - return P_norm; -} -void from_lat_long( - const string& proj4, - const fs::topo::Point& point, - MathSize* x, - MathSize* y) -{ - // see https://proj.org/en/stable/development/quickstart.html - // do this in a function so we can hide and clean up intial context - PJ_CONTEXT* C = proj_context_create(); - auto P = normalized_context(C, "EPSG:4326", proj4); - // Given that we have used proj_normalize_for_visualization(), the order - // of coordinates is longitude, latitude, and values are expressed in - // degrees. - const PJ_COORD a = proj_coord(point.longitude(), point.latitude(), 0, 0); - // transform to UTM, then back to geographical - const PJ_COORD b = proj_trans(P, PJ_FWD, a); - *x = b.enu.e; - *y = b.enu.n; - // #ifdef DEBUG_PROJ - PJ_COORD c = proj_trans(P, PJ_INV, b); - fs::logging::verbose( - "longitude: %f, latitude: %f => easting: %.3f, northing: %.3f => x: %.3f, y: %.3f => longitude: %g, latitude: %g", - point.longitude(), - point.latitude(), - b.enu.e, - b.enu.n, - b.xy.x, - b.xy.y, - c.lp.lam, - c.lp.phi); - // #endif - proj_destroy(P); - proj_context_destroy(C); -} -fs::topo::Point to_lat_long( - const string& proj4, - const MathSize x, - const MathSize y) -{ - // see https://proj.org/en/stable/development/quickstart.html - // do this in a function so we can hide and clean up intial context - PJ_CONTEXT* C = proj_context_create(); - auto P = normalized_context(C, proj4, "EPSG:4326"); - // Given that we have used proj_normalize_for_visualization(), the order - // of coordinates is longitude, latitude, and values are expressed in - // degrees. - logging::verbose("proj_coord(%f, %f, 0, 0)", x, y); - const PJ_COORD a = proj_coord(x, y, 0, 0); - // transform to geographical - const PJ_COORD b = proj_trans(P, PJ_FWD, a); - // Point is (lat, lon) - Point point{b.lp.phi, b.lp.lam}; - proj_destroy(P); - proj_context_destroy(C); - return point; -} -string&& try_fix_meridian(string&& proj4) -{ - const auto zone_pos = proj4.find("+zone="); - // if proj4 is defined by zone then convert to be defined by meridian - if (string::npos != zone_pos && string::npos != proj4.find("+proj=utm")) - { - // NOTE: using proj for actual projections, but we want proj4 strings to use meridian - // and not zone so outputs are consistent regardless of input - // convert from utm zone to tmerc - const auto zone_str = proj4.substr(zone_pos + 6); - const auto zone = stoi(zone_str); - // zone 15 is -93 and other zones are 6 degrees difference - const auto degrees = fs::topo::utm_central_meridian(zone); - // HACK: assume utm zone is at start - proj4 = string( - "+proj=tmerc +lat_0=0.000000000 +lon_0=" + to_string(degrees) + " +k=0.999600 +x_0=500000.000 +y_0=0.000"); - logging::verbose("Adjusted proj4 is %s\n", proj4.c_str()); - } - return std::forward(proj4); -} -} diff --git a/firestarr/src/cpp/UTM.h b/firestarr/src/cpp/UTM.h deleted file mode 100644 index 53454f92e..000000000 --- a/firestarr/src/cpp/UTM.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once - -#include -#include "stdafx.h" -#include "Util.h" -namespace fs::topo -{ -class Point; -/** - * \brief Calculate the UTM zone for the given meridian - * \param meridian A MathSize designating the meridian to calculate the UTM zone for (degrees) - * \return UTM zone for given meridian - */ -[[nodiscard]] constexpr MathSize meridian_to_zone(const MathSize meridian) noexcept -{ - return (meridian + 183.0) / 6.0; -} -/** - * \brief Determines the central meridian for the given UTM zone. - * \param zone A MathSize designating the UTM zone, range [1,60]. - * \return The central meridian for the given UTM zone (degrees) - */ -[[nodiscard]] constexpr MathSize utm_central_meridian(const MathSize zone) noexcept -{ - return -183.0 + zone * 6.0; -} -void from_lat_long( - const string& proj4, - const fs::topo::Point& point, - MathSize* x, - MathSize* y); -fs::topo::Point to_lat_long( - const string& proj4, - const MathSize x, - const MathSize y); -string&& try_fix_meridian(string&& proj4); -} diff --git a/firestarr/src/cpp/Util.cpp b/firestarr/src/cpp/Util.cpp deleted file mode 100644 index bea990233..000000000 --- a/firestarr/src/cpp/Util.cpp +++ /dev/null @@ -1,290 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Util.h" -#include "Log.h" -#include -#include -#ifdef _WIN32 -#include -#endif - -TIFF* GeoTiffOpen(const char* const filename, const char* const mode) -{ - TIFF* tif = XTIFFOpen(filename, mode); - static const TIFFFieldInfo xtiffFieldInfo[] = { - {TIFFTAG_GDAL_NODATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, true, false, (char*)"GDALNoDataValue"}, - {TIFFTAG_GEOPIXELSCALE, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, true, true, (char*)"GeoPixelScale"}, - {TIFFTAG_GEOTRANSMATRIX, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, true, true, (char*)"GeoTransformationMatrix"}, - {TIFFTAG_GEOTIEPOINTS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, true, true, (char*)"GeoTiePoints"}, - {TIFFTAG_GEOKEYDIRECTORY, -1, -1, TIFF_SHORT, FIELD_CUSTOM, true, true, (char*)"GeoKeyDirectory"}, - {TIFFTAG_GEODOUBLEPARAMS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, true, true, (char*)"GeoDoubleParams"}, - {TIFFTAG_GEOASCIIPARAMS, -1, -1, TIFF_ASCII, FIELD_CUSTOM, true, true, (char*)"GeoASCIIParams"}}; - TIFFMergeFieldInfo(tif, xtiffFieldInfo, sizeof(xtiffFieldInfo) / sizeof(xtiffFieldInfo[0])); - return tif; -} -int sxprintf(char* buffer, size_t N, const char* format, va_list* args) -{ - // printf("int sxprintf(char* buffer, size_t N, const char* format, va_list* args)\n"); - auto r = vsnprintf(buffer, N, format, *args); - if (!(r < static_cast(N))) - { - printf("**************** ERROR ****************\n"); - printf("\tTrying to write to buffer resulted in string being cut off at %ld characters\n", N); - printf("Should have written:\n\t\""); - vprintf(format, *args); - printf("\"\n\t\"%s\"", buffer); - // HACK: just loop - printf("\n\t"); - for (size_t i = 0; i < (N - 1); ++i) - { - printf(" "); - } - printf("^-- cut off here at character %ld\n", N); - printf("**************** ERROR ****************\n"); - throw std::runtime_error("String buffer overflow avoided"); - } - return r; -} -int sxprintf(char* buffer, size_t N, const char* format, ...) -{ - // printf("int sxprintf(char* buffer, size_t N, const char* format, ...)\n"); - va_list args; - va_start(args, format); - auto r = sxprintf(buffer, N, format, &args); - va_end(args); - return r; -} -namespace fs::util -{ -void read_directory(const bool for_files, const string& name, vector* v, const string& match) -{ - string full_match = ".*/" + match; - logging::verbose(("Matching '" + full_match + "'").c_str()); - static const std::regex re(full_match, std::regex_constants::icase); - for (const auto& entry : std::filesystem::directory_iterator(name)) - { - logging::verbose(("Checking if file: " + entry.path().string()).c_str()); - if ( - (for_files && std::filesystem::is_regular_file(entry)) - || (!for_files && std::filesystem::is_directory(entry))) - { - logging::extensive(("Checking regex match: " + entry.path().string()).c_str()); - if (std::regex_match(entry.path().string(), re)) - { -#ifdef _WIN32 - v->push_back(entry.path().generic_string()); -#else - v->push_back(entry.path()); -#endif - } - } - } -} -void read_directory(const string& name, vector* v, const string& match) -{ - read_directory(true, name, v, match); -} -void read_directory(bool for_files, const string& name, vector* v) -{ - read_directory(for_files, name, v, "*"); -} -void read_directory(const string& name, vector* v) -{ - read_directory(name, v, "*"); -} -vector find_rasters(const string& dir, const int year) -{ - const auto for_year = dir + "/" + to_string(year) + "/"; - const auto for_default = dir + "/default/"; - // use first existing folder of dir/year, dir/default, or dir in that order - const auto raster_root = directory_exists(for_year.c_str()) - ? for_year - : ( - directory_exists(for_default.c_str()) - ? for_default - : dir); - vector results{}; - try - { - read_directory(raster_root, &results, "fuel.*\\.tif"); - } - catch (const std::exception& ex) - { - logging::error("Unable to read directory %s", raster_root.c_str()); - logging::error("%s", ex.what()); - } - return results; -} -bool directory_exists(const char* dir) noexcept -{ - struct stat dir_info{}; - return stat(dir, &dir_info) == 0 && dir_info.st_mode & S_IFDIR; -} -bool file_exists(const char* path) noexcept -{ - struct stat path_info{}; - // FIX: check that this works on symlinks - return stat(path, &path_info) == 0 && path_info.st_mode & S_IFREG; -} -void make_directory(const char* dir) noexcept -{ -#ifdef _WIN32 - if (-1 == _mkdir(dir) && errno != EEXIST) -#else - if (-1 == mkdir(dir, 0777) && errno != EEXIST) -#endif - { - struct stat dir_info{}; - if (stat(dir, &dir_info) != 0) - { - logging::fatal("Cannot create directory %s", dir); - } - else if (dir_info.st_mode & S_IFDIR) - { - // everything is fine - } - else - { - logging::fatal("%s is not a directory\n", dir); - } - } -} -void make_directory_recursive(const char* dir) noexcept -{ - char tmp[256]; - sxprintf(tmp, "%s", dir); - const auto len = strlen(tmp); - if (tmp[len - 1] == '/') - tmp[len - 1] = 0; - for (auto p = tmp + 1; *p; ++p) - if (*p == '/') - { - *p = 0; - make_directory(tmp); - *p = '/'; - } - make_directory(tmp); -} -tm to_tm(const int year, - const int month, - const int day, - const int hour, - const int minute) -{ - // do this to calculate yday - tm t{}; - t.tm_year = year - 1900; - t.tm_mon = month - 1; - t.tm_mday = day; - t.tm_hour = hour; - t.tm_min = minute; - mktime(&t); - return t; -} -DurationSize to_time(const tm& t) -{ - return t.tm_yday - + ((t.tm_hour + (static_cast(t.tm_min) / HOUR_MINUTES)) / DAY_HOURS); -} -DurationSize to_time(const int year, - const int month, - const int day, - const int hour, - const int minute) -{ - return to_time(to_tm(year, month, day, hour, minute)); -} -void read_date(istringstream* iss, string* str, tm* t) -{ - *t = {}; - // Date - getline(iss, str, ','); - string ds; - istringstream dss(*str); - getline(dss, ds, '-'); - t->tm_year = stoi(ds) - 1900; - getline(dss, ds, '-'); - t->tm_mon = stoi(ds) - 1; - getline(dss, ds, ' '); - t->tm_mday = stoi(ds); - getline(dss, ds, ':'); - t->tm_hour = stoi(ds); - logging::verbose("Date is %4d-%02d-%02d %02d:00", - t->tm_year + 1900, - t->tm_mon + 1, - t->tm_mday, - t->tm_hour); -} -UsageCount::~UsageCount() -{ - logging::note("%s called %d times", for_what_.c_str(), count_.load()); -} -UsageCount::UsageCount(string for_what) noexcept - : count_(0), for_what_(std::move(for_what)) -{ -} -UsageCount& UsageCount::operator++() noexcept -{ - ++count_; - return *this; -} -} -constexpr int DAYS_IN_MONTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -constexpr int DAYS_IN_MONTH_LEAP[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - -void fs::month_and_day(const int year, const size_t day_of_year, size_t* month, size_t* day_of_month) -{ - auto days = (is_leap_year(year) ? DAYS_IN_MONTH_LEAP : DAYS_IN_MONTH); - *month = 1; - int days_left = day_of_year; - while (days[*month - 1] <= days_left) - { - days_left -= days[*month - 1]; - ++(*month); - } - *day_of_month = days_left + 1; -} -bool fs::is_leap_year(const int year) -{ - if (year % 400 == 0) - { - return true; - } - if (year % 100 == 0) - { - return false; - } - return (year % 4 == 0); -} -string fs::make_timestamp(const int year, const DurationSize time) -{ - size_t day = floor(time); - size_t hour = (time - day) * static_cast(DAY_HOURS); - size_t minute = round(((time - day) * static_cast(DAY_HOURS) - hour) * HOUR_MINUTES); - if (60 == minute) - { - minute = 0; - hour += 1; - } - if (24 == hour) - { - day += 1; - hour = 0; - } - size_t month; - size_t day_of_month; - month_and_day(year, day, &month, &day_of_month); - char buffer[128]; - sxprintf(buffer, - "%4d-%02ld-%02ld %02ld:%02ld", - year, - month, - day_of_month, - hour, - minute); - return {buffer}; -} diff --git a/firestarr/src/cpp/Util.h b/firestarr/src/cpp/Util.h deleted file mode 100644 index 365219532..000000000 --- a/firestarr/src/cpp/Util.h +++ /dev/null @@ -1,522 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "stdafx.h" -#include -#include -#include -#include - -#ifndef TIFFTAG_GDAL_NODATA -#define TIFFTAG_GDAL_NODATA 42113 -#endif - -/** - * Open file and register GeoTIFF tags so we can read and write properly - * @param filename Name of file to open - * @param mode Mode to open file with - * @return Handle to open TIFF with fields registered - */ -TIFF* GeoTiffOpen(const char* const filename, const char* const mode); - -/** - * Call snprintf() but show where and throw exception when anything gets cut off. - */ -int sxprintf(char* buffer, size_t N, const char* format, va_list* args); -int sxprintf(char* buffer, size_t N, const char* format, ...); -/** - * Call snprintf() but don't need to determine size of array when calling - * and complain when anything gets cut off. - */ -template -int sxprintf(char (&buffer)[N], const char* format, va_list* args) -{ - // printf("int sxprintf(char (&buffer)[N], const char* format, va_list* args)\n"); - return sxprintf(&buffer[0], N, format, args); -} -template -int sxprintf(char (&buffer)[N], const char* format, ...) -{ - // printf("int sxprintf(char (&buffer)[N], const char* format, ...)\n"); - va_list args; - va_start(args, format); - auto r = sxprintf(buffer, format, &args); - va_end(args); - return r; -} - -namespace fs -{ -namespace util -{ -/** - * \brief Convert day and hour to DurationSize representing time - * \tparam T Type used for representing day - * \param day Day - * \param hour Hour - * \return DurationSize representing time at day and hour - */ -template -[[nodiscard]] DurationSize to_time(const T day, const int hour) noexcept -{ - return day + hour / (1.0 * DAY_HOURS); -} -/** - * \brief Convert time index to DurationSize representing time - * \param t_index index for array of times - * \return DurationSize representing time at day and hour - */ -[[nodiscard]] constexpr DurationSize to_time(const size_t t_index) noexcept -{ - return static_cast(t_index) / DAY_HOURS; -} -/** - * \brief Convert day and hour into time index - * \tparam T Type used for representing day - * \param day Day - * \param hour Hour - * \return index for array of times - */ -template -[[nodiscard]] size_t time_index(const T day, const int hour) noexcept -{ - return static_cast(day) * DAY_HOURS + hour; -} -/** - * \brief Convert day and hour into time index since min_date - * \param day Day - * \param hour Hour - * \param min_date Date at time index 0 - * \return index for array of times - */ -template -[[nodiscard]] size_t time_index(const T day, - const int hour, - const Day min_date) noexcept -{ - return time_index(day, hour) - static_cast(DAY_HOURS) * min_date; -} -/** - * \brief Convert DurationSize into time index - * \param time time to get time index for - * \return index for array of times - */ -[[nodiscard]] constexpr size_t time_index(const DurationSize time) noexcept -{ - return static_cast(time * DAY_HOURS); -} -/** - * \brief Convert DurationSize into time index since min_date - * \param time Time to convert to time index - * \param min_date Date at time index 0 - * \return index for array of times - */ -[[nodiscard]] constexpr size_t time_index(const DurationSize time, - const Day min_date) noexcept -{ - return time_index(time) - static_cast(DAY_HOURS) * min_date; -} -/** - * \brief Return the passed value as given type - * \tparam T Type to return - * \param value Value to return - * \return Value casted to type T - */ -template -[[nodiscard]] T no_convert(int value, int) noexcept -{ - return static_cast(value); -} -/** - * \brief Ensure that value lies between 0 and 2 * PI - * \param theta value to ensure is within bounds - * \return value within range of (0, 2 * PI] - */ -[[nodiscard]] constexpr MathSize fix_radians(const MathSize theta) -{ - if (theta > M_2_X_PI) - { - return theta - M_2_X_PI; - } - if (theta < 0) - { - return theta + M_2_X_PI; - } - return theta; -} -/** - * \brief Convert degrees to radians - * \param degrees Angle in degrees - * \return Angle in radians - */ -[[nodiscard]] constexpr MathSize to_radians(const MathSize degrees) noexcept -{ - return fix_radians(degrees / M_RADIANS_TO_DEGREES); -} -// only calculate this once and reuse it -/** - * \brief 360 degrees in radians - */ -static constexpr MathSize RAD_360 = to_radians(360); -/** - * \brief 270 degrees in radians - */ -static constexpr MathSize RAD_270 = to_radians(270); -/** - * \brief 180 degrees in radians - */ -static constexpr MathSize RAD_180 = to_radians(180); -/** - * \brief 90 degrees in radians - */ -static constexpr MathSize RAD_090 = to_radians(90); -/** - * \brief Convert radians to degrees - * \param radians Value in radians - * \return Value in degrees - */ -[[nodiscard]] constexpr MathSize to_degrees(const MathSize radians) -{ - return fix_radians(radians) * M_RADIANS_TO_DEGREES; -} -/** - * \brief Convert Bearing to Heading (opposite angle) - * \param azimuth Bearing - * \return Heading - */ -[[nodiscard]] constexpr MathSize to_heading(const MathSize azimuth) -{ - return fix_radians(azimuth + RAD_180); -} -/** - * \brief Read from a stream until delimiter is found - * \tparam Elem Elem for stream - * \tparam Traits Traits for stream - * \tparam Alloc Allocator for stream - * \param stream Stream to read from - * \param str gstring to read into - * \param delimiter Delimiter to stop at - * \return - */ -template -std::basic_istream& getline( - std::basic_istream* stream, - std::basic_string* str, - const Elem delimiter) -{ - // make template so we can use pointers directly - return getline(*stream, *str, delimiter); -} -/** - * \brief Check if a directory exists - * \param dir Directory to check existence of - * \return Whether or not the directory exists - */ -[[nodiscard]] bool directory_exists(const char* dir) noexcept; -/** - * \brief Check if a file exists - * \param path File to check existence of - * \return Whether or not the file exists - */ -[[nodiscard]] bool file_exists(const char* path) noexcept; -/** - * \brief Get a list of items in the given directory matching the given regex - * \param for_files Match files and not directories - * \param name Directory to search - * \param v vector to put found paths into - * \param match regular expression to match in names - */ -void read_directory(const bool for_files, - const string& name, - vector* v, - const string& match); -/** - * \brief Get a list of files in the given directory matching the given regex - * \param name Directory to search for files - * \param v vector to put found file names into - * \param match regular expression to match in file names - */ -void read_directory(const string& name, - vector* v, - const string& match); -/** - * \brief Get a list of items in the given directory matching the given regex - * \param for_files Match files and not directories - * \param name Directory to search - * \param v vector to put found paths into - */ -void read_directory(const bool for_files, - const string& name, - vector* v); -/** - * \brief Get a list of files in the given directory - * \param name Directory to search for files - * \param v vector to put found file names into - */ -void read_directory(const string& name, vector* v); -/** - * \brief Get a list of rasters in the given directory for the specified year - * \param dir Root directory to look for rasters in - * \param year Year to use rasters for if available, else default - * \return List of rasters in the directory - */ -[[nodiscard]] vector find_rasters(const string& dir, int year); -/** - * \brief Make the given directory if it does not exist - * \param dir Directory to create - */ -void make_directory(const char* dir) noexcept; -/** - * \brief Make the given directory and any parent directories that do not exist - * \param dir Directory to create - */ -void make_directory_recursive(const char* dir) noexcept; -/** - * \brief Square a number - * \param x number to square - * \return x squared - */ -template -[[nodiscard]] inline constexpr MathSize sq(const T& x) -{ - return static_cast(x) * static_cast(x); -} -/** - * \brief Return base raised to the power N - * \tparam N Power to raise to - * \tparam T Type passed and returned - * \param base Base to raise to power N - * \return base raised to power N - */ -template -[[nodiscard]] constexpr T pow_int(const T& base) -{ - // https://stackoverflow.com/questions/16443682/c-power-of-integer-template-meta-programming - return N == 0 - ? 1 - : N % 2 == 0 - ? pow_int(base) * pow_int(base) - : base * pow_int<(N - 1) / 2, T>(base) * pow_int<(N - 1) / 2, T>(base); -} -/** - * \brief Makes a bit mask of all 1's the specified size - * \tparam N Number of bits - * \tparam T Type to return - * \return Bit mask of all 1's of specified size - */ -template -[[nodiscard]] constexpr T bit_mask() -{ - return static_cast(pow_int(2) - 1); -} -/** - * \brief Round value to specified precision - * \tparam N Precision to round to - * \param value Value to round - * \return Value after rounding - */ -template -[[nodiscard]] MathSize round_to_precision(const MathSize value) noexcept -{ - // HACK: this can't actually make the value be the precision we want due to - // floating point storage, but we can round it to what it would be if it were - // that precision - static const auto b = pow_int(10); - return round(value * b) / b; -} -/** - * \brief Convert date and time into tm struct - * \param year year - * \param month month - * \param day day of month - * \param hour hour - * \param minute minute - * \return tm representing date and time - */ -tm to_tm(const int year, - const int month, - const int day, - const int hour, - const int minute); -/** - * \brief Convert tm struct into internal represenation - * \param t tm struct to convert - * \return internal time representation - */ - -DurationSize to_time(const tm& t); -/** - * \brief Convert date and time into internal represenation - * \param year year - * \param month month - * \param day day of month - * \param hour hour - * \param minute minute - * \return internal time representation - */ -DurationSize to_time(const int year, - const int month, - const int day, - const int hour, - const int minute); -/** - * \brief Read a date from the given stream - * \param iss Stream to read from - * \param str string to read date into - * \param t tm to parse date into - */ -void read_date(istringstream* iss, string* str, tm* t); -/** - * \brief Provides the ability to determine how many times something is used during a simulation. - */ -class UsageCount -{ - /** - * \brief How many times this has been used - */ - atomic count_; - /** - * \brief What this is tracking usage of - */ - string for_what_; -public: - ~UsageCount(); - /** - * \brief Constructor - * \param for_what What this is tracking usage of - */ - explicit UsageCount(string for_what) noexcept; - UsageCount(UsageCount&& rhs) noexcept = delete; - UsageCount(const UsageCount& rhs) noexcept = delete; - UsageCount& operator=(UsageCount&& rhs) noexcept = delete; - UsageCount& operator=(const UsageCount& rhs) noexcept = delete; - /** - * \brief Increment operator - * \return Value after increment - */ - UsageCount& operator++() noexcept; -}; -// https://stackoverflow.com/questions/15843525/how-do-you-insert-the-value-in-a-sorted-vector -/** - * \brief Insert value into vector in sorted order even if it is already present - * \tparam T Type of value to insert - * \param vec vector to insert into - * \param item Item to insert - * \return iterator result of insert - */ -template -[[nodiscard]] typename std::vector::iterator - insert_sorted(std::vector* vec, T const& item) -{ - return vec->insert(std::upper_bound(vec->begin(), vec->end(), item), item); -} -/** - * \brief Insert value into vector in sorted order if it does not already exist - * \tparam T Type of value to insert - * \param vec vector to insert into - * \param item Item to insert - */ -template -void insert_unique(std::vector* vec, T const& item) -{ - const auto i = std::lower_bound(vec->begin(), vec->end(), item); - if (i == vec->end() || *i != item) - { - vec->insert(i, item); - } -} -} -/** - * \brief Do binary search over function for T that results in value - * \tparam T Type of input values - * \tparam V Result type of fct - * \param lower Lower bound - * \param upper Upper bound - * \param value Value to look for - * \param fct Function taking T and returning V - * \return T value that results in closest to value - * \return - */ -template -T binary_find(const T lower, - const T upper, - const MathSize value, - const std::function& fct) -{ - const auto mid = lower + (upper - lower) / 2; - if (lower == upper) - { - return lower; - } - if (fct(mid) < value) - { - return binary_find(lower, mid, value, fct); - } - return binary_find(mid + 1, upper, value, fct); -} -/** - * \brief Check lower and upper limits before doing binary search over function for T that results in value - * \tparam T Type of input values - * \tparam V Result type of fct - * \param lower Lower bound to check - * \param upper Upper bound to check - * \param value Value to look for - * \param fct Function taking T and returning V - * \return T value that results in closest to value - */ -template -T binary_find_checked(const T lower, - const T upper, - const MathSize value, - const std::function& fct) -{ - if (fct(lower) < value) - { - return lower; - } - if (fct(upper) >= value) - { - return upper; - } - return binary_find(lower, upper, value, fct); -} -/** - * \brief Determine month and day that a day of the year represents - * \param year Year to determine for - * \param day_of_year Day of year to determine for - * \param month Month that was determined - * \param day_of_month Day of month that was determined - */ -void month_and_day(const int year, const size_t day_of_year, size_t* month, size_t* day_of_month); -/** - * \brief Determine if year is a leap year - * \param year Year to determine for - * \return Whether or not the given year is a leap year - */ -[[nodiscard]] bool is_leap_year(const int year); -/** - * Make a nicely formatted timestamp string for the given simulation time - * @param year Year time is for - * @param time Simulation time (fractional day of year) - * @return YYYY-mm-dd HH:00 time string for given time - */ -[[nodiscard]] string make_timestamp(const int year, const DurationSize time); -/** - * Convert circle angle to the angle that would be on an ellipse with - * given length-to-breadth ratio - * @param length_to_breadth length-to-breadth ratio - * @param theta direction to convert to ellipse direction (radians) - */ -[[nodiscard]] inline MathSize ellipse_angle(const MathSize length_to_breadth, - const MathSize theta) -{ - return (util::fix_radians( - atan2(sin(theta) / length_to_breadth, - cos(theta)))); -} -} diff --git a/firestarr/src/cpp/Weather.cpp b/firestarr/src/cpp/Weather.cpp deleted file mode 100644 index a77ae3b84..000000000 --- a/firestarr/src/cpp/Weather.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "stdafx.h" -#include "Weather.h" -#include "Log.h" -namespace fs::wx -{ -const Temperature Temperature::Zero = Temperature(0); -const RelativeHumidity RelativeHumidity::Zero = RelativeHumidity(0); -const Direction Direction::Zero = Direction(0, false); -const Speed Speed::Zero = Speed(0); -const Wind Wind::Zero = Wind(Direction::Zero, Speed::Zero); -const Precipitation Precipitation::Zero = Precipitation(0); -const Temperature Temperature::Invalid = Temperature(-1); -const RelativeHumidity RelativeHumidity::Invalid = RelativeHumidity(-1); -const Direction Direction::Invalid = Direction(-1, false); -const Speed Speed::Invalid = Speed(-1); -const Wind Wind::Invalid = Wind(Direction::Invalid, Speed::Invalid); -const Precipitation Precipitation::Invalid = Precipitation(-1); -} diff --git a/firestarr/src/cpp/Weather.h b/firestarr/src/cpp/Weather.h deleted file mode 100644 index 3aca2aa7c..000000000 --- a/firestarr/src/cpp/Weather.h +++ /dev/null @@ -1,399 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#include "Index.h" -#include "Util.h" -#include "unstable.h" -namespace fs -{ -using data::Index; -namespace wx -{ -/** - * \brief Temperature in degrees Celsius. - */ -class Temperature : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond - /** - * \brief 0 degrees Celsius - */ - static const Temperature Zero; - static const Temperature Invalid; -}; -/** - * \brief Relative humidity as a percentage. - */ -class RelativeHumidity : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond - /** - * \brief 0% Relative Humidity - */ - static const RelativeHumidity Zero; - static const RelativeHumidity Invalid; -}; -/** - * \brief Speed in kilometers per hour. - */ -class Speed : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond - /** - * \brief 0 km/h - */ - static const Speed Zero; - static const Speed Invalid; -}; -/** - * \brief Direction with access to degrees or radians. - */ -class Direction : public Index -{ -public: - /** - * \brief Destructor - */ - ~Direction() = default; - /** - * \brief Construct with Direction of 0 (North) - */ - constexpr Direction() noexcept = default; - /** - * \brief Constructor - * \param value Direction to use - * \param is_radians Whether the given direction is in radians (as opposed to degrees) - */ - constexpr Direction(const MathSize value, const bool is_radians) noexcept - : Index(is_radians ? util::to_degrees(value) : value) - { - } - /** - * \brief Copy constructor - * \param rhs Direction to copy from - */ - constexpr Direction(const Direction& rhs) noexcept = default; - /** - * \brief Move constructor - * \param rhs Direction to move from - */ - constexpr Direction(Direction&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs Direction to copy from - * \return This, after assignment - */ - Direction& operator=(const Direction& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs Direction to move from - * \return This, after assignment - */ - Direction& operator=(Direction&& rhs) noexcept = default; - /** - * \brief Direction as radians, where 0 is North and values increase clockwise - * \return Direction as radians, where 0 is North and values increase clockwise - */ - [[nodiscard]] constexpr MathSize asRadians() const - { - return util::to_radians(asDegrees()); - } - /** - * \brief Direction as degrees, where 0 is North and values increase clockwise - * \return Direction as degrees, where 0 is North and values increase clockwise - */ - [[nodiscard]] constexpr MathSize asDegrees() const - { - return asValue(); - } - /** - * \brief Heading (opposite of this direction) - * \return Heading (opposite of this direction) - */ - [[nodiscard]] constexpr MathSize heading() const - { - return util::to_heading(asRadians()); - } - /** - * \brief Direction of 0 (North) - */ - static const Direction Zero; - static const Direction Invalid; -}; -/** - * \brief Wind with a Speed and Direction. - */ -class Wind -{ -public: - /** - * \brief Destructor - */ - ~Wind() = default; - /** - * \brief Construct with 0 values - */ - constexpr Wind() noexcept - : wsv_x_(0), - wsv_y_(0), - direction_(0.0, false), - speed_(0) - { - } - /** - * \brief Constructor - * \param direction Direction wind is coming from - * \param speed Speed of wind - */ - Wind(const Direction& direction, const Speed speed) noexcept - : wsv_x_(speed.asValue() * _sin(direction.heading())), - wsv_y_(speed.asValue() * _cos(direction.heading())), - direction_(direction), - speed_(speed) - { - } - /** - * \brief Copy constructor - * \param rhs Wind to copy from - */ - constexpr Wind(const Wind& rhs) noexcept = default; - /** - * \brief Move constructor - * \param rhs Wind to move from - */ - constexpr Wind(Wind&& rhs) noexcept = default; - /** - * \brief Copy assignment - * \param rhs Wind to copy into this one - * \return This, after assignment - */ - Wind& operator=(const Wind& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs Wind to move into this one - * \return This, after assignment - */ - Wind& operator=(Wind&& rhs) noexcept = default; - /** - * \brief Speed of wind (km/h) - * \return Speed of wind (km/h) - */ - [[nodiscard]] constexpr const Speed& speed() const noexcept - { - return speed_; - } - /** - * \brief Direction wind is coming from. - * \return Direction wind is coming from. - */ - [[nodiscard]] constexpr const Direction& direction() const noexcept - { - return direction_; - } - /** - * \brief Direction wind is going towards - * \return Direction wind is going towards - */ - [[nodiscard]] constexpr MathSize heading() const noexcept - { - return direction_.heading(); - } - /** - * \brief X component of wind vector (km/h) - * \return X component of wind vector (km/h) - */ - [[nodiscard]] constexpr MathSize wsvX() const noexcept - { - return wsv_x_; - } - /** - * \brief Y component of wind vector (km/h) - * \return Y component of wind vector (km/h) - */ - [[nodiscard]] constexpr MathSize wsvY() const noexcept - { - return wsv_y_; - } - /** - * \brief Not equal to operator - * \param rhs Wind to compare to - * \return Whether or not these are not equivalent - */ - [[nodiscard]] constexpr bool operator!=(const Wind& rhs) const noexcept - { - return direction_ != rhs.direction_ || speed_ != rhs.speed_; - } - /** - * \brief Equals to operator - * \param rhs Wind to compare to - * \return Whether or not these are equivalent - */ - [[nodiscard]] constexpr bool operator==(const Wind& rhs) const noexcept - { - return direction_ == rhs.direction_ && speed_ == rhs.speed_; - } - /** - * \brief Less than operator - * \param rhs Wind to compare to - * \return Whether or not this is less than the compared to Wind - */ - [[nodiscard]] constexpr bool operator<(const Wind& rhs) const noexcept - { - if (direction_ == rhs.direction_) - { - return speed_ < rhs.speed_; - } - return direction_ < rhs.direction_; - } - /** - * \brief Wind with 0 Speed from Direction 0 - */ - [[maybe_unused]] static const Wind Zero; - static const Wind Invalid; -private: - /** - * \brief Wind speed vector in X direction (East is positive) - */ - MathSize wsv_x_; - /** - * \brief Wind speed vector in Y direction (North is positive) - */ - MathSize wsv_y_; - /** - * \brief Direction (degrees or radians, N is 0) - */ - Direction direction_; - /** - * \brief Speed (km/h) - */ - Speed speed_; -}; -/** - * \brief Precipitation (1hr accumulation) (mm) - */ -class Precipitation : public Index -{ -public: - //! @cond Doxygen_Suppress - using Index::Index; - //! @endcond - /** - * \brief Accumulated Precipitation of 0 mm - */ - static const Precipitation Zero; - static const Precipitation Invalid; -}; -/** - * \brief Collection of weather indices used for calculating FwiWeather. - */ -class Weather -{ -public: - /** - * \brief Destructor - */ - virtual ~Weather() = default; - /** - * \brief Constructor with no initialization - */ - constexpr Weather() noexcept = default; - /** - * \brief Construct with given indices - * \param temp Temperature (Celsius) - * \param rh Relative Humidity (%) - * \param wind Wind (km/h) - * \param prec Precipitation (1hr accumulation) (mm) - */ - constexpr Weather(const Temperature& temp, - const RelativeHumidity& rh, - const Wind& wind, - const Precipitation& prec) noexcept - : temp_(temp), rh_(rh), wind_(wind), prec_(prec) - { - } - /** - * \brief Move constructor - * \param rhs Weather to move from - */ - constexpr Weather(Weather&& rhs) noexcept = default; - /** - * \brief Copy constructor - * \param rhs Weather to copy from - */ - constexpr Weather(const Weather& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs Weather to move into this one - * \return This, after assignment - */ - Weather& operator=(Weather&& rhs) noexcept = default; - /** - * \brief Move assignment - * \param rhs Weather to copy into this one - * \return This, after assignment - */ - Weather& operator=(const Weather& rhs) = default; - /** - * \brief Temperature (Celsius) - * \return Temperature (Celsius) - */ - [[nodiscard]] constexpr const Temperature& temp() const noexcept - { - return temp_; - } - /** - * \brief Relative Humidity (%) - * \return Relative Humidity (%) - */ - [[nodiscard]] constexpr const RelativeHumidity& rh() const noexcept - { - return rh_; - } - /** - * \brief Wind (km/h) - * \return Wind (km/h) - */ - [[nodiscard]] constexpr const Wind& wind() const noexcept - { - return wind_; - } - /** - * \brief Precipitation (1hr accumulation) (mm) - * \return Precipitation (1hr accumulation) (mm) - */ - [[nodiscard]] constexpr const Precipitation& prec() const noexcept - { - return prec_; - } -private: - /** - * \brief Temperature (Celsius) - */ - Temperature temp_; - /** - * \brief Relative Humidity (%) - */ - RelativeHumidity rh_; - /** - * \brief Wind (km/h) - */ - Wind wind_; - /** - * \brief Precipitation (1hr accumulation) (mm) - */ - Precipitation prec_; -}; -} -} diff --git a/firestarr/src/cpp/debug_settings.cpp b/firestarr/src/cpp/debug_settings.cpp deleted file mode 100644 index 228d7a476..000000000 --- a/firestarr/src/cpp/debug_settings.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "debug_settings.h" -#include -#include -#include - -namespace fs::debug -{ -constexpr auto HR = "**********************************************************************"; -constexpr auto EDGE = 3; -constexpr auto WIDTH = static_cast(sizeof(HR)) - (2 * (EDGE + 1)); - -void printf_centered(const char* str) -{ - const auto n = static_cast(strlen(str)); - const auto pad_left = ((WIDTH - n) / 2) + n; - const auto pad_right = WIDTH - pad_left; - printf("%.*s %*s%*s %.*s\n", EDGE, HR, pad_left, str, pad_right, "", EDGE, HR); -}; - -void show_debug_settings() -{ -#ifdef DEBUG_ANY - printf("%s\n", HR); - printf_centered("DEBUG OPTIONS"); - printf("%s\n", HR); -#endif - -#ifndef NDEBUG - printf_centered("DEBUG"); -#endif -#ifdef DEBUG_DIRECTIONS - printf_centered("DEBUG_DIRECTIONS"); -#endif -#ifdef DEBUG_FUEL_VARIABLE - printf_centered("DEBUG_FUEL_VARIABLE"); -#endif -#ifdef DEBUG_FWI_WEATHER - printf_centered("DEBUG_FWI_WEATHER"); -#endif -#ifdef DEBUG_GRIDS - printf_centered("DEBUG_GRIDS"); -#endif -#ifdef DEBUG_POINTS - printf_centered("DEBUG_POINTS"); -#endif -#ifdef DEBUG_PROBABILITY - printf_centered("DEBUG_PROBABILITY"); -#endif -#ifdef DEBUG_SIMULATION - printf_centered("DEBUG_SIMULATION"); -#endif -#ifdef DEBUG_STATISTICS - printf_centered("DEBUG_STATISTICS"); -#endif -#ifdef DEBUG_WEATHER - printf_centered("DEBUG_WEATHER"); -#endif -#ifdef DEBUG_ANY - printf("%s\n", HR); -#endif -} -} diff --git a/firestarr/src/cpp/debug_settings.h b/firestarr/src/cpp/debug_settings.h deleted file mode 100644 index 196950b84..000000000 --- a/firestarr/src/cpp/debug_settings.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once - -// if in debug mode then set everything, otherwise uncomment turning things off if trying to debug specific things -#define DEBUG_DIRECTIONS -#define DEBUG_FUEL_VARIABLE -#define DEBUG_FWI_WEATHER -#define DEBUG_GRIDS -#define DEBUG_POINTS -#define DEBUG_PROBABILITY -#define DEBUG_SIMULATION -#define DEBUG_STATISTICS -// #define DEBUG_WEATHER -// // use this for trying to troubleshoot specific problems short-term -// #define DEBUG_TEMPORARY - -#ifdef NDEBUG - -#undef DEBUG_DIRECTIONS -#undef DEBUG_FUEL_VARIABLE -#undef DEBUG_FWI_WEATHER -#undef DEBUG_GRIDS -#undef DEBUG_POINTS -#undef DEBUG_PROBABILITY -#undef DEBUG_SIMULATION -#undef DEBUG_STATISTICS -#undef DEBUG_WEATHER - -#endif - -#if not(defined(NDEBUG)) || defined(DEBUG_DIRECTIONS) || defined(DEBUG_FUEL_VARIABLE) || defined(DEBUG_FWI_WEATHER) || defined(DEBUG_GRIDS) || defined(DEBUG_POINTS) || defined(DEBUG_PROBABILITY) || defined(DEBUG_SIMULATION) || defined(DEBUG_STATISTICS) || defined(DEBUG_WEATHER) -#define DEBUG_ANY -#endif - -namespace fs::debug -{ -void show_debug_settings(); -} diff --git a/firestarr/src/cpp/stdafx.cpp b/firestarr/src/cpp/stdafx.cpp deleted file mode 100644 index 80732b054..000000000 --- a/firestarr/src/cpp/stdafx.cpp +++ /dev/null @@ -1,4 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ diff --git a/firestarr/src/cpp/stdafx.h b/firestarr/src/cpp/stdafx.h deleted file mode 100644 index da997ffce..000000000 --- a/firestarr/src/cpp/stdafx.h +++ /dev/null @@ -1,342 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2021-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#pragma once -#if __cplusplus >= 202211L // C++23 -#define CPP23 -#endif -#if __cpp_constexpr >= 202211L // C++23 -#define CONSTEXPR constexpr -#else -#define CONSTEXPR -#endif -#include "debug_settings.h" - -// #define VLD_FORCE_ENABLE -// #include "vld.h" -#define _USE_MATH_DEFINES -#define NOMINMAX -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "tiff.h" -#include -#include -#include "unstable.h" -// unreferenced inline function has been removed -// Informational: catch(...) semantics changed since Visual C++ 7.1; structured exceptions (SEH) are no longer caught -// function 'X' selected for automatic inline expansion -// function not inlined -// selected for automatic inline expansion -// Do not assign the result of an allocation or a function call with an owner return value to a raw pointer, use owner instead -// Do not delete a raw pointer that is not an owner -// Return a scoped object instead of a heap-allocated if it has a move constructor -// Reset or explicitly delete an owner pointer -// Do not assign to an owner which may be in valid state -// Do not assign a raw pointer to an owner -// Prefer scoped objects, don't heap-allocate unnecessarily -// Avoid calling new and delete explicitly, use std::make_unique instead -// Global initializer calls a non-constexpr function -// Symbol is never tested for nullness, it can be marked as not_null -// Function hides a non-virtual function -// prefer to use gsl::at() -// Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow -// Don't use pointer arithmetic. Use span instead -// Only index into arrays using constant expressions -// No array to pointer decay -using std::abs; -using std::array; -using std::async; -using std::atomic; -using std::endl; -using std::fixed; -using std::function; -using std::future; -using std::get; -using std::getline; -using std::hash; -using std::ifstream; -using std::istringstream; -using std::launch; -using std::list; -using std::lock_guard; -using std::make_shared; -using std::make_tuple; -using std::make_unique; -using std::map; -using std::max; -using std::min; -using std::mt19937; -using std::mutex; -using std::numeric_limits; -using std::ofstream; -using std::ostream; -using std::ostringstream; -using std::pair; -using std::put_time; -using std::runtime_error; -using std::set; -using std::setprecision; -using std::shared_ptr; -using std::stod; -using std::stoi; -using std::stol; -using std::string; -using std::string_view; -using std::stringstream; -using std::to_string; -using std::to_wstring; -using std::tuple; -using std::uniform_real_distribution; -using std::unique_ptr; -using std::unordered_map; -using std::vector; -using std::wstring; -namespace fs -{ -/** - * \brief Size of the hash of a Cell - */ -using HashSize = uint32_t; -/** - * \brief Size of the index for a Cell - */ -using CellIndex = uint8_t; -// want to be able to make a bitmask of all directions it came from -// 064 008 032 -// 001 000 002 -// 016 004 128 -static constexpr CellIndex DIRECTION_NONE = 0b00000000; -static constexpr CellIndex DIRECTION_W = 0b00000001; -static constexpr CellIndex DIRECTION_E = 0b00000010; -static constexpr CellIndex DIRECTION_S = 0b00000100; -static constexpr CellIndex DIRECTION_N = 0b00001000; -static constexpr CellIndex DIRECTION_SW = 0b00010000; -static constexpr CellIndex DIRECTION_NE = 0b00100000; -static constexpr CellIndex DIRECTION_NW = 0b01000000; -static constexpr CellIndex DIRECTION_SE = 0b10000000; -/** - * \brief A row or column index for a grid - */ -using Idx = int16_t; -/** - * \brief A row or column index for a grid not in memory yet - */ -using FullIdx = int64_t; -/** - * \brief Type used for perimeter raster values (uses [0, 1]) - */ -// FIX: could try to get really fancy and use number of bits per pixel options -using PerimSize = uint8_t; -// using PerimSize = uint16_t; -/** - * \brief Type used for fuel values (uses [0 - 999]?) - */ -// FIX: seriously does not like uint for some reason -using FuelSize = uint16_t; -// using FuelSize = int16_t; -using DirectionSize = uint16_t; -constexpr auto INVALID_DIRECTION = std::numeric_limits::max(); -/** - * \brief Type used for aspect values (uses [0 - 359]) - */ -using AspectSize = DirectionSize; -/** - * \brief Type used for elevation values (uses [? - 9800?]) - */ -using ElevationSize = int16_t; -/** - * \brief Type used for slope values (uses [0 - MAX_SLOPE_FOR_DISTANCE]) - */ -using SlopeSize = uint16_t; -/** - * \brief Type used for storing intensities - */ -using IntensitySize = uint32_t; -/** - * \brief Type used for storing distances within cells - */ -// using DistanceSize = _Float16; -using DistanceSize = double; -/** - * \brief Type used for storing locations within cells - */ -// FIX: results in "Invalid fuel type in fuel map" -// using InnerSize = _Float16; -// using InnerSize = float; -using InnerSize = double; -/** - * \brief Type used for storing locations in environment - */ -using XYSize = double; -// using InnerSize = float; -// using InnerSize = double; -/** - * \brief Type used for probability thresholds - */ -using ThresholdSize = double; -/** - * \brief Type used for storing locations within cells - */ -using DurationSize = double; -constexpr DurationSize INVALID_TIME = -1; -constexpr auto NO_INTENSITY = static_cast(0); -using ROSSize = MathSize; -constexpr auto NO_ROS = static_cast(0.0); -/** - * \brief A day (0 - 366) - */ -using Day = uint16_t; -static constexpr Day MAX_DAYS = 366; -/** - * \brief Maximum number of columns for an Environment - */ -static constexpr Idx MAX_COLUMNS = 4096; -/** - * \brief Maximum number of rows for an Environment - */ -static constexpr Idx MAX_ROWS = MAX_COLUMNS; -// static_assert(static_cast(MAX_ROWS) * (MAX_COLUMNS - 1) <= std::numeric_limits::max()); -static constexpr Idx PREFERRED_TILE_WIDTH = 256; -static constexpr Idx TILE_WIDTH = min(MAX_COLUMNS, static_cast(PREFERRED_TILE_WIDTH)); -/** - * \brief Maximum aspect value (360 == 0) - */ -static constexpr auto MAX_ASPECT = 359; -/** - * \brief Maximum slope that can be stored - this is used in the horizontal distance calculation - */ -static constexpr auto MAX_SLOPE_FOR_DISTANCE = 500; -/** - * \brief Invalid angle value - */ -static constexpr auto INVALID_ANGLE = 361; -/** - * \brief Invalid aspect value - */ -static constexpr auto INVALID_ASPECT = INVALID_ANGLE; -/** - * \brief Invalid grass curing value - */ -static constexpr MathSize INVALID_CURING = -1; -/** - * \brief Invalid slope value - */ -static constexpr auto INVALID_SLOPE = 511; -/** - * \brief Number of all possible fuels in simulation - */ -static constexpr auto NUMBER_OF_FUELS = 141; -/** - * \brief 2*pi - */ -static constexpr auto M_2_X_PI = 2.0 * M_PI; -/** - * \brief 3/2*pi - */ -static constexpr auto M_3_X_PI_2 = 3.0 * M_PI_2; -/** - * \brief Ratio of degrees to radians - */ -static constexpr auto M_RADIANS_TO_DEGREES = 180.0 / M_PI; -/** - * \brief Number of hours in a day - */ -static constexpr int DAY_HOURS = 24; -/** - * \brief Number of minutes in an hour - */ -static constexpr int HOUR_MINUTES = 60; -/** - * \brief Number of seconds in a minute - */ -static constexpr int MINUTE_SECONDS = 60; -/** - * \brief Number of seconds in an hour - */ -static constexpr int HOUR_SECONDS = HOUR_MINUTES * MINUTE_SECONDS; -/** - * \brief Number of minutes in a day - */ -static constexpr int DAY_MINUTES = DAY_HOURS * HOUR_MINUTES; -/** - * \brief Number of seconds in a day - */ -static constexpr int DAY_SECONDS = DAY_MINUTES * MINUTE_SECONDS; -/** - * \brief Number of hours in a year - */ -static constexpr int YEAR_HOURS = MAX_DAYS * DAY_HOURS; -/** - * \brief Array of results of a function for all possible integer percent slopes - */ -using SlopeTableArray = array; -/** - * \brief Size of an angle in degrees - */ -using DegreesSize = uint16_t; -/** - * \brief Array of results of a function for all possible integer angles in degrees - */ -using AngleTableArray = array; -/** - * \brief Size to use for representing fuel types - */ -using FuelCodeSize = uint8_t; -/** - * \brief Size to use for representing the data in a Cell - */ -using Topo = uint64_t; -/** - * \brief Size to use for representing sub-coordinates for location within a Cell - */ -using SubSize = uint16_t; -/** - * \brief Coordinates (row, column, sub-row, sub-column) - */ -using Coordinates = tuple; -/** - * \brief FullCoordinates (row, column, sub-row, sub-column) - */ -using FullCoordinates = tuple; -/** - * \brief Type of clock to use for times - */ -using Clock = std::chrono::steady_clock; -} diff --git a/firestarr/src/cpp/tbd.vcxproj b/firestarr/src/cpp/tbd.vcxproj deleted file mode 100644 index 1d09d751a..000000000 --- a/firestarr/src/cpp/tbd.vcxproj +++ /dev/null @@ -1,255 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 17.0 - Win32Proj - {5a086e89-ca67-4aa2-9293-b0f1b6b6f2da} - fs - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - false - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - Debug - true - false - - - true - false - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS - true - stdcpplatest - stdc17 - MultiThreadedDebug - true - - - Console - true - - - - - Level3 - - - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS - true - stdcpplatest - stdc17 - MultiThreaded - false - true - - - Console - true - true - true - - - true - - - - - - \ No newline at end of file diff --git a/firestarr/src/cpp/tbd.vcxproj.filters b/firestarr/src/cpp/tbd.vcxproj.filters deleted file mode 100644 index 0176a65e6..000000000 --- a/firestarr/src/cpp/tbd.vcxproj.filters +++ /dev/null @@ -1,255 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - \ No newline at end of file diff --git a/firestarr/src/cpp/tbd.vcxproj.user b/firestarr/src/cpp/tbd.vcxproj.user deleted file mode 100755 index 0f14913f3..000000000 --- a/firestarr/src/cpp/tbd.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/firestarr/src/cpp/unstable.cpp b/firestarr/src/cpp/unstable.cpp deleted file mode 100644 index 7b9c7b120..000000000 --- a/firestarr/src/cpp/unstable.cpp +++ /dev/null @@ -1,16 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -#include "unstable.h" -#include - -MathSize _cos(const double angle) noexcept -{ - return static_cast(cos(angle)); -} -MathSize _sin(const double angle) noexcept -{ - return static_cast(sin(angle)); -} diff --git a/firestarr/src/cpp/unstable.h b/firestarr/src/cpp/unstable.h deleted file mode 100644 index eba892e8c..000000000 --- a/firestarr/src/cpp/unstable.h +++ /dev/null @@ -1,22 +0,0 @@ -/* Copyright (c) Queen's Printer for Ontario, 2020. */ -/* Copyright (c) His Majesty the King in Right of Canada as represented by the Minister of Natural Resources, 2024-2025. */ - -/* SPDX-License-Identifier: AGPL-3.0-or-later */ - -// Provides wrappers around functions that have different results when using -// different optimization flags, so they can be compiled with the same -// flags in release and debug mode to avoid changing the outputs. -#pragma once - -#ifdef _WIN32 -#pragma comment(lib, "Ws2_32.Lib") -#pragma comment(lib, "Crypt32.Lib") -#endif - -/** - * \brief Type used for math operations - */ -using MathSize = double; - -MathSize _cos(double angle) noexcept; -MathSize _sin(double angle) noexcept; diff --git a/firestarr/src/cpp/version.h b/firestarr/src/cpp/version.h deleted file mode 100644 index 1360eed77..000000000 --- a/firestarr/src/cpp/version.h +++ /dev/null @@ -1,5 +0,0 @@ -extern "C" -{ - extern const char* VERSION; - extern const char* COMPILE_DATE; -} diff --git a/firestarr/vcpkg-configuration.json b/firestarr/vcpkg-configuration.json deleted file mode 100644 index bc42fde43..000000000 --- a/firestarr/vcpkg-configuration.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "default-registry": { - "kind": "git", - "baseline": "2c401863dd54a640aeb26ed736c55489c079323b", - "repository": "https://github.com/microsoft/vcpkg" - }, - "registries": [ - { - "kind": "artifact", - "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", - "name": "microsoft" - } - ] -} diff --git a/firestarr/vcpkg.json b/firestarr/vcpkg.json deleted file mode 100644 index f1a8a4710..000000000 --- a/firestarr/vcpkg.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "dependencies": [ - "libgeotiff", - "tiff", - "curl" - ] -} From 3c09d52c0336be624fc771f6a81a341e82e994a3 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Mon, 27 Apr 2026 12:01:11 -0400 Subject: [PATCH 12/38] update to main for firestarr-cpp --- .docker/Dockerfile | 41 +++-------------------------------------- .env | 2 +- docker-compose.yml | 4 ++-- firestarr | 2 +- 4 files changed, 7 insertions(+), 42 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 85772e06e..12ac5a209 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -44,41 +44,6 @@ WORKDIR /appl/firestarr USER ${USERNAME} -FROM gcc-cmake-with-libs AS firestarr-build -ARG USERNAME -ARG VERSION -ENV VERSION=${VERSION} -WORKDIR /appl/firestarr -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/src/cpp src/cpp/ -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/CMakeLists.txt . -USER ${USERNAME} -RUN cmake -S . -B /appl/firestarr/build -D CMAKE_BUILD_TYPE=Release \ - && cmake --build /appl/firestarr/build --config Release --target all -j $(nproc) - - -FROM minimal-with-libs AS firestarr-base-env -ARG USERNAME -ARG VERSION -ENV VERSION=${VERSION} -USER root -RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# make a layer that's the environment for firestarr with scratch to clear out old layers -FROM scratch AS firestarr-base -COPY --from=firestarr-base-env / / - -# derive final image from base so updates to binary are small layers -FROM firestarr-base AS firestarr -ARG USERNAME -USER ${USERNAME} -WORKDIR /appl/firestarr -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/fuel.lut /appl/firestarr/ -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/settings.ini /appl/firestarr/ -COPY --chown=${USERNAME}:${USERNAME} --from=firestarr-build /appl/firestarr/firestarr /appl/firestarr/ -USER ${USERNAME} -RUN echo cd /appl/firestarr >> /home/${USERNAME}/.bashrc - - # saves more work to reuse python than it does to reuse gcc FROM minimal-with-libs AS py-gdal ARG USERNAME @@ -164,10 +129,10 @@ ENV VERSION=${VERSION} ENV TMPDIR=/tmp WORKDIR /appl/firestarr COPY --chown=${USERNAME}:${USERNAME} ./config /appl/ -COPY --chown=${USERNAME}:${USERNAME} --from=firestarr-build /appl/firestarr/firestarr . +COPY --chown=${USERNAME}:${USERNAME} --from=firestarr /appl/firestarr/firestarr . COPY --chown=${USERNAME}:${USERNAME} ./bounds.geojson /appl/ -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/fuel.lut /appl/firestarr/ -COPY --chown=${USERNAME}:${USERNAME} ./firestarr/settings.ini /appl/firestarr/ +COPY --chown=${USERNAME}:${USERNAME} --from=firestarr /appl/firestarr/fuel.lut /appl/firestarr/ +COPY --chown=${USERNAME}:${USERNAME} --from=firestarr /appl/firestarr/settings.ini /appl/firestarr/ COPY --chown=${USERNAME}:${USERNAME} ./scripts/ /appl/firestarr/scripts/ COPY --chown=${USERNAME}:${USERNAME} ./src/py/firestarr/ /appl/firestarr/src/py/firestarr/ WORKDIR /appl/firestarr/src/py/cffdrs-ng diff --git a/.env b/.env index bc6755150..27325dcb9 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -VERSION=0.9.5.5 +VERSION=0.9.6 USERNAME=user USER_ID=1000 diff --git a/docker-compose.yml b/docker-compose.yml index af03249b5..4f4420425 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,7 +87,7 @@ services: build: context: . target: firestarr - dockerfile: .docker/Dockerfile + dockerfile: firestarr/.docker/Dockerfile args: VERSION: ${VERSION} USERNAME: ${USERNAME} @@ -108,7 +108,7 @@ services: build: context: . target: firestarr-dev - dockerfile: .docker/Dockerfile + dockerfile: firestarr/.docker/Dockerfile args: VERSION: ${VERSION} USERNAME: ${USERNAME} diff --git a/firestarr b/firestarr index a4deb756c..4d2fd6f48 160000 --- a/firestarr +++ b/firestarr @@ -1 +1 @@ -Subproject commit a4deb756c8153cc683fce6bdf635a9bc3744c3b0 +Subproject commit 4d2fd6f482f08a70e634ade4cfeba78f5ec31ac0 From cc15c7cdd5bbf97b6854a5dec4528b907b45d422 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 07:40:41 -0400 Subject: [PATCH 13/38] fix link for bounds generation input --- src/py/firestarr/make_bounds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/firestarr/make_bounds.py b/src/py/firestarr/make_bounds.py index 0b18f2140..31ef2d057 100644 --- a/src/py/firestarr/make_bounds.py +++ b/src/py/firestarr/make_bounds.py @@ -12,7 +12,7 @@ BUFFER_RESOLUTION = 32 SERVER_CENSUS = "https://www12.statcan.gc.ca/census-recensement" URL_CANADA = f"{SERVER_CENSUS}/2011/geo/bound-limit/files-fichiers/2016/lpr_000b16a_e.zip" -URL_PARKS = "https://clss.nrcan-rncan.gc.ca/data-donnees/nplb_llpn/CLAB_CA_2023-09-08.zip" +URL_PARKS = "https://clss.nrcan-rncan.gc.ca/data-donnees/nplb_llpn/CLAB_CA.zip" centroids_canada = None DIR_BOUNDS = os.path.join(DIR_GENERATED, "bounds") FILE_BOUNDS = "bounds.geojson" From 13ba31f5d69badf7dd99e82565e96ef0db1a9698 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 07:47:38 -0400 Subject: [PATCH 14/38] warn and continue if scan_queue() doesn't work --- src/py/firestarr/main.py | 50 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/py/firestarr/main.py b/src/py/firestarr/main.py index b813eba20..5f9ed7b79 100644 --- a/src/py/firestarr/main.py +++ b/src/py/firestarr/main.py @@ -273,29 +273,33 @@ def requeue(): logging.info("Called with args %s", str(sys.argv)) FROM_QUEUE = "--queue" in sys.argv or 1 == len(sys.argv) if FROM_QUEUE: - msg, args = scan_queue() - logging.info("Queue triggered with message:\n%s\ngives arguments:\n%s", msg, args) - # HACK: double-check that we're using only `--` args for now - for a in args: - # HACK: should filter things out if they aren't valid args elsewhere - if not a.startswith("--"): - logging.fatal("Invalid argument given: %s", a) - raise ValueError("Invalid argument given: %s" % a) - # HACK: sketched out about this, but will let us tell things to re-run via queue - sys.argv.extend(args) - # allow other arguments but remove duplicates - QUEUE_ARGS = ["--no-publish", "--no-merge", "--no-retry"] - # HACK: if not using batch then wait for results - if assign_sim_batch(): - logging.debug("Not waiting since running in batch") - QUEUE_ARGS.extend(["--no-wait"]) - REMOVE_ARGS = QUEUE_ARGS + ["--queue"] - for a in REMOVE_ARGS: - try: - sys.argv.remove(a) - except ValueError: - pass - sys.argv.extend(QUEUE_ARGS) + try: + msg, args = scan_queue() + logging.info("Queue triggered with message:\n%s\ngives arguments:\n%s", msg, args) + # HACK: double-check that we're using only `--` args for now + for a in args: + # HACK: should filter things out if they aren't valid args elsewhere + if not a.startswith("--"): + logging.fatal("Invalid argument given: %s", a) + raise ValueError("Invalid argument given: %s" % a) + # HACK: sketched out about this, but will let us tell things to re-run via queue + sys.argv.extend(args) + # allow other arguments but remove duplicates + QUEUE_ARGS = ["--no-publish", "--no-merge", "--no-retry"] + # HACK: if not using batch then wait for results + if assign_sim_batch(): + logging.debug("Not waiting since running in batch") + QUEUE_ARGS.extend(["--no-wait"]) + REMOVE_ARGS = QUEUE_ARGS + ["--queue"] + for a in REMOVE_ARGS: + try: + sys.argv.remove(a) + except ValueError: + pass + sys.argv.extend(QUEUE_ARGS) + except Exception as ex: + logging.warning("Unable to scan queue:\n\t" + str(ex)) + FROM_QUEUE = False args_orig = sys.argv[1:] # rely on argument parsing later while do_retry: From 97274f386fc4040ee0101c0d370e384bcf6bace0 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 07:48:19 -0400 Subject: [PATCH 15/38] change to use numpy 2 and add pytz to requirements.txt --- .docker/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.docker/requirements.txt b/.docker/requirements.txt index 868199920..9c3075564 100644 --- a/.docker/requirements.txt +++ b/.docker/requirements.txt @@ -8,13 +8,14 @@ fiona geopandas ipython multiprocess -numpy<2 +numpy pandas psutil pyarrow pycrs pyogrio pyrate-limiter==2.10.0 +pytz rasterio requests scipy From 87398e6ecb0bd3317c06f551d0483a3d0d4f8894 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 07:48:51 -0400 Subject: [PATCH 16/38] use containers built by submodule and rejig firestarr-app containers to build again --- .docker/Dockerfile | 120 +++++++++++++++++++-------------------------- .dockerignore | 1 + docker-compose.yml | 18 ++----- 3 files changed, 54 insertions(+), 85 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 12ac5a209..514acaa11 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,47 +1,9 @@ -FROM debian:bookworm-slim AS minimal-with-user -ARG USERNAME -ARG USER_ID +FROM debian:trixie-slim AS minimal-with-libs ENV TMPDIR=/tmp -RUN apt-get update --fix-missing \ - && apt-get install -y locales \ - && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \ - && locale-gen \ - && apt-get install -y sudo \ - && groupadd --gid ${USER_ID} ${USERNAME} \ - && useradd --uid ${USER_ID} --gid ${USER_ID} -m ${USERNAME} \ - && chsh --shell /bin/bash ${USERNAME} \ - && echo ${USERNAME} ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${USERNAME} \ - && chmod 0440 /etc/sudoers.d/${USERNAME} \ - && mkdir -p /appl/firestarr \ - && chown -R ${USERNAME}:${USERNAME} /appl - - -FROM minimal-with-user AS minimal-with-libs RUN apt-get update --fix-missing \ && apt-get install -y --no-install-recommends \ libgeotiff5 - - -FROM minimal-with-libs AS gcc-cmake-with-libs -RUN apt-get update --fix-missing \ - && apt-get install -y --no-install-recommends \ - libtiff-dev libgeotiff-dev \ - cmake gcc g++ make - - -FROM gcc-cmake-with-libs AS firestarr-dev -ARG USERNAME -ARG VERSION -ENV VERSION=${VERSION} -RUN apt-get update --fix-missing \ - && apt-get install -y --no-install-recommends \ - git time nano less \ - libtiff-dev libgeotiff-dev \ - gdb valgrind libdwarf-dev libelf-dev libdw-dev linux-perf clang-format \ - cmake gcc g++ make -RUN git config --global core.editor "nano" -WORKDIR /appl/firestarr -USER ${USERNAME} +RUN mkdir -p /appl/firestarr # saves more work to reuse python than it does to reuse gcc @@ -56,7 +18,6 @@ RUN apt-get update --fix-missing \ python3 gdal-bin python3-gdal python3-setuptools python3-pip python3-venv \ && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \ && python -c "from osgeo import gdal" -USER ${USERNAME} WORKDIR /appl/firestarr/ # if we make venv with with user then permissions should be correct # HACK: make sure python folder doesn't break if version changes @@ -65,16 +26,14 @@ RUN python -m venv --system-site-packages /appl/.venv \ && echo /appl/firestarr/src/py/firestarr > /appl/.venv/lib/`ls -1 /appl/.venv/lib/ | grep python`/site-packages/firestarr.pth \ && . /appl/.venv/bin/activate \ && python -c "from osgeo import gdal" \ - && echo source /appl/.venv/bin/activate >> /home/${USERNAME}/.bashrc + && echo source /appl/.venv/bin/activate >> /root/.bashrc FROM py-gdal AS firestarr-app-base -ARG USERNAME WORKDIR /appl/firestarr/ # if we make venv with with user then permissions should be correct -COPY --chown=${USERNAME}:${USERNAME} .docker/requirements.txt /appl/requirements.txt -USER ${USERNAME} -RUN echo cd /appl/firestarr >> /home/${USERNAME}/.bashrc +COPY .docker/requirements.txt /appl/requirements.txt +RUN echo cd /appl/firestarr >> /root/.bashrc # HACK: make sure osgeo/gdal is available before & after installing requirements RUN python -c "from osgeo import gdal" \ && . /appl/.venv/bin/activate \ @@ -84,69 +43,90 @@ RUN python -c "from osgeo import gdal" \ FROM firestarr-app-base AS firestarr-app-gcc -ARG USERNAME -USER root RUN apt-get update --fix-missing \ && apt-get install -y --no-install-recommends \ libtiff-dev libgeotiff-dev \ gdb valgrind libdwarf-dev libelf-dev libdw-dev linux-perf clang-format \ cmake gcc g++ make -USER ${USERNAME} FROM firestarr-app-gcc AS firestarr-app-dev -ARG USERNAME ARG VERSION ENV VERSION=${VERSION} ENV TMPDIR=/tmp WORKDIR /appl/firestarr/ +COPY bounds.geojson /appl/firestarr/ +RUN apt-get update --fix-missing \ + && apt-get install -y --no-install-recommends \ + cmake gcc g++ make ninja-build pkg-config \ + curl zip unzip tar ca-certificates git +# vcpkg says: Environment variable VCPKG_FORCE_SYSTEM_BINARIES must be set on arm, s390x, ppc64le and riscv platforms. +ENV VCPKG_FORCE_SYSTEM_BINARIES=1 +ENV VCPKG_ROOT=/appl/vcpkg +WORKDIR /appl +RUN --mount=type=bind,source=./firestarr/vcpkg-configuration.json,target=./vcpkg-configuration.json \ + --mount=type=bind,source=./firestarr/vcpkg.json,target=./vcpkg.json \ + git clone -v https://github.com/microsoft/vcpkg.git \ + && cd vcpkg \ + && git checkout $(sed -n '/baseline/{s/.*\"\([^"]*\)",$/\1/g;p}' ../firestarr/vcpkg-configuration.json) \ + && cd .. \ + && vcpkg/bootstrap-vcpkg.sh -disableMetrics \ + && cd firestarr \ + && ../vcpkg/vcpkg install +# RUN --mount=type=bind,source=./firestarr/cmake,target=./cmake \ +# --mount=type=bind,source=./firestarr/CMakeLists.txt,target=./CMakeLists.txt \ +# --mount=type=bind,source=./firestarr/CMakePresets.json,target=./CMakePresets.json \ +# --mount=type=bind,source=./firestarr/.clang-format,target=./.clang-format \ +# --mount=type=bind,source=./firestarr/.clang-tidy,target=./.clang-tidy \ +# --mount=type=bind,source=./firestarr/.env,target=./.env \ +# --mount=type=bind,source=./firestarr/src/cpp,target=./src/cpp \ +# --mount=type=bind,source=./firestarr/test/data,target=./test/data \ +# --mount=type=bind,source=./firestarr/test/input,target=./test/input \ +# --mount=type=bind,source=./firestarr/fuel.lut,target=./fuel.lut \ +# --mount=type=bind,source=./firestarr/settings.ini,target=./settings.ini \ +# cmake --preset Release \ +# && cmake --build --parallel --preset Release \ +# && ctest --preset Release + FROM firestarr-app-dev AS firestarr-setup-gis -ARG USERNAME -USER ${USERNAME} ENV TMPDIR=/tmp WORKDIR /appl/gis/ -RUN echo cd /appl/gis >> /home/${USERNAME}/.bashrc +RUN echo cd /appl/gis >> /root/.bashrc FROM firestarr-app-base AS firestarr-prod-base-env -ARG USERNAME USER root RUN apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /home/user/.cache RUN find -type d -name __pycache__ / | xargs -tI {} rm -rf {} -USER ${USERNAME} FROM scratch AS firestarr-prod-base COPY --from=firestarr-prod-base-env / / # derive final image from base so updates to binary are small layers FROM firestarr-prod-base AS firestarr-prod -ARG USERNAME -ARG VERSION +ARG VERSION9* ENV VERSION=${VERSION} ENV TMPDIR=/tmp WORKDIR /appl/firestarr -COPY --chown=${USERNAME}:${USERNAME} ./config /appl/ -COPY --chown=${USERNAME}:${USERNAME} --from=firestarr /appl/firestarr/firestarr . -COPY --chown=${USERNAME}:${USERNAME} ./bounds.geojson /appl/ -COPY --chown=${USERNAME}:${USERNAME} --from=firestarr /appl/firestarr/fuel.lut /appl/firestarr/ -COPY --chown=${USERNAME}:${USERNAME} --from=firestarr /appl/firestarr/settings.ini /appl/firestarr/ -COPY --chown=${USERNAME}:${USERNAME} ./scripts/ /appl/firestarr/scripts/ -COPY --chown=${USERNAME}:${USERNAME} ./src/py/firestarr/ /appl/firestarr/src/py/firestarr/ +COPY ./config /appl/ +COPY --from=firestarr-build /appl/firestarr/firestarr . +COPY ./bounds.geojson /appl/ +COPY ./firestarr/fuel.lut /appl/firestarr/ +COPY ./firestarr/settings.ini /appl/firestarr/ +COPY ./scripts/ /appl/firestarr/scripts/ +COPY ./src/py/firestarr/ /appl/firestarr/src/py/firestarr/ WORKDIR /appl/firestarr/src/py/cffdrs-ng -COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/NG_FWI.py . -COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/old_cffdrs.py . -COPY --chown=${USERNAME}:${USERNAME} ./src/py/cffdrs-ng/util.py . -USER ${USERNAME} +COPY ./src/py/cffdrs-ng/NG_FWI.py . +COPY ./src/py/cffdrs-ng/old_cffdrs.py . +COPY ./src/py/cffdrs-ng/util.py . # doesn't delete intermediaries, but maybe smaller between versions? FROM firestarr-prod AS firestarr-app -ARG USERNAME ARG VERSION ENV VERSION=${VERSION} ENV TMPDIR=/tmp -USER ${USERNAME} WORKDIR /appl/firestarr ENTRYPOINT [ "/appl/firestarr/scripts/publish_after.sh" ] diff --git a/.dockerignore b/.dockerignore index 6ad6df216..541315f2a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,6 +10,7 @@ !bounds.geojson !**/scripts/*.sh !firestarr/settings.ini +!firestarr/vcpkg* !./config **/src/py/**/*.sh diff --git a/docker-compose.yml b/docker-compose.yml index 4f4420425..2d02d5dfc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,6 @@ services: source: ./config target: /appl/config - data:/appl/data - - sims:/appl/data/sims - py:/appl/firestarr/src/py - type: bind source: ./gis @@ -46,10 +45,9 @@ services: source: ./config target: /appl/config - data:/appl/data - - sims:/appl/data/sims - py:/appl/firestarr/src/py - scripts:/appl/firestarr/scripts - - cppscripts:/appl/cppscripts + - cppscripts:/appl/firestarr/cppscripts - type: bind source: ./firestarr target: /appl/firestarr @@ -78,7 +76,6 @@ services: volumes: - /etc/ssl/certs:/etc/ssl/certs - data:/appl/data - - sims:/appl/data/sims env_file: - .env @@ -87,7 +84,7 @@ services: build: context: . target: firestarr - dockerfile: firestarr/.docker/Dockerfile + dockerfile: .docker/Dockerfile args: VERSION: ${VERSION} USERNAME: ${USERNAME} @@ -99,7 +96,6 @@ services: source: ./firestarr/settings.ini target: /appl/firestarr/settings.ini - data:/appl/data - - sims:/appl/data/sims env_file: - .env @@ -108,7 +104,7 @@ services: build: context: . target: firestarr-dev - dockerfile: firestarr/.docker/Dockerfile + dockerfile: .docker/Dockerfile args: VERSION: ${VERSION} USERNAME: ${USERNAME} @@ -124,7 +120,6 @@ services: source: ./firestarr target: /appl/firestarr - data:/appl/data - - sims:/appl/data/sims cap_add: - SYS_PTRACE - SYS_ADMIN @@ -139,13 +134,6 @@ volumes: type: none o: bind device: ./data - sims: - # symlink to your actual directory if you don't want it in the project folder - driver: local - driver_opts: - type: none - o: bind - device: ./sims py: driver: local driver_opts: From 752d51855494d36d6de0f2ec0f52ad23b8ac9912 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 07:49:25 -0400 Subject: [PATCH 17/38] add setup.sh --- setup.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 setup.sh diff --git a/setup.sh b/setup.sh new file mode 100644 index 000000000..e2bef0f41 --- /dev/null +++ b/setup.sh @@ -0,0 +1,20 @@ +mkdir -p data +mkdir -p data/sims +mkdir -p data/download +pushd data/download/ +wget -c https://fgmfiles.spyd.com/datasets/FireSTARR_Dataset_2025_V1.1.zip +popd +pushd firestarr +docker compose build firestarr +docker compose build firestarr-dev +# HACK: make image for building c++ available for firestarr-app dockerfile +for image in vcpkg-installed firestarr-build +do + docker build -f .docker/Dockerfile -t $image --target $image . +done +popd +docker compose build firestarr-app-dev +# build with this since it's mounting the ./firestarr directory at /appl/firestarr +cp bounds.geojson firestarr/ +docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cppscripts/build.sh' +docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'python src/py/firestarr/make_bounds.geojson' From 7dc9a70fff1492ba7f0a21a6dcbc8992c0bae440 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 08:41:57 -0400 Subject: [PATCH 18/38] fix download and extract rasters --- setup.sh | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/setup.sh b/setup.sh index e2bef0f41..68e387bc8 100644 --- a/setup.sh +++ b/setup.sh @@ -1,9 +1,23 @@ +RASTERS=FireSTARR_Dataset_2025_V1.1.zip +# get rasters mkdir -p data mkdir -p data/sims mkdir -p data/download -pushd data/download/ -wget -c https://fgmfiles.spyd.com/datasets/FireSTARR_Dataset_2025_V1.1.zip +pushd data +pushd download +wget -c https://fgmfiles.spyd.com/datasets/${RASTERS} +RASTERS=$(pwd)/${RASTERS} popd +mkdir -p generated/grid/100m/default +pushd generated/grid/100m/default +# HACK: can't figure out how to use regex for extract files +d=$(7za l "${RASTERS}" | grep default | head -n1 | sed "s/.* \([^ ]*\/default.*\)/\1/") +7za e "${RASTERS}" "$d" +# unzip makes an empty directory +rmdir default +popd +popd +# build containers pushd firestarr docker compose build firestarr docker compose build firestarr-dev From e9b5b3f93da5cd98630eccf182039c19a7e948c6 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 08:58:17 -0400 Subject: [PATCH 19/38] fix call for make_bounds --- setup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 68e387bc8..26306317c 100644 --- a/setup.sh +++ b/setup.sh @@ -30,5 +30,6 @@ popd docker compose build firestarr-app-dev # build with this since it's mounting the ./firestarr directory at /appl/firestarr cp bounds.geojson firestarr/ +cp bounds.geojson data/ docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cppscripts/build.sh' -docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'python src/py/firestarr/make_bounds.geojson' +docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cd /appl/firestarr/; source ../.venv/bin/activate; python src/py/firestarr/make_bounds.py' From 42a1770177dc25b355412856297a0cc0e1e8d7b8 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 09:16:16 -0400 Subject: [PATCH 20/38] add workaround for unzipping when python doesn't work but 7zip does --- src/py/firestarr/common.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/py/firestarr/common.py b/src/py/firestarr/common.py index 45a09f39a..0e6c20e2a 100644 --- a/src/py/firestarr/common.py +++ b/src/py/firestarr/common.py @@ -448,13 +448,19 @@ def split_line(line): def unzip(path, to_dir, match=None): if not os.path.exists(to_dir): os.mkdir(to_dir) - with zipfile.ZipFile(path, "r") as zip_ref: - if match is None: - names = zip_ref.namelist() - zip_ref.extractall(to_dir) - else: - names = [x for x in zip_ref.namelist() if match in x] - zip_ref.extractall(to_dir, names) + try: + with zipfile.ZipFile(path, "r") as zip_ref: + if match is None: + names = zip_ref.namelist() + zip_ref.extractall(to_dir) + else: + names = [x for x in zip_ref.namelist() if match in x] + zip_ref.extractall(to_dir, names) + except Exception as ex: + logging.warning(str(ex)) + # HACK: python doesn't see some things as zip but 7z does + logging.warning("Trying to use 7zip to unzip {} when python failed", path) + run_process(["7za", "x", path], to_dir) return [os.path.join(to_dir, x) for x in names] From 8924a57d8a7d00a954d14c039e45e94044b3c9e6 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 09:16:45 -0400 Subject: [PATCH 21/38] return None when no area to calculate --- src/py/firestarr/gis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/py/firestarr/gis.py b/src/py/firestarr/gis.py index 3959ad468..da6618cab 100644 --- a/src/py/firestarr/gis.py +++ b/src/py/firestarr/gis.py @@ -582,6 +582,8 @@ def find_closest(df, lat, lon, crs=CRS_COMPARISON, fill_missing=False): def area_ha(df): + if 0 == len(df): + return None return np.round(df.to_crs(CRS_COMPARISON).area / HA_TO_MSQ, 2) From 9020617f47150bfec48a1b144a7a2caaa9bc71a2 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 09:18:26 -0400 Subject: [PATCH 22/38] remove PC shape before join --- src/py/firestarr/make_bounds.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/py/firestarr/make_bounds.py b/src/py/firestarr/make_bounds.py index 31ef2d057..a87446040 100644 --- a/src/py/firestarr/make_bounds.py +++ b/src/py/firestarr/make_bounds.py @@ -138,6 +138,8 @@ def update_bounds( ): df_canada = get_features_canada(file_bounds=file_bounds, dir_out=DIR_BOUNDS).set_index(["EN"]) crs_orig = df_canada.crs + # remove parks from canada shape + df_canada = df_canada[df_canada.ID != "PC"] df_parks = load_geometry_file(URL_PARKS) df_parks_all = df_parks.dissolve()[["geometry"]] From d1782f81aff74bac918ec0668b4d7a9e49ad7ae5 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 09:18:57 -0400 Subject: [PATCH 23/38] fix assigning date when empty --- src/py/firestarr/datasources/cwfis.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/py/firestarr/datasources/cwfis.py b/src/py/firestarr/datasources/cwfis.py index 5e4377b12..e656ec4c4 100644 --- a/src/py/firestarr/datasources/cwfis.py +++ b/src/py/firestarr/datasources/cwfis.py @@ -127,8 +127,11 @@ def get_shp(filename): return gdf df = get_shp("perimeters") - # HACK: if empty then no results returned so fill with today where missing - df.loc[df["LASTDATE"].isna(), "LASTDATE"] = datetime.date.today() + df["LASTDATE"] = pd.to_datetime(df["LASTDATE"]) + empty = df["LASTDATE"].isna() + if empty.any(): + # HACK: if empty then no results returned so fill with today where missing + df.loc[empty, "LASTDATE"] = datetime.date.today() df["datetime"] = to_utc(df["LASTDATE"]) since = pd.to_datetime(self._last_active_since, utc=True) df = df.loc[df["datetime"] >= since] From ae13ba9ca60a2e064faac7a62d622221396234a1 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 09:19:13 -0400 Subject: [PATCH 24/38] don't try file for hotspots first --- src/py/firestarr/datasources/cwfis.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/py/firestarr/datasources/cwfis.py b/src/py/firestarr/datasources/cwfis.py index e656ec4c4..eefcf9817 100644 --- a/src/py/firestarr/datasources/cwfis.py +++ b/src/py/firestarr/datasources/cwfis.py @@ -217,13 +217,14 @@ def _get_features(self): # HACK: fallback in either direction # FIX: duplicated elsewhere tried_file = False - try: - if not CONFIG["PREFER_CWFIS_WMS"]: - tried_file = True - return self._source_file._get_features() - except Exception as ex: - logging.error("Unable to use file source for M3 data") - logging.error(ex) + # HACK: this doesn't work so just comment for now + # try: + # if not CONFIG["PREFER_CWFIS_WMS"]: + # tried_file = True + # return self._source_file._get_features() + # except Exception as ex: + # logging.error("Unable to use file source for M3 data") + # logging.error(ex) try: return self._source_service._get_features() except Exception as ex: From 6645a2b373a7fe7f061ede85974ca8ad8bc14283 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 09:20:00 -0400 Subject: [PATCH 25/38] fix error when queue not configured --- src/py/firestarr/main.py | 51 +++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/py/firestarr/main.py b/src/py/firestarr/main.py index 5f9ed7b79..9660c4095 100644 --- a/src/py/firestarr/main.py +++ b/src/py/firestarr/main.py @@ -181,6 +181,9 @@ def clear_queue(): AZURE_QUEUE_CONNECTION = CONFIG.get("AZURE_QUEUE_CONNECTION") AZURE_QUEUE_NAME = CONFIG.get("AZURE_QUEUE_NAME") + if not (AZURE_QUEUE_CONNECTION and AZURE_QUEUE_NAME): + logging.warning("No configured queue to clear") + return queue_service_client = QueueServiceClient.from_connection_string(AZURE_QUEUE_CONNECTION) queue_client = queue_service_client.get_queue_client(AZURE_QUEUE_NAME) queue_client.clear_messages() @@ -193,6 +196,9 @@ def scan_queue(): args = [] AZURE_QUEUE_CONNECTION = CONFIG.get("AZURE_QUEUE_CONNECTION") AZURE_QUEUE_NAME = CONFIG.get("AZURE_QUEUE_NAME") + if not (AZURE_QUEUE_CONNECTION and AZURE_QUEUE_NAME): + logging.warning("No configured queue to scan") + return None, args queue_service_client = QueueServiceClient.from_connection_string(AZURE_QUEUE_CONNECTION) queue_client = queue_service_client.get_queue_client(AZURE_QUEUE_NAME) response = queue_client.receive_messages(max_messages=1, visibility_timeout=60) @@ -275,28 +281,29 @@ def requeue(): if FROM_QUEUE: try: msg, args = scan_queue() - logging.info("Queue triggered with message:\n%s\ngives arguments:\n%s", msg, args) - # HACK: double-check that we're using only `--` args for now - for a in args: - # HACK: should filter things out if they aren't valid args elsewhere - if not a.startswith("--"): - logging.fatal("Invalid argument given: %s", a) - raise ValueError("Invalid argument given: %s" % a) - # HACK: sketched out about this, but will let us tell things to re-run via queue - sys.argv.extend(args) - # allow other arguments but remove duplicates - QUEUE_ARGS = ["--no-publish", "--no-merge", "--no-retry"] - # HACK: if not using batch then wait for results - if assign_sim_batch(): - logging.debug("Not waiting since running in batch") - QUEUE_ARGS.extend(["--no-wait"]) - REMOVE_ARGS = QUEUE_ARGS + ["--queue"] - for a in REMOVE_ARGS: - try: - sys.argv.remove(a) - except ValueError: - pass - sys.argv.extend(QUEUE_ARGS) + if msg: + logging.info("Queue triggered with message:\n%s\ngives arguments:\n%s", msg, args) + # HACK: double-check that we're using only `--` args for now + for a in args: + # HACK: should filter things out if they aren't valid args elsewhere + if not a.startswith("--"): + logging.fatal("Invalid argument given: %s", a) + raise ValueError("Invalid argument given: %s" % a) + # HACK: sketched out about this, but will let us tell things to re-run via queue + sys.argv.extend(args) + # allow other arguments but remove duplicates + QUEUE_ARGS = ["--no-publish", "--no-merge", "--no-retry"] + # HACK: if not using batch then wait for results + if assign_sim_batch(): + logging.debug("Not waiting since running in batch") + QUEUE_ARGS.extend(["--no-wait"]) + REMOVE_ARGS = QUEUE_ARGS + ["--queue"] + for a in REMOVE_ARGS: + try: + sys.argv.remove(a) + except ValueError: + pass + sys.argv.extend(QUEUE_ARGS) except Exception as ex: logging.warning("Unable to scan queue:\n\t" + str(ex)) FROM_QUEUE = False From 22df077c055a06bef2062b50b7f07632b90e358e Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 09:20:14 -0400 Subject: [PATCH 26/38] deal with empty dataframe --- src/py/firestarr/datasources/default.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/py/firestarr/datasources/default.py b/src/py/firestarr/datasources/default.py index da0049d41..df45f8c7b 100644 --- a/src/py/firestarr/datasources/default.py +++ b/src/py/firestarr/datasources/default.py @@ -4,6 +4,7 @@ import datasources.cwfif import datasources.spotwx +import geopandas as gpd import numpy as np import pandas as pd from common import ( @@ -137,11 +138,16 @@ def assign_fires( df_status[["status", "status_rank"]] = df_first.loc[df_status.index][["status", "status_rank"]] # dissolve by fire_name but use max so highest lastdate stays df_dissolve = df_status.dissolve(by="fire_name", aggfunc="max").reset_index() - df_dissolve["datetime"] = pick_max(df_dissolve["datetime_left"], df_dissolve["datetime_right"]) - # at this point we might have the same geometry for multiple fires, but that - # just means they'll all get replaced with it and then the group dissolve - # will take care of duplicates - df_matched = df_dissolve[["fire_name", "datetime", "status", "geometry"]] + cols_matched = ["fire_name", "datetime", "status", "geometry"] + if 0 < len(df_dissolve): + df_dissolve["datetime"] = pick_max(df_dissolve["datetime_left"], df_dissolve["datetime_right"]) + # at this point we might have the same geometry for multiple fires, but that + # just means they'll all get replaced with it and then the group dissolve + # will take care of duplicates + df_matched = df_dissolve[cols_matched] + else: + # make a fake dataframe + df_matched = gpd.GeoDataFrame(data=None, columns=cols_matched, crs=CRS_WGS84) df_features = df_matched.reset_index(drop=True) df_features["area"] = area_ha(df_features) df_unmatched["area"] = area_ha(df_unmatched) From dfa3af12901a1640495b28b202aa6f394962dacd Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 09:20:31 -0400 Subject: [PATCH 27/38] check instead of handling error so debugger doesn't stop --- src/py/firestarr/datasources/spotwx.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/py/firestarr/datasources/spotwx.py b/src/py/firestarr/datasources/spotwx.py index 7386087a1..c6cd9127d 100644 --- a/src/py/firestarr/datasources/spotwx.py +++ b/src/py/firestarr/datasources/spotwx.py @@ -41,7 +41,11 @@ def get_spotwx_key(): def get_spotwx_limit(): try: - return int(CONFIG.get("SPOTWX_API_LIMIT")) + # use non-pythonic check so debug doesn't keep catching on this + limit = CONFIG.get("SPOTWX_API_LIMIT") + if not limit: + return 0 + return int(limit) except ValueError as ex: logging.error("spotwx api request limit not set") return 0 From 40b1fc00fff0165e01f8aadb264a67ae3fff4f72 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 10:14:05 -0400 Subject: [PATCH 28/38] replace RASTER_ROOT with absolute path during setup.sh --- setup.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.sh b/setup.sh index 26306317c..66a2ef4f8 100644 --- a/setup.sh +++ b/setup.sh @@ -33,3 +33,4 @@ cp bounds.geojson firestarr/ cp bounds.geojson data/ docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cppscripts/build.sh' docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cd /appl/firestarr/; source ../.venv/bin/activate; python src/py/firestarr/make_bounds.py' +sed -i "s/\(RASTER_ROOT = \).*/\1\/appl\/data\/generated\/grid\/100m/" firestarr/settings.ini From 8c326aa39da7a55c2507ce599bb5f586391d02c5 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 10:14:35 -0400 Subject: [PATCH 29/38] add --tz argument required for firestarr now --- src/py/firestarr/sim_wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/py/firestarr/sim_wrapper.py b/src/py/firestarr/sim_wrapper.py index b12dfac53..3775894c1 100644 --- a/src/py/firestarr/sim_wrapper.py +++ b/src/py/firestarr/sim_wrapper.py @@ -593,6 +593,8 @@ def dolog(msg, *args, **kwargs): hour = start_time.hour minute = start_time.minute tz = start_time.tz.utcoffset(start_time).total_seconds() / SECONDS_PER_HOUR + # HACK: use actual offset for simulation but round for weather + data["utc_offset"] = tz # HACK: I think there might be issues with forecasts being at # the half hour? if math.floor(tz) != tz: @@ -624,6 +626,7 @@ def strip_dir(path): f"--dmc {data['dmc_old']}", f"--dc {data['dc_old']}", f"--apcp_prev {data['apcp_prev']}", + f"--tz {data['utc_offset']}", "-v", # "-v", # "-v", From 582409ddc20d23cb73b0c820e5c983be0e7e3264 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 10:14:53 -0400 Subject: [PATCH 30/38] avoid error with empty dataframe --- src/py/firestarr/run.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/py/firestarr/run.py b/src/py/firestarr/run.py index 921c8bcd3..578ae7915 100644 --- a/src/py/firestarr/run.py +++ b/src/py/firestarr/run.py @@ -6,6 +6,7 @@ import time import timeit +import geopandas as gpd import numpy as np import pandas as pd import sim_wrapper @@ -707,23 +708,31 @@ def prioritize(self, df_fires, df_bounds=None): # HACK: to_gdf will convert these into points df_reset = df.reset_index().to_crs(CRS_COMPARISON) df_small = df_reset[df_reset["area"] < FIRE_SIZE_BOUNDS_LIMIT].set_index("fire_name") + cols = list(set(df_bounds.columns).union(df_reset.columns)) + if 0 < len(df_small): + df_join_small = df_small[["geometry"]].sjoin(df_bounds) + else: + df_join_small = gpd.GeoDataFrame(data=None, columns=cols, crs=df_small.crs).set_index("fire_name") df_large = df_reset[df_reset["area"] >= FIRE_SIZE_BOUNDS_LIMIT].set_index("fire_name") - df_large.loc[:, "geometry"] = df_large.centroid - df_join_small = df_small[["geometry"]].sjoin(df_bounds) - df_join_centroids = df_large[["geometry"]].sjoin(df_bounds).drop(axis=1, columns=["geometry"]) - df_join_large = df_join_centroids.join(df_fires) - df_join_large = df_join_large[df_join_small.columns] + if 0 < len(df_large): + df_large.loc[:, "geometry"] = df_large.centroid + df_join_centroids = df_large[["geometry"]].sjoin(df_bounds).drop(axis=1, columns=["geometry"]) + df_join_large = df_join_centroids.join(df_fires) + df_join_large = df_join_large[df_join_small.columns] + else: + df_join_large = gpd.GeoDataFrame(data=None, columns=cols, crs=df_large.crs).set_index("fire_name") df_join = pd.concat([df_join_small, df_join_large]) # only keep fires that are in bounds df = df.loc[np.unique(df_join.index)] - if "PRIORITY" in df_join.columns: - df_priority = df_join.sort_values(["PRIORITY"]).groupby("fire_name").first() - df["ID"] = df_priority.loc[df.index, "ID"] - df["PRIORITY"] = df_priority.loc[df.index, "PRIORITY"] - if "DURATION" in df_bounds.columns: - df["DURATION"] = ( - df_join.sort_values(["DURATION"], ascending=False).groupby("fire_name").first()["DURATION"] - ) + if 0 < len(df_join): + if "PRIORITY" in df_join.columns: + df_priority = df_join.sort_values(["PRIORITY"]).groupby("fire_name").first() + df["ID"] = df_priority.loc[df.index, "ID"] + df["PRIORITY"] = df_priority.loc[df.index, "PRIORITY"] + if "DURATION" in df_bounds.columns: + df["DURATION"] = ( + df_join.sort_values(["DURATION"], ascending=False).groupby("fire_name").first()["DURATION"] + ) df["DURATION"] = np.min(list(zip([self._max_days] * len(df), df["DURATION"])), axis=1) df = df.sort_values(["PRIORITY", "ID", "DURATION", "area"]) return df From 17265fed8bac016528c5860cb68dac54011a6b72 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 10:18:51 -0400 Subject: [PATCH 31/38] fix typo in error message --- src/py/firestarr/tqdm_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/firestarr/tqdm_util.py b/src/py/firestarr/tqdm_util.py index 566ea2ed1..d0385d64c 100644 --- a/src/py/firestarr/tqdm_util.py +++ b/src/py/firestarr/tqdm_util.py @@ -305,7 +305,7 @@ def fct_try(dir_fire): done = False remaining = unsuccessful if num_cur > 0 and num_cur == num_prev: - logging.error("Settled on having %ds results not working", num_cur) + logging.error("Settled on having %d results not working", num_cur) break num_prev = num_cur except BrokenPipeError: From 9b1c800882848ba64f37d3aecc3aca47dc18ddff Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 10:22:51 -0400 Subject: [PATCH 32/38] turn off ON agency data sources since not working right now --- .../firestarr/datasources/public/agency_on.py | 723 +++++++++--------- 1 file changed, 362 insertions(+), 361 deletions(-) diff --git a/src/py/firestarr/datasources/public/agency_on.py b/src/py/firestarr/datasources/public/agency_on.py index 1bd7b9559..50450e619 100644 --- a/src/py/firestarr/datasources/public/agency_on.py +++ b/src/py/firestarr/datasources/public/agency_on.py @@ -1,361 +1,362 @@ -"""Ontario's publicly available data""" - -import datetime -import os -import urllib.parse -from collections import Counter -from functools import cache - -import numpy as np -import pandas as pd -import tqdm_util -from common import ( - DIR_DOWNLOAD, - FMT_DATE_YMD, - FMT_DATETIME, - FMT_FILE_MINUTE, - SECONDS_PER_HOUR, - do_nothing, - ensure_dir, - force_remove, - is_empty, - locks_for, - logging, - read_json_safe, - remove_timezone_utc, -) -from datasources.cwfif import fix_coords, fmt_rounded -from datasources.datatypes import ( - COLUMN_TIME, - SourceFwi, - SourceHourly, - check_columns, - make_template_empty, -) -from make_bounds import get_bounds_from_id -from net import RETRY_MAX_ATTEMPTS, try_save_http - -from gis import find_closest, gdf_from_file, save_geojson - -DIR_AGENCY_ON = ensure_dir(os.path.join(DIR_DOWNLOAD, "agency", "ON")) -SERVER_LIO = "https://ws.lioservices.lrc.gov.on.ca" -URL_SERVER = f"{SERVER_LIO}/arcgis1/rest/services/MNRF/Ontario_Fires_Map/MapServer" -LAYER_FIRE_POINT = 0 -LAYER_HOURLY = 29 -LAYER_DAILY = 30 -DATE_FIELDS = { - LAYER_HOURLY: "OBSERVATION_DATE", - LAYER_DAILY: "DFOSS_WEATHER_DATE", -} -QUERY_ALL = "1=1" -FIELDS_ALL = ["*"] -BOUNDS_ON = get_bounds_from_id("ON") - - -def fix_date(t): - if not t: - return None - # HACK: this was failing on something but I can't remember what - try: - if np.isnan(t): - return None - except TypeError: - pass - # HACK: shp can't save datetime - return fmt_date((datetime.datetime(1970, 1, 1) + datetime.timedelta(milliseconds=t))) - - -def fix_dates(df): - for x in df.columns: - # HACK: assume anything with 'DATE' in the name is a time since epoch - if "DATE" in x and df.dtypes[x] != "O": - df[x] = tqdm_util.apply(df[x], fix_date, desc="Fixing dates") - return df - - -def parse_by_extension(path): - format = os.path.splitext(path)[-1][1:] - if "geojson" == format: - return fix_dates(gdf_from_file(path)) - if "pjson" == format: - return read_json_safe(path) - with open(path) as f: - return f.readlines() - - -def do_query(save_as, layer, fct_parse=None, query=QUERY_ALL, fields=FIELDS_ALL, other=None): - url = f"{URL_SERVER}/{layer}/query?" + "&".join( - [ - f"where={urllib.parse.quote(query)}", - f"outFields={','.join(fields)}", - ] - + (other or []) - + [ - f"f={os.path.splitext(save_as)[-1][1:]}", - ] - ) - - # HACK: keep getting - # { - # "error": { - # "code": 400, - # "message": "Unable to complete operation.", - # "details": [ - # "Invalid connection property." - # ] - # } - # } - return try_save_http( - url, - save_as, - keep_existing=False, - fct_pre_save=None, - fct_post_save=lambda _: (fct_parse or do_nothing)(parse_by_extension(_)), - fct_is_invalid=lambda r: "error" in r.text, - ) - - -def fmt_date(d): - return d.strftime(FMT_DATETIME) - - -def get_query_date(field, datetime_start=None, datetime_end=None): - if datetime_start is None: - datetime_start = datetime.date.today() - datetime.timedelta(days=1) - query = f"{field}>=TIMESTAMP '{fmt_date(datetime_start)}'" - if datetime_end is not None: - query += f" AND {field}<=TIMESTAMP '{fmt_date(datetime_end)}'" - return query - - -# don't worry about updating this during same run right now -@cache -def check_latest(layer): - save_as = os.path.join(DIR_AGENCY_ON, f"on_wx_layer{layer}_latest.pjson") - # look up closest station in layer and make a query - stats = '[{"statisticType":"max","onStatisticField":"' + DATE_FIELDS[layer] + '","outStatisticFieldName":"latest"}]' - other = [f"outStatistics={urllib.parse.quote(stats)}"] - - def do_parse(df): - return pd.to_datetime(fix_date(df["features"][0]["attributes"]["LATEST"]), utc=True) - - return do_query(save_as, layer, fct_parse=do_parse, fields=["latest"], other=other) - - -@cache -def get_stns(layer, latest): - save_as = os.path.join( - DIR_AGENCY_ON, - f"on_wx_layer{layer}_stns_{latest.strftime(FMT_FILE_MINUTE)}.geojson", - ) - return do_query( - save_as, - layer, - query=f"{DATE_FIELDS[layer]}=TIMESTAMP '{fmt_date(latest)}'", - fields=["WEATHER_STATION_CODE", "LATITUDE", "LONGITUDE"], - ) - - -@cache -def try_download_fwi(date): - layer = LAYER_DAILY - latest = check_latest(layer) - date_utc = pd.Timestamp(date).tz_localize("UTC") - if date_utc > latest: - return make_template_empty("fwi") - datetime_start = date_utc - datetime_end = date_utc + datetime.timedelta(days=1) - # always use PM so it's actuals - wx_type = "PM" - query = " AND ".join( - [ - f"{get_query_date(DATE_FIELDS[layer], datetime_start, datetime_end)}", - f"DFOSS_WEATHER_TYPE='{wx_type}'", - ] - ) - save_as = os.path.join(DIR_AGENCY_ON, f"on_wx_layer{layer}_{date.strftime(FMT_DATE_YMD)}.geojson") - - def do_parse(df): - if is_empty(df): - return make_template_empty("fwi") - df.columns = [x.lower() for x in df.columns] - df = df.rename( - columns={ - "latitude": "lat", - "longitude": "lon", - } - ) - df[COLUMN_TIME] = tqdm_util.apply( - df["dfoss_weather_date"], - lambda x: pd.to_datetime(x.replace("00:00:00", "12:00:00")), - desc="Assigning datetimes", - ) - return df - - return do_query(save_as, layer, query=query, fct_parse=do_parse) - - -@cache -def get_fwi(date): - # once we have fwi actuals for a date they shouldn't change - file_fwi_date = os.path.join(DIR_AGENCY_ON, f"fwi_{date.strftime(FMT_DATE_YMD)}.geojson") - # can't ensure that this is going to be created if no data exists - with locks_for(file_fwi_date): - if not os.path.exists(file_fwi_date): - # want this in another function so it caches - df = try_download_fwi(date) - if is_empty(df): - return df - save_geojson(df, file_fwi_date) - return gdf_from_file(file_fwi_date) - - -class SourceFwiON(SourceFwi): - def __init__(self, dir_out) -> None: - super().__init__(bounds=BOUNDS_ON) - self._dir_out = dir_out - - def _get_fwi(self, lat, lon, date): - return find_closest(get_fwi(date), lat, lon) - - -def make_file_name(layer, hr_begin, hr_end, dir_out=DIR_AGENCY_ON): - fmt_end = "" if hr_end is None else f"_{hr_end.strftime(FMT_FILE_MINUTE)}" - return os.path.join( - dir_out, - f"on_wx_layer{layer}_" f"{hr_begin.strftime(FMT_FILE_MINUTE)}" f"{fmt_end}.geojson", - ) - - -def file_for_date(layer, d, dir_out=DIR_AGENCY_ON): - return os.path.join( - dir_out, - f"on_wx_layer{layer}_{d.strftime(FMT_DATE_YMD)}.geojson", - ) - - -@cache -def get_hourly_date(dir_out, layer, date): - latest = check_latest(layer) - date_utc = pd.Timestamp(date).tz_localize("UTC") - if date_utc > latest: - return make_template_empty("hourly") - - # 5000 row limit on query results means 208 stations with 24 hours of data - # can be returned - should be more than enough, so just get full day for all - # stations - file_wx_date = file_for_date(layer, date, dir_out) - with locks_for(file_wx_date): - if os.path.isfile(file_wx_date): - df = gdf_from_file(file_wx_date) - times = np.unique(df["datetime"]) - expected = ( - 24 - if np.max(df["datetime"]).date() < latest.date() - else (latest - date_utc).total_seconds() / SECONDS_PER_HOUR - ) - # chance that a station is missing data even though hours exist for others - c = Counter(df["datetime"]) - if expected > len(times) or 1 != len(np.unique(list(c.values()))): - # need to get again because old file isn't complete - force_remove(file_wx_date) - else: - # same amount of data for all hours so should be okay - return df - hr_begin = pd.to_datetime(date) - hr_end = pd.to_datetime(date) + datetime.timedelta(hours=23) - # ask for any ranges we're missing - query = " AND ".join( - [ - f"{get_query_date(DATE_FIELDS[layer], hr_begin, hr_end)}", - ] - ) - - def do_parse(df): - df.columns = [x.lower() for x in df.columns] - df = df.rename( - columns={ - "observation_date": COLUMN_TIME, - "winddir": "wd", - "adjwindspeed": "ws", - "rainfall": "prec", - "latitude": "lat", - "longitude": "lon", - } - ) - df["id"] = 0 - df["model"] = "observed" - # HACK: wind can be 'null' so set to 0 if it is - df.loc[df["wd"].isna(), "wd"] = 0 - df[COLUMN_TIME] = remove_timezone_utc(df[COLUMN_TIME]) - return df - - df = do_query( - file_for_date(layer, date, DIR_AGENCY_ON), - layer, - query=query, - fct_parse=do_parse, - ) - df = check_columns(df, "hourly") - save_geojson(df, file_wx_date) - if 1 != len(np.unique(list(Counter(df["datetime"]).values()))): - logging.warning("Some stations are missing data for some hours") - return gdf_from_file(file_wx_date) - - -@cache -def get_hourly(dir_out, layer, datetime_start, datetime_end): - file_stn_wx = make_file_name(layer, datetime_start, datetime_end, dir_out) - with locks_for(file_stn_wx): - if datetime_end is None: - datetime_end = check_latest(layer) - # remove timezone so matching works later - datetime_start = remove_timezone_utc(datetime_start) - datetime_end = remove_timezone_utc(datetime_end) - if datetime_start > datetime_end: - return make_template_empty("hourly") - if not os.path.exists(file_stn_wx): - df_wx = None - for d in pd.date_range( - datetime_start.date(), - datetime_end.date(), - freq="D", - inclusive="both", - ): - df_wx = pd.concat([df_wx, get_hourly_date(dir_out, layer, d)]) - df_wx["datetime"] = remove_timezone_utc(df_wx["datetime"]) - df_wx = df_wx.sort_values([COLUMN_TIME]) - df_wx = df_wx.loc[(df_wx["datetime"] >= datetime_start) & (df_wx["datetime"] <= datetime_end)] - save_geojson(df_wx, file_stn_wx) - return gdf_from_file(file_stn_wx) - - -@cache -def get_wx_hourly(dir_out, lat, lon, datetime_start, datetime_end=None): - layer = LAYER_HOURLY - file_wx = os.path.join( - dir_out, - f"on_wx_layer{layer}_{fmt_rounded(lat)}_{fmt_rounded(lon)}.geojson", - ) - - # don't try checking for updates within the same run - with locks_for(file_wx): - if not os.path.exists(file_wx): - df_hourly = get_hourly(dir_out, layer, datetime_start, datetime_end) - if is_empty(df_hourly): - return df_hourly - # CHECK: might get station that's closest but doesn't exist for timespan - df_wx = find_closest(df_hourly, lat, lon, fill_missing=True) - save_geojson(df_wx, file_wx) - return gdf_from_file(file_wx) - - -class SourceHourlyON(SourceHourly): - def __init__(self, dir_out) -> None: - super().__init__(bounds=BOUNDS_ON) - self._dir_out = dir_out - - def _get_wx_hourly(self, lat, lon, datetime_start, datetime_end=None): - lat, lon = fix_coords(lat, lon) - # want this in another function so it caches - return get_wx_hourly(self._dir_out, lat, lon, datetime_start, datetime_end) +# """Ontario's publicly available data""" + +# import datetime +# import os +# import urllib.parse +# from collections import Counter +# from functools import cache + +# import numpy as np +# import pandas as pd +# import tqdm_util +# from common import ( +# DIR_DOWNLOAD, +# FMT_DATE_YMD, +# FMT_DATETIME, +# FMT_FILE_MINUTE, +# SECONDS_PER_HOUR, +# do_nothing, +# ensure_dir, +# force_remove, +# is_empty, +# locks_for, +# logging, +# read_json_safe, +# remove_timezone_utc, +# ) +# from datasources.cwfif import fix_coords, fmt_rounded +# from datasources.datatypes import ( +# COLUMN_TIME, +# SourceFwi, +# SourceHourly, +# check_columns, +# make_template_empty, +# ) +# from gis import find_closest, gdf_from_file, save_geojson +# from make_bounds import get_bounds_from_id +# from net import RETRY_MAX_ATTEMPTS, try_save_http + +# DIR_AGENCY_ON = ensure_dir(os.path.join(DIR_DOWNLOAD, "agency", "ON")) +# SERVER_LIO = "https://ws.lioservices.lrc.gov.on.ca" +# URL_SERVER = f"{SERVER_LIO}/arcgis1/rest/services/MNRF/Ontario_Fires_Map/MapServer" +# LAYER_FIRE_POINT = 0 +# LAYER_HOURLY = 29 +# LAYER_DAILY = 30 +# DATE_FIELDS = { +# LAYER_HOURLY: "OBSERVATION_DATE", +# LAYER_DAILY: "DFOSS_WEATHER_DATE", +# } +# QUERY_ALL = "1=1" +# FIELDS_ALL = ["*"] +# BOUNDS_ON = get_bounds_from_id("ON") + + +# def fix_date(t): +# if not t: +# return None +# # HACK: this was failing on something but I can't remember what +# try: +# ms = datetime.timedelta(milliseconds=float(t)) +# if np.isnan(t): +# return None +# # HACK: shp can't save datetime +# return fmt_date((datetime.datetime(1970, 1, 1) + ms)) +# except TypeError: +# pass +# return None + + +# def fix_dates(df): +# for x in df.columns: +# # HACK: assume anything with 'DATE' in the name is a time since epoch +# if "DATE" in x and df.dtypes[x] != "O": +# df[x] = tqdm_util.apply(df[x], fix_date, desc="Fixing dates") +# return df + + +# def parse_by_extension(path): +# format = os.path.splitext(path)[-1][1:] +# if "geojson" == format: +# return fix_dates(gdf_from_file(path)) +# if "pjson" == format: +# return read_json_safe(path) +# with open(path) as f: +# return f.readlines() + + +# def do_query(save_as, layer, fct_parse=None, query=QUERY_ALL, fields=FIELDS_ALL, other=None): +# url = f"{URL_SERVER}/{layer}/query?" + "&".join( +# [ +# f"where={urllib.parse.quote(query)}", +# f"outFields={','.join(fields)}", +# ] +# + (other or []) +# + [ +# f"f={os.path.splitext(save_as)[-1][1:]}", +# ] +# ) + +# # HACK: keep getting +# # { +# # "error": { +# # "code": 400, +# # "message": "Unable to complete operation.", +# # "details": [ +# # "Invalid connection property." +# # ] +# # } +# # } +# return try_save_http( +# url, +# save_as, +# keep_existing=False, +# fct_pre_save=None, +# fct_post_save=lambda _: (fct_parse or do_nothing)(parse_by_extension(_)), +# fct_is_invalid=lambda r: "error" in r.text, +# ) + + +# def fmt_date(d): +# return d.strftime(FMT_DATETIME) + + +# def get_query_date(field, datetime_start=None, datetime_end=None): +# if datetime_start is None: +# datetime_start = datetime.date.today() - datetime.timedelta(days=1) +# query = f"{field}>=TIMESTAMP '{fmt_date(datetime_start)}'" +# if datetime_end is not None: +# query += f" AND {field}<=TIMESTAMP '{fmt_date(datetime_end)}'" +# return query + + +# # don't worry about updating this during same run right now +# @cache +# def check_latest(layer): +# save_as = os.path.join(DIR_AGENCY_ON, f"on_wx_layer{layer}_latest.pjson") +# # look up closest station in layer and make a query +# stats = '[{"statisticType":"max","onStatisticField":"' + DATE_FIELDS[layer] + '","outStatisticFieldName":"latest"}]' +# other = [f"outStatistics={urllib.parse.quote(stats)}"] + +# def do_parse(df): +# return pd.to_datetime(fix_date(df["features"][0]["attributes"]["LATEST"]), utc=True) + +# return do_query(save_as, layer, fct_parse=do_parse, fields=["latest"], other=other) + + +# @cache +# def get_stns(layer, latest): +# save_as = os.path.join( +# DIR_AGENCY_ON, +# f"on_wx_layer{layer}_stns_{latest.strftime(FMT_FILE_MINUTE)}.geojson", +# ) +# return do_query( +# save_as, +# layer, +# query=f"{DATE_FIELDS[layer]}=TIMESTAMP '{fmt_date(latest)}'", +# fields=["WEATHER_STATION_CODE", "LATITUDE", "LONGITUDE"], +# ) + + +# @cache +# def try_download_fwi(date): +# layer = LAYER_DAILY +# latest = check_latest(layer) +# date_utc = pd.Timestamp(date).tz_localize("UTC") +# if date_utc > latest: +# return make_template_empty("fwi") +# datetime_start = date_utc +# datetime_end = date_utc + datetime.timedelta(days=1) +# # always use PM so it's actuals +# wx_type = "PM" +# query = " AND ".join( +# [ +# f"{get_query_date(DATE_FIELDS[layer], datetime_start, datetime_end)}", +# f"DFOSS_WEATHER_TYPE='{wx_type}'", +# ] +# ) +# save_as = os.path.join(DIR_AGENCY_ON, f"on_wx_layer{layer}_{date.strftime(FMT_DATE_YMD)}.geojson") + +# def do_parse(df): +# if is_empty(df): +# return make_template_empty("fwi") +# df.columns = [x.lower() for x in df.columns] +# df = df.rename( +# columns={ +# "latitude": "lat", +# "longitude": "lon", +# } +# ) +# df[COLUMN_TIME] = tqdm_util.apply( +# df["dfoss_weather_date"], +# lambda x: pd.to_datetime(x.replace("00:00:00", "12:00:00")), +# desc="Assigning datetimes", +# ) +# return df + +# return do_query(save_as, layer, query=query, fct_parse=do_parse) + + +# @cache +# def get_fwi(date): +# # once we have fwi actuals for a date they shouldn't change +# file_fwi_date = os.path.join(DIR_AGENCY_ON, f"fwi_{date.strftime(FMT_DATE_YMD)}.geojson") +# # can't ensure that this is going to be created if no data exists +# with locks_for(file_fwi_date): +# if not os.path.exists(file_fwi_date): +# # want this in another function so it caches +# df = try_download_fwi(date) +# if is_empty(df): +# return df +# save_geojson(df, file_fwi_date) +# return gdf_from_file(file_fwi_date) + + +# class SourceFwiON(SourceFwi): +# def __init__(self, dir_out) -> None: +# super().__init__(bounds=BOUNDS_ON) +# self._dir_out = dir_out + +# def _get_fwi(self, lat, lon, date): +# return find_closest(get_fwi(date), lat, lon) + + +# def make_file_name(layer, hr_begin, hr_end, dir_out=DIR_AGENCY_ON): +# fmt_end = "" if hr_end is None else f"_{hr_end.strftime(FMT_FILE_MINUTE)}" +# return os.path.join( +# dir_out, +# f"on_wx_layer{layer}_" f"{hr_begin.strftime(FMT_FILE_MINUTE)}" f"{fmt_end}.geojson", +# ) + + +# def file_for_date(layer, d, dir_out=DIR_AGENCY_ON): +# return os.path.join( +# dir_out, +# f"on_wx_layer{layer}_{d.strftime(FMT_DATE_YMD)}.geojson", +# ) + + +# @cache +# def get_hourly_date(dir_out, layer, date): +# latest = check_latest(layer) +# date_utc = pd.Timestamp(date).tz_localize("UTC") +# if date_utc > latest: +# return make_template_empty("hourly") + +# # 5000 row limit on query results means 208 stations with 24 hours of data +# # can be returned - should be more than enough, so just get full day for all +# # stations +# file_wx_date = file_for_date(layer, date, dir_out) +# with locks_for(file_wx_date): +# if os.path.isfile(file_wx_date): +# df = gdf_from_file(file_wx_date) +# times = np.unique(df["datetime"]) +# expected = ( +# 24 +# if np.max(df["datetime"]).date() < latest.date() +# else (latest - date_utc).total_seconds() / SECONDS_PER_HOUR +# ) +# # chance that a station is missing data even though hours exist for others +# c = Counter(df["datetime"]) +# if expected > len(times) or 1 != len(np.unique(list(c.values()))): +# # need to get again because old file isn't complete +# force_remove(file_wx_date) +# else: +# # same amount of data for all hours so should be okay +# return df +# hr_begin = pd.to_datetime(date) +# hr_end = pd.to_datetime(date) + datetime.timedelta(hours=23) +# # ask for any ranges we're missing +# query = " AND ".join( +# [ +# f"{get_query_date(DATE_FIELDS[layer], hr_begin, hr_end)}", +# ] +# ) + +# def do_parse(df): +# df.columns = [x.lower() for x in df.columns] +# df = df.rename( +# columns={ +# "observation_date": COLUMN_TIME, +# "winddir": "wd", +# "adjwindspeed": "ws", +# "rainfall": "prec", +# "latitude": "lat", +# "longitude": "lon", +# } +# ) +# df["id"] = 0 +# df["model"] = "observed" +# # HACK: wind can be 'null' so set to 0 if it is +# df.loc[df["wd"].isna(), "wd"] = 0 +# df[COLUMN_TIME] = remove_timezone_utc(df[COLUMN_TIME]) +# return df + +# df = do_query( +# file_for_date(layer, date, DIR_AGENCY_ON), +# layer, +# query=query, +# fct_parse=do_parse, +# ) +# df = check_columns(df, "hourly") +# save_geojson(df, file_wx_date) +# if 1 != len(np.unique(list(Counter(df["datetime"]).values()))): +# logging.warning("Some stations are missing data for some hours") +# return gdf_from_file(file_wx_date) + + +# @cache +# def get_hourly(dir_out, layer, datetime_start, datetime_end): +# file_stn_wx = make_file_name(layer, datetime_start, datetime_end, dir_out) +# with locks_for(file_stn_wx): +# if datetime_end is None: +# datetime_end = check_latest(layer) +# # remove timezone so matching works later +# datetime_start = remove_timezone_utc(datetime_start) +# datetime_end = remove_timezone_utc(datetime_end) +# if datetime_start > datetime_end: +# return make_template_empty("hourly") +# if not os.path.exists(file_stn_wx): +# df_wx = None +# for d in pd.date_range( +# datetime_start.date(), +# datetime_end.date(), +# freq="D", +# inclusive="both", +# ): +# df_wx = pd.concat([df_wx, get_hourly_date(dir_out, layer, d)]) +# df_wx["datetime"] = remove_timezone_utc(df_wx["datetime"]) +# df_wx = df_wx.sort_values([COLUMN_TIME]) +# df_wx = df_wx.loc[(df_wx["datetime"] >= datetime_start) & (df_wx["datetime"] <= datetime_end)] +# save_geojson(df_wx, file_stn_wx) +# return gdf_from_file(file_stn_wx) + + +# @cache +# def get_wx_hourly(dir_out, lat, lon, datetime_start, datetime_end=None): +# layer = LAYER_HOURLY +# file_wx = os.path.join( +# dir_out, +# f"on_wx_layer{layer}_{fmt_rounded(lat)}_{fmt_rounded(lon)}.geojson", +# ) + +# # don't try checking for updates within the same run +# with locks_for(file_wx): +# if not os.path.exists(file_wx): +# df_hourly = get_hourly(dir_out, layer, datetime_start, datetime_end) +# if is_empty(df_hourly): +# return df_hourly +# # CHECK: might get station that's closest but doesn't exist for timespan +# df_wx = find_closest(df_hourly, lat, lon, fill_missing=True) +# save_geojson(df_wx, file_wx) +# return gdf_from_file(file_wx) + + +# class SourceHourlyON(SourceHourly): +# def __init__(self, dir_out) -> None: +# super().__init__(bounds=BOUNDS_ON) +# self._dir_out = dir_out + +# def _get_wx_hourly(self, lat, lon, datetime_start, datetime_end=None): +# lat, lon = fix_coords(lat, lon) +# # want this in another function so it caches +# return get_wx_hourly(self._dir_out, lat, lon, datetime_start, datetime_end) From 56fb2b8bc8a825cb847370743418ae55b9f7603c Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 10:25:02 -0400 Subject: [PATCH 33/38] add command to run at end of setup.sh --- setup.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.sh b/setup.sh index 66a2ef4f8..aff0ee1d7 100644 --- a/setup.sh +++ b/setup.sh @@ -34,3 +34,4 @@ cp bounds.geojson data/ docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cppscripts/build.sh' docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cd /appl/firestarr/; source ../.venv/bin/activate; python src/py/firestarr/make_bounds.py' sed -i "s/\(RASTER_ROOT = \).*/\1\/appl\/data\/generated\/grid\/100m/" firestarr/settings.ini +docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cd /appl/firestarr/; scripts/force_run.sh --no-publish --no-retry' From db01298178c671f27b80c73b8b509dab308031d6 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 10:27:26 -0400 Subject: [PATCH 34/38] add submodule init --- setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.sh b/setup.sh index aff0ee1d7..34bfad846 100644 --- a/setup.sh +++ b/setup.sh @@ -1,4 +1,6 @@ RASTERS=FireSTARR_Dataset_2025_V1.1.zip +# make sure submodules are initialized +git submodule update --init # get rasters mkdir -p data mkdir -p data/sims From 1786100fe95aa0a3957edef0d43469be475c0166 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 11:20:58 -0400 Subject: [PATCH 35/38] change settings so that simulations are run much quicker for this test --- setup.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/setup.sh b/setup.sh index 34bfad846..349ee0bfa 100644 --- a/setup.sh +++ b/setup.sh @@ -35,5 +35,13 @@ cp bounds.geojson firestarr/ cp bounds.geojson data/ docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cppscripts/build.sh' docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cd /appl/firestarr/; source ../.venv/bin/activate; python src/py/firestarr/make_bounds.py' -sed -i "s/\(RASTER_ROOT = \).*/\1\/appl\/data\/generated\/grid\/100m/" firestarr/settings.ini -docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cd /appl/firestarr/; scripts/force_run.sh --no-publish --no-retry' +sed -i "s/^\(RASTER_ROOT = \).*/\1\/appl\/data\/generated\/grid\/100m/" firestarr/settings.ini +# turn off multiple simulations per scenario for now +sed -i "s/^\(MAXIMUM_SIMULATIONS = \).*/\10/" firestarr/settings.ini +# sed -i "s/\(OUTPUT_DATE_OFFSETS = \).*/\1[1]/" firestarr/settings.ini +# FIX: this should be a setting and not a constant +sed -i "s/^\(MAX_NUM_DAYS = \).*/\11/" src/py/firestarr/common.py +# create inputs +docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cd /appl/firestarr/; scripts/force_run.sh --no-publish --no-retry --prepare-only' +# actually run +docker compose run -it --entrypoint /bin/bash firestarr-app-dev -c 'cd /appl/firestarr/; scripts/force_run.sh --resume --no-publish --no-retry' From f80c947dd0474130ed60796e5e7547320da467cd Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 11:46:04 -0400 Subject: [PATCH 36/38] fix typo --- .docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 514acaa11..a1323446b 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -107,7 +107,7 @@ COPY --from=firestarr-prod-base-env / / # derive final image from base so updates to binary are small layers FROM firestarr-prod-base AS firestarr-prod -ARG VERSION9* +ARG VERSION ENV VERSION=${VERSION} ENV TMPDIR=/tmp WORKDIR /appl/firestarr From 8b2991e7c08fb5a0d98fd701c43fd194eca79680 Mon Sep 17 00:00:00 2001 From: "download.docker.com" Date: Tue, 28 Apr 2026 11:54:20 -0400 Subject: [PATCH 37/38] add firestarr-app-hotfix temporarily to patch azure without updating numpy --- Dockerfile_hotfix | 5 +++++ docker-compose.yml | 17 +++++++++++++++++ make_hotfix.sh | 13 +++++++++++++ setup.sh | 0 4 files changed, 35 insertions(+) create mode 100644 Dockerfile_hotfix create mode 100755 make_hotfix.sh mode change 100644 => 100755 setup.sh diff --git a/Dockerfile_hotfix b/Dockerfile_hotfix new file mode 100644 index 000000000..071c4eb6f --- /dev/null +++ b/Dockerfile_hotfix @@ -0,0 +1,5 @@ +FROM registrycwfisdev.azurecr.io/firestarr/firestarr-app:latest AS firestarr-app-hotfix +ENV VERSION=0.9.6 +WORKDIR /appl/firestarr +COPY ./src/py/firestarr/ /appl/firestarr/src/py/firestarr/ +RUN echo "This is a patch of v0.9.5.5 to fix errors with empty dataframes but not upgrade numpy or anything else" > /appl/firestarr/NOTE.md diff --git a/docker-compose.yml b/docker-compose.yml index 2d02d5dfc..73f410b2f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,21 @@ services: + firestarr-app-hotfix: + image: firestarr-app + build: + context: . + target: firestarr-app-hotfix + dockerfile: Dockerfile_hotfix + args: + VERSION: ${VERSION} + tags: + - firestarr-app:${VERSION} + entrypoint: tail -f /dev/null + volumes: + - /etc/ssl/certs:/etc/ssl/certs + - data:/appl/data + env_file: + - .env + firestarr-setup-gis: image: firestarr-setup-gis build: diff --git a/make_hotfix.sh b/make_hotfix.sh new file mode 100755 index 000000000..7d2e9547e --- /dev/null +++ b/make_hotfix.sh @@ -0,0 +1,13 @@ +#/bin/bash +# undo max days from setup.sh +git restore src/py/firestarr/common.py +# turn ON data back on +git restore -s ebd6cb405 src/py/firestarr/datasources/public/agency_on.py +# go to version that doesn't use --tz +git restore -s ebd6cb405 src/py/firestarr/sim_wrapper.py +docker compose down firestarr-app-hotfix +docker compose build --no-cache firestarr-app-hotfix +# docker compose up -d firestarr-app-hotfix +docker compose run -it --entrypoint /bin/bash firestarr-app-hotfix -c 'cd /appl/firestarr/; scripts/force_run.sh --no-publish --no-retry --prepare-only' +# actually run +docker compose run -it --entrypoint /bin/bash firestarr-app-hotfix -c 'cd /appl/firestarr/; scripts/force_run.sh --resume --no-publish --no-retry' diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 From 15bd05bb69198478a348c6abec8fe3de3a202706 Mon Sep 17 00:00:00 2001 From: Jethro Date: Tue, 5 May 2026 12:10:16 -0600 Subject: [PATCH 38/38] #13 column fix crash from numpy 2 upgrade --- src/py/firestarr/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/firestarr/run.py b/src/py/firestarr/run.py index 578ae7915..123676152 100644 --- a/src/py/firestarr/run.py +++ b/src/py/firestarr/run.py @@ -716,7 +716,7 @@ def prioritize(self, df_fires, df_bounds=None): df_large = df_reset[df_reset["area"] >= FIRE_SIZE_BOUNDS_LIMIT].set_index("fire_name") if 0 < len(df_large): df_large.loc[:, "geometry"] = df_large.centroid - df_join_centroids = df_large[["geometry"]].sjoin(df_bounds).drop(axis=1, columns=["geometry"]) + df_join_centroids = df_large[["geometry"]].sjoin(df_bounds).drop("geometry", axis=1) df_join_large = df_join_centroids.join(df_fires) df_join_large = df_join_large[df_join_small.columns] else: