from collections import namedtuple
from six import string_types
from ..tecutil import _tecutil, ArgList, IndexSet, Index, sv, lock
from .. import layout
from ..constant import ValueLocation, FieldDataType
from ..exception import *
from collections import Iterable
Range = namedtuple('Range', 'min max step')
"""Limit the data altered by the `execute_equation` function.
The Range specification of I,J,K range indices for `execute_equation`
follow these rules:
* All indices start with 0 and go to some maximum index *m*.
* Negative values represent the indexes starting with the maximum at -1
and continuing back to the beginning of the range.
* A step of None, 0 and 1 are all equivalent and mean that no elements
are skipped.
* A negative step indicates a skip less than the maximum.
Example:
Add one to variable 'X' for a zone 'Rectangular' for data points
in I Range 1 to max, skipping every three points:
>>> execute_equation('{X} = {X}+1', i_range=Range(min=1, max=None, step=3),
... zone_set='Rectangular')
"""
Range.__new__.__defaults__ = (None, None, None)
@lock()
[docs]def execute_equation(equation, zones=None, i_range=None, j_range=None,
k_range=None, value_location=None, variable_data_type=None,
ignore_divide_by_zero=None):
"""The execute_equation function operates on a data set within the
|Tecplot Engine| using FORTRAN-like equations.
Parameters:
equation (`string <str>`): String containing the equation.
Multiple equations can be processed by separating each equation
with a newline. See Section 20 - 1 "Data Alteration through
Equations" in the Tecplot User's Manual for more information on
using equations. Iterable container of `Zone` objects to operate
on. May be a list, set, tuple, or any iterable container. If
`None`, the equation will be applied to all zones.
.. note:: In the equation string, variable names should be enclosed
in curly braces. For example, '{X} = {X} + 1'
zones: (Iterable container of `Zone` objects, optional):
Iterable container of `Zone` objects to operate on. May be a list,
set, tuple, or any iterable container. If `None`, the equation will
be applied to all zones.
i_range (`Range`, optional):
Tuple of integers for I: (min, max, step). If `None`, then the
equation will operate on the entire range.
Not used for finite element nodal data.
j_range (`Range`, optional):
Tuple of integers for J: (min, max, step). If `None`, then the
equation will operate on the entire range.
Not used for finite element nodal data.
k_range (`Range`, optional):
Tuple of integers for K: (min, max, step). If `None`, then the
equation will operate on the entire range.
Not used for finite element nodal data.
value_location (`ValueLocation`, optional):
Variable `ValueLocation` for the variable on the left hand side.
This is used only if this variable is being created for the first
time.
If `None`, |Tecplot Engine| will choose the location for you.
variable_data_type (`FieldDataType`, optional):
Data type for the variable on the left hand side.
This is used only if this variable is being created for the first
time.
If `None`, |Tecplot Engine| will choose the type for you.
ignore_divide_by_zero (`bool`, optional):
`bool` value which instructs |Tecplot Engine| to ignore
divide by zero errors. The result is clamped
such that 0/0 is clamped to zero and (+/-N)/0
where N != 0 clamps to +/-maximum value for the given type.
Raises:
`TecplotSystemError`
Add one to variable 'X' for zones 'Rectangular' and 'Circular' for every
data point:
>>> import tecplot
>>> dataset = tecplot.active_frame().dataset
>>> execute_equation('{X} = {X} + 1', zones=[dataset.zone('Rectangular'),
>>> dataset.zone('Circular')])
Create a new, double precision variable called DIST:
>>> execute_equation('{DIST} = SQRT({X}**2 + {Y}**2)',
... variable_data_type=FieldDataType.double)
Set a variable called **P** to zero along the boundary of an IJ-ordered
zone:
>>> execute_equation('{P} = 0', i_range=Range(step=-1))
>>> execute_equation('{P} = 0', j_range=Range(step=-1))
"""
if __debug__:
if not isinstance(equation, string_types):
raise TecplotTypeError('Equation must be a string')
elif len(equation) == 0:
raise TecplotValueError('Equation can not be empty')
if not isinstance(value_location, (ValueLocation, type(None))):
msg = 'value_location must be a ValueLocation'
raise TecplotTypeError(msg)
if not isinstance(variable_data_type, (FieldDataType, type(None))):
msg = 'variable_data_type must be a FieldDataType'
raise TecplotTypeError(msg)
if not isinstance(ignore_divide_by_zero, (bool, type(None))):
raise TecplotTypeError('ignore_divide_by_zero must be a bool')
# Check that all zones belong to the active dataset
# (which is currently the only dataset option available)
if zones:
try:
current_dataset = layout.active_frame().dataset
parent_ids = {U.dataset.uid for U in zones} if isinstance(
zones, Iterable) else {zones.dataset.uid}
if {current_dataset.uid} != parent_ids:
raise TecplotValueError(
'All zones must have the same parent dataset')
except AttributeError:
# At least one member of the input zone set was not
# a Zone object
raise TecplotTypeError(
'All zones must be Zone objects')
with ArgList() as arglist:
allocd = []
arglist[sv.EQUATION] = equation
if zones is not None:
s = IndexSet(zones)
allocd.append(s)
arglist[sv.ZONESET] = s
for dim, rng in zip(['I', 'J', 'K'], [i_range, j_range, k_range]):
if rng is not None:
if not isinstance(rng, Range):
rng = Range(*rng)
if rng.min is not None:
arglist[dim+'MIN'] = Index(rng.min)
if rng.max is not None:
arglist[dim+'MAX'] = Index(rng.max)
if rng.step is not None:
step = int(rng.step)
if step != 0:
arglist[dim+'SKIP'] = rng.step
if value_location is not None:
arglist[sv.VALUELOCATION] = value_location
if variable_data_type is not None:
arglist[sv.VARDATATYPE] = variable_data_type
if ignore_divide_by_zero is not None:
arglist[sv.IGNOREDIVIDEBYZERO] = ignore_divide_by_zero
try:
if not _tecutil.DataAlterX(arglist):
raise TecplotSystemError()
finally:
for a in allocd:
a.dealloc()