Summary
A network created with nodes=[...] filtering (topology-only nodes for the rest) cannot be matched. ms.match() fails with:
AssertionError: time must be datetime
The underlying data values are correct — only the time coordinate's dtype is wrong.
Root cause
Network._build_dataframe (src/modelskill/network.py:497-508) drops nodes with no data using if v["data"] is not None (line 500), then concatenates the rest:
data_in_nodes = {
k: v["data"] for k, v in g.nodes.items() if v["data"] is not None
}
...
df = pd.concat(data_in_nodes, axis=1)
But topology-only nodes never store None. Res1DNode.__init__ and GridPoint.__init__ (src/modelskill/model/adapters/_res1d.py:45,66) both default missing data to an empty pd.DataFrame(), so the is not None check never filters anything out.
Concatenating an empty (default RangeIndex) DataFrame alongside real DatetimeIndex-indexed DataFrames degrades the result's index to object dtype:
>>> pd.concat({'real': real_df, 'topo': pd.DataFrame()}, axis=1).index.dtype
dtype('O')
This flows through Network.to_dataset() (network.py:532-547) into the model result dataset, and eventually fails the assertion in _validate_dataset (src/modelskill/timeseries/_timeseries.py:79), raised inside NodeModelResult.__init__ — far from where the dtype actually broke.
Reproduction
network = Network.from_res1d("network.res1d", nodes=["108", "101"], reaches=[])
network._df.index.dtype # object, should be datetime64[ns]
tests/test_network.py already has tests covering this exact nodes=[...], reaches=[] combination (test_from_res1d_nodes_filter_only_selected_have_data, test_dataframe_from_partial_network, lines 542-584), but none of them check the time dtype, so this passes CI today.
Workaround
mr.data = mr.data.assign_coords(time=pd.DatetimeIndex(mr.data.time.to_index()))
Suggested fixes
Any of these would work:
- Store
None (not an empty DataFrame) for topology-only nodes, and keep the existing is not None filter.
- Filter out empty DataFrames before the
pd.concat in _build_dataframe.
- Coerce the index to
DatetimeIndex explicitly after building the dataset.
Found by a test user evaluating the network functionality ahead of a 1.4.0 release.
Summary
A network created with
nodes=[...]filtering (topology-only nodes for the rest) cannot be matched.ms.match()fails with:The underlying data values are correct — only the time coordinate's dtype is wrong.
Root cause
Network._build_dataframe(src/modelskill/network.py:497-508) drops nodes with no data usingif v["data"] is not None(line 500), then concatenates the rest:But topology-only nodes never store
None.Res1DNode.__init__andGridPoint.__init__(src/modelskill/model/adapters/_res1d.py:45,66) both default missing data to an emptypd.DataFrame(), so theis not Nonecheck never filters anything out.Concatenating an empty (default
RangeIndex) DataFrame alongside realDatetimeIndex-indexed DataFrames degrades the result's index toobjectdtype:This flows through
Network.to_dataset()(network.py:532-547) into the model result dataset, and eventually fails the assertion in_validate_dataset(src/modelskill/timeseries/_timeseries.py:79), raised insideNodeModelResult.__init__— far from where the dtype actually broke.Reproduction
tests/test_network.pyalready has tests covering this exactnodes=[...], reaches=[]combination (test_from_res1d_nodes_filter_only_selected_have_data,test_dataframe_from_partial_network, lines 542-584), but none of them check the time dtype, so this passes CI today.Workaround
Suggested fixes
Any of these would work:
None(not an empty DataFrame) for topology-only nodes, and keep the existingis not Nonefilter.pd.concatin_build_dataframe.DatetimeIndexexplicitly after building the dataset.Found by a test user evaluating the network functionality ahead of a 1.4.0 release.