Repository: DHI/mikeio

Issue:
Dataset item assignment
![image](https://user-images.githubusercontent.com/614215/132855983-021049d6-ad62-4bcf-8ff6-e137932fa4e8.png)


Comments:

Patch:
diff --git a/mikeio/dataset.py b/mikeio/dataset.py
index b447f1c7..39255424 100644
--- a/mikeio/dataset.py
+++ b/mikeio/dataset.py
@@ -162,31 +162,44 @@ class Dataset(TimeSeries):
     def __len__(self):
         return len(self.items)
 
-    def __getitem__(self, x):
+    def __setitem__(self, key, value):
 
-        if isinstance(x, slice):
-            s = self.time.slice_indexer(x.start, x.stop)
+        if isinstance(key, int):
+            self.data[key] = value
+
+        elif isinstance(key, str):
+            item_lookup = {item.name: i for i, item in enumerate(self.items)}
+            key = item_lookup[key]
+            self.data[key] = value
+        else:
+
+            raise ValueError(f"indexing with a {type(key)} is not (yet) supported")
+
+    def __getitem__(self, key):
+
+        if isinstance(key, slice):
+            s = self.time.slice_indexer(key.start, key.stop)
             time_steps = list(range(s.start, s.stop))
             return self.isel(time_steps, axis=0)
 
-        if isinstance(x, int):
-            return self.data[x]
+        if isinstance(key, int):
+            return self.data[key]
 
-        if isinstance(x, str):
+        if isinstance(key, str):
             item_lookup = {item.name: i for i, item in enumerate(self.items)}
-            x = item_lookup[x]
-            return self.data[x]
+            key = item_lookup[key]
+            return self.data[key]
 
-        if isinstance(x, ItemInfo):
-            return self.__getitem__(x.name)
+        if isinstance(key, ItemInfo):
+            return self.__getitem__(key.name)
 
-        if isinstance(x, list):
+        if isinstance(key, list):
             data = []
             items = []
 
             item_lookup = {item.name: i for i, item in enumerate(self.items)}
 
-            for v in x:
+            for v in key:
                 data_item = self.__getitem__(v)
                 if isinstance(v, str):
                     i = item_lookup[v]
@@ -199,7 +212,7 @@ class Dataset(TimeSeries):
 
             return Dataset(data, self.time, items)
 
-        raise ValueError(f"indexing with a {type(x)} is not (yet) supported")
+        raise ValueError(f"indexing with a {type(key)} is not (yet) supported")
 
     def __radd__(self, other):
         return self.__add__(other)
@@ -229,51 +242,61 @@ class Dataset(TimeSeries):
         else:
             return self._multiply_value(other)
 
-    def _add_dataset(self, other, sign=1.0):        
+    def _add_dataset(self, other, sign=1.0):
         self._check_datasets_match(other)
         try:
-            data = [self[x] + sign*other[y] for x, y in zip(self.items, other.items)]
+            data = [self[x] + sign * other[y] for x, y in zip(self.items, other.items)]
         except:
             raise ValueError("Could not add data in Dataset")
         time = self.time.copy()
         items = deepcopy(self.items)
-        return Dataset(data, time, items) 
+        return Dataset(data, time, items)
 
     def _check_datasets_match(self, other):
         if self.n_items != other.n_items:
-            raise ValueError(f"Number of items must match ({self.n_items} and {other.n_items})")
+            raise ValueError(
+                f"Number of items must match ({self.n_items} and {other.n_items})"
+            )
         for j in range(self.n_items):
             if self.items[j].type != other.items[j].type:
-                raise ValueError(f"Item types must match. Item {j}: {self.items[j].type} != {other.items[j].type}")
+                raise ValueError(
+                    f"Item types must match. Item {j}: {self.items[j].type} != {other.items[j].type}"
+                )
             if self.items[j].unit != other.items[j].unit:
-                raise ValueError(f"Item units must match. Item {j}: {self.items[j].unit} != {other.items[j].unit}")            
+                raise ValueError(
+                    f"Item units must match. Item {j}: {self.items[j].unit} != {other.items[j].unit}"
+                )
         if not np.all(self.time == other.time):
             raise ValueError("All timesteps must match")
         if self.shape != other.shape:
-            raise ValueError("shape must match")    
+            raise ValueError("shape must match")
 
     def _add_value(self, value):
         try:
             data = [value + self[x] for x in self.items]
         except:
-            raise ValueError(f"{value} could not be added to Dataset")        
+            raise ValueError(f"{value} could not be added to Dataset")
         items = deepcopy(self.items)
         time = self.time.copy()
         return Dataset(data, time, items)
 
-
     def _multiply_value(self, value):
         try:
             data = [value * self[x] for x in self.items]
         except:
-            raise ValueError(f"{value} could not be multiplied to Dataset")        
+            raise ValueError(f"{value} could not be multiplied to Dataset")
         items = deepcopy(self.items)
         time = self.time.copy()
         return Dataset(data, time, items)
 
     def describe(self, **kwargs):
         """Generate descriptive statistics by wrapping pandas describe()"""
-        all_df = [pd.DataFrame(self.data[j].flatten(), columns=[self.items[j].name]).describe(**kwargs) for j in range(self.n_items)]
+        all_df = [
+            pd.DataFrame(self.data[j].flatten(), columns=[self.items[j].name]).describe(
+                **kwargs
+            )
+            for j in range(self.n_items)
+        ]
         return pd.concat(all_df, axis=1)
 
     def copy(self):
diff --git a/mikeio/eum.py b/mikeio/eum.py
index 88f16571..0dfe3360 100644
--- a/mikeio/eum.py
+++ b/mikeio/eum.py
@@ -347,7 +347,7 @@ class EUMType(IntEnum):
     Ground_Water_Abstraction_Flux = 100238
     Fraction = 100239
     Yield_Factor = 100240
-    Specific_Solute_Flux_Per_Area = 100241
+    Specific_Solute_Flux_per_Area = 100241
     Current_Speed = 100242
     Current_Direction = 100243
     Current_Magnitude = 100244
@@ -621,6 +621,7 @@ class EUMType(IntEnum):
     Heat_Flux_Resistance = 110200
     Absolute_Humidity = 110210
     Length = 110220
+    Area = 110225
     Volume = 110230
     Element_Volume = 110231
     Wave_Power = 110232
@@ -694,6 +695,13 @@ class EUMType(IntEnum):
     Molal_Concentration = 110300
     Suspended_sediment_load_per_area = 110301
     Bollard_Force = 110302
+    Discharge_per_Pressure = 110303
+    RotationalSpeed = 110304
+    Infiltration_per_Area = 110305
+    Mass_per_Length_per_Time = 110306
+    NearBedLoad_per_Length = 110307
+    Substance_per_UnitArea = 110308
+    AccNearBedLoad_per_Length = 110309
 
     def __init__(self, code):
         self.code = code
@@ -832,6 +840,17 @@ class EUMUnit(IntEnum):
     pound = 1207
     kiloton = 1205
     megaton = 1206
+    ounce = 1209
+    per_kg = 1250
+    per_gram = 1251
+    per_mg = 1252
+    per_mu_g = 1253
+    per_ton = 1254
+    per_kiloton = 1255
+    per_megaton = 1256
+    per_pound = 1257
+    per_ton_US = 1258
+    per_ounce = 1259
     kg_per_sec = 4200
     gram_per_sec = 4203
     mg_per_sec = 4202
@@ -915,6 +934,8 @@ class EUMUnit(IntEnum):
     pound_per_feet_per_sec_pow_2 = 5402
     newton_per_meter_pow_3 = 5500
     kN_per_meter_pow_3 = 5501
+    kilogram_M2 = 5550
+    poundSqrFeet = 5551
     gram_per_meter_pow_3_per_day = 4601
     _mg_per_l__pow__1_per_2__per_day = 5300
     _mg_per_l__pow__1_per_2__per_hour = 5301
@@ -1062,11 +1083,15 @@ class EUMUnit(IntEnum):
     meter_pow_3_per_hour_per_meter = 7503
     meter_pow_3_per_day_per_meter = 7504
     feet_pow_3_per_hour_per_feet = 4719
+    m3_per_hour_per_M = 4730
+    m3_per_day_per_M = 4731
     feet_pow_3_per_day_per_feet = 4732
     galUK_per_day_per_feet = 4712
     gallon_per_day_per_feet = 4713
     gallon_per_min_per_feet = 4714
     liter_per_day_per_meter = 4715
+    liter_per_minute_per_meter = 4716
+    liter_per_second_per_meter = 4717
     liter_per_min_per_meter = 7501
     liter_per_sec_per_meter = 7500
     gram_per_min = 4208
@@ -1087,15 +1112,6 @@ class EUMUnit(IntEnum):
     meter_pow_3_per_mu_g = 6903
     ton_per_day = 4219
     millimeterD50 = 1012
-    per_kg = 1250
-    per_gram = 1251
-    per_mg = 1252
-    per_mu_g = 1253
-    per_ton = 1254
-    per_kiloton = 1255
-    per_megaton = 1256
-    per_pound = 1257
-    per_ton_US = 1258
     millisecond = 1406
     feet_US_pow_2 = 3208
     yard_US_pow_2 = 3209
@@ -1112,6 +1128,7 @@ class EUMUnit(IntEnum):
     galUK_per_sec = 1826
     galUK_per_year = 1827
     galUK_per_PE_per_day = 1828
+    ydUS3_per_sec = 1829
     acre_feet_per_day_per_acre = 2022
     Mgal_per_day_per_acre = 2032
     MgalUK_per_day_per_acre = 2033
@@ -1121,8 +1138,25 @@ class EUMUnit(IntEnum):
     meter_per_sec_pow_2 = 2100
     feet_per_sec_pow_2 = 2101
     pound_per_feet_US_pow_2 = 2215
+    ounce_per_cubic_feet = 2216
+    ounce_per_cubic_feet_US = 2217
+    ounce_per_Yard3 = 2218
+    ounce_per_yard_US3 = 2219
+    ounce_per_square_feet = 2220
+    ounce_per_square_feet_US = 2221
     kg_per_meter_per_sec = 2300
     Pascal_second = 2301
+    kilogram_per_meter_per_day = 2302
+    gram_per_Meter_per_Day = 2303
+    gram_per_Km_per_Day = 2304
+    pound_per_Feet_per_Day = 2305
+    pound_per_FeetUS_per_Day = 2306
+    ounce_per_Feet_per_Day = 2307
+    ounce_per_FeetUS_per_Day = 2308
+    kilogram_per_Yard_per_Second = 2309
+    kilogram_per_Feet_per_Second = 2310
+    pound_per_Yard_per_Second = 2311
+    pound_per_Feet_per_Second = 2312
     degree50 = 2402
     degree_pow_2 = 2403
     degree_per_meter = 2500
@@ -1134,6 +1168,8 @@ class EUMUnit(IntEnum):
     rev_per_min = 2610
     percent_per_hour = 2611
     percent_per_sec = 2613
+    revolution_per_second = 2614
+    revolution_per_hour = 2615
     _per_degree_C = 2850
     _per_degree_F = 2851
     per_meter_pow_3 = 3003
@@ -1161,6 +1197,16 @@ class EUMUnit(IntEnum):
     mg_per_mile_pow_2 = 4428
     pound_per_meter = 4429
     ton_per_meter = 4430
+    pound_per_Feet = 4431
+    pound_per_Yard = 4432
+    pound_per_FeetUS = 4433
+    pound_per_YardUS = 4434
+    ounce_per_Feet = 4435
+    ounce_per_Yard = 4436
+    ounce_per_FeetUS = 4437
+    ounce_per_YardUS = 4438
+    kilogram_per_Yard = 4439
+    kilogram_per_Feet = 4440
     mg_per_liter_per_day = 4603
     yard_pow_3_per_year_per_yard = 4728
     yard_US_pow_3_per_year_per_yard_US = 4729
@@ -1222,6 +1268,7 @@ class EUMUnit(IntEnum):
     Mgal_per_day_per_psi = 7507
     MgalUK_per_day_per_psi = 7508
     acre_feet_per_day_per_psi = 7509
+    m3_per_hour_per_bar = 7510
     _per_meter_per_sec = 9200
     meter_per_sec_per_ha = 9201
     feet_per_sec_per_acre = 9202
@@ -1229,6 +1276,7 @@ class EUMUnit(IntEnum):
     per_acre = 9301
     per_hectare = 9302
     per_km_pow_2 = 9303
+    per_cubic_meter = 9350
     currency_per_meter_pow_3 = 9351
     currency_per_feet_pow_3 = 9352
     per_watt = 9600
@@ -1253,6 +1301,10 @@ class EUMUnit(IntEnum):
     mmol_per_kg = 12041
     mu_mol_per_kg = 12042
     nmol_per_kg = 12043
+    mole_per_m2 = 12060
+    millimole_per_m2 = 12061
+    micromole_per_m2 = 12062
+    nanomole_per_m2 = 12063
     meter_per_meter = 99014
     per_minute = 99015
     percent_per_min = 2612


Test patch:
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index 1191c867..36b453b9 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -11,6 +11,7 @@ from mikeio.eum import EUMType, ItemInfo, EUMUnit
 def _get_time(nt):
     return list(rrule(freq=SECONDLY, count=nt, dtstart=datetime(2000, 1, 1)))
 
+
 @pytest.fixture
 def ds1():
     nt = 10
@@ -24,6 +25,7 @@ def ds1():
     items = [ItemInfo("Foo"), ItemInfo("Bar")]
     return Dataset(data, time, items)
 
+
 @pytest.fixture
 def ds2():
     nt = 10
@@ -37,6 +39,7 @@ def ds2():
     items = [ItemInfo("Foo"), ItemInfo("Bar")]
     return Dataset(data, time, items)
 
+
 def test_get_names():
 
     data = []
@@ -241,8 +244,9 @@ def test_select_subset_isel_multiple_idxs():
 def test_decribe(ds1):
     df = ds1.describe()
     assert df.columns[0] == "Foo"
-    assert df.loc['mean'][1] == pytest.approx(0.2)
-    assert df.loc['max'][0] == pytest.approx(0.1)
+    assert df.loc["mean"][1] == pytest.approx(0.2)
+    assert df.loc["max"][0] == pytest.approx(0.1)
+
 
 def test_create_undefined():
 
@@ -468,6 +472,21 @@ def test_get_data_name():
     assert ds["Foo"].shape == (100, 100, 30)
 
 
+def test_set_data_name():
+
+    nt = 100
+
+    time = _get_time(nt)
+    items = [ItemInfo("Foo")]
+    ds = Dataset([np.zeros((nt, 10))], time, items)
+
+    assert ds["Foo"][0, 0] == 0.0
+
+    ds["Foo"] = np.zeros((nt, 10)) + 1.0
+
+    assert ds["Foo"][0, 0] == 1.0
+
+
 def test_get_bad_name():
     nt = 100
     data = []
@@ -909,29 +928,32 @@ def test_init():
     assert ds.n_elements == n_elements
     assert ds.items[0].name == "Foo"
 
+
 def test_add_scalar(ds1):
     ds2 = ds1 + 10.0
     assert np.all(ds2[0] - ds1[0] == 10.0)
 
     ds3 = 10.0 + ds1
     assert np.all(ds3[0] == ds2[0])
-    assert np.all(ds3[1] == ds2[1])       
-    
+    assert np.all(ds3[1] == ds2[1])
+
+
 def test_sub_scalar(ds1):
     ds2 = ds1 - 10.0
     assert np.all(ds1[0] - ds2[0] == 10.0)
 
     ds3 = 10.0 - ds1
     assert np.all(ds3[0] == 9.9)
-    assert np.all(ds3[1] == 9.8) 
+    assert np.all(ds3[1] == 9.8)
+
 
 def test_mul_scalar(ds1):
     ds2 = ds1 * 2.0
-    assert np.all(ds2[0]*0.5 == ds1[0]) 
+    assert np.all(ds2[0] * 0.5 == ds1[0])
 
     ds3 = 2.0 * ds1
     assert np.all(ds3[0] == ds2[0])
-    assert np.all(ds3[1] == ds2[1])    
+    assert np.all(ds3[1] == ds2[1])
 
 
 def test_add_dataset(ds1, ds2):
@@ -941,7 +963,7 @@ def test_add_dataset(ds1, ds2):
 
     ds4 = ds2 + ds1
     assert np.all(ds3[0] == ds4[0])
-    assert np.all(ds3[1] == ds4[1])    
+    assert np.all(ds3[1] == ds4[1])
 
     ds2b = ds2.copy()
     ds2b.items[0] = ItemInfo(EUMType.Wind_Velocity)
@@ -951,17 +973,19 @@ def test_add_dataset(ds1, ds2):
 
     ds2c = ds2.copy()
     tt = ds2c.time.to_numpy()
-    tt[-1] = tt[-1] + np.timedelta64(1, 's')
+    tt[-1] = tt[-1] + np.timedelta64(1, "s")
     ds2c.time = pd.DatetimeIndex(tt)
     with pytest.raises(ValueError):
         # time does not match
         ds1 + ds2c
 
+
 def test_sub_dataset(ds1, ds2):
     ds3 = ds2 - ds1
     assert np.all(ds3[0] == 0.9)
     assert np.all(ds3[1] == 1.8)
 
+
 def test_non_equidistant():
     nt = 4
     d = np.random.uniform(size=nt)
diff --git a/tests/test_eum.py b/tests/test_eum.py
index 74b7c6b2..43628b7f 100644
--- a/tests/test_eum.py
+++ b/tests/test_eum.py
@@ -1,4 +1,6 @@
-from mikeio.eum import EUMType, ItemInfo
+from mikeio.eum import EUMType, EUMUnit, ItemInfo
+
+from mikecore.eum import eumItem, eumUnit
 
 
 def test_item_is_equivalent_to_int():
@@ -53,3 +55,15 @@ def test_eum_type_search():
     types = EUMType.search("velocity")
 
     assert len(types) > 0
+
+
+def test_eum_conversion():
+    """Verify that all EUM types and units in mikecore have equivalents in MIKE IO"""
+
+    for code in eumItem:
+        EUMType(code)
+
+    for code in eumUnit:
+        EUMUnit(code)
+
+    assert True