ecological-agent-skills 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_CONTEXT.md +191 -0
- package/CATALOG.md +329 -0
- package/LICENSE +692 -0
- package/README.md +347 -0
- package/bin/install.mjs +168 -0
- package/docs/comparison-with-alternatives.md +38 -0
- package/docs/global-examples-index.md +103 -0
- package/docs/repository-statistics.md +101 -0
- package/docs/theoretical-foundations.md +188 -0
- package/environment.yaml +106 -0
- package/examples/community/arctic_tundra_vegetation_example.md +247 -0
- package/examples/community/bird_landuse_example.md +63 -0
- package/examples/community/phytoplankton_reservoir_example.md +60 -0
- package/examples/community/reef_fish_indopacific_example.md +221 -0
- package/examples/impact/baci_road_example.md +57 -0
- package/examples/impact/ecosystem_services_atlantic_forest.md +83 -0
- package/examples/impact/forest_loss_borneo_timeseries_example.md +225 -0
- package/examples/occupancy/puma_camera_example.md +61 -0
- package/examples/occupancy/snow_leopard_himalayas_example.md +204 -0
- package/examples/reproducible/whittaker_biome_sdm_example.md +406 -0
- package/examples/sdm/anteater_cerrado_example.md +69 -0
- package/examples/sdm/jaguar_amazon_example.md +80 -0
- package/examples/sdm/koala_climate_change_example.md +170 -0
- package/examples/sdm/wolf_recolonization_europe_example.md +193 -0
- package/package.json +43 -0
- package/renv.lock +194 -0
- package/skills/SKILL_INDEX.json +1020 -0
- package/skills/acoustic-monitoring/SKILL.md +163 -0
- package/skills/acoustic-monitoring/examples/example-prompts.md +100 -0
- package/skills/acoustic-monitoring/examples/temperate_forest_birds_example.md +285 -0
- package/skills/acoustic-monitoring/resources/acoustic-indices-reference.md +93 -0
- package/skills/acoustic-monitoring/resources/soundscape-ecology-guide.md +90 -0
- package/skills/acoustic-monitoring/resources/species-id-tools-comparison.md +89 -0
- package/skills/acoustic-monitoring/scripts/batch_species_detection.py +360 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.R +235 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.py +374 -0
- package/skills/biostatistics-workbench/SKILL.md +140 -0
- package/skills/biostatistics-workbench/examples/example-prompts.md +39 -0
- package/skills/biostatistics-workbench/resources/effect-size-reference.md +81 -0
- package/skills/biostatistics-workbench/resources/glm-family-link-reference.md +47 -0
- package/skills/biostatistics-workbench/resources/test-selection-guide.md +93 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.R +78 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.py +210 -0
- package/skills/camera-trap-processing/SKILL.md +159 -0
- package/skills/camera-trap-processing/examples/example-prompts.md +103 -0
- package/skills/camera-trap-processing/examples/leopard_serengeti_example.md +231 -0
- package/skills/camera-trap-processing/resources/activity-patterns-reference.md +113 -0
- package/skills/camera-trap-processing/resources/camtrapR-workflow-guide.md +130 -0
- package/skills/camera-trap-processing/resources/detection-event-definition-guide.md +89 -0
- package/skills/camera-trap-processing/scripts/estimate_activity.R +169 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.R +179 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.py +192 -0
- package/skills/community-ecology-ordination/SKILL.md +133 -0
- package/skills/community-ecology-ordination/examples/example-prompts.md +35 -0
- package/skills/community-ecology-ordination/resources/dissimilarity-metric-guide.md +53 -0
- package/skills/community-ecology-ordination/resources/nmds-interpretation-guide.md +104 -0
- package/skills/community-ecology-ordination/scripts/__pycache__/community_analysis.cpython-311.pyc +0 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.R +143 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.py +231 -0
- package/skills/ecological-data-foundation/SKILL.md +129 -0
- package/skills/ecological-data-foundation/examples/example-prompts.md +40 -0
- package/skills/ecological-data-foundation/resources/coordinate-cleaning-flags.md +66 -0
- package/skills/ecological-data-foundation/resources/darwin-core-glossary.md +91 -0
- package/skills/ecological-data-foundation/resources/data-citation-guide.md +265 -0
- package/skills/ecological-data-foundation/resources/gbif-data-citation-guide.md +193 -0
- package/skills/ecological-data-foundation/resources/qa-checklist.md +83 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/clean_occurrences.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_ebird.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_inat.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_iucn.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_obis.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.R +230 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.py +268 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.R +251 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.py +364 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.R +315 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.py +407 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.R +238 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.py +304 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.R +273 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.py +344 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.R +248 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.py +318 -0
- package/skills/ecological-impact-assessment/SKILL.md +123 -0
- package/skills/ecological-impact-assessment/examples/example-prompts.md +32 -0
- package/skills/ecological-impact-assessment/resources/baci-design-guide.md +55 -0
- package/skills/ecological-impact-assessment/resources/fragmentation-metrics-reference.md +86 -0
- package/skills/ecological-impact-assessment/resources/pressure-index-template.md +78 -0
- package/skills/ecological-impact-assessment/resources/study-design-guide.md +168 -0
- package/skills/ecological-impact-assessment/scripts/baci_analysis.R +161 -0
- package/skills/ecological-impact-assessment/scripts/fragmentation_analysis.py +141 -0
- package/skills/ecological-impact-assessment/scripts/power_analysis_baci.R +274 -0
- package/skills/ecosystem-services-assessment/SKILL.md +125 -0
- package/skills/ecosystem-services-assessment/examples/example-prompts.md +24 -0
- package/skills/ecosystem-services-assessment/resources/es-indicator-reference.md +45 -0
- package/skills/ecosystem-services-assessment/resources/invest-parameter-guide.md +86 -0
- package/skills/ecosystem-services-assessment/resources/rusle-coefficients.md +88 -0
- package/skills/ecosystem-services-assessment/scripts/__pycache__/compute_es.cpython-311.pyc +0 -0
- package/skills/ecosystem-services-assessment/scripts/compute_es.py +189 -0
- package/skills/ecosystem-services-assessment/scripts/tradeoff_analysis.R +161 -0
- package/skills/environmental-time-series/SKILL.md +125 -0
- package/skills/environmental-time-series/examples/example-prompts.md +33 -0
- package/skills/environmental-time-series/resources/anomaly-indices-reference.md +88 -0
- package/skills/environmental-time-series/resources/bfast-parameter-guide.md +69 -0
- package/skills/environmental-time-series/scripts/__pycache__/recovery_trajectory.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/__pycache__/trend_analysis.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.R +305 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.py +178 -0
- package/skills/environmental-time-series/scripts/trend_analysis.R +192 -0
- package/skills/environmental-time-series/scripts/trend_analysis.py +184 -0
- package/skills/geoprocessing-for-ecology/SKILL.md +123 -0
- package/skills/geoprocessing-for-ecology/examples/example-prompts.md +32 -0
- package/skills/geoprocessing-for-ecology/resources/crs-reference.md +62 -0
- package/skills/geoprocessing-for-ecology/resources/global-predictor-sources.md +331 -0
- package/skills/geoprocessing-for-ecology/resources/resampling-methods.md +57 -0
- package/skills/geoprocessing-for-ecology/scripts/__pycache__/download_predictors.cpython-311.pyc +0 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.R +239 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.py +379 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.R +224 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.py +172 -0
- package/skills/landscape-connectivity/SKILL.md +170 -0
- package/skills/landscape-connectivity/examples/example-prompts.md +96 -0
- package/skills/landscape-connectivity/examples/jaguar_mesoamerica_corridor_example.md +271 -0
- package/skills/landscape-connectivity/resources/circuitscape-parameter-guide.md +155 -0
- package/skills/landscape-connectivity/resources/graph-theory-for-ecology.md +134 -0
- package/skills/landscape-connectivity/resources/resistance-surface-guide.md +141 -0
- package/skills/landscape-connectivity/scripts/connectivity_analysis.py +387 -0
- package/skills/landscape-connectivity/scripts/connectivity_metrics.R +274 -0
- package/skills/landscape-connectivity/scripts/resistance_surface.R +239 -0
- package/skills/model-validation-and-uncertainty/SKILL.md +131 -0
- package/skills/model-validation-and-uncertainty/examples/example-prompts.md +30 -0
- package/skills/model-validation-and-uncertainty/resources/extrapolation-risk-guide.md +236 -0
- package/skills/model-validation-and-uncertainty/resources/metric-selection-guide.md +52 -0
- package/skills/model-validation-and-uncertainty/resources/threshold-selection-guide.md +64 -0
- package/skills/model-validation-and-uncertainty/scripts/__pycache__/validate_model.cpython-311.pyc +0 -0
- package/skills/model-validation-and-uncertainty/scripts/extrapolation_risk.R +315 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_model.py +226 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_sdm.R +162 -0
- package/skills/occupancy-and-detection/SKILL.md +126 -0
- package/skills/occupancy-and-detection/examples/example-prompts.md +33 -0
- package/skills/occupancy-and-detection/resources/detection-history-format.md +100 -0
- package/skills/occupancy-and-detection/resources/occupancy-study-design.md +47 -0
- package/skills/occupancy-and-detection/scripts/__pycache__/occupancy_analysis.cpython-311.pyc +0 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.R +160 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.py +159 -0
- package/skills/population-viability-analysis/SKILL.md +161 -0
- package/skills/population-viability-analysis/examples/african_elephant_pva_example.md +266 -0
- package/skills/population-viability-analysis/examples/example-prompts.md +95 -0
- package/skills/population-viability-analysis/resources/extinction-risk-thresholds.md +128 -0
- package/skills/population-viability-analysis/resources/matrix-model-guide.md +139 -0
- package/skills/population-viability-analysis/resources/sensitivity-elasticity-reference.md +182 -0
- package/skills/population-viability-analysis/scripts/matrix_pva.R +258 -0
- package/skills/population-viability-analysis/scripts/pva_analysis.py +442 -0
- package/skills/population-viability-analysis/scripts/stochastic_pva.R +353 -0
- package/skills/predictive-modeling-best-practices/SKILL.md +136 -0
- package/skills/predictive-modeling-best-practices/examples/example-prompts.md +58 -0
- package/skills/predictive-modeling-best-practices/resources/collinearity-decision-tree.md +65 -0
- package/skills/predictive-modeling-best-practices/resources/sampling-bias-correction.md +267 -0
- package/skills/predictive-modeling-best-practices/resources/spatial-cv-guide.md +73 -0
- package/skills/predictive-modeling-best-practices/scripts/__pycache__/spatial_cv.cpython-311.pyc +0 -0
- package/skills/predictive-modeling-best-practices/scripts/collinearity_check.R +112 -0
- package/skills/predictive-modeling-best-practices/scripts/spatial_cv.py +182 -0
- package/skills/reproducible-ecology-pipeline/SKILL.md +139 -0
- package/skills/reproducible-ecology-pipeline/examples/example-prompts.md +35 -0
- package/skills/reproducible-ecology-pipeline/resources/directory-structure-template.md +94 -0
- package/skills/reproducible-ecology-pipeline/resources/params-yaml-template.yaml +84 -0
- package/skills/reproducible-ecology-pipeline/resources/reproducibility-checklist-template.md +66 -0
- package/skills/reproducible-ecology-pipeline/scripts/generate_file_manifest.py +110 -0
- package/skills/reproducible-ecology-pipeline/scripts/init_project.sh +53 -0
- package/skills/spatial-prioritization/SKILL.md +162 -0
- package/skills/spatial-prioritization/examples/biodiversity_hotspot_prioritization_example.md +289 -0
- package/skills/spatial-prioritization/examples/example-prompts.md +93 -0
- package/skills/spatial-prioritization/resources/cost-surface-reference.md +130 -0
- package/skills/spatial-prioritization/resources/marxan-vs-prioritizr-comparison.md +125 -0
- package/skills/spatial-prioritization/resources/prioritizr-formulation-guide.md +188 -0
- package/skills/spatial-prioritization/resources/representation-targets-guide.md +186 -0
- package/skills/spatial-prioritization/scripts/prioritization_sensitivity.R +320 -0
- package/skills/spatial-prioritization/scripts/run_prioritization.R +336 -0
- package/skills/species-distribution-modeling/SKILL.md +139 -0
- package/skills/species-distribution-modeling/examples/example-prompts.md +36 -0
- package/skills/species-distribution-modeling/resources/algorithm-comparison.md +25 -0
- package/skills/species-distribution-modeling/resources/calibration-area-guide.md +71 -0
- package/skills/species-distribution-modeling/resources/climate-scenario-preparation.md +170 -0
- package/skills/species-distribution-modeling/resources/maxent-calibration-guide.md +211 -0
- package/skills/species-distribution-modeling/resources/sdm-checklist.md +37 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.R +236 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.py +286 -0
- package/skills/species-distribution-modeling/scripts/prepare_future_layers.R +351 -0
- package/skills/species-distribution-modeling/scripts/project_scenarios.R +220 -0
- package/skills/species-distribution-modeling/scripts/run_ensemble_sdm.R +99 -0
- package/skills/species-distribution-modeling/scripts/sdm_pipeline.py +318 -0
- package/skills/species-distribution-modeling/scripts/tune_maxnet.R +344 -0
- package/templates/SKILL_TEMPLATE.md +225 -0
- package/templates/checklists/data-submission-checklist.md +38 -0
- package/templates/checklists/post-analysis-checklist.md +55 -0
- package/templates/checklists/pre-analysis-checklist.md +31 -0
- package/templates/prompts/debug-skill.md +47 -0
- package/templates/prompts/invoke-skill.md +34 -0
- package/templates/prompts/invoke-workflow.md +45 -0
- package/templates/reports/technical-report-template.md +80 -0
- package/templates/scripts/logger_setup.R +79 -0
- package/templates/scripts/logger_setup.py +119 -0
- package/templates/scripts/params_loader.R +28 -0
- package/templates/scripts/params_loader.py +38 -0
- package/workflows/analyze-community-structure/WORKFLOW.md +72 -0
- package/workflows/analyze-environmental-change/WORKFLOW.md +73 -0
- package/workflows/assess-ecological-impact/WORKFLOW.md +75 -0
- package/workflows/assess-ecosystem-services/WORKFLOW.md +68 -0
- package/workflows/assess-landscape-connectivity/WORKFLOW.md +84 -0
- package/workflows/build-fire-risk-map/WORKFLOW.md +79 -0
- package/workflows/produce-technical-report/WORKFLOW.md +113 -0
- package/workflows/run-camera-trap-occupancy/WORKFLOW.md +87 -0
- package/workflows/run-conservation-prioritization/WORKFLOW.md +89 -0
- package/workflows/run-multispecies-screening/WORKFLOW.md +197 -0
- package/workflows/run-occupancy-analysis/WORKFLOW.md +74 -0
- package/workflows/run-population-viability/WORKFLOW.md +90 -0
- package/workflows/run-sdm-study/WORKFLOW.md +99 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript prioritization_sensitivity.R <pu_raster> <features_dir>
|
|
5
|
+
# <output_dir> [targets] [locked_in_raster] [locked_out_raster]
|
|
6
|
+
#
|
|
7
|
+
# Sensitivity analysis for conservation prioritization:
|
|
8
|
+
# 1. BLM calibration (cost vs compactness tradeoff)
|
|
9
|
+
# 2. Target sensitivity (50%, 75%, 100%, 125%, 150% of baseline targets)
|
|
10
|
+
# 3. Cost scenario sensitivity (-30%, baseline, +30%)
|
|
11
|
+
# 4. Portfolio irreplaceability (selection frequency across scenarios)
|
|
12
|
+
#
|
|
13
|
+
# Outputs:
|
|
14
|
+
# blm_calibration.csv — Cost and boundary at each BLM value
|
|
15
|
+
# blm_calibration_plot.png — Elbow plot for BLM selection
|
|
16
|
+
# target_sensitivity.csv — Targets met and cost at each target scaling
|
|
17
|
+
# cost_scenario_sensitivity.csv — Cost and PU selection under cost uncertainty
|
|
18
|
+
# portfolio_frequency.tif — Selection frequency raster across all scenarios
|
|
19
|
+
|
|
20
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
21
|
+
SKILL_NAME <- "spatial-prioritization"
|
|
22
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
23
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
24
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
25
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
26
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
27
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
28
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
29
|
+
|
|
30
|
+
suppressPackageStartupMessages(library(prioritizr))
|
|
31
|
+
suppressPackageStartupMessages(library(terra))
|
|
32
|
+
suppressPackageStartupMessages(library(dplyr))
|
|
33
|
+
suppressPackageStartupMessages(library(ggplot2))
|
|
34
|
+
|
|
35
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
36
|
+
if (length(args) < 3) {
|
|
37
|
+
cat("Usage: Rscript prioritization_sensitivity.R <pu_raster> <features_dir>",
|
|
38
|
+
"<output_dir> [targets] [locked_in] [locked_out]\n")
|
|
39
|
+
quit(status = 1)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pu_path <- args[1]
|
|
43
|
+
features_dir <- args[2]
|
|
44
|
+
output_dir <- args[3]
|
|
45
|
+
targets_arg <- if (length(args) >= 4 && args[4] != "NA") args[4] else "0.30"
|
|
46
|
+
locked_in_p <- if (length(args) >= 5 && args[5] != "NA") args[5] else NULL
|
|
47
|
+
locked_out_p <- if (length(args) >= 6 && args[6] != "NA") args[6] else NULL
|
|
48
|
+
|
|
49
|
+
# ── Input precondition checks ─────────────────────────────────────────────────
|
|
50
|
+
if (!file.exists(pu_path)) {
|
|
51
|
+
log_error("Input nao encontrado: %s\nCausa provavel: passo anterior nao concluiu.\nVerifique: outputs do skill anterior.\nSkill anterior: spatial-prioritization (run_prioritization)", pu_path)
|
|
52
|
+
stop("Missing input: ", pu_path)
|
|
53
|
+
}
|
|
54
|
+
if (!dir.exists(features_dir)) {
|
|
55
|
+
log_error("Diretorio de features nao encontrado: %s\nCausa provavel: passo anterior nao concluiu ou caminho incorreto.\nVerifique: outputs do skill anterior.\nSkill anterior: spatial-prioritization (run_prioritization)", features_dir)
|
|
56
|
+
stop("Missing features directory: ", features_dir)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
log_decision("targets_arg", targets_arg, "Baseline targets: single proportion applied to all features, or path to CSV with per-feature targets")
|
|
60
|
+
log_decision("locked_in_p", ifelse(is.null(locked_in_p), "none", locked_in_p), "Locked-in raster constrains solver to always select these PUs")
|
|
61
|
+
log_decision("locked_out_p", ifelse(is.null(locked_out_p), "none", locked_out_p), "Locked-out raster constrains solver to never select these PUs")
|
|
62
|
+
|
|
63
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
64
|
+
|
|
65
|
+
# ── Load data ─────────────────────────────────────────────────────────────────
|
|
66
|
+
log_step(1, "Load planning unit raster and feature layers")
|
|
67
|
+
tryCatch({
|
|
68
|
+
pu <- rast(pu_path)
|
|
69
|
+
feat_files <- list.files(features_dir, pattern = "\\.tif$",
|
|
70
|
+
full.names = TRUE, ignore.case = TRUE)
|
|
71
|
+
if (length(feat_files) == 0) {
|
|
72
|
+
log_error("Nenhum arquivo .tif encontrado em: %s\nCausa provavel: features_dir incorreto ou features nao geradas.\nVerifique: conteudo do diretorio de features.\nSkill anterior: spatial-prioritization (run_prioritization)", features_dir)
|
|
73
|
+
stop("No .tif feature files found in: ", features_dir)
|
|
74
|
+
}
|
|
75
|
+
features <- rast(feat_files)
|
|
76
|
+
features <- resample(features, pu, method = "bilinear")
|
|
77
|
+
names(features) <- tools::file_path_sans_ext(basename(feat_files))
|
|
78
|
+
|
|
79
|
+
# Remove zero-sum features
|
|
80
|
+
feat_sums <- global(features, "sum", na.rm = TRUE)[[1]]
|
|
81
|
+
zero_feats <- names(features)[feat_sums == 0]
|
|
82
|
+
if (length(zero_feats) > 0) {
|
|
83
|
+
log_warn("%d features com soma zero excluidas: %s", length(zero_feats), paste(zero_feats, collapse = ", "))
|
|
84
|
+
}
|
|
85
|
+
features <- features[[feat_sums > 0]]
|
|
86
|
+
n_feats <- nlyr(features)
|
|
87
|
+
log_info("Planning unit raster loaded: %d x %d cells.", nrow(pu), ncol(pu))
|
|
88
|
+
log_info("Feature layers loaded: %d (after removing zero-sum).", n_feats)
|
|
89
|
+
|
|
90
|
+
if (n_feats < 1) {
|
|
91
|
+
log_error("Nenhuma feature valida apos remocao de zero-sum.\nCausa provavel: todas as features tem distribuicao zero na area de estudo.\nVerifique: extensao espacial dos rasters de features.\nSkill anterior: spatial-prioritization (run_prioritization)")
|
|
92
|
+
stop("No valid feature layers remaining after zero-sum removal.")
|
|
93
|
+
}
|
|
94
|
+
}, error = function(e) {
|
|
95
|
+
log_error("Falha em load_data: %s\nCausa provavel: rasters corrompidos, incompativeis ou caminho incorreto.\nVerifique: arquivos .tif e resolucao espacial.\nSkill anterior: spatial-prioritization (run_prioritization)", conditionMessage(e))
|
|
96
|
+
stop(e)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
# Baseline targets
|
|
100
|
+
log_step(2, "Set baseline targets")
|
|
101
|
+
tryCatch({
|
|
102
|
+
if (file.exists(targets_arg)) {
|
|
103
|
+
target_df <- read.csv(targets_arg)
|
|
104
|
+
targets_base <- target_df$target[match(names(features), target_df$feature_name)]
|
|
105
|
+
n_unmatched <- sum(is.na(targets_base))
|
|
106
|
+
targets_base[is.na(targets_base)] <- 0.30
|
|
107
|
+
if (n_unmatched > 0) {
|
|
108
|
+
log_warn("%d features sem alvo no CSV; usando padrao 0.30.", n_unmatched)
|
|
109
|
+
}
|
|
110
|
+
log_decision("targets_base", "from_csv", paste0("Per-feature targets loaded from ", targets_arg))
|
|
111
|
+
} else {
|
|
112
|
+
targets_base <- rep(as.numeric(targets_arg), n_feats)
|
|
113
|
+
log_decision("targets_base", targets_arg, "Single proportion applied uniformly to all features")
|
|
114
|
+
}
|
|
115
|
+
log_info("Baseline targets: min=%.2f, mean=%.2f, max=%.2f",
|
|
116
|
+
min(targets_base), mean(targets_base), max(targets_base))
|
|
117
|
+
}, error = function(e) {
|
|
118
|
+
log_error("Falha em set_targets: %s\nCausa provavel: CSV de alvos malformado ou proporcao invalida.\nVerifique: formato do arquivo de alvos e nomes das features.\nSkill anterior: spatial-prioritization (run_prioritization)", conditionMessage(e))
|
|
119
|
+
stop(e)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
# Helper: build and solve base problem with given cost and targets
|
|
123
|
+
solve_scenario <- function(cost_r, targets_v, blm_val = 0, name = "scenario") {
|
|
124
|
+
p <- problem(cost_r, features) %>%
|
|
125
|
+
add_min_set_objective() %>%
|
|
126
|
+
add_relative_targets(pmin(targets_v, 0.999)) %>%
|
|
127
|
+
add_binary_decisions() %>%
|
|
128
|
+
add_highs_solver(gap = 0.05, time_limit = 300, verbose = FALSE)
|
|
129
|
+
|
|
130
|
+
if (!is.null(locked_in_p)) {
|
|
131
|
+
li <- resample(rast(locked_in_p), cost_r, method = "near")
|
|
132
|
+
p <- p %>% add_locked_in_constraints(li)
|
|
133
|
+
}
|
|
134
|
+
if (!is.null(locked_out_p)) {
|
|
135
|
+
lo <- resample(rast(locked_out_p), cost_r, method = "near")
|
|
136
|
+
p <- p %>% add_locked_out_constraints(lo)
|
|
137
|
+
}
|
|
138
|
+
if (blm_val > 0) {
|
|
139
|
+
p <- p %>% add_boundary_penalties(penalty = blm_val, data = NULL)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
s <- tryCatch(solve(p), error = function(e) {
|
|
143
|
+
log_warn("Solver falhou para cenario '%s': %s. Retornando NULL.", name, conditionMessage(e))
|
|
144
|
+
return(NULL)
|
|
145
|
+
})
|
|
146
|
+
if (is.null(s)) return(NULL)
|
|
147
|
+
|
|
148
|
+
rep_s <- eval_feature_representation_summary(p, s)
|
|
149
|
+
cost_s <- eval_cost_summary(p, s)$cost
|
|
150
|
+
bound_s <- tryCatch(eval_boundary_summary(p, s)$boundary,
|
|
151
|
+
error = function(e) NA_real_)
|
|
152
|
+
|
|
153
|
+
list(
|
|
154
|
+
solution = s,
|
|
155
|
+
n_selected = sum(values(s) == 1, na.rm = TRUE),
|
|
156
|
+
total_cost = cost_s,
|
|
157
|
+
boundary = bound_s,
|
|
158
|
+
targets_met = sum(rep_s$relative_held >= targets_v, na.rm = TRUE),
|
|
159
|
+
mean_held = mean(rep_s$relative_held, na.rm = TRUE)
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# ── 1. BLM Calibration ────────────────────────────────────────────────────────
|
|
164
|
+
log_step(3, "BLM calibration: cost vs compactness tradeoff")
|
|
165
|
+
log_info("Running BLM calibration...")
|
|
166
|
+
blm_values <- c(0, 0.001, 0.01, 0.05, 0.1, 0.5, 1.0)
|
|
167
|
+
log_decision("blm_values", paste(blm_values, collapse = ", "),
|
|
168
|
+
"Standard BLM range spanning several orders of magnitude to identify elbow in cost-boundary tradeoff")
|
|
169
|
+
|
|
170
|
+
tryCatch({
|
|
171
|
+
blm_results <- lapply(blm_values, function(blm) {
|
|
172
|
+
log_info(" BLM = %g", blm)
|
|
173
|
+
res <- solve_scenario(pu, targets_base, blm_val = blm,
|
|
174
|
+
name = paste0("blm_", blm))
|
|
175
|
+
if (is.null(res)) {
|
|
176
|
+
log_warn("Cenario BLM=%g nao produziu solucao.", blm)
|
|
177
|
+
return(NULL)
|
|
178
|
+
}
|
|
179
|
+
data.frame(blm = blm, cost = res$total_cost,
|
|
180
|
+
boundary = res$boundary, n_selected = res$n_selected)
|
|
181
|
+
})
|
|
182
|
+
blm_df <- dplyr::bind_rows(Filter(Negate(is.null), blm_results))
|
|
183
|
+
write.csv(blm_df, file.path(output_dir, "blm_calibration.csv"), row.names = FALSE)
|
|
184
|
+
log_info("BLM calibration done. %d / %d scenarios solved successfully.", nrow(blm_df), length(blm_values))
|
|
185
|
+
|
|
186
|
+
if (nrow(blm_df) < 3) {
|
|
187
|
+
log_warn("Menos de 3 cenarios BLM resolvidos. Grafico de cotovelo pode ser insuficiente para selecao de BLM.")
|
|
188
|
+
}
|
|
189
|
+
}, error = function(e) {
|
|
190
|
+
log_error("Falha em blm_calibration: %s\nCausa provavel: falha do solver HiGHS ou dados raster invalidos.\nVerifique: instalacao do HiGHS e integridade dos rasters.\nSkill anterior: spatial-prioritization (run_prioritization)", conditionMessage(e))
|
|
191
|
+
stop(e)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
# BLM elbow plot
|
|
195
|
+
log_step(4, "Generate BLM elbow plot")
|
|
196
|
+
if (nrow(blm_df) > 2) {
|
|
197
|
+
tryCatch({
|
|
198
|
+
p_blm <- ggplot(blm_df, aes(x = boundary, y = cost, label = blm)) +
|
|
199
|
+
geom_path(colour = "steelblue") +
|
|
200
|
+
geom_point(size = 3, colour = "steelblue") +
|
|
201
|
+
ggrepel::geom_text_repel(size = 3) +
|
|
202
|
+
labs(x = "Total boundary length", y = "Total cost",
|
|
203
|
+
title = "BLM calibration: cost vs compactness tradeoff") +
|
|
204
|
+
theme_minimal(base_size = 10)
|
|
205
|
+
tryCatch(
|
|
206
|
+
ggsave(file.path(output_dir, "blm_calibration_plot.png"), p_blm,
|
|
207
|
+
width = 7, height = 5, dpi = 150),
|
|
208
|
+
error = function(e) log_warn("BLM plot falhou ao salvar: %s", conditionMessage(e))
|
|
209
|
+
)
|
|
210
|
+
log_info("BLM calibration plot saved.")
|
|
211
|
+
}, error = function(e) {
|
|
212
|
+
log_warn("Falha ao gerar grafico BLM: %s. Continuando sem o grafico.", conditionMessage(e))
|
|
213
|
+
})
|
|
214
|
+
} else {
|
|
215
|
+
log_warn("Dados insuficientes para grafico BLM (menos de 3 pontos). Grafico nao gerado.")
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# ── 2. Target Sensitivity ─────────────────────────────────────────────────────
|
|
219
|
+
log_step(5, "Target sensitivity analysis")
|
|
220
|
+
log_info("Running target sensitivity analysis...")
|
|
221
|
+
target_scalings <- c(0.50, 0.75, 1.00, 1.25, 1.50)
|
|
222
|
+
log_decision("target_scalings", paste(target_scalings, collapse = ", "),
|
|
223
|
+
"Scaling factors applied to baseline targets to assess sensitivity of solution cost and coverage")
|
|
224
|
+
|
|
225
|
+
tryCatch({
|
|
226
|
+
target_results <- lapply(target_scalings, function(sc) {
|
|
227
|
+
log_info(" Target scaling = %.2fx", sc)
|
|
228
|
+
tgts <- pmin(targets_base * sc, 0.999)
|
|
229
|
+
res <- solve_scenario(pu, tgts, name = paste0("target_", sc))
|
|
230
|
+
if (is.null(res)) {
|
|
231
|
+
log_warn("Cenario de alvo %.2fx nao produziu solucao.", sc)
|
|
232
|
+
return(NULL)
|
|
233
|
+
}
|
|
234
|
+
data.frame(target_scaling = sc,
|
|
235
|
+
mean_target = mean(tgts),
|
|
236
|
+
cost = res$total_cost,
|
|
237
|
+
targets_met = res$targets_met,
|
|
238
|
+
n_selected = res$n_selected)
|
|
239
|
+
})
|
|
240
|
+
target_df <- dplyr::bind_rows(Filter(Negate(is.null), target_results))
|
|
241
|
+
write.csv(target_df, file.path(output_dir, "target_sensitivity.csv"),
|
|
242
|
+
row.names = FALSE)
|
|
243
|
+
log_info("Target sensitivity done. %d / %d scenarios solved.", nrow(target_df), length(target_scalings))
|
|
244
|
+
}, error = function(e) {
|
|
245
|
+
log_error("Falha em target_sensitivity: %s\nCausa provavel: falha do solver ou alvos fora do intervalo [0, 0.999].\nVerifique: valores de targets_base e instalacao do HiGHS.\nSkill anterior: spatial-prioritization (run_prioritization)", conditionMessage(e))
|
|
246
|
+
stop(e)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
# ── 3. Cost Scenario Sensitivity ──────────────────────────────────────────────
|
|
250
|
+
log_step(6, "Cost scenario sensitivity analysis")
|
|
251
|
+
log_info("Running cost scenario sensitivity analysis...")
|
|
252
|
+
log_decision("cost_scenarios", "low=-30%, baseline, high=+30%",
|
|
253
|
+
"Standard cost uncertainty range to assess robustness of prioritization to cost data errors")
|
|
254
|
+
|
|
255
|
+
tryCatch({
|
|
256
|
+
cost_scenarios <- list(
|
|
257
|
+
low = pu * 0.70,
|
|
258
|
+
baseline = pu,
|
|
259
|
+
high = pu * 1.30
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
cost_results <- lapply(names(cost_scenarios), function(name) {
|
|
263
|
+
log_info(" Cost scenario: %s", name)
|
|
264
|
+
res <- solve_scenario(cost_scenarios[[name]], targets_base, name = name)
|
|
265
|
+
if (is.null(res)) {
|
|
266
|
+
log_warn("Cenario de custo '%s' nao produziu solucao.", name)
|
|
267
|
+
return(NULL)
|
|
268
|
+
}
|
|
269
|
+
data.frame(cost_scenario = name,
|
|
270
|
+
total_cost = res$total_cost,
|
|
271
|
+
n_selected = res$n_selected,
|
|
272
|
+
targets_met = res$targets_met)
|
|
273
|
+
})
|
|
274
|
+
cost_df <- dplyr::bind_rows(Filter(Negate(is.null), cost_results))
|
|
275
|
+
write.csv(cost_df, file.path(output_dir, "cost_scenario_sensitivity.csv"),
|
|
276
|
+
row.names = FALSE)
|
|
277
|
+
log_info("Cost scenario sensitivity done. %d / %d scenarios solved.", nrow(cost_df), length(cost_scenarios))
|
|
278
|
+
}, error = function(e) {
|
|
279
|
+
log_error("Falha em cost_scenario_sensitivity: %s\nCausa provavel: falha do solver ou raster de custo invalido.\nVerifique: valores do raster pu e instalacao do HiGHS.\nSkill anterior: spatial-prioritization (run_prioritization)", conditionMessage(e))
|
|
280
|
+
stop(e)
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
# ── 4. Portfolio Irreplaceability ─────────────────────────────────────────────
|
|
284
|
+
log_step(7, "Build portfolio irreplaceability (selection frequency across all scenarios)")
|
|
285
|
+
log_info("Building portfolio irreplaceability (selection frequency across scenarios)...")
|
|
286
|
+
|
|
287
|
+
tryCatch({
|
|
288
|
+
# Collect all solutions computed above
|
|
289
|
+
all_solutions <- list()
|
|
290
|
+
for (blm in blm_values) {
|
|
291
|
+
res <- solve_scenario(pu, targets_base, blm_val = blm, name = paste0("blm_", blm))
|
|
292
|
+
if (!is.null(res)) all_solutions[[length(all_solutions) + 1]] <- res$solution
|
|
293
|
+
}
|
|
294
|
+
for (sc in target_scalings) {
|
|
295
|
+
tgts <- pmin(targets_base * sc, 0.999)
|
|
296
|
+
res <- solve_scenario(pu, tgts, name = paste0("target_", sc))
|
|
297
|
+
if (!is.null(res)) all_solutions[[length(all_solutions) + 1]] <- res$solution
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (length(all_solutions) < 2) {
|
|
301
|
+
log_warn("Solucoes insuficientes para analise de portfolio (%d). Sao necessarias pelo menos 2.", length(all_solutions))
|
|
302
|
+
} else {
|
|
303
|
+
freq_raster <- Reduce("+", all_solutions) / length(all_solutions)
|
|
304
|
+
names(freq_raster) <- "selection_frequency"
|
|
305
|
+
writeRaster(freq_raster, file.path(output_dir, "portfolio_frequency.tif"),
|
|
306
|
+
overwrite = TRUE)
|
|
307
|
+
log_info("Portfolio frequency raster saved (%d scenarios).", length(all_solutions))
|
|
308
|
+
}
|
|
309
|
+
}, error = function(e) {
|
|
310
|
+
log_error("Falha em portfolio_irreplaceability: %s\nCausa provavel: solucoes incompativeis (extensoes diferentes) ou falha na soma de rasters.\nVerifique: consistencia espacial das solucoes individuais.\nSkill anterior: spatial-prioritization (run_prioritization)", conditionMessage(e))
|
|
311
|
+
stop(e)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
# ── Summary report ────────────────────────────────────────────────────────────
|
|
315
|
+
log_step(8, "Print sensitivity analysis summary")
|
|
316
|
+
log_info("=== Sensitivity Analysis Summary ===")
|
|
317
|
+
log_info("BLM calibration:\n%s", paste(capture.output(print(blm_df)), collapse = "\n"))
|
|
318
|
+
log_info("Target sensitivity:\n%s", paste(capture.output(print(target_df)), collapse = "\n"))
|
|
319
|
+
log_info("Cost scenario sensitivity:\n%s", paste(capture.output(print(cost_df)), collapse = "\n"))
|
|
320
|
+
log_info("Sensitivity analysis complete.")
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript run_prioritization.R <pu_raster> <features_dir> <output_dir>
|
|
5
|
+
# [targets] [locked_in_raster] [locked_out_raster] [blm] [budget]
|
|
6
|
+
#
|
|
7
|
+
# Runs systematic conservation prioritization using prioritizr (minimum-set
|
|
8
|
+
# or maximum-coverage ILP problem solved with HiGHS solver).
|
|
9
|
+
#
|
|
10
|
+
# Arguments:
|
|
11
|
+
# pu_raster — Planning unit cost raster (.tif); NA = excluded
|
|
12
|
+
# features_dir — Directory of feature rasters (.tif, one per species/habitat)
|
|
13
|
+
# output_dir — Directory for output files
|
|
14
|
+
# targets — Single value (applied to all features) or path to CSV
|
|
15
|
+
# with columns feature_name, target (default: 0.30)
|
|
16
|
+
# locked_in_raster — Binary raster, 1 = must select (default: none)
|
|
17
|
+
# locked_out_raster — Binary raster, 1 = must exclude (default: none)
|
|
18
|
+
# blm — Boundary length modifier (default: 0)
|
|
19
|
+
# budget — For maximum coverage problem; if provided, switches
|
|
20
|
+
# objective to max-features (default: NA = min-set)
|
|
21
|
+
#
|
|
22
|
+
# Outputs:
|
|
23
|
+
# solution.tif — Binary raster: 1 = selected PU
|
|
24
|
+
# feature_representation.csv — Amount of each feature in solution
|
|
25
|
+
# cost_summary.csv — Total cost and number of PUs selected
|
|
26
|
+
# irreplaceability.tif — Rarity-weighted importance of each PU
|
|
27
|
+
# prioritization_map.png — Visual output
|
|
28
|
+
|
|
29
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
30
|
+
SKILL_NAME <- "spatial-prioritization"
|
|
31
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
32
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
33
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
34
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
35
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
36
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
37
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
38
|
+
|
|
39
|
+
suppressPackageStartupMessages(library(prioritizr))
|
|
40
|
+
suppressPackageStartupMessages(library(terra))
|
|
41
|
+
suppressPackageStartupMessages(library(sf))
|
|
42
|
+
suppressPackageStartupMessages(library(dplyr))
|
|
43
|
+
suppressPackageStartupMessages(library(ggplot2))
|
|
44
|
+
|
|
45
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
46
|
+
if (length(args) < 3) {
|
|
47
|
+
cat("Usage: Rscript run_prioritization.R <pu_raster> <features_dir>",
|
|
48
|
+
"<output_dir> [targets] [locked_in] [locked_out] [blm] [budget]\n")
|
|
49
|
+
quit(status = 1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pu_path <- args[1]
|
|
53
|
+
features_dir <- args[2]
|
|
54
|
+
output_dir <- args[3]
|
|
55
|
+
targets_arg <- if (length(args) >= 4 && args[4] != "NA") args[4] else "0.30"
|
|
56
|
+
locked_in_p <- if (length(args) >= 5 && args[5] != "NA") args[5] else NULL
|
|
57
|
+
locked_out_p <- if (length(args) >= 6 && args[6] != "NA") args[6] else NULL
|
|
58
|
+
blm <- if (length(args) >= 7) as.numeric(args[7]) else 0
|
|
59
|
+
budget <- if (length(args) >= 8 && args[8] != "NA") as.numeric(args[8]) else NA_real_
|
|
60
|
+
|
|
61
|
+
# ── Input precondition checks ─────────────────────────────────────────────────
|
|
62
|
+
if (!file.exists(pu_path)) {
|
|
63
|
+
log_error("Input nao encontrado: %s\nCausa provavel: passo anterior nao concluiu.\nVerifique: outputs do skill anterior.\nSkill anterior: species-distribution-modeling", pu_path)
|
|
64
|
+
stop("Missing input: ", pu_path)
|
|
65
|
+
}
|
|
66
|
+
if (!dir.exists(features_dir)) {
|
|
67
|
+
log_error("Diretorio de features nao encontrado: %s\nCausa provavel: passo anterior nao concluiu ou caminho incorreto.\nVerifique: outputs do skill anterior.\nSkill anterior: species-distribution-modeling", features_dir)
|
|
68
|
+
stop("Missing features directory: ", features_dir)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
log_decision("targets_arg", targets_arg, "Conservation targets: single proportion for all features or path to per-feature CSV")
|
|
72
|
+
log_decision("blm", blm, "Boundary length modifier: 0 = no compactness penalty; increase to promote spatially compact solutions")
|
|
73
|
+
log_decision("budget", ifelse(is.na(budget), "NA (min-set)", budget), "Budget for max-coverage objective; NA triggers minimum-set formulation")
|
|
74
|
+
log_decision("locked_in_p", ifelse(is.null(locked_in_p), "none", locked_in_p), "Locked-in raster: PUs that must always be selected (e.g., existing protected areas)")
|
|
75
|
+
log_decision("locked_out_p", ifelse(is.null(locked_out_p), "none", locked_out_p), "Locked-out raster: PUs that must never be selected (e.g., urban areas)")
|
|
76
|
+
|
|
77
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
78
|
+
|
|
79
|
+
# ── Load planning units ───────────────────────────────────────────────────────
|
|
80
|
+
log_step(1, "Load planning unit raster")
|
|
81
|
+
tryCatch({
|
|
82
|
+
pu <- rast(pu_path)
|
|
83
|
+
log_info("Planning units: %d x %d cells, CRS: %s",
|
|
84
|
+
nrow(pu), ncol(pu), crs(pu, describe = TRUE)$name)
|
|
85
|
+
|
|
86
|
+
n_pu_total <- sum(!is.na(values(pu)))
|
|
87
|
+
log_info("Valid planning units: %d", n_pu_total)
|
|
88
|
+
|
|
89
|
+
if (n_pu_total < 10) {
|
|
90
|
+
log_warn("Numero muito baixo de unidades de planejamento validas (%d). Verifique a mascara de NA no raster pu.", n_pu_total)
|
|
91
|
+
}
|
|
92
|
+
}, error = function(e) {
|
|
93
|
+
log_error("Falha em load_planning_units: %s\nCausa provavel: raster corrompido, caminho incorreto ou CRS ausente.\nVerifique: arquivo pu_raster e sua integridade.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
94
|
+
stop(e)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
# ── Load features ─────────────────────────────────────────────────────────────
|
|
98
|
+
log_step(2, "Load feature rasters")
|
|
99
|
+
tryCatch({
|
|
100
|
+
feat_files <- list.files(features_dir, pattern = "\\.tif$",
|
|
101
|
+
full.names = TRUE, ignore.case = TRUE)
|
|
102
|
+
if (length(feat_files) == 0) {
|
|
103
|
+
log_error("Nenhum arquivo .tif encontrado em: %s\nCausa provavel: features nao geradas ou caminho incorreto.\nVerifique: conteudo do diretorio features_dir.\nSkill anterior: species-distribution-modeling", features_dir)
|
|
104
|
+
stop("No .tif feature files found in: ", features_dir)
|
|
105
|
+
}
|
|
106
|
+
log_info("Loading %d feature layers...", length(feat_files))
|
|
107
|
+
|
|
108
|
+
features <- rast(feat_files)
|
|
109
|
+
# Ensure same extent/resolution as planning units
|
|
110
|
+
features <- resample(features, pu, method = "bilinear")
|
|
111
|
+
names(features) <- tools::file_path_sans_ext(basename(feat_files))
|
|
112
|
+
|
|
113
|
+
# Check for zero-sum features
|
|
114
|
+
feat_sums <- global(features, "sum", na.rm = TRUE)[[1]]
|
|
115
|
+
zero_feats <- names(features)[feat_sums == 0]
|
|
116
|
+
if (length(zero_feats) > 0) {
|
|
117
|
+
log_warn("%d features com soma zero excluidas (nenhuma ocorrencia na area de estudo): %s",
|
|
118
|
+
length(zero_feats), paste(zero_feats, collapse = ", "))
|
|
119
|
+
features <- features[[!names(features) %in% zero_feats]]
|
|
120
|
+
}
|
|
121
|
+
n_feats <- nlyr(features)
|
|
122
|
+
log_info("Features loaded: %d (after removing zero-sum)", n_feats)
|
|
123
|
+
|
|
124
|
+
if (n_feats == 0) {
|
|
125
|
+
log_error("Nenhuma feature valida apos remocao de zero-sum.\nCausa provavel: features nao sobrepõem a area de planejamento.\nVerifique: extensao e CRS dos rasters de features vs pu_raster.\nSkill anterior: species-distribution-modeling")
|
|
126
|
+
stop("No valid features remaining after zero-sum removal.")
|
|
127
|
+
}
|
|
128
|
+
}, error = function(e) {
|
|
129
|
+
log_error("Falha em load_features: %s\nCausa provavel: rasters corrompidos, incompativeis ou diretorio vazio.\nVerifique: arquivos .tif em features_dir e compatibilidade com pu_raster.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
130
|
+
stop(e)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
# ── Set targets ────────────────────────────────────────────────────────────────
|
|
134
|
+
log_step(3, "Set conservation targets")
|
|
135
|
+
tryCatch({
|
|
136
|
+
if (file.exists(targets_arg)) {
|
|
137
|
+
target_df <- read.csv(targets_arg)
|
|
138
|
+
targets_vec <- target_df$target[match(names(features), target_df$feature_name)]
|
|
139
|
+
na_targets <- is.na(targets_vec)
|
|
140
|
+
targets_vec[na_targets] <- 0.30 # default for unmatched features
|
|
141
|
+
if (any(na_targets)) {
|
|
142
|
+
log_warn("Alvo nao encontrado para %d features; usando padrao 0.30: %s",
|
|
143
|
+
sum(na_targets), paste(names(features)[na_targets], collapse = ", "))
|
|
144
|
+
}
|
|
145
|
+
log_decision("targets_vec", "from_csv", paste0("Per-feature targets loaded from ", targets_arg))
|
|
146
|
+
} else {
|
|
147
|
+
targets_vec <- rep(as.numeric(targets_arg), n_feats)
|
|
148
|
+
log_decision("targets_vec", targets_arg, "Uniform proportion target applied to all features")
|
|
149
|
+
}
|
|
150
|
+
log_info("Targets: min=%.2f, mean=%.2f, max=%.2f",
|
|
151
|
+
min(targets_vec), mean(targets_vec), max(targets_vec))
|
|
152
|
+
|
|
153
|
+
if (any(targets_vec > 0.90)) {
|
|
154
|
+
log_warn("%d features com alvo > 90%%. Alvos muito altos podem tornar o problema inviavel.", sum(targets_vec > 0.90))
|
|
155
|
+
}
|
|
156
|
+
}, error = function(e) {
|
|
157
|
+
log_error("Falha em set_targets: %s\nCausa provavel: CSV de alvos malformado ou proporcao invalida fora de [0,1].\nVerifique: formato do arquivo de alvos e nomes das features.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
158
|
+
stop(e)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
# ── Build problem ─────────────────────────────────────────────────────────────
|
|
162
|
+
log_step(4, "Build prioritizr problem")
|
|
163
|
+
tryCatch({
|
|
164
|
+
if (is.na(budget)) {
|
|
165
|
+
# Minimum set problem
|
|
166
|
+
p <- problem(pu, features) %>%
|
|
167
|
+
add_min_set_objective() %>%
|
|
168
|
+
add_relative_targets(targets_vec)
|
|
169
|
+
log_info("Objective: minimum cost (min-set)")
|
|
170
|
+
log_decision("objective", "min_set", "No budget specified; minimize cost while meeting all targets")
|
|
171
|
+
} else {
|
|
172
|
+
# Maximum coverage problem
|
|
173
|
+
p <- problem(pu, features) %>%
|
|
174
|
+
add_max_features_objective(budget = budget) %>%
|
|
175
|
+
add_absolute_targets(1e-6) # minimum feasibility constraint
|
|
176
|
+
log_info("Objective: maximum coverage (budget = %g)", budget)
|
|
177
|
+
log_decision("objective", "max_features", paste0("Budget ", budget, " specified; maximize feature coverage within budget"))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
p <- p %>%
|
|
181
|
+
add_binary_decisions()
|
|
182
|
+
|
|
183
|
+
# Locked-in
|
|
184
|
+
if (!is.null(locked_in_p)) {
|
|
185
|
+
li <- rast(locked_in_p)
|
|
186
|
+
li <- resample(li, pu, method = "near")
|
|
187
|
+
p <- p %>% add_locked_in_constraints(li)
|
|
188
|
+
n_li <- sum(values(li) == 1, na.rm = TRUE)
|
|
189
|
+
log_info("Locked-in: %d PUs", n_li)
|
|
190
|
+
if (n_li == 0) {
|
|
191
|
+
log_warn("Raster locked-in fornecido mas nenhuma PU com valor 1 encontrada.")
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Locked-out
|
|
196
|
+
if (!is.null(locked_out_p)) {
|
|
197
|
+
lo <- rast(locked_out_p)
|
|
198
|
+
lo <- resample(lo, pu, method = "near")
|
|
199
|
+
p <- p %>% add_locked_out_constraints(lo)
|
|
200
|
+
n_lo <- sum(values(lo) == 1, na.rm = TRUE)
|
|
201
|
+
log_info("Locked-out: %d PUs", n_lo)
|
|
202
|
+
if (n_lo == 0) {
|
|
203
|
+
log_warn("Raster locked-out fornecido mas nenhuma PU com valor 1 encontrada.")
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Boundary penalty
|
|
208
|
+
if (blm > 0) {
|
|
209
|
+
p <- p %>% add_boundary_penalties(penalty = blm, data = NULL)
|
|
210
|
+
log_info("Boundary length modifier applied: %g", blm)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Solver (HiGHS preferred)
|
|
214
|
+
p <- p %>%
|
|
215
|
+
add_highs_solver(gap = 0.01, time_limit = 600, verbose = TRUE)
|
|
216
|
+
|
|
217
|
+
log_info("Problem built successfully.")
|
|
218
|
+
}, error = function(e) {
|
|
219
|
+
log_error("Falha em build_problem: %s\nCausa provavel: incompatibilidade entre rasters, alvos invalidos ou pacote prioritizr desatualizado.\nVerifique: versoes de prioritizr e terra, e compatibilidade espacial dos rasters.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
220
|
+
stop(e)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
# ── Solve ─────────────────────────────────────────────────────────────────────
|
|
224
|
+
log_step(5, "Solve prioritization problem with HiGHS solver")
|
|
225
|
+
log_info("Solving...")
|
|
226
|
+
tryCatch({
|
|
227
|
+
s <- tryCatch(
|
|
228
|
+
solve(p),
|
|
229
|
+
error = function(e) {
|
|
230
|
+
log_warn("HiGHS falhou: %s. Tentando fallback para lpsymphony...", conditionMessage(e))
|
|
231
|
+
p2 <- p %>% add_lpsymphony_solver(gap = 0.05, time_limit = 600)
|
|
232
|
+
solve(p2)
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
log_info("Solver completed successfully.")
|
|
236
|
+
}, error = function(e) {
|
|
237
|
+
log_error("Falha em solve_problem: %s\nCausa provavel: problema inviavel (alvos inalcancaveis), solver nao instalado, ou timeout.\nVerifique: alvos vs disponibilidade de features, e instalacao do HiGHS/lpsymphony.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
238
|
+
stop(e)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
# ── Write solution raster ─────────────────────────────────────────────────────
|
|
242
|
+
log_step(6, "Write solution raster and compute selection summary")
|
|
243
|
+
tryCatch({
|
|
244
|
+
sol_path <- file.path(output_dir, "solution.tif")
|
|
245
|
+
writeRaster(s, sol_path, overwrite = TRUE)
|
|
246
|
+
n_selected <- sum(values(s) == 1, na.rm = TRUE)
|
|
247
|
+
log_info("Solution: %d PUs selected (%.1f%% of valid PUs)",
|
|
248
|
+
n_selected, n_selected / n_pu_total * 100)
|
|
249
|
+
|
|
250
|
+
if (n_selected == 0) {
|
|
251
|
+
log_warn("Nenhuma PU selecionada na solucao. Verifique viabilidade do problema e restricoes locked-out.")
|
|
252
|
+
}
|
|
253
|
+
if (n_selected / n_pu_total > 0.80) {
|
|
254
|
+
log_warn("Mais de 80%% das PUs selecionadas (%.1f%%). Alvos podem ser muito altos ou area de estudo muito restrita.", n_selected / n_pu_total * 100)
|
|
255
|
+
}
|
|
256
|
+
}, error = function(e) {
|
|
257
|
+
log_error("Falha em write_solution: %s\nCausa provavel: permissoes de escrita ou solucao invalida.\nVerifique: output_dir e resultado do solver.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
258
|
+
stop(e)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
# ── Feature representation ────────────────────────────────────────────────────
|
|
262
|
+
log_step(7, "Evaluate feature representation in solution")
|
|
263
|
+
tryCatch({
|
|
264
|
+
rep_df <- eval_feature_representation_summary(p, s)
|
|
265
|
+
rep_df$target <- targets_vec
|
|
266
|
+
rep_df$target_met <- rep_df$relative_held >= targets_vec
|
|
267
|
+
|
|
268
|
+
write.csv(rep_df, file.path(output_dir, "feature_representation.csv"),
|
|
269
|
+
row.names = FALSE)
|
|
270
|
+
n_targets_met <- sum(rep_df$target_met, na.rm = TRUE)
|
|
271
|
+
log_info("Targets met: %d / %d features", n_targets_met, n_feats)
|
|
272
|
+
|
|
273
|
+
if (n_targets_met < n_feats) {
|
|
274
|
+
log_warn("%d features nao atingiram seus alvos de representacao. Verifique viabilidade e restricoes.", n_feats - n_targets_met)
|
|
275
|
+
}
|
|
276
|
+
}, error = function(e) {
|
|
277
|
+
log_error("Falha em feature_representation: %s\nCausa provavel: incompatibilidade entre problema e solucao, ou falha na avaliacao.\nVerifique: objetos p e s e versao do prioritizr.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
278
|
+
stop(e)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
# ── Cost summary ──────────────────────────────────────────────────────────────
|
|
282
|
+
log_step(8, "Compute and write cost summary")
|
|
283
|
+
tryCatch({
|
|
284
|
+
cost_df <- data.frame(
|
|
285
|
+
metric = c("total_cost", "n_pu_selected", "n_pu_total",
|
|
286
|
+
"pct_pu_selected", "n_targets_met", "n_features"),
|
|
287
|
+
value = c(eval_cost_summary(p, s)$cost,
|
|
288
|
+
n_selected, n_pu_total,
|
|
289
|
+
n_selected / n_pu_total * 100,
|
|
290
|
+
n_targets_met, n_feats)
|
|
291
|
+
)
|
|
292
|
+
write.csv(cost_df, file.path(output_dir, "cost_summary.csv"), row.names = FALSE)
|
|
293
|
+
log_info("Cost summary:\n%s", paste(capture.output(print(cost_df)), collapse = "\n"))
|
|
294
|
+
}, error = function(e) {
|
|
295
|
+
log_error("Falha em cost_summary: %s\nCausa provavel: falha na avaliacao de custo ou permissoes de escrita.\nVerifique: objetos p e s e output_dir.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
296
|
+
stop(e)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
# ── Irreplaceability ──────────────────────────────────────────────────────────
|
|
300
|
+
log_step(9, "Compute irreplaceability (rarity-weighted richness)")
|
|
301
|
+
tryCatch({
|
|
302
|
+
irr <- eval_rare_richness_importance(p, s)
|
|
303
|
+
writeRaster(irr, file.path(output_dir, "irreplaceability.tif"), overwrite = TRUE)
|
|
304
|
+
log_info("Irreplaceability map written.")
|
|
305
|
+
}, error = function(e) {
|
|
306
|
+
log_error("Falha em irreplaceability: %s\nCausa provavel: falha na avaliacao de raridade ou permissoes de escrita.\nVerifique: objetos p e s e output_dir.\nSkill anterior: species-distribution-modeling", conditionMessage(e))
|
|
307
|
+
stop(e)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
# ── Map visualisation ─────────────────────────────────────────────────────────
|
|
311
|
+
log_step(10, "Generate prioritization map visualisation")
|
|
312
|
+
tryCatch({
|
|
313
|
+
s_agg <- aggregate(s, fact = max(1, floor(nrow(s) / 300)))
|
|
314
|
+
df_sol <- as.data.frame(s_agg, xy = TRUE)
|
|
315
|
+
names(df_sol)[3] <- "selected"
|
|
316
|
+
df_sol$selected <- factor(df_sol$selected, levels = c(0, 1),
|
|
317
|
+
labels = c("Not selected", "Selected"))
|
|
318
|
+
|
|
319
|
+
p_map <- ggplot(df_sol, aes(x = x, y = y, fill = selected)) +
|
|
320
|
+
geom_raster() +
|
|
321
|
+
scale_fill_manual(values = c("Not selected" = "grey90", "Selected" = "#2166AC"),
|
|
322
|
+
na.value = "white") +
|
|
323
|
+
coord_equal() +
|
|
324
|
+
labs(x = "Easting", y = "Northing", fill = "",
|
|
325
|
+
title = sprintf("Conservation solution — %d PUs selected, %d/%d targets met",
|
|
326
|
+
n_selected, n_targets_met, n_feats)) +
|
|
327
|
+
theme_minimal(base_size = 10)
|
|
328
|
+
|
|
329
|
+
ggsave(file.path(output_dir, "prioritization_map.png"), p_map,
|
|
330
|
+
width = 9, height = 7, dpi = 150)
|
|
331
|
+
log_info("Prioritization map saved.")
|
|
332
|
+
}, error = function(e) {
|
|
333
|
+
log_warn("Nao foi possivel gerar o mapa de priorizacao: %s. Continuando sem o grafico.", conditionMessage(e))
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
log_info("Prioritization complete.")
|