Source code for mfexport.units

"""
Stuff for handling units
"""
import numpy as np

lenuni_values = {'unknown': 0,
                 'undefined': 0,
                 'feet': 1,
                 'meters': 2,
                 'centimeters': 3,
                 'millimeters': 4,
                 'kilometers': 9,
                 'inches': 10,
                 'miles': 11,
                 'ft': 1,
                 'm': 2,
                 'cm': 3,
                 'mm': 4,
                 'in': 10,
                 'mi': 11,
                 'km': 9,
                 'foot': 1,
                 'meter': 2,
                 'centimeter': 3,
                 'millimeter': 4,
                 'kilometer': 9,
                 'inch': 10,
                 'mile': 11,
                 }

fullnames = {'unknown', 'undefined', 'feet', 'meters', 'centimeters',
             'millimeters', 'inches', 'miles', 'kilometers',
             'seconds', 'minutes', 'hours', 'days', 'years',
             'liters', 'gallons', 'million gallons', 'acre-feet'
             }

abbreviations = {'ft', 'm', 'cm', 's', 'm', 'h', 'd', 'yr', 'mgal', 'L', 'gal', 'acre-ft'}
lenuni_text = {v: k for k, v in lenuni_values.items() if k in fullnames}
unit_abbreviated_text = {v: k for k, v in lenuni_values.items() if k in abbreviations}

volumetric_units = {'liters': 13,
                    'L': 13,
                    'gallons': 14,
                    'gallon': 14,
                    'gal': 14,
                    'mgal': 15,
                    'million gallons': 15,
                    'acre feet': 16,
                    'acre-feet': 16,
                    'af': 16,
                    'acre-ft': 16,
                    'acre foot': 16,
                    'acre-foot': 16
                    }
volumetric_units_text = {v: k for k, v in volumetric_units.items()
                         if k in fullnames}
unit_abbreviated_text.update({v: k for k, v in volumetric_units.items()
                              if k in abbreviations})


itmuni_values = {"unknown": 0,
                 "seconds": 1,
                 "minutes": 2,
                 "hours": 3,
                 "days": 4,
                 "years": 5,
                 "second": 1,
                 "minute": 2,
                 "hour": 3,
                 "day": 4,
                 "year": 5,
                 "s": 1,
                 "m": 2,
                 "h": 3,
                 "d": 4,
                 "y": 5
                 }

# convert from model length units to the unit abbreviations that pandas uses
pandas_units = {"seconds": "s",
                "minutes": "m",
                "hours": "h",
                "days": "D"
                }

itmuni_text = {v: k for k, v in itmuni_values.items() if k in fullnames}
itmuni_abbreviated_text = {v: k for k, v in itmuni_values.items() if k in abbreviations}


[docs] def get_time_units(model): """Return time units for model as text.""" if model.version == 'mf6': return model.simulation.tdis.time_units.array else: return itmuni_text[model.dis.itmuni]
[docs] def get_length_units(model): """Return length units for model as text.""" if model.version == 'mf6': return model.dis.length_units.array else: return lenuni_text[model.dis.lenuni]
[docs] def convert_length_units(lenuni1, lenuni2): """Convert length units, takes MODFLOW-2005 style lenuni numbers or MF-6 style text. Parameters ---------- lenuni1 : int or str Convert from. lenuni2 : int or str Convert to. Returns ------- mult : float Multiplier to convert from lenuni1 to lenuni2. """ if lenuni1 is None or lenuni2 is None: return 1. if isinstance(lenuni1, str): lenuni1 = lenuni_values.get(lenuni1.lower(), 0) if isinstance(lenuni2, str): lenuni2 = lenuni_values.get(lenuni2.lower(), 0) length_conversions = get_length_conversions() mult = length_conversions[lenuni1, lenuni2] return mult
[docs] def convert_time_units(itmuni1, itmuni2): """Convert time units, takes MODFLOW-2005 style itmuni numbers or MF-6 style text. Parameters ---------- itmuni1 : int or str Convert from. itmuni2 : int or str Convert to. Returns ------- mult : float Multiplier to convert from itmuni1 to itmuni2. """ if itmuni1 is None or itmuni2 is None: return 1. if isinstance(itmuni1, str): itmuni1 = itmuni_values.get(itmuni1.lower(), 0) if isinstance(itmuni2, str): itmuni2 = itmuni_values.get(itmuni2.lower(), 0) yearlen = 365.25 mults = {(1, 2): 1/60, (1, 3): 1/3600, (1, 4): 1/86400, (1, 5): 1/(86400 * yearlen), (2, 3): 1/60, (2, 4): 1/1440, (2, 5): 1/(1440 * yearlen), (3, 4): 1/24, (3, 5): 1/(24 * yearlen), (4, 5): 1/yearlen} convert_time_units = np.ones((6, 6), dtype=float) for (u0, u1), mult in mults.items(): convert_time_units[u0, u1] = mult convert_time_units[u1, u0] = 1/mult mult = convert_time_units[itmuni1, itmuni2] return mult
[docs] def get_length_conversions(): mults = {(1, 2): 1 * 0.3048, # feet to m (1, 3): 100 * 0.3048, (1, 4): 1000 * 0.3048, (1, 9): 1 * 0.3048 / 5280, # feet to km (1, 10): 1 * 12, (1, 11): 1 / 5280, # feet to miles (2, 3): 100, # meters to cm (2, 4): 1000, (2, 9): 1 / 1000, (2, 10): 1 * 12 / .3048, (2, 11): 1 / (.3048 * 5280), (3, 4): 10, # cm to mm (3, 9): 1 / (100 * 1000), (3, 10): 1 * 12 / (100 * .3048), (3, 11): 1 / (.3048 * 5280 * 100), (4, 9): 1 / 1e6, # mm to km (4, 10): 1 * 12 / (1000 * .3048), (4, 11): 1 / (.3048 * 5280 * 1000), } length_conversions = np.ones((12, 12), dtype=float) for (u0, u1), mult in mults.items(): length_conversions[u0, u1] = mult length_conversions[u1, u0] = 1 / mult return length_conversions
[docs] def get_volume_conversions(): length_conversions = get_length_conversions() m, n = length_conversions.shape size = np.max(list(volumetric_units.values())) + 1 volume_conversions = np.ones((size, size), dtype=float) volume_conversions[:m, :n] = length_conversions **3 mults = {(13, 1): (1/.3048**3)/1000, # liters to ft3 (13, 2): 1/1000, (13, 3): 1000, (13, 4): 1e6, (13, 10): (1/.3048**3)/1000/(12**3), # liters to cubic inches (13, 14): 1/3.78541, # liters to gallons (13, 15): 1/(3.78541 * 1e6), # liters to million gallons (13, 16): (1/.3048**3)/1000/43560, # liters to acre feet (14, 1): 1 / 7.48052, # gallons to ft3 (14, 2): (.3048**3) / 7.48052, # gallons to m3 (14, 3): 1e6 * (.3048**3) / 7.48052, # gallons to cm3 (14, 4): 1e9 * (.3048**3) / 7.48052, # gallons to mm3 (14, 10): 1/231, # gallons to cubic inches (14, 15): 1/1e6, # gallons to million gallons (14, 16): 1 / 7.48052 / 43560, # gallons to acre feet (15, 1): 1e6 / 7.48052, # million gallons to ft3 (15, 2): 1e6 * (.3048 ** 3) / 7.48052, # million gallons to m3 (15, 10): 1e6 / 231, (15, 16): 1e6 / 7.48052 / 43560, # million gallons to acre feet (16, 1): 1/43560, # acre feet to ft3 (16, 2): 1/43560 * (.3048 ** 3), # acre feet to m3 } for (u0, u1), mult in mults.items(): volume_conversions[u0, u1] = mult volume_conversions[u1, u0] = 1 / mult return volume_conversions
[docs] def get_unit_text(length_unit, time_unit, length_unit_exp): """Get text abbreviation for common units. Needs to be filled out more.""" if isinstance(length_unit, str): length_unit = lenuni_values.get(length_unit.lower(), 0) if isinstance(time_unit, str): time_unit = itmuni_values.get(time_unit.lower(), 0) text = {(1, 1, 3): 'cfs', (1, 4, 3): 'cfd', (2, 1, 3): 'cms', (2, 4, 3): 'cmd' } return text.get((length_unit, time_unit, length_unit_exp), 'units')
[docs] def convert_volume_units(input_volume_units, output_volume_units): if input_volume_units is None or output_volume_units is None: return 1. # if both units are expressed as lengths cubed in_units = parse_length_units(input_volume_units, text_output=False) if in_units is not None: if isinstance(in_units, str): in_units = lenuni_values.get(in_units.lower(), 0) else: in_units = volumetric_units.get(input_volume_units.lower(), 0) out_units = parse_length_units(output_volume_units, text_output=False) if out_units is not None: if isinstance(out_units, str): out_units = lenuni_values.get(out_units.lower(), 0) else: out_units = volumetric_units.get(output_volume_units.lower(), 0) # get the volume conversions matrix vol_conversions = get_volume_conversions() # look up the multiplier mult = vol_conversions[in_units, out_units] return mult
[docs] def convert_flux_units(input_length_units, input_time_units, output_length_units, output_time_units): # TODO: add support for areas and volumes lmult = convert_length_units(input_length_units, output_length_units) tmult = convert_time_units(input_time_units, output_time_units) return lmult / tmult
[docs] def parse_length_units(text, text_output=True): for k in volumetric_units.keys(): if k in text.lower(): return for k, v in lenuni_values.items(): if k in text: if text_output: return k else: return v
[docs] def convert_temperature_units(input_temp_units, output_temp_units): temp_units = {'celsius': 1, 'c': 1, 'fahrenheit': 2, 'f': 2 } input_temp_units = temp_units.get(input_temp_units.lower(), 0) output_temp_units = temp_units.get(output_temp_units.lower(), 0) def unknown(temp): return temp def c_to_f(temp): return temp * (9/5) + 32 def f_to_c(temp): return (5/9) * (temp - 32) conversions = {(1, 2): c_to_f, (2, 1): f_to_c} conversion = conversions.get((input_temp_units, output_temp_units), unknown) return conversion
[docs] def get_figure_label_unit_text(length_unit, time_unit=None, length_unit_exp=None): """Get text abbreviation for common units. Needs to be filled out more.""" if isinstance(length_unit, str): if length_unit.lower() in lenuni_values: length_unit = lenuni_values.get(length_unit.lower(), 0) else: length_unit = volumetric_units.get(length_unit.lower(), 0) # if volume-only units (liters, gallons, etc.), no exponent if length_unit != 0: length_unit_exp = None else: raise ValueError(f"Unrecognized length unit: {length_unit}") if isinstance(time_unit, str): time_unit = itmuni_values.get(time_unit.lower(), 0) else: raise ValueError(f"Unrecognized time unit: {time_unit}") text = f"${unit_abbreviated_text.get(length_unit, 'L')}" if length_unit_exp is not None: text += f"^{length_unit_exp}" if time_unit is not None: text += f"/{itmuni_abbreviated_text.get(time_unit, 'T')}$" else: text += '$' return text
[docs] def parse_flux_units(text): divby_char = {'/', 'per'} split_char = [c for c in divby_char if c in text] if split_char: split_char = split_char[0] length_units, time_units = text.split(split_char) exp = [c for c in length_units if c.isdigit()] if exp: if len(exp) > 0: exp = exp[0] exp = int(exp) else: exp = '' return length_units.strip(str(exp)).strip(), time_units.strip()