Source code for iode.iode_database.scalars_database

import sys
from pathlib import Path
from typing import Union, Tuple, List, Dict, Optional, Any
if sys.version_info.minor >= 11:
    from typing import Self
else:
    Self = Any

import pandas as pd
from iode.util import table2str, JUSTIFY
from iode.objects.scalar import Scalar
from iode.iode_database.abstract_database import IodeDatabase, PositionalIndexer
from iode.iode_cython import Scalars as CythonScalars

ScalarInput = Union[int, float, List[float], Tuple[float, float], Dict[str, float], Scalar]


[docs] class Scalars(IodeDatabase): r""" IODE Scalars database. Attributes ---------- filename: str description: str Parameters ---------- filepath: str, optional file containing the IODE scalars to load. Returns ------- Scalars Examples -------- >>> from iode import scalars, SAMPLE_DATA_DIR >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> len(scalars) 161 >>> scalars # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Workspace: Scalars nb scalars: 161 filename: ...\tests\data\fun.scl <BLANKLINE> name value relax std acaf1 0.0158 1.0000 0.0014 acaf2 -0.0000 1.0000 0.0000 acaf3 2.5026 1.0000 0.8730 acaf4 -0.0085 1.0000 0.0021 dlnpaf 0.9000 1.0000 na ... ... ... ... y6 0.0000 0.0000 0.0000 y7 -0.1550 1.0000 0.3239 zkf1 0.2011 1.0000 0.3757 zkf2 0.7921 1.0000 0.1812 zkf3 -7.2712 1.0000 2.6764 <BLANKLINE> """
[docs] def __init__(self, filepath: str=None): raise TypeError("This class cannot be instantiated directly.")
@classmethod def get_instance(cls) -> Self: instance = cls.__new__(cls) instance._cython_instance = CythonScalars() instance = cls._get_instance(instance) return instance def _load(self, filepath: str): self._cython_instance._load(filepath) def _subset(self, pattern: str, copy: bool) -> Self: instance = Scalars.get_instance() instance._cython_instance = self._cython_instance.initialize_subset(instance._cython_instance, pattern, copy) instance = self._subset_(instance, copy) return instance @property def i(self) -> PositionalIndexer: r""" Allow to select the ith scalar in the database. Examples -------- >>> from iode import scalars, SAMPLE_DATA_DIR >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> # get the first scalar >>> scalars.i[0] Scalar(0.0157684, 1, 0.00136871) >>> # get the last scalar >>> scalars.i[-1] Scalar(-7.27124, 1, 2.6764) >>> # update first scalar >>> scalars.i[0] = 0.02 >>> scalars.i[0] Scalar(0.02, 1, na) >>> # update last scalar >>> scalars.i[-1] = -7.3, 0.5 >>> scalars.i[-1] Scalar(-7.3, 0.5, na) """ return PositionalIndexer(self) def _get_object(self, key: Union[str, int]) -> Scalar: name = self._single_object_key_to_name(key) if not name in self: raise KeyError(f"Name '{name}' not found in the {type(self).__name__} workspace") scalar = Scalar.get_instance() scalar._cython_instance = self._cython_instance._get_object(name, scalar._cython_instance) return scalar def _set_object(self, key: Union[str, int], value): name = self._single_object_key_to_name(key) if isinstance(value, int): value = float(value) if isinstance(value, pd.Series): value = value.to_dict() # update a scalar if name in self: if isinstance(value, float): scalar = self._get_object(name) scalar.value = value elif isinstance(value, Scalar): scalar = value elif isinstance(value, (tuple, list)) and all(isinstance(elem, (int, float)) for elem in value): if len(value) > 2: raise ValueError(f"Cannot update scalar '{name}': Expected input to be a tuple or list of length 2. " f"Got {type(value).__name__} of length {len(value)}") scalar = self._get_object(name) scalar.value = float(value[0]) scalar.relax = float(value[1]) elif isinstance(value, dict): scalar = self._get_object(name) scalar.value = value.pop('value', scalar.value) scalar.relax = value.pop('relax', scalar.relax) if len(value): raise ValueError(f"Cannot update scalar '{name}': only 'value' and 'relax' keys are accepted. " f"Got unknown key(s): {';'.join(value.keys())}") else: raise TypeError(f"Cannot update scalar '{name}': Expected input to be of type float or tuple(float, float) " f"or list(float, float) or dict(str, float) or Scalar. Got value of type {type(value).__name__}") # add a new scalar else: if isinstance(value, float): scalar = Scalar(value) elif isinstance(value, Scalar): scalar = value elif isinstance(value, (tuple, list)) and all(isinstance(elem, (int, float)) for elem in value): if len(value) > 2: raise ValueError(f"Cannot add scalar '{name}': Expected input to be a tuple or list of length 2. " f"Got {type(value).__name__} of length {len(value)}") scalar = Scalar(float(value[0]), float(value[1])) elif isinstance(value, dict): scalar = Scalar(**value) else: raise TypeError(f"Cannot add scalar '{name}': Expected input to be of type float or tuple(float, float) " f"or list(float, float) or dict(str, float) or Scalar. Got value of type {type(value).__name__}") self._cython_instance._set_object(name, scalar._cython_instance)
[docs] def __getitem__(self, key: Union[str, List[str]]) -> Union[Scalar, Self]: r""" Return the (subset of) scalar(s) referenced by `key`. The `key` can represent a single object name (e.g. "ACAF") or a list of object names ("ACAF;ACAG;AOUC") or a pattern (e.g. "A*") or a list of sub-patterns (e.g. "A*;*_"). If the `key` represents a list of object names or of sub-patterns, each name or sub-pattern is separated by a `separator` character which is either a whitespace ` `, or a comma `,`, or a semi-colon `;`, or a tabulation `\t`, or a newline `\n`. A (sub-)`pattern` is a list of characters representing a group of object names. It includes some special characters which have a special meaning: - `*` : any character sequence, even empty - `?` : any character (one and only one) - `@` : any alphanumerical char [A-Za-z0-9] - `&` : any non alphanumerical char - `|` : any alphanumeric character or none at the beginning and end of a string - `!` : any non-alphanumeric character or none at the beginning and end of a string - `\` : escape the next character Note that the `key` can contain references to IODE lists which are prefixed with the symbol `$`. Parameters ---------- key: str or list(str) (the list of) name(s) of the scalar(s) to get. The list of scalars to get can be specified by a pattern or by a list of sub-patterns (e.g. "A*;*_"). Returns ------- Single scalar or a subset of the database. Examples -------- >>> from iode import SAMPLE_DATA_DIR >>> from iode import scalars >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> # a) get one scalar >>> acaf1 = scalars["acaf1"] >>> acaf1 Scalar(0.0157684, 1, 0.00136871) >>> acaf1.value 0.01576840691268444 >>> acaf1.relax 1.0 >>> acaf1.std 0.0013687137980014086 >>> # b) get a subset of the Scalars database using a pattern >>> scalars_subset = scalars["a*"] >>> scalars_subset.names ['acaf1', 'acaf2', 'acaf3', 'acaf4'] >>> # c) get a subset of the Scalars database using a list of names >>> scalars_subset = scalars[["acaf1", "acaf4", "dpuh_1", "dpuh_2"]] >>> scalars_subset.names ['acaf1', 'acaf4', 'dpuh_1', 'dpuh_2'] """ return super().__getitem__(key)
[docs] def __setitem__(self, key: Union[str, List[str]], value: Union[float, Tuple[float, float], Scalar, Dict[str, Any], List[Union[float, Tuple[float, float], Scalar, Dict[str, Any]]]]): r""" Update/add a (subset of) scalar(s) referenced by `key` from/to the Scalars database. The `key` can represent a single object name (e.g. "ACAF") or a list of object names ("ACAF;ACAG;AOUC") or a pattern (e.g. "A*") or a list of sub-patterns (e.g. "A*;*_"). If the `key` represents a list of object names or of sub-patterns, each name or sub-pattern is separated by a `separator` character which is either a whitespace ` `, or a comma `,`, or a semi-colon `;`, or a tabulation `\t`, or a newline `\n`. A (sub-)`pattern` is a list of characters representing a group of object names. It includes some special characters which have a special meaning: - `*` : any character sequence, even empty - `?` : any character (one and only one) - `@` : any alphanumerical char [A-Za-z0-9] - `&` : any non alphanumerical char - `|` : any alphanumeric character or none at the beginning and end of a string - `!` : any non-alphanumeric character or none at the beginning and end of a string - `\` : escape the next character Note that the `key` can contain references to IODE lists which are prefixed with the symbol `$`. Parameters ---------- key: str or list(str) (the list of) name(s) of the scalar(s) to update/add. The list of scalars to update/add can be specified by a pattern or by a list of sub-patterns (e.g. "A*;*_"). value: float or tuple(float, float) or Scalar or dict(str, ...) or pd.Series or pd.DataFrame or Scalars If float, then it is interpreted as the value of the scalar. If tuple of two float, then it is interpreted as the value and relax of the scalar. The standard deviation is computed during the estimation process and cannot be modified manually. If Scalar, then it is used to update an existing scalar or to create a new scalar if it does not exist yet. If pandas Series, then it is interpreted as a list of couples (scalar name, scalar value). If pandas DataFrame, then only the columns names 'value' and 'relax' are allowed. See Also -------- iode.Scalar Examples -------- >>> import pandas as pd >>> from iode import SAMPLE_DATA_DIR >>> from iode import scalars, Scalar >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> # a) -------- add one scalar -------- >>> # 1. default relax to 1.0 >>> scalars["a0"] = 0.1 >>> scalars["a0"] Scalar(0.1, 1, na) >>> # 2. value + relax >>> scalars["a1"] = 0.1, 0.9 >>> scalars["a1"] Scalar(0.1, 0.9, na) >>> # b) -------- update one scalar -------- >>> scalars["acaf1"] Scalar(0.0157684, 1, 0.00136871) >>> # only update the value >>> scalars["acaf1"] = 0.8 >>> scalars["acaf1"] Scalar(0.8, 1, na) >>> # update value and relax (tuple) >>> scalars["acaf2"] = 0.8, 0.9 >>> scalars["acaf2"] Scalar(0.8, 0.9, na) >>> # update value and relax (list) >>> scalars["acaf2"] = (0.7, 0.8) >>> scalars["acaf2"] Scalar(0.7, 0.8, na) >>> # update value and relax (dictionary) >>> scalars["acaf3"] = {"relax": 0.9, "value": 0.8} >>> scalars["acaf3"] Scalar(0.8, 0.9, na) >>> # update value and/or relax (Scalar object) >>> # NOTE: the standard deviation (std) cannot be changed manually >>> scalars["acaf4"] Scalar(-0.00850518, 1, 0.0020833) >>> scalars["acaf4"].value = 0.8 >>> scalars["acaf4"].relax = 0.9 >>> scalars["acaf4"] Scalar(0.8, 0.9, na) >>> # c) add/update multiple scalars at once >>> # 1) using a dict of values >>> values = {"acaf1": 0.016, "acaf2": (-8.e-04, 0.9), "acaf3": Scalar(2.5)} >>> scalars["acaf1, acaf2, acaf3"] = values >>> scalars["acaf1, acaf2, acaf3"] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Workspace: Scalars nb scalars: 3 filename: ...fun.scl <BLANKLINE> name value relax std acaf1 0.0160 1.0000 na acaf2 -0.0008 0.9000 na acaf3 2.5000 1.0000 na <BLANKLINE> >>> # 2) using a pandas series (only scalar's values) >>> data = [0.015, -9.e-04, 2.8] >>> series = pd.Series(data, index=["acaf1", "acaf2", "acaf3"]) >>> scalars["acaf1, acaf2, acaf3"] = series >>> scalars["acaf1, acaf2, acaf3"] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Workspace: Scalars nb scalars: 3 filename: ...fun.scl <BLANKLINE> name value relax std acaf1 0.0150 1.0000 na acaf2 -0.0009 0.9000 na acaf3 2.8000 1.0000 na <BLANKLINE> >>> # 3) using a pandas DataFrame (value + relax) >>> data = [(0.014, 0.98), (-7.e-04, 0.95), (2.3, 0.92)] >>> df = pd.DataFrame(data, index=["acaf1", "acaf2", "acaf3"], columns=["value", "relax"]) >>> scalars["acaf1, acaf2, acaf3"] = df >>> scalars["acaf1, acaf2, acaf3"] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Workspace: Scalars nb scalars: 3 filename: ...fun.scl <BLANKLINE> name value relax std acaf1 0.0140 0.9800 na acaf2 -0.0007 0.9500 na acaf3 2.3000 0.9200 na <BLANKLINE> >>> # 4) using another Scalars database (subset) >>> scalars_subset = scalars["acaf1, acaf2, acaf3"].copy() >>> scalars_subset["acaf1"] = 0.02 >>> scalars_subset["acaf2"] = (-5.e-04, 0.94) >>> scalars_subset["acaf3"] = Scalar(2.9) >>> scalars["acaf1, acaf2, acaf3"] = scalars_subset >>> scalars["acaf1, acaf2, acaf3"] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Workspace: Scalars nb scalars: 3 filename: ...fun.scl <BLANKLINE> name value relax std acaf1 0.0200 0.9800 na acaf2 -0.0005 0.9400 na acaf3 2.9000 1.0000 na <BLANKLINE> >>> # d) working on a subset >>> # 1) get subset >>> scalars_subset = scalars["a*"] >>> scalars_subset.names ['a0', 'a1', 'acaf1', 'acaf2', 'acaf3', 'acaf4'] >>> # 2) add a scalar to the subset >>> scalars_subset["acaf0"] = 1.0, 1.0 >>> scalars_subset["acaf0"] Scalar(1, 1, na) >>> # --> new scalar also appears in the global workspace >>> "acaf0" in scalars True >>> scalars["acaf0"] Scalar(1, 1, na) >>> # 3) update a scalar in the subset >>> scalars_subset["acaf0"] = 0.1 >>> scalars_subset["acaf0"] Scalar(0.1, 1, na) >>> # --> scalar is also updated in the global workspace >>> scalars["acaf0"] Scalar(0.1, 1, na) """ super().__setitem__(key, value)
[docs] def __delitem__(self, key): r""" Remove the (subset of) scalar(s) referenced by `key` from the Scalars database. Parameters ---------- key: str or list(str) (list of) name(s) of the scalar(s) to be removed. The list of names can be given as a string pattern (e.g. "A*;*_"). Examples -------- >>> from iode import SAMPLE_DATA_DIR >>> from iode import scalars >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> # a) delete one scalar >>> scalars.get_names("a*") ['acaf1', 'acaf2', 'acaf3', 'acaf4'] >>> del scalars["acaf4"] >>> scalars.get_names("a*") ['acaf1', 'acaf2', 'acaf3'] >>> # b) delete several scalars at once using a pattern >>> del scalars["a*"] >>> scalars.get_names("a*") [] >>> # c) delete several scalars at once using a list of names >>> scalars.get_names("g*") ['gamma0', 'gamma1', 'gamma2', 'gamma3', 'gamma4', 'gamma5', 'gamma_'] >>> del scalars[["gamma3", "gamma_"]] >>> scalars.get_names("g*") ['gamma0', 'gamma1', 'gamma2', 'gamma4', 'gamma5'] >>> # delete one scalar from a subset of the global database >>> scalars_subset = scalars["z*"] >>> scalars_subset.names ['zkf1', 'zkf2', 'zkf3'] >>> del scalars_subset["zkf2"] >>> scalars_subset.names ['zkf1', 'zkf3'] >>> # NOTE: the scalar has also been deleted from the global database >>> "zkf2" in scalars False >>> scalars.get_names("z*") ['zkf1', 'zkf3'] """ super().__delitem__(key)
[docs] def copy_from(self, input_files: Union[str, List[str]], names: Union[str, List[str]]='*'): r""" Copy (a subset of) scalars from the input file(s) 'input_files' into the current database. Parameters ---------- input_file: str or list(str) file(s) from which the copied scalars are read. names: str or list(str) list of scalars to copy from the input file(s). Defaults to load all scalars from the input file(s). """ input_files, names = self._copy_from(input_files, names) self._cython_instance.copy_from(input_files, names)
[docs] def from_series(self, s: pd.Series): r""" Copy the pandas Series `s` into the IODE Scalars database. The scalar names to copy are deduced from the index of the Series. Parameters ---------- s: Series pandas Series containing the scalars values to copy into the Scalars database. The 'relax' value of all scalars will be set to 1. Notes ----- The index of the passed Series is sorted in alphabetical order before copying to IODE Scalars database. See Also -------- Scalars.from_frame Scalars.to_frame Examples -------- >>> from iode import scalars >>> import pandas as pd >>> scalars.clear() >>> len(scalars) 0 >>> # create the pandas Series >>> data = {"alpha_0": 0.9, "alpha_1": 0.1, "alpha_": 5.5, ... "beta_0": 0.9, "beta_1": 0.01, "beta_": 3.6} >>> s = pd.Series(data, dtype="float64") >>> # display the pandas series >>> s # doctest: +NORMALIZE_WHITESPACE alpha_0 0.90 alpha_1 0.10 alpha_ 5.50 beta_0 0.90 beta_1 0.01 beta_ 3.60 dtype: float64 >>> # load into the IODE Scalars database >>> scalars.from_series(s) >>> len(scalars) 6 >>> scalars.names ['alpha_', 'alpha_0', 'alpha_1', 'beta_', 'beta_0', 'beta_1'] >>> scalars["alpha_1"] Scalar(0.1, 1, na) >>> scalars["beta_"] Scalar(3.6, 1, na) """ if not (self.is_global_workspace or self.is_detached): # check that all names in the pandas object are present in the current subset self._check_same_names(self.names, s.index.tolist()) for index, value in s.items(): self._set_object(index, value)
[docs] def from_frame(self, df: pd.DataFrame): r""" Copy the pandas DataFrame `df` into the IODE Scalars database. The scalar names to copy are deduced from the index of the DataFrame. Parameters ---------- df: DataFrame pandas DataFrame containing the scalars to copy into the Scalars database. The passed DataFrame should have the following structure: - index: names of the scalars - a column labeled 'value': values of the scalars. - a column labeled 'relax' (optional): relaxation parameter values of the scalars. - a column labeled 'std' (optional): standard deviation values resulting from estimation. If the DataFrame does not contain a 'relax' column, relax values will be all set to 1. If the DataFrame does not contain a 'std' column, std values will be all set to :math:`NA`. Notes ----- The index of the passed DataFrame is sorted in alphabetical order before copying to IODE Scalars database. See Also -------- Scalars.from_series Scalars.to_frame Examples -------- >>> from iode import scalars >>> import pandas as pd >>> import numpy as np >>> scalars.clear() >>> len(scalars) 0 >>> # create the pandas DataFrame >>> data = {"alpha_0": [0.9, 1., np.nan], ... "alpha_1": [0.1, 0.8, np.nan], ... "alpha_": [5.5, 0., np.nan], ... "beta_0": [0.9, 1., 0.05246], ... "beta_1": [0.01, 0.8, 0.25687], ... "beta_": [3.6, 0., 0.]} >>> df = pd.DataFrame.from_dict(data, orient='index', dtype="float64", ... columns=["value", "relax", "std"]) >>> # display the pandas series >>> df # doctest: +NORMALIZE_WHITESPACE value relax std alpha_0 0.90 1.0 NaN alpha_1 0.10 0.8 NaN alpha_ 5.50 0.0 NaN beta_0 0.90 1.0 0.05246 beta_1 0.01 0.8 0.25687 beta_ 3.60 0.0 0.00000 >>> # load into the IODE Scalars database >>> scalars.from_frame(df) >>> len(scalars) 6 >>> scalars.names ['alpha_', 'alpha_0', 'alpha_1', 'beta_', 'beta_0', 'beta_1'] >>> df.loc["alpha_1"] value 0.1 relax 0.8 std NaN Name: alpha_1, dtype: float64 >>> scalars["alpha_1"] Scalar(0.1, 0.8, na) >>> df.loc["beta_1"] value 0.01000 relax 0.80000 std 0.25687 Name: beta_1, dtype: float64 >>> scalars["beta_1"] Scalar(0.01, 0.8, 0.25687) """ if not (self.is_global_workspace or self.is_detached): # check that all names in the pandas object are present in the current subset self._check_same_names(self.names, df.index.to_list()) if 'value' not in df.columns: raise ValueError("Expected at least one column with name 'value'. " f"Got dataframe with column names {df.columns.to_list()}") if 'relax' not in df.columns: self.from_series(df['value']) else: is_std_values = 'std' in df.columns for row in df.itertuples(name='Scalar'): scalar = Scalar(row.value, row.relax) if is_std_values: scalar._set_std(row.std) self._set_object(row[0], scalar)
[docs] def to_frame(self) -> pd.DataFrame: r""" Create a pandas DataFrame from the current Scalars database. The index of the returned DataFrame is build from the Scalars names. See Also -------- Scalars.from_series Scalars.from_frame Examples -------- >>> from iode import SAMPLE_DATA_DIR, scalars >>> import pandas as pd >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> len(scalars) 161 >>> # Export the IODE Scalars database as a pandas DataFrame >>> df = scalars.to_frame() >>> len(df) 161 >>> df.index.to_list() # doctest: +ELLIPSIS ['acaf1', 'acaf2', 'acaf3', ..., 'zkf1', 'zkf2', 'zkf3'] >>> scalars["acaf1"] # doctest: +NORMALIZE_WHITESPACE Scalar(0.0157684, 1, 0.00136871) >>> df.loc["acaf1"] # doctest: +NORMALIZE_WHITESPACE value 0.015768 relax 1.000000 std 0.001369 Name: acaf1, dtype: float64 >>> scalars["qc0_"] # doctest: +NORMALIZE_WHITESPACE Scalar(0.178165, 1, 0.102838) >>> df.loc["qc0_"] # doctest: +NORMALIZE_WHITESPACE value 0.178165 relax 1.000000 std 0.102838 Name: qc0_, dtype: float64 >>> # Export a subset of the IODE Scalars database as a pandas DataFrame >>> df = scalars["a*;*_"].to_frame() >>> len(df) 19 >>> df.index.to_list() # doctest: +ELLIPSIS ['acaf1', 'acaf2', 'acaf3', ..., 'vs1_', 'vs2_', 'vs3_'] >>> scalars["acaf1"] # doctest: +NORMALIZE_WHITESPACE Scalar(0.0157684, 1, 0.00136871) >>> df.loc["acaf1"] # doctest: +NORMALIZE_WHITESPACE value 0.015768 relax 1.000000 std 0.001369 Name: acaf1, dtype: float64 >>> scalars["qc0_"] # doctest: +NORMALIZE_WHITESPACE Scalar(0.178165, 1, 0.102838) >>> df.loc["qc0_"] # doctest: +NORMALIZE_WHITESPACE value 0.178165 relax 1.000000 std 0.102838 Name: qc0_, dtype: float64 """ data = {name: self._get_object(name)._as_tuple() for name in self.names} return pd.DataFrame.from_dict(data, orient='index', dtype="float64", columns=["value", "relax", "std"])
@property def df(self) -> pd.DataFrame: r""" Create a pandas DataFrame from the current Scalars database. The index of the returned DataFrame is build from the Scalars names. See Also -------- Scalars.to_frame Examples -------- >>> from iode import SAMPLE_DATA_DIR, scalars >>> import pandas as pd >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> len(scalars) 161 >>> # Export the IODE Scalars database as a pandas DataFrame >>> df = scalars.df >>> len(df) 161 >>> df.index.to_list() # doctest: +ELLIPSIS ['acaf1', 'acaf2', 'acaf3', ..., 'zkf1', 'zkf2', 'zkf3'] >>> scalars["acaf1"] # doctest: +NORMALIZE_WHITESPACE Scalar(0.0157684, 1, 0.00136871) >>> df.loc["acaf1"] # doctest: +NORMALIZE_WHITESPACE value 0.015768 relax 1.000000 std 0.001369 Name: acaf1, dtype: float64 >>> scalars["qc0_"] # doctest: +NORMALIZE_WHITESPACE Scalar(0.178165, 1, 0.102838) >>> df.loc["qc0_"] # doctest: +NORMALIZE_WHITESPACE value 0.178165 relax 1.000000 std 0.102838 Name: qc0_, dtype: float64 >>> # Export a subset of the IODE Scalars database as a pandas DataFrame >>> df = scalars["a*;*_"].df >>> len(df) 19 >>> df.index.to_list() # doctest: +ELLIPSIS ['acaf1', 'acaf2', 'acaf3', ..., 'vs1_', 'vs2_', 'vs3_'] >>> scalars["acaf1"] # doctest: +NORMALIZE_WHITESPACE Scalar(0.0157684, 1, 0.00136871) >>> df.loc["acaf1"] # doctest: +NORMALIZE_WHITESPACE value 0.015768 relax 1.000000 std 0.001369 Name: acaf1, dtype: float64 >>> scalars["qc0_"] # doctest: +NORMALIZE_WHITESPACE Scalar(0.178165, 1, 0.102838) >>> df.loc["qc0_"] # doctest: +NORMALIZE_WHITESPACE value 0.178165 relax 1.000000 std 0.102838 Name: qc0_, dtype: float64 """ return self.to_frame() def _str_table(self, names: List[str]) -> str: data = [[name] + list(self._get_object(name)._as_tuple()) for name in names] # transpose data data = [list(row) for row in zip(*data)] columns = {"name": data[0], "value": data[1], "relax": data[2], "std": data[3]} return table2str(columns, max_lines=10, precision=4, justify_funcs={"name": JUSTIFY.LEFT}) @property def print_nb_decimals(self) -> int: r""" Number of decimals to print. Parameters ---------- value: int number of decimals to print (equations coeffs, scalars and computed tables values). Examples -------- >>> from iode import scalars >>> scalars.print_nb_decimals = 4 >>> scalars.print_nb_decimals 4 """ return self._get_print_nb_decimals() @print_nb_decimals.setter def print_nb_decimals(self, value: int): self._set_print_nb_decimals(value)
[docs] def print_to_file(self, filepath: Union[str, Path], names: Union[str, List[str]]=None, format: str=None): r""" Print the list scalars defined by `names` to the file `filepath` using the format `format`. Argument `format` must be in the list: - 'H' (HTML file) - 'M' (MIF file) - 'R' (RTF file) - 'C' (CSV file) If argument `format` is null (default), the *A2M* format will be used to print the output. If the filename does not contain an extension, it is automatically added based on the value of `format`. If `names` is a string, it is considered as a *pattern* and the function will print all scalars matching the pattern. The following characters in *pattern* have a special meaning: - `*` : any character sequence, even empty - `?` : any character (one and only one) - `@` : any alphanumerical char [A-Za-z0-9] - `&` : any non alphanumerical char - `|` : any alphanumeric character or none at the beginning and end of a string - `!` : any non-alphanumeric character or none at the beginning and end of a string - `\` : escape the next character If `names` is None, print all scalars of the (subset of the) current database. Parameters ---------- filepath: str or Path path to the file to print. If the filename does not contain an extension, it is automatically added based on the value of the format argument. names: str or list of str, optional pattern or list of names of the scalars to print. If None, print all scalars of the (subset of the) current database. Defaults to None. format: str, optional format of the output file. Possible values are: 'H' (HTML file), 'M' (MIF file), 'R' (RTF file) or 'C' (CSV file). Defaults to None meaning that the scalars will be dumped in the *A2M* format. Examples -------- >>> from iode import scalars, SAMPLE_DATA_DIR >>> output_dir = getfixture('tmp_path') >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> scalars.print_nb_decimals = 4 >>> scalars.print_to_file(output_dir / "scalars.csv", "a*") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Printing IODE objects definition to file '...scalars.csv'... Printing acaf1 ... Printing acaf2 ... Printing acaf3 ... Printing acaf4 ... Print done >>> with open(output_dir / "scalars.csv") as f: # doctest: +NORMALIZE_WHITESPACE ... print(f.read()) ... " - acaf1 : 0.0158 (1, 0.0014, 11.5206)" " - acaf2 : -0.0000 (1, 0.0000, -5.3691)" " - acaf3 : 2.5026 (1, 0.8730, 2.8666)" " - acaf4 : -0.0085 (1, 0.0021, -4.0825)" <BLANKLINE> """ super().print_to_file(filepath, names, format)
def __hash__(self) -> int: r""" Return a hash value for the current Scalars database. Examples -------- >>> from iode import SAMPLE_DATA_DIR, scalars >>> scalars.load(f"{SAMPLE_DATA_DIR}/fun.scl") # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Loading .../fun.scl 161 objects loaded >>> len(scalars) 161 >>> original_hash = hash(scalars) >>> # rename 1 scalar >>> scalars.rename("acaf1", "acaf1_") >>> original_hash == hash(scalars) False >>> scalars.rename("acaf1_", "acaf1") # revert the change >>> original_hash == hash(scalars) True >>> # modify one scalar >>> saved_scalar = scalars["acaf1"].copy() >>> scalars["acaf1"] Scalar(0.0157684, 1, 0.00136871) >>> scalars["acaf1"] = 0.02 >>> scalars["acaf1"] Scalar(0.02, 1, na) >>> original_hash == hash(scalars) # modified scalar False >>> scalars["acaf1"] = saved_scalar # revert the change >>> scalars["acaf1"] Scalar(0.0157684, 1, 0.00136871) >>> original_hash == hash(scalars) True >>> # delete a scalar >>> saved_scalar = scalars["acaf1"] >>> del scalars["acaf1"] >>> original_hash == hash(scalars) # deleted scalar False >>> scalars["acaf1"] = saved_scalar >>> original_hash == hash(scalars) True >>> # add a scalar >>> scalars["new_scalar"] = (0.9, 1.0) >>> original_hash == hash(scalars) # added scalar False >>> del scalars["new_scalar"] >>> original_hash == hash(scalars) True """ return super().__hash__()
scalars: Scalars = Scalars.get_instance()