Skip to content

Commit a100edd

Browse files
Handle NaN/INF values in FormulaEngine
The MetricFetcher propagates NaN to handle None values when None values are not treat from the stream as 0s. So that any FormulaStep can compute the results without checking for None on each value involved in the FormulaStep. However the final result is written as None rather than NaN/INF in the FormulaEngine. Signed-off-by: Daniel Zullo <[email protected]>
1 parent a1ce9b3 commit a100edd

File tree

2 files changed

+18
-19
lines changed

2 files changed

+18
-19
lines changed

src/frequenz/sdk/timeseries/logical_meter/_formula_engine.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import weakref
1111
from collections import deque
1212
from datetime import datetime
13+
from math import isinf, isnan
1314
from typing import Dict, List, Optional, Set, Tuple
1415
from uuid import UUID, uuid4
1516

@@ -154,7 +155,11 @@ async def _apply(self) -> Sample:
154155
if len(eval_stack) != 1:
155156
raise RuntimeError(f"Formula application failed: {self._name}")
156157

157-
return Sample(metric_ts, eval_stack[0])
158+
res = eval_stack.pop()
159+
if isnan(res) or isinf(res):
160+
res = None
161+
162+
return Sample(metric_ts, res)
158163

159164
async def _run(self) -> None:
160165
sender = self._channel.new_sender()

src/frequenz/sdk/timeseries/logical_meter/_formula_steps.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import annotations
77

88
from abc import ABC, abstractmethod
9+
from math import isinf, isnan
910
from typing import List, Optional
1011

1112
from frequenz.channels import Receiver
@@ -57,10 +58,7 @@ def apply(self, eval_stack: List[Optional[float]]) -> None:
5758
"""
5859
val2 = eval_stack.pop()
5960
val1 = eval_stack.pop()
60-
if val1 is None or val2 is None:
61-
res = None
62-
else:
63-
res = val1 + val2
61+
res = val1 + val2
6462
eval_stack.append(res)
6563

6664

@@ -83,10 +81,7 @@ def apply(self, eval_stack: List[Optional[float]]) -> None:
8381
"""
8482
val2 = eval_stack.pop()
8583
val1 = eval_stack.pop()
86-
if val1 is None or val2 is None:
87-
res = None
88-
else:
89-
res = val1 - val2
84+
res = val1 - val2
9085
eval_stack.append(res)
9186

9287

@@ -109,10 +104,7 @@ def apply(self, eval_stack: List[Optional[float]]) -> None:
109104
"""
110105
val2 = eval_stack.pop()
111106
val1 = eval_stack.pop()
112-
if val1 is None or val2 is None:
113-
res = None
114-
else:
115-
res = val1 * val2
107+
res = val1 * val2
116108
eval_stack.append(res)
117109

118110

@@ -135,10 +127,7 @@ def apply(self, eval_stack: List[Optional[float]]) -> None:
135127
"""
136128
val2 = eval_stack.pop()
137129
val1 = eval_stack.pop()
138-
if val1 is None or val2 is None:
139-
res = None
140-
else:
141-
res = val1 / val2
130+
res = val1 / val2
142131
eval_stack.append(res)
143132

144133

@@ -265,7 +254,12 @@ def apply(self, eval_stack: List[Optional[float]]) -> None:
265254
"""
266255
if self._next_value is None:
267256
raise RuntimeError("No next value available to append.")
268-
if self._next_value.value is None and self._nones_are_zeros:
269-
eval_stack.append(0.0)
257+
258+
next_value = self._next_value.value
259+
if next_value is None or isnan(next_value) or isinf(next_value):
260+
if self._nones_are_zeros:
261+
eval_stack.append(0.0)
262+
else:
263+
eval_stack.append(float("NaN"))
270264
else:
271265
eval_stack.append(self._next_value.value)

0 commit comments

Comments
 (0)