# -*- coding: utf-8 -*-
#
# This file is subject to the terms and conditions defined in
# file 'LICENSE.txt', which is part of this source code package.
#
""" Functions to parse text into different types
"""
from typing import Any, Type, Union, Iterable, Mapping, List, TypeVar, Callable
import math
import numpy as np
from Muscat.Types import MuscatFloat, MuscatIndex
from Muscat.Helpers.LocalVariables import ApplyGlobalDictionary
from Muscat.Helpers.SymExpr import SymExprBase
[docs]
def Read(inputData: Any, inout: Any):
"""Try to convert the data of inputData to the target type of inout
the string is treated to search and replace the values of known variable
using the global variable space of Muscat. please read py:class:Muscat.Helpers.LocalVariables
In the case the inout is a numpy array, we iterate on the inputData.
if inputData is a string we split it using the " " (space character)
Parameters
----------
inputData : Any
data to be converted can be a string "2 4 5"
inout : Any
an instance of the target type (we use only type(inout) )
Returns
-------
type(inout)
the input data converted to the type of inout
"""
if isinstance(inout,np.ndarray):
return ReadVector(inputData, inout.dtype.type)
elif isinstance(inout,list):
if len(inout) > 0:
return ReadVector(inputData, type(inout[0])).tolist()
else:
return ReadStrings(inputData)
else:
if inout is None:
inputData = ApplyGlobalDictionary(inputData)
return inputData
else:
return ReadScalar(inputData, type(inout))
R = TypeVar("R")
D = TypeVar("D")
[docs]
def ReadScalar(inputData: Any, inputType: Callable[[D], R]) -> R:
"""Try to convert the data of inputData to the target type of inout
if inputData is a string then it is treated to search and replace the values of known variable
using the global variable space of Muscat. please read py:class:Muscat.Helpers.LocalVariables
Parameters
----------
inputData : Any
source data can be a string or a scalar type (int, float, double, ...)
inputType : Type
the type to be used to do the conversion ( Callable[[D], Type] )
Returns
-------
Type instance
the data in inputData converted to type inputType
"""
if isinstance(inputData, str):
inputData = ApplyGlobalDictionary(inputData)
if inputType is bool:
return ReadBool(inputData)
try:
if type(inputData) is str:
return inputType(inputData.strip())
else:
return inputType(inputData)
except ValueError:
try:
return inputType(SymExprBase(inputData, derivatives=False).GetValue())
except TypeError as e:
print("Cannot convert expression to {}, expr : {}".format(inputType, inputData))
raise e
[docs]
def ReadVector(inputData: Iterable, dtype: Type) -> np.ndarray:
"""Convert inputData to a numpy array of dtype dtype
if inputData is a string, first we split it by the spaces
then convert every element to dtype
Parameters
----------
inputData : Iterable
input data to extract every element for the vector
dtype : Type
the target type for the conversion
Returns
-------
np.ndarray
array of type dtype with the converted elements
"""
if isinstance(inputData, (list, tuple, np.ndarray)):
return np.array([ReadScalar(x, dtype) for x in inputData], dtype=dtype)
elif isinstance(inputData,str):
tmp = inputData.lstrip().rstrip()
return np.array([ReadScalar(x, dtype) for x in tmp.split()], dtype=dtype)
else:
raise TypeError(f"type ({type(inputData)}) not supported for ReadVector")
[docs]
def ReadString(inputData: Any) -> str:
"""if inputData is a string then it is treated to search and replace the values of known variable
using the global variable space of Muscat. please read py:class:Muscat.Helpers.LocalVariables
Parameters
----------
inputData : Any
input data
Returns
-------
str
a string representation of the input data
"""
string = ApplyGlobalDictionary(inputData)
return str(string)
[docs]
def ReadStrings(inputData: Any) -> List[str]:
"""Return a list of string (it use the split to cut the string)
"""
if isinstance(inputData, (list, tuple, np.ndarray)):
return [ReadScalar(x, str) for x in inputData]
else:
tmp = inputData.lstrip().rstrip()
return [ReadScalar(x, str) for x in tmp.split()]
[docs]
def ReadFloat(inputData: Any) -> MuscatFloat:
"""return ReadScalar(inputData,MuscatFloat)
please read ReadScalar documentation
"""
return ReadScalar(inputData, MuscatFloat)
[docs]
def ReadFloats(inputData: Any) -> np.ndarray:
"""return ReadVector(inputData,MuscatFloat)
please read ReadVector documentation
"""
return ReadVector(inputData, MuscatFloat)
[docs]
def ReadInt(inputData: Any) -> MuscatIndex:
"""return ReadScalar(inputData,MuscatIndex)
please read ReadScalar documentation
"""
return ReadScalar(inputData, MuscatIndex)
[docs]
def ReadInts(inputData: Any) -> np.ndarray:
"""return ReadVector(inputData,MuscatIndex)
please read ReadVector documentation
"""
return ReadVector(inputData, MuscatIndex)
[docs]
def ReadBool(inputData: Any) -> bool:
"""Convert the input data can be a string into a bool
the following words can be converted to bool:
"false", "no", "true", "yes", "on"
the inputData is converted to lowercase before testing
Parameters
----------
inputData : Any
a string of other type of data, must be
Returns
-------
bool
_description_
Raises
------
ValueError
_description_
"""
if type(inputData) is bool:
return bool(inputData)
if type(inputData) is not str:
return bool(inputData)
inputData = ApplyGlobalDictionary(inputData)
tmp = inputData.lstrip().rstrip().lower()
if tmp in ["false", "no"]:
return False
if tmp in ["true", "yes", "on"]:
return True
try:
d = ReadInt(tmp)
return bool(d)
except:
pass
try:
d = ReadFloat(tmp)
return bool(d) # pragma: no cover
except:
pass
raise ValueError("cant convert '" + inputData + "' into bool")
[docs]
def ReadBools(inputData: Any) -> np.ndarray:
"""return ReadVector(inputData,bool)
please read ReadVector documentation
"""
return ReadVector(inputData, bool)
[docs]
def ReadVectorXY(inputData: Iterable, normalized=False) -> np.ndarray:
"""Read a vector of size 2
Parameters
----------
inputData : Iterable
input data. Can be a string
normalized : bool, optional
if True the output vector is normalized, by default False
Returns
-------
np.ndarray
a vector of size 2
Raises
------
Exception
in the case the input data has more than 2 elements to be parsed
"""
res: np.ndarray = ReadFloats(inputData)
if len(res) != 2:
raise RuntimeError("Input Data must have exactly 2 elements") # pragma: no cover
if normalized:
res /= np.linalg.norm(res)
return res
[docs]
def ReadVectorXYZ(inputData, normalized=False) -> np.ndarray:
"""Read a vector of size 3
Parameters
----------
inputData : Iterable
input data. Can be a string
normalized : bool, optional
if True the output vector is normalized, by default False
Returns
-------
np.ndarray
a vector of size 3
Raises
------
Exception
in the case the input data has more than 2 elements to be parsed
"""
res: np.ndarray = ReadFloats(inputData)
if len(res) != 3:
raise RuntimeError("Input Data must have exactly 3 elements") # pragma: no cover
if normalized:
res /= np.linalg.norm(res)
return res
[docs]
def ReadVectorPhiThetaMag(string, normalized=False) -> np.ndarray:
"""Read a vector in phi (in degrees), theta(in degrees) and magnitude
Parameters
----------
inputData : Iterable
input data. Can be a string
if inputData has only 2 elements then magnitude == 1.
normalized : bool, optional
if True the output vector is normalized, by default False
Returns
-------
np.ndarray
a cartesian vector of size 3
"""
res = ReadFloats(string)
if len(res) == 3:
phi, theta, mag = res
else:
phi, theta = res
mag = 1.
if normalized:
mag = 1.
phi = phi*math.pi/180.
theta = theta*math.pi/180.
res = np.array([math.sin(phi)*math.cos(theta), math.sin(phi)*math.sin(theta), math.cos(phi)])
return res*mag
[docs]
def ReadProperties(data: Mapping, props: Iterable, obj_or_dic: Union[Mapping, Any], typeConversion: bool = True) -> None:
"""Read information from "data", using the mapping interface ([]).
the keys are extracted from the iterable "props" if provided,
The data is converted to the type of every member in obj_or_dic with the same name
using the Read function. In the presence of a function called Set... (SetAlpha for
example), we check if the function Get... (GetAlpha) is present, extract the type of the value
and use this type for the conversion, if the getter is absent no conversion is done
and the value (of alpha in the example) is passed directly to the Setter.
Parameters
----------
data : Mapping
Source of the data (dict-like object)
props : Iterable
the properties to parse (list-like object)
obj_or_dic : Union[Mapping,Any]
the destinations of the data, a dictionary or an object
typeConversion : bool, optional
if true the data is converted using the Read() function before pushing the information
to the destination , by default True
"""
if props is None: # pragma: no cover
raise RecursionError("second argument (props) cant be none")
try:
for prop in props:
if prop in data:
theSetter = getattr(obj_or_dic, "Set"+prop[0].upper() + str(prop[1:]), None)
inputData = ApplyGlobalDictionary(data[prop])
if theSetter is None:
if type(obj_or_dic) == dict:
if typeConversion and (obj_or_dic[prop] is not None):
obj_or_dic[prop] = Read(inputData, obj_or_dic[prop])
else:
obj_or_dic[prop] = inputData
else:
if typeConversion and (obj_or_dic.__dict__[prop] is not None):
obj_or_dic.__dict__[prop] = Read(inputData, obj_or_dic.__dict__[prop])
else:
obj_or_dic.__dict__[prop] = inputData
else:
prefixes = ["Get", "is"]
theGetter = None
for prefix in prefixes:
theGetter = getattr(obj_or_dic, prefix+prop[0].upper() + str(prop[1:]), None)
if theGetter is not None:
break
if theGetter is not None and typeConversion:
theSetter(Read(inputData, theGetter()))
else:
theSetter(inputData)
except KeyError as e: # pragma: no cover
print(f" object of type {type(obj_or_dic)} does not have attribute {e}: ")
raise
def __TestFunction(func: callable, data: Any, correctVal: Any) -> None:
"""Helper function to test this module
Parameters
----------
func : callable
Function to call
data : Any
argument to pass to the function
correctVal : Any
reference return value (and type)
Raises
------
ValueError
if the values are not equal
TypeError
if the types are not equal
"""
val = func(data)
print(str(func.__name__) + "(" + str(data) + ") = " + str(val) + " =? " + str(correctVal))
if type(val) != type(correctVal): # pragma: no cover
raise ValueError("returned values does not have the correct type")
if np.any(val != correctVal): # pragma: no cover
raise TypeError("returned value does not match")
[docs]
def CheckIntegrity(GUI: bool = False):
from Muscat.Helpers.CheckTools import MustFailFunction
np.testing.assert_equal(Read("1 2 3", np.zeros(3, dtype=int)), np.arange(3, dtype=int) + 1)
assert Read("1 2 3", None) == "1 2 3"
assert Read("1 2 3", []) == ["1","2","3"]
assert Read("1 2 3", [""]) == ["1","2","3"]
assert Read("1 2 3", ("",)) == ("1"," ","2"," ","3")
assert ReadStrings((1,2)) == ["1","2"]
MustFailFunction(ReadVector, 0 , int)
__TestFunction(ReadBool, "true", True)
__TestFunction(ReadBool, True, True)
__TestFunction(ReadBool, "false", False)
__TestFunction(ReadBool, "0", False)
__TestFunction(ReadBool, "1", True)
__TestFunction(ReadBool, "1.1", True)
__TestFunction(ReadBool, " no", False)
__TestFunction(ReadBool, "YES ", True)
__TestFunction(ReadBool, 1, True)
__TestFunction(ReadBools, "YES no 2 1 0 True FALSe ", np.array([True, False, True, True, False, True, False]))
__TestFunction(ReadInt, "24", MuscatIndex(24))
__TestFunction(ReadInt, 24.0, MuscatIndex(24))
__TestFunction(ReadInts, "1 2 3 ", np.array([1, 2, 3]))
__TestFunction(ReadInts, np.array([1.1, 2.2, 3.3]), np.array([1, 2, 3]))
__TestFunction(ReadFloat, "3.14159", MuscatFloat(3.14159))
__TestFunction(ReadFloat, "3.14159*10/5/2", MuscatFloat(3.14159))
__TestFunction(ReadFloat, "exp(pi)", MuscatFloat(math.exp(math.pi)))
__TestFunction(ReadFloat, 3.14159, MuscatFloat(3.14159))
__TestFunction(ReadFloats, "1 2 3 ", np.array([1, 2, 3], dtype=MuscatFloat))
__TestFunction(ReadFloats, "1 4/2 9/3 ", np.array([1, 2, 3], dtype=MuscatFloat))
__TestFunction(ReadInts, "1 4/2 9/3 ", np.array([1, 2, 3], dtype=MuscatIndex))
from Muscat.Helpers.LocalVariables import AddCommonConstants, AddToGlobalDictionary, RemoveFromGlobalDictionary
AddCommonConstants()
__TestFunction(ReadFloat, "{pico}", MuscatFloat(1e-12))
__TestFunction(ReadFloat, "{pi}*{GradToDegree}*{DegreeToGrad}", MuscatFloat(3.1415926535897927))
__TestFunction(ReadFloat, "arctan(1)*{GradToDegree}", MuscatFloat(45.))
__TestFunction(ReadStrings, "Hola Chao", ["Hola", "Chao"])
__TestFunction(ReadBool, "1.5e4", True)
np.testing.assert_equal(ReadVectorXY("1 2"), np.array([1, 2], dtype=MuscatFloat))
np.testing.assert_equal(ReadVectorXY("0 5", normalized=True), np.array([0, 1], dtype=MuscatFloat))
np.testing.assert_equal(ReadVectorXYZ("1 2 3"), np.array([1, 2, 3], dtype=MuscatFloat))
np.testing.assert_equal(ReadVectorXYZ("0 5 0 ", normalized=True), np.array([0, 1, 0], dtype=MuscatFloat))
np.testing.assert_allclose(ReadVectorPhiThetaMag("90 0 2"), np.array([2., 0., 0.], dtype=MuscatFloat), atol=1.e-15)
np.testing.assert_allclose(ReadVectorPhiThetaMag("90 0 2", normalized=True), np.array([1., 0., 0.], dtype=MuscatFloat), atol=1.e-15)
np.testing.assert_allclose(ReadVectorPhiThetaMag("90 0", normalized=True), np.array([1., 0., 0.], dtype=MuscatFloat), atol=1.e-15)
# this call must fail
try:
ReadBool("toto")
raise # pragma: no cover
except:
pass
# Reading data into a class of dictionary with type conversion
data = {"monInt": "2.2", "monFloat": "3.14159", "monFloat2": "123.456", "optionBool":"True"}
class Options():
def __init__(self):
self.monInt: MuscatIndex = MuscatIndex(1)
self.monFloat: MuscatFloat = MuscatFloat(0.1)
self.monFloat2: MuscatFloat = MuscatFloat(10)
self.optionBool = False
def SetMonFloat(self, data):
self.monFloat = ReadFloat(data)
def SetMonFloat2(self, data):
self.monFloat2 = data
def GetMonFloat2(self):
return self.monFloat2
def SetOptionBool(self, data):
self.optionBool = bool(data)
def isOptionBool(self):
return self.optionBool
ops = Options()
ReadProperties(data, data.keys(), ops)
if type(ops.monInt) != MuscatIndex or ops.monInt != 2: # pragma: no cover
raise
if type(ops.monFloat) != MuscatFloat or ops.monFloat != 3.14159: # pragma: no cover
raise
ReadProperties(data, data.keys(), ops, False)
if type(ops.monInt) != str or ops.monInt != "2.2": # pragma: no cover
raise
if type(ops.monFloat) != MuscatFloat or ops.monFloat != 3.14159: # pragma: no cover
raise RuntimeError(f"{type(ops.monFloat)} : {ops.monFloat}")
outputData = {"monInt": 00, "monFloat": 00.00, "monFloat2": 00.00, "optionBool":False}
ReadProperties(data, data.keys(), outputData)
if type(outputData['monInt']) != int or outputData['monInt'] != 2: # pragma: no cover
raise
if type(outputData['monFloat']) != float or outputData['monFloat'] != 3.14159: # pragma: no cover
raise
ReadProperties(data, data.keys(), outputData, False)
if type(outputData['monInt']) != str or outputData['monInt'] != "2.2": # pragma: no cover
raise
if type(outputData['monFloat']) != str or outputData['monFloat'] != "3.14159": # pragma: no cover
raise
if type(outputData['monFloat2']) != str or outputData['monFloat2'] != "123.456": # pragma: no cover
raise
AddToGlobalDictionary("FILENAME", "toto.xml")
print(ReadString("my FILENAME is '{FILENAME}' "))
RemoveFromGlobalDictionary("FILENAME")
return "ok"
if __name__ == '__main__':
print(CheckIntegrity()) # pragma: no cover