Skip to content

Remove R/rpy2 dependency; replace with cffdrs_py#5176

Open
conbrad wants to merge 14 commits intomainfrom
task/cffdrs-py-no-r
Open

Remove R/rpy2 dependency; replace with cffdrs_py#5176
conbrad wants to merge 14 commits intomainfrom
task/cffdrs-py-no-r

Conversation

@conbrad
Copy link
Collaborator

@conbrad conbrad commented Mar 2, 2026

Replaces all R/rpy2 calls in app/fire_behaviour/cffdrs.py with the pure-Python cffdrs_py package, eliminating the R runtime as a dependency.

Changes:

  • Rewrote all FWI and FBP wrappers in cffdrs.py to use cffdrs_py submodule imports
  • Removed CFFDRS singleton, r_importer.py, and CFFDRS pre-warming from startup
  • Removed rpy2 from pyproject.toml; added cffdrs git dependency (pinned to commit)
  • Removed R installation from base image Dockerfile, OpenShift build.yaml, and local dev Dockerfile
  • Improved None guards on bui_calc, initial_spread_index, fire_weather_index, and crown_fraction_burned; fixed drought_code silently substituting rh=0 when RH is None; fixed
    length_to_breadth_ratio returning rather than raising CFFDRSException; added per-row error handling to hourly_fine_fuel_moisture_code
  • Updated 32 fba_calc tests (removed CFFDRS.instance() pre-warming); deleted test_r.py

Behavioural notes:

  • duff_moisture_code returns 0.0 (not ~0.256) when temperature < 1.1°C — cffdrs_py clamps temp to -1.1 before computing the drying rate (Eq. 16), zeroing it out. The R implementation did not apply this clamp.
  • cbh uses a fuel type default if it is None, this was handled internally before in the R library.

Closes #1926

Test Links:
Landing Page
MoreCast
Percentile Calculator
C-Haines
FireCalc
FireCalc bookmark
Auto Spatial Advisory (ASA)
HFI Calculator
SFMS Insights
Fire Watch

@codecov
Copy link

codecov bot commented Mar 2, 2026

Codecov Report

❌ Patch coverage is 72.65625% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.78%. Comparing base (462ad8c) to head (04d5b76).

Files with missing lines Patch % Lines
.../packages/wps-api/src/app/fire_behaviour/cffdrs.py 72.22% 23 Missing and 12 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5176      +/-   ##
==========================================
- Coverage   68.80%   68.78%   -0.02%     
==========================================
  Files         392      391       -1     
  Lines       16373    16296      -77     
  Branches     1846     1815      -31     
==========================================
- Hits        11265    11210      -55     
+ Misses       4527     4515      -12     
+ Partials      581      571      -10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@conbrad conbrad requested review from brettedw and dgboss March 2, 2026 22:45
FMC, SFC, PC, PDF, CC, CBH, ISI, output = "WSV")
WSV <- ifelse(GS > 0 & FFMC > 0, WSV0, WS)
Calculate the net effective windspeed (WSV).
WSV = Slopecalc(..., output="WSV") when GS > 0 and FFMC > 0, else WS.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:nit - Maybe remove this line of the comment, I don't think it's very helpful.

fuel_type=fuel_type.value,
ffmc=ffmc,
bui=bui,
wsv=wsv if wsv is not None else 0,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take it setting wsv = 0 here is equivalent to setting wsv = NULL in the R implementation?

return buildup_index(dmc, dc)


def rate_of_spread_t(fuel_type: FuelTypeEnum,
Copy link
Collaborator

@dgboss dgboss Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the R implementation we used to check the value being returned from the calculation and would raise a CFFDRSException if necessary. We're now returning the value without a check. Is this safe and intentional? We're doing this in a number of places now.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 4, 2026

@brettedw
Copy link
Collaborator

brettedw commented Mar 5, 2026

cbh uses a fuel type default if it is None, this was handled internally before in the R library.

Where does this happen? I think cffdrs_py still handles it internally

Comment on lines +484 to +486
calc_step: bool = False,
batch: bool = True,
hourly_fwi: bool = False,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 args aren't used. I actually don't think hourly_fine_fuel_moisture_code is used in our code base. Do we need it?

raise CFFDRSException("Failed to calculate CFB")
csi = critical_surface_intensity(fmc, cbh)
rso = surface_fire_rate_of_spread(csi, sfc)
return _crown_fraction_burned(ros, rso)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crown fraction burned is calculated differently for the C6 fuel type. cffdrs_py has a different function for that calculation, wondering if we should take care of that in this PR

return ws


def flank_rate_of_spread(ros: float, bros: float, lb: float):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think functions like this might be unnecessary

@conbrad
Copy link
Collaborator Author

conbrad commented Mar 5, 2026

cbh uses a fuel type default if it is None, this was handled internally before in the R library.

Where does this happen? I think cffdrs_py still handles it internally

backend/packages/wps-api/src/app/routers/fba_calc.py

@brettedw
Copy link
Collaborator

brettedw commented Mar 6, 2026

cbh uses a fuel type default if it is None, this was handled internally before in the R library.

Where does this happen? I think cffdrs_py still handles it internally

backend/packages/wps-api/src/app/routers/fba_calc.py

Took me awhile to figure out what was happening here, I was confused because R doesn't have fuel type default handling in those functions either, but it comes down to Python vs R math operations on NULL/None. Python will error, R won't.

Non-C6 fuel: ROS still comes from RSS (rate_of_spread.r (line 173)), so you often still get a numeric ROS.
C6 fuel: ROS depends on CFB/RSO, so it typically becomes NA (rate_of_spread.r (line 174)).

This is why C6 fuel type has built in 2 and 7m CBH on the front end

Regardless, all the calculations in FireCalc look the same to me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CFFDRS API : Get R the F out of our S (Reference ticket)

3 participants