Skip to content

Commit b32fc43

Browse files
committed
merge/rebase from main
1 parent 8ae3f32 commit b32fc43

3 files changed

Lines changed: 48 additions & 42 deletions

File tree

codecarbon/core/monte_carlo.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,18 @@ def compute_confidence_interval(
160160
n = len(samples)
161161
if n >= 100:
162162
try:
163-
# Use optimized quantiles function from Python 3.8+
163+
# Use 40 quantiles for proper 95% CI bounds (1/0.025 = 40)
164+
# This ensures we get exact 2.5% and 97.5% percentiles
165+
n_quantiles = int(1.0 / (alpha / 2.0)) # For α=0.05: 1/0.025 = 40
164166
quantiles = statistics.quantiles(
165-
samples, n=int(1.0/alpha), method='inclusive'
167+
samples, n=n_quantiles, method='inclusive'
166168
)
169+
# Map to confidence interval bounds
167170
lower_idx = int(len(quantiles) * alpha / 2)
168171
upper_idx = int(len(quantiles) * (1 - alpha / 2)) - 1
172+
# Ensure bounds are within quantiles array
173+
lower_idx = max(0, min(lower_idx, len(quantiles) - 1))
174+
upper_idx = max(0, min(upper_idx, len(quantiles) - 1))
169175
return (quantiles[lower_idx], quantiles[upper_idx])
170176
except (AttributeError, ValueError):
171177
# Fall back to manual sorting for older Python or edge cases

codecarbon/core/uncertainty_emissions.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
precision assessments for carbon footprint estimates.
77
"""
88

9+
import math
910
from typing import Optional
1011

1112
from codecarbon.core.emissions import Emissions
@@ -248,6 +249,19 @@ def _extract_carbon_intensity_from_emissions(
248249
ValueError: If energy is zero or calculation is invalid
249250
ZeroDivisionError: If energy or PUE is zero
250251
"""
252+
# Guard against NaN/Infinity inputs
253+
if not math.isfinite(energy_kwh) or not math.isfinite(emissions_kg) or not math.isfinite(pue):
254+
logger.warning(
255+
"Non-finite input detected (energy_kwh=%s, emissions_kg=%s, pue=%s). "
256+
"Using world average fallback.",
257+
energy_kwh, emissions_kg, pue
258+
)
259+
try:
260+
carbon_intensity_per_source = self._data_source.get_carbon_intensity_per_source_data()
261+
return carbon_intensity_per_source.get("world_average", 475.0)
262+
except Exception:
263+
return 475.0
264+
251265
if energy_kwh <= 1e-6: # Below 1 milliwatt-hour threshold
252266
logger.warning(
253267
f"Energy consumption too small for reliable reverse calculation: {energy_kwh} kWh. "
@@ -267,6 +281,17 @@ def _extract_carbon_intensity_from_emissions(
267281
# Therefore: carbon_intensity = emissions * 1000 / (energy * pue)
268282
carbon_intensity_gco2_kwh = (emissions_kg * 1000) / (energy_kwh * pue)
269283

284+
# Guard against non-finite result from calculation
285+
if not math.isfinite(carbon_intensity_gco2_kwh):
286+
logger.warning(
287+
"Calculated carbon intensity is non-finite. Using world average fallback."
288+
)
289+
try:
290+
carbon_intensity_per_source = self._data_source.get_carbon_intensity_per_source_data()
291+
return carbon_intensity_per_source.get("world_average", 475.0)
292+
except Exception:
293+
return 475.0
294+
270295
if carbon_intensity_gco2_kwh < 0 or carbon_intensity_gco2_kwh > 2000:
271296
logger.warning(
272297
f"Calculated carbon intensity ({carbon_intensity_gco2_kwh:.1f} g CO₂/kWh) "

codecarbon/output_methods/uncertainty_emissions_data.py

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,9 @@
1414
from codecarbon.output_methods.emissions_data import EmissionsData, TaskEmissionsData
1515

1616

17-
@dataclass
18-
class UncertaintyAwareEmissionsData(EmissionsData):
19-
"""
20-
Enhanced emissions data with uncertainty quantification.
17+
class UncertaintyMixin:
18+
"""Mixin for uncertainty quantification fields and methods."""
2119

22-
Extends the base EmissionsData to include confidence intervals
23-
and uncertainty metadata from Monte Carlo analysis.
24-
"""
25-
26-
# Uncertainty analysis results
2720
uncertainty_enabled: bool = False
2821
uncertainty_method: Optional[str] = None
2922
emissions_ci_lower_kg: Optional[float] = None
@@ -64,6 +57,16 @@ def set_uncertainty_data(self, uncertainty_summary: UncertaintySummary) -> None:
6457
else:
6558
self.uncertainty_quality = "very_low_precision"
6659

60+
61+
@dataclass
62+
class UncertaintyAwareEmissionsData(EmissionsData, UncertaintyMixin):
63+
"""
64+
Enhanced emissions data with uncertainty quantification.
65+
66+
Extends the base EmissionsData to include confidence intervals
67+
and uncertainty metadata from Monte Carlo analysis.
68+
"""
69+
6770
@property
6871
def values(self) -> OrderedDict:
6972
"""Extended values property including uncertainty fields."""
@@ -161,39 +164,11 @@ def toJSON(self):
161164

162165

163166
@dataclass
164-
class UncertaintyAwareTaskEmissionsData(TaskEmissionsData):
167+
class UncertaintyAwareTaskEmissionsData(TaskEmissionsData, UncertaintyMixin):
165168
"""
166169
Task-level emissions data with uncertainty quantification.
167170
168171
Inherits all base task fields from TaskEmissionsData and adds
169-
uncertainty-specific metadata and confidence intervals.
172+
uncertainty-specific metadata and confidence intervals via UncertaintyMixin.
170173
"""
171-
172-
# Uncertainty fields - only declare new fields, inherit the rest
173-
uncertainty_enabled: bool = False
174-
uncertainty_method: Optional[str] = None
175-
emissions_ci_lower_kg: Optional[float] = None
176-
emissions_ci_upper_kg: Optional[float] = None
177-
confidence_level_pct: Optional[float] = None
178-
relative_uncertainty_pct: Optional[float] = None
179-
uncertainty_quality: Optional[str] = None
180-
181-
def set_uncertainty_data(self, uncertainty_summary: UncertaintySummary) -> None:
182-
"""Populate uncertainty fields from analysis results."""
183-
self.uncertainty_enabled = True
184-
self.uncertainty_method = uncertainty_summary.method
185-
self.emissions_ci_lower_kg = uncertainty_summary.ci_lower_kg
186-
self.emissions_ci_upper_kg = uncertainty_summary.ci_upper_kg
187-
self.confidence_level_pct = uncertainty_summary.confidence_level_pct
188-
self.relative_uncertainty_pct = uncertainty_summary.relative_uncertainty_pct
189-
190-
# Quality assessment
191-
if self.relative_uncertainty_pct is not None:
192-
if self.relative_uncertainty_pct <= 5.0:
193-
self.uncertainty_quality = "high_precision"
194-
elif self.relative_uncertainty_pct <= 15.0:
195-
self.uncertainty_quality = "moderate_precision"
196-
elif self.relative_uncertainty_pct <= 25.0:
197-
self.uncertainty_quality = "low_precision"
198-
else:
199-
self.uncertainty_quality = "very_low_precision"
174+
pass # All uncertainty functionality provided by UncertaintyMixin

0 commit comments

Comments
 (0)