"""Generic calculator class"""
import os
from typing import Union, Any, Tuple
from collections.abc import Callable
if os.environ.get('__USE_CUDA', '0') == '1':
import cupy as np
from cupy import ndarray, float64
else:
import numpy as np
from numpy import ndarray, float64
from g3d.defs import G3DCode
from g3d.defs import CodeType
from g3d.defs import G3DWorld
from g3d.gas import Gas
from g3d.defs import CFlowVariables
from g3d.defs import CFlowRegime
from g3d.defs import AngleType
from g3d.defs import CFlowAdditionMode
from .newton_raphson import G3DNewtonRaphsonSolver
[docs]
class G3DGenericCalculator(G3DCode):
"""Generic calculator class (base class for all problem-specific calculators)"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize calculator parameters"""
self._vars = CFlowVariables()
self._gas = Gas()
# default
self.__default_mode = CFlowRegime.SUBSONIC
self.__default_outdim = 1
self.__default_precision = 2
self.__default_angle_type = AngleType.RADIANS
self.__default_round = False
self.__default_add_mode = CFlowAdditionMode.ABSOLUTE
# initialize member variables
self._mode = self.__default_mode
self._outdim = self.__default_outdim
self._precision = self.__default_precision
self._angle_type = self.__default_angle_type
self.__round = self.__default_round
self._add_mode = self.__default_add_mode
super().__init__(*args, **kwargs)
self._newton = G3DNewtonRaphsonSolver(update_code_type=False)
self.__eval_is_reset = False
def _set_code_type(self) -> None:
super()._set_code_type()
g3d_world = G3DWorld()
g3d_world.info.code_type = CodeType.CALCULATOR
g3d_world.info.code_name = 'cflow.py'
g3d_world.info.code_description = 'a python-based compressible flow calculator'
def _set_default(self) -> None:
super()._set_default()
self._tag = 'g3d_calculator'
self.__reset_eval()
@property
def gas(self) -> Gas:
"""Gas object"""
return self._gas
@gas.setter
def gas(self, g: Gas) -> None:
self._gas = g
self._associate()
@property
def newton(self) -> G3DNewtonRaphsonSolver:
"""Newton-Raphson solver"""
return self._newton
@newton.setter
def newton(self, n: G3DNewtonRaphsonSolver) -> None:
self._newton = n
def _get_newton_kernel(self) -> G3DNewtonRaphsonSolver:
newton = G3DNewtonRaphsonSolver(nrs=self._newton)
return newton
def _angle_import(self, *args: Any, **kwargs: Any) -> Union[tuple, int]:
if len(args) < 1:
return -1
angle_type = AngleType.UNDEFINED
for key, value in kwargs.items():
if key == "angle_type":
angle_type = value
a = []
if self._angle_type is AngleType.RADIANS or angle_type is AngleType.RADIANS:
for arg in args:
a.append(arg)
else:
for arg in args:
a.append(np.pi * arg / 180.)
if len(a) == 1:
return a[0]
return tuple(a)
def _angle_export(self, *args: Any, **kwargs: Any) -> Union[tuple, int]:
if len(args) < 1:
return -1
angle_type = AngleType.UNDEFINED
for key, value in kwargs.items():
if key == "angle_type":
angle_type = value
a = []
if self._angle_type is AngleType.RADIANS or angle_type is AngleType.RADIANS:
for arg in args:
a.append(arg)
else:
for arg in args:
a.append(180. * arg / np.pi)
if len(a) == 1:
return a[0]
return tuple(a)
def __reset_eval(self) -> None:
self._mode = self.__default_mode
self._outdim = self.__default_outdim
self._precision = self.__default_precision
self._angle_type = self.__default_angle_type
self.__round = self.__default_round
self._add_mode = self.__default_add_mode
def _reset_eval(self, **kwargs: Any) -> None:
if not self.__eval_is_reset:
# reset eval parameters to default values
self.__reset_eval()
# override default values
if len(kwargs) != 0:
for key, value in kwargs.items():
if not self.set_from_key_value(key, value):
print('warning! parameter %s is not defined' % key)
self.__eval_is_reset = True
def _nullify(self) -> None:
self._vars.nullify()
[docs]
def print(self) -> None:
"""Prints allocated variables"""
self._vars.print()
[docs]
def backup(self) -> None:
"""Backup of stored variables"""
self._vars.backup()
[docs]
def restore(self) -> None:
"""Restore variable state from backup"""
self._vars.restore()
def _init(self, names: list, *args: Any, **kwargs: Any) -> None:
self._vars.init(names)
if len(args) != 0 or len(kwargs) != 0:
self._vars.get_from_args(*args)
if len(kwargs) != 0:
for key, value in kwargs.items():
if not self.set_from_key_value(key, value):
print('warning! parameter %s is not defined' % key)
self._vars.arrayify()
self.__eval_is_reset = False
[docs]
def set_from_key_value(self, key: str, value: Any) -> bool:
"""Checks if provided key is in list of defined keys and
updates calculator settings accordingly"""
if super().set_from_key_value(key, value):
return True
elif self._vars.set_from_key_value(key, value):
return True
elif self._newton.set_from_key_value(key, value):
return True
elif self._gas.set_from_key_value(key, value):
return True
elif key == 'precision' or key == 'prec':
self._precision = value
self.__round = True
return True
elif key == 'angle_type':
if type(value) is str:
if value == 'radians' or value == 'rad':
self._angle_type = AngleType.RADIANS
elif value == 'degrees' or value == 'deg':
self._angle_type = AngleType.DEGREES
else:
self._angle_type = value
return True
elif key == 'mode':
if type(value) is str:
if value == 'supersonic':
self._mode = CFlowRegime.SUPERSONIC
elif value == 'subsonic':
self._mode = CFlowRegime.SUBSONIC
elif value == 'weak':
self._mode = CFlowRegime.WEAK
elif value == 'strong':
self._mode = CFlowRegime.STRONG
else:
self._mode = value
return True
elif key == 'add_mode':
if type(value) is str:
if value == 'absolute' or value == 'abs':
self._add_mode = CFlowAdditionMode.ABSOLUTE
if value == 'relative' or value == 'rel':
self._add_mode = CFlowAdditionMode.RELATIVE
if value == 'factor' or value == 'fact':
self._add_mode = CFlowAdditionMode.FACTOR
if value == 'percent' or value == 'per':
self._add_mode = CFlowAdditionMode.PERCENT
else:
self._add_mode = value
return True
elif key == 'outdim':
if value in [int, np.int64, np.int32]:
self._outdim = value
else:
self._outdim = np.int32(value)
return True
return False
def _associate(self) -> None:
pass
def _eval(self, names: list, func: Callable[
[], Union[float, Tuple[float, ...], Tuple[float64, ...], ndarray]],
*args: Any, **kwargs: Any) -> Union[Union[tuple[Any, ...], list[Any]], Any]:
self._reset_eval()
self._init(names, *args, **kwargs)
truncate_result = self.__round
outdim = self._outdim
prec = self._precision
ret = []
for i in range(outdim):
ret.append(np.zeros(self._vars.size))
for i in range(self._vars.size):
self._vars.get_from_array(i)
out = func()
if type(out) is not tuple:
if type(out) is ndarray:
if out.size == 1:
out = float64(out)
out = tuple([out])
if not truncate_result:
for j in range(outdim):
ret[j][i] = out[j]
else:
if type(prec) is not tuple:
if type(prec) is list:
if len(prec) == outdim:
prec = tuple(prec)
else:
prec = prec[0]
if type(prec) is not tuple:
tmp = []
for j in range(outdim):
tmp.append(prec)
prec = tuple(tmp)
for j in range(outdim):
ret[j][i] = np.round(out[j], prec[j])
if self._vars.isscalar:
for j in range(outdim):
ret[j] = float64(np.squeeze(ret[j]))
if outdim == 1:
return ret[0]
else:
return tuple(ret)
else:
if outdim == 1:
return ret[0]
return ret
[docs]
def reset_newton(self) -> None:
"""Resets Newton-Raphson solver to its default state"""
self._newton.reset()
[docs]
def newton_reset(self) -> None:
"""
.. deprecated:: 2.6
redirects to :func:`reset_newton`
"""
self.reset_newton()
[docs]
def Newton_Raphson_solver(self, func: Callable[[float], float], *args: Any, **kwargs: Any) -> float:
"""Runs Newton-Raphson solver on specified function
input: function (func), iteration start value (start_value), function target value (target_value)
optional input (kwargs): numerical differentiation delta (delta, default 0.0001),
maximum number of iterations (maxiter, default 100), tolerance (tol, default 1.e-6),
lower function limit (lower_bound), upper function limit (upper_bound)
Parameters
----------
func : Callable :
function to solve
*args : Any
arguments (optional input):
1. start value
2. target value
variable value(s) in above-listed order
**kwargs : Any
keyword-value pairs (optional input):
valid keywords: 'delta', 'maxiter', 'tol', 'lower_bound', 'upper_bound'
(any other keyword->argument input will be passed on to the specified function)
Returns
-------
float
Newton-Raphson solver result
Examples
--------
Basic usage:
$_>>> from g3d import cflow
$_>>> result = cflow.Newton_Raphson_solver(func, start_value=10., target_value=0.)
"""
newton = self._get_newton_kernel()
newton.set(*args, **kwargs)
return newton.solve(func)