@@ -14,12 +14,14 @@ class TestCdfUtils(TempFileTestCase):
1414 def test_write_cdf (self ):
1515 path = str (self .temp_directory / "write_cdf.cdf" )
1616 data = TestDataProduct ()
17- regular_var , time_var , non_rec_varying_var = data .to_data_product_variables ()
17+ var_without_explicit_type , int_var , float_var , time_var , non_rec_varying_var = data .to_data_product_variables ()
1818 attribute_manager = Mock (spec = ImapAttributeManager )
1919 attribute_manager .get_global_attributes .return_value = {"global1" : "global_val1" , "global2" : "global_val2" }
2020 attribute_manager .get_variable_attributes .side_effect = [
21- {"variable_attr1" : "var_val1" , "variable_attr2" : "var_val2" },
22- {"variable_attr3" : "var_val3" , "variable_attr4" : "var_val4" },
21+ {"variable_attr1" : "var_val1" },
22+ {"variable_attr1" : "var_val1" , "FILLVAL" : - 9223372036854775808 },
23+ {"variable_attr1" : "var_val1" , "FILLVAL" : - 1e31 },
24+ {"variable_attr3" : "var_val3" , "FILLVAL" : - 9223372036854775808 },
2325 {"variable_attr5" : "var_val5" , "variable_attr6" : "var_val6" },
2426 ]
2527
@@ -36,15 +38,30 @@ def test_write_cdf(self):
3638 self .assertEqual ('global_val1' , actual_cdf .attrs ['global1' ][...][0 ])
3739 self .assertEqual ('global_val2' , actual_cdf .attrs ['global2' ][...][0 ])
3840
39- np .testing .assert_array_equal (regular_var .value , actual_cdf [regular_var .name ][...])
40- self .assertEqual ('var_val1' , actual_cdf [regular_var .name ].attrs ['variable_attr1' ])
41- self .assertEqual ('var_val2' , actual_cdf [regular_var .name ].attrs ['variable_attr2' ])
42- self .assertEqual (pycdf .const .CDF_INT8 .value , actual_cdf [regular_var .name ].type ())
43- self .assertTrue (actual_cdf [regular_var .name ].rv ())
44- np .testing .assert_array_equal (time_var .value , actual_cdf .raw_var (time_var .name ))
41+ np .testing .assert_array_equal (var_without_explicit_type .value ,
42+ actual_cdf [var_without_explicit_type .name ][...])
43+ self .assertEqual ('var_val1' , actual_cdf [var_without_explicit_type .name ].attrs ['variable_attr1' ])
44+ self .assertEqual (pycdf .const .CDF_INT8 .value , actual_cdf [var_without_explicit_type .name ].type ())
45+ self .assertTrue (actual_cdf [var_without_explicit_type .name ].rv ())
46+
47+ np .testing .assert_array_equal (int_var .value , actual_cdf .raw_var (int_var .name ))
48+ self .assertEqual ('var_val1' , actual_cdf [int_var .name ].attrs ['variable_attr1' ])
49+ self .assertEqual (- 9223372036854775808 , actual_cdf [int_var .name ].attrs ['FILLVAL' ])
50+ self .assertEqual (pycdf .const .CDF_INT8 .value , actual_cdf [int_var .name ].attrs .type ("FILLVAL" ))
51+ self .assertEqual (pycdf .const .CDF_INT8 .value , actual_cdf [int_var .name ].type ())
52+ self .assertTrue (actual_cdf [int_var .name ].rv ())
53+
54+ np .testing .assert_array_equal (float_var .value , actual_cdf .raw_var (float_var .name ))
55+ self .assertEqual ('var_val1' , actual_cdf [float_var .name ].attrs ['variable_attr1' ])
56+ self .assertEqual (- 1e31 , actual_cdf [float_var .name ].attrs ['FILLVAL' ])
57+ self .assertEqual (pycdf .const .CDF_REAL4 .value , actual_cdf [float_var .name ].attrs .type ("FILLVAL" ))
58+ self .assertEqual (pycdf .const .CDF_REAL4 .value , actual_cdf [float_var .name ].type ())
59+ self .assertTrue (actual_cdf [float_var .name ].rv ())
4560
61+ np .testing .assert_array_equal (time_var .value , actual_cdf .raw_var (time_var .name ))
4662 self .assertEqual ('var_val3' , actual_cdf [time_var .name ].attrs ['variable_attr3' ])
47- self .assertEqual ('var_val4' , actual_cdf [time_var .name ].attrs ['variable_attr4' ])
63+ self .assertEqual (datetime (9999 , 12 , 31 , 23 , 59 , 59 , 999999 ), actual_cdf [time_var .name ].attrs ['FILLVAL' ])
64+ self .assertEqual (pycdf .const .CDF_TIME_TT2000 .value , actual_cdf [time_var .name ].attrs .type ("FILLVAL" ))
4865 self .assertEqual (pycdf .const .CDF_TIME_TT2000 .value , actual_cdf [time_var .name ].type ())
4966 self .assertTrue (actual_cdf [time_var .name ].rv ())
5067
@@ -74,13 +91,45 @@ def test_write_cdf_trims_numbers_in_logical_source_when_fetching_global_metadata
7491 self .assertEqual ('global_val1' , str (actual_cdf .attrs ['global1' ]))
7592 self .assertEqual ('global_val2' , str (actual_cdf .attrs ['global2' ]))
7693
94+ def test_write_cdf_replaces_nan_with_fill_value (self ):
95+ epoch_data = [datetime (2025 , 3 , 7 , 17 , 0 )]
96+
97+ class DataProductWithNan (DataProduct ):
98+ def __init__ (self ):
99+ self .input_metadata = Mock ()
100+
101+ def to_data_product_variables (self ) -> list [DataProductVariable ]:
102+ return [
103+ DataProductVariable ("epoch" , epoch_data , pycdf .const .CDF_TIME_TT2000 ),
104+ DataProductVariable ("float_var" , np .array ([3 , 5 , np .nan , 9 , np .nan , np .nan ]),
105+ pycdf .const .CDF_REAL4 ),
106+ ]
107+
108+ path = str (self .temp_directory / "write_cdf.cdf" )
109+ data = DataProductWithNan ()
110+ attribute_manager = Mock (spec = ImapAttributeManager )
111+ attribute_manager .get_global_attributes .return_value = {}
112+ attribute_manager .get_variable_attributes .side_effect = [
113+ {"VAR_NAME" : "epoch" , "FILLVAL" : datetime .fromisoformat ("9999-12-31T23:59:59.999999999" )},
114+ {"VAR_NAME" : "float_var" , "FILLVAL" : - 1e31 },
115+ ]
116+
117+ write_cdf (path , data , attribute_manager )
118+ with pycdf .CDF (path ) as actual_cdf :
119+ self .assertFalse (np .any (np .isnan (actual_cdf ["float_var" ][...])))
120+ np .testing .assert_array_equal (np .array ([3 , 5 , - 1e31 , 9 , - 1e31 , - 1e31 ], dtype = np .float32 ),
121+ actual_cdf ["float_var" ][...], strict = True )
122+ np .testing .assert_array_equal (actual_cdf ["epoch" ][...], np .array (epoch_data ), strict = True )
123+
77124 def test_does_not_write_depend_0_variable_attribute_if_it_is_empty (self ):
78125 path = str (self .temp_directory / "write_cdf.cdf" )
79126 data = TestDataProduct ()
80- regular_var , time_var , non_rec_varying_var = data .to_data_product_variables ()
127+ _ , _ , regular_var , time_var , non_rec_varying_var = data .to_data_product_variables ()
81128 attribute_manager = Mock (spec = ImapAttributeManager )
82129 attribute_manager .get_global_attributes .return_value = {"global1" : "global_val1" , "global2" : "global_val2" }
83130 attribute_manager .get_variable_attributes .side_effect = [
131+ {"ignored_attr" : "var_val3" , "variable_attr4" : "var_val4" , "DEPEND_0" : "" },
132+ {"ignored_attr" : "var_val3" , "variable_attr4" : "var_val4" , "DEPEND_0" : "" },
84133 {"variable_attr1" : "var_val1" , "variable_attr2" : "var_val2" , "DEPEND_0" : "epoch" },
85134 {"variable_attr3" : "var_val3" , "variable_attr4" : "var_val4" , "DEPEND_0" : "" },
86135 {"variable_attr5" : "var_val5" , "variable_attr6" : "var_val6" , "DEPEND_0" : "" },
@@ -114,7 +163,9 @@ def __init__(self):
114163
115164 def to_data_product_variables (self ) -> list [DataProductVariable ]:
116165 return [
117- DataProductVariable ("var1" , np .arange (0 , 10 )),
118- DataProductVariable ("var2" , np .arange (10 , 20 ), pycdf .const .CDF_TIME_TT2000 ),
119- DataProductVariable ("var3" , 100 , record_varying = False )
166+ DataProductVariable ("var_without_explicit_type" , np .arange (0 , 10 )),
167+ DataProductVariable ("int_var" , np .arange (0 , 10 ), pycdf .const .CDF_INT8 ),
168+ DataProductVariable ("float_var" , np .arange (0 , 10 ), pycdf .const .CDF_REAL4 ),
169+ DataProductVariable ("time_var" , np .arange (10 , 20 ), pycdf .const .CDF_TIME_TT2000 ),
170+ DataProductVariable ("non_record_varying" , 100 , record_varying = False )
120171 ]
0 commit comments