Source code for endf_parserpy.interpreter.fortran_utils

############################################################
#
# Author(s):       Georg Schnabel
# Email:           g.schnabel@iaea.org
# Creation date:   2022/05/30
# Last modified:   2024/12/07
# License:         MIT
# Copyright (c) 2022-2024 International Atomic Energy Agency (IAEA)
#
############################################################

from .custom_exceptions import InvalidIntegerError, InvalidFloatError
from math import log10, floor
from copy import deepcopy
from ..utils.math_utils import EndfFloat


def read_fort_int(valstr):
    if valstr.strip() == "":
        return 0
    else:
        try:
            return int(valstr)
        except ValueError as valerr:
            raise InvalidIntegerError(valerr)


[docs] def fortstr2float(valstr, read_opts=None): """Convert a Fortran number string to float. Convert a number string to a :class:`float`. Fortran number strings with the ``e`` character omitted are supported. Parameters ---------- valstr : str String that should be converted to :class:`float`. read_opts : Optional[dict] Dictionary with field/value pairs to influence conversion process. Supported options are ``accept_spaces`` and ``preserve_value_strings``. Consider the equally named options of the :class:`~endf_parserpy.EndfParser` class for an explanation of their meaning. Returns ------- float :class:`float` that corresponds to string representation of number. """ orig_valstr = valstr if read_opts is None: read_opts = {} accept_spaces = read_opts.get("accept_spaces", True) preserve_value_strings = read_opts.get("preserve_value_strings", False) width = read_opts.get("width", None) if width is not None: valstr = valstr[:width] if valstr.strip() == "": return 0.0 if accept_spaces: valstr = valstr.replace(" ", "") for i, c in enumerate(valstr): if i > 0 and (c == "+" or c == "-"): if valstr[i - 1].isdigit(): valstr = valstr[:i] + "E" + valstr[i:] try: if preserve_value_strings: return EndfFloat(valstr, orig_valstr) else: return float(valstr) except ValueError as valerr: raise InvalidFloatError(valerr)
def float2basicnumstr(val, write_opts=None): width = write_opts.get("width", 11) effwidth = width abuse_signpos = write_opts.get("abuse_signpos", False) skip_intzero = write_opts.get("skip_intzero", False) intpart = int(val) len_intpart = len(str(abs(intpart))) is_integer = intpart == val if is_integer and intpart == 0: return "0".rjust(effwidth) # -1 due to a minus sign slot # -1 due to the decimal point waste_space = 2 if abuse_signpos and val > 0: waste_space -= 1 should_skip_zero = skip_intzero and intpart == 0 if should_skip_zero: effwidth += 1 if is_integer: waste_space -= 1 floatwidth = effwidth - waste_space - len_intpart if floatwidth > 0 and not is_integer: numstr = f"{{:{effwidth}.{floatwidth}f}}".format(val) if should_skip_zero: dotpos = numstr.index(".") numstr = numstr[: dotpos - 1] + numstr[dotpos:] else: numstr = "{:d}".format(int(val)) if val > 0 and not abuse_signpos: numstr = " " + numstr if len(numstr) <= width - 2: numstr += "." numstr = numstr.ljust(width, "0") numstr = numstr.rjust(width) return numstr def float2expformstr(val, write_opts=None): width = write_opts.get("width", 11) abuse_signpos = write_opts.get("abuse_signpos", False) keep_E = write_opts.get("keep_E", False) av = abs(val) if av >= 1e-9 and av < 1e10: nexp = 1 elif av >= 1e-99 and av < 1e100: nexp = 2 elif av == 0.0: nexp = 1 else: nexp = 3 is_pos = val >= 0 sign_dec = 0 if abuse_signpos and is_pos else 1 expsymb_dec = 1 if keep_E else 0 exponent = floor(log10(av)) if av != 0 else 0 mantissa = abs(val / 10**exponent) is_expo_pos = exponent >= 0 absexponent = abs(exponent) mantissa_len = width - 1 - nexp - sign_dec - expsymb_dec mantissa_str = f"{{:.{mantissa_len-2}f}}".format(mantissa) expsymb_str = "E" if keep_E else "" exposign_str = "+" if is_expo_pos else "-" exponent_str = f"{{:{nexp}d}}".format(absexponent) if is_pos: sign_str = "" if abuse_signpos else " " else: sign_str = "-" res_str = sign_str + mantissa_str + expsymb_str + exposign_str + exponent_str if len(res_str) > width: # special case: we have 9.999999999 in the mantissa, which will # be rounded to 10.0000000 so we have too many digits. # Hack: Pass the rounded number again to this function. tmp_str = sign_str + mantissa_str + "e" + exposign_str + exponent_str res_str = float2expformstr(float(tmp_str), write_opts=write_opts) return res_str
[docs] def float2fortstr(val, write_opts=None): """Convert a float value to string. This function converts a :class:`float` value to a string representation. Various options are supported to influence the conversion process. Parameters ---------- val : float The float variable/number whose string representation is desired. write_opts : dict Python dictionary with field/value pairs to influence conversion process. Supported field names are ``prefer_noexp``, ``width``, ``abuse_signpos``, ``keep_E``, and ``skip_intzero``. Consider the equally named arguments of the :class:`~endf_parserpy.EndfParser` constructor for an explanation of these options. Returns ------- str String representation of the :class:`float` number """ width = write_opts.get("width", 11) if isinstance(val, EndfFloat): orig_str = val.get_original_string() if width != len(orig_str): raise InvalidFloatError( f"Length of string representation of float number " f"'{orig_str}' incompatible with specified width={width}" ) return orig_str prefer_noexp = write_opts.get("prefer_noexp", False) valstr_exp = float2expformstr(val, write_opts=write_opts) if not prefer_noexp: return valstr_exp valstr_basic = float2basicnumstr(val, write_opts=write_opts) if "." in valstr_basic: valstr_basic = valstr_basic.rstrip("0").rstrip(".") # next line to deal with case -.00 and .00 if valstr_basic in ("+", "-", ""): valstr_basic = "0" if len(valstr_basic) > width: return valstr_exp delta1 = abs(fortstr2float(valstr_basic) - val) delta2 = abs(fortstr2float(valstr_exp) - val) if delta2 < delta1: return valstr_exp valstr_basic = valstr_basic.rjust(width) return valstr_basic
[docs] def read_fort_floats(line, n=6, read_opts=None): """Read several floats from a string. The string representations of each number are assumed to be stored one-after-another in text fields of a fixed size. Parameters ---------- line : str String containing numbers to read n : int Number of float numbers to read read_opts : Optional[dict] The field ``width`` specifies the number of character-slots assigned to each float. For the other available options, see the help of :func:`fortstr2float`. Returns ------- list[float] A list with the extracted :class:`float` numbers """ width = read_opts.get("width", 11) assert isinstance(line, str) vals = [] for i in range(0, n * width, width): vals.append(fortstr2float(line[i : i + width], read_opts=read_opts)) return vals
[docs] def write_fort_floats(vals, write_opts=None): """Write several floats to a string. Floats are written as text fields of fixed width one-after-another. Parameters ---------- vals : list[float] write_opts : Optional[dict] Dictionary with options to influence number formatting, see help of :func:`float2fortstr` for available options. Returns ------- str String with numbers written one-after-another in text fields of fixed width. """ line = "" for i, v in enumerate(vals): line += float2fortstr(v, write_opts=write_opts) return line