aggregate() returns NaN weights instead of raising when all results report num_examples=0
Describe the bug
aggregate() in framework/py/flwr/server/strategy/aggregate.py divides by the total number of examples without checking for zero. If every result in a round reports num_examples=0, the division produces NaN for every parameter and returns it as the aggregated model. There is no exception, only a numpy RuntimeWarning, so the NaN model is redistributed to clients and the run continues.
The if not results guards upstream do not catch this: the results list is non empty, the counts inside it are zero.
Steps/Code to Reproduce
import numpy as np
from flwr.server.strategy.aggregate import aggregate, weighted_loss_avg
results = [([np.array([1.0, 2.0])], 0), ([np.array([3.0, 4.0])], 0)]
print(aggregate(results)) # -> [array([nan, nan])], no exception
weighted_loss_avg([(0, 0.5), (0, 0.7)]) # -> raises ZeroDivisionErrorOutput:
aggregate() RETURNED (no exception): [array([nan, nan])]
contains NaN: True
warning: RuntimeWarning
weighted_loss_avg RAISED: ZeroDivisionError - float division by zeroExpected Results
aggregate() should fail loudly, like the other aggregation paths already do. weighted_loss_avg and aggregate_inplace both raise ZeroDivisionError on the same all zero input, so today the three entry points disagree and only aggregate() corrupts silently.
Actual Results
A NaN model is returned and becomes the new global model. Training can continue for many rounds before anyone notices.
Additional context
Happy to send a PR, i have a fix and a regression test ready.
Source: flwrlabs/flower