python creating custom syntax - python

or-tools module in python adds custom syntax where functions can take arbitrary expressions as arguments (something like below), which is evaluated not instantaneously but solved as a constraint later
model.Add(x + 2 * y -1 >= z)
When I print the type of the argument from my function, it shows
<class 'ortools.sat.python.cp_model.BoundedLinearExpression'>
A simple way is to pass the expression as a string, but it feels better. I wish to understand how this is achieved. Is this a way to create custom syntax in python? Does it require updating the parser or something like that?
Here is the simple program
from ortools.sat.python import cp_model
def foo(expr):
print(expr, type(expr))
def main():
model = cp_model.CpModel()
var_upper_bound = max(50, 45, 37)
x = model.NewIntVar(0, var_upper_bound, 'x')
y = model.NewIntVar(0, var_upper_bound, 'y')
z = model.NewIntVar(0, var_upper_bound, 'z')
a = 0
b = 0
c = 0
model.Add(2*x + 7*y + 3*z == 50)
solver = cp_model.CpSolver()
status = solver.Solve(model)
if status == cp_model.OPTIMAL:
print('x value: ', solver.Value(x))
print('y value: ', solver.Value(y))
print('z value: ', solver.Value(z))
foo(2*x + 7*y + 3*z == 50)
foo(2*a + 7*b + 3*c == 50)
if __name__ == '__main__':
main()
x, y, z are special variables (instances of some class) and the expression with x, y, z is stored as expression
a, b, c are simple integers and the expression is evaluated immediately and the result is stored as a bool

They override the python operators.
References:
https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types
https://github.com/google/or-tools/blob/b37d9c786b69128f3505f15beca09e89bf078a89/ortools/sat/python/cp_model.py#L212-L233
def __mul__(self, arg):
if isinstance(arg, numbers.Integral):
if arg == 1:
return self
elif arg == 0:
return 0
cp_model_helper.AssertIsInt64(arg)
return _ProductCst(self, arg)
else:
raise TypeError('Not an integer linear expression: ' + str(arg))

The or-tools module defines its own classes for model variables, and within the class definitions the module defines methods for applying operators to those classes.
As a trivial example we can define our own class along with a method for '+'.
# New class will normally not support math operators.
class Blah(object):
def __init__(self, context):
self.context = context
def __add__(self, value):
# Support '+' operator with class Blah.
return self.context.format(value)
x = Blah("Chocolate with {} is the result.")
# '*' operator is not supported.
x * 2
# Traceback (most recent call last):
#
# File "<ipython-input-26-80b83cb135a7>", line 1, in <module>
# x * 2
#
# TypeError: unsupported operand type(s) for *: 'Blah' and 'int'
# '+' operator is implemented for class Blah.
x + 3
# 'Chocolate with 3 is the result.'

Related

Allocation dynamic truthtable values to a dictionary

NOTE: This post is heavy edited. I found a solution.
My question was, how I can apply all possible combinations of a truth table for an arbitary number of variables to a dictionary. E.g. for 3 vars I have a dictionary like 'lib = {"x":0, "y":0, "z":0}' I wanted to generate all 8 possible combinations.
I need the n-tuples in this format to evaluate a given expression.
Because some wanted to know why I needed it. I was working on an exercise where I created a class with subclasses to handle boolean expressions.
While brooding over the problem I looked over the previous chapters in the exercise book and realized, that I might solve it with recursion.
For those interested here is my implementation and with my solution:
# classes to handle boolean expressions
class ExprBoolean:
def __str__(self): # necessary for considering precedence: not > and > or > equal
return self.str_aux(0)
def init_Lib_Keys(self): # if not existent, generate class var: lib - dictionary with all
try: # vars, keys - list with all vars, tt - empty list for truth table
self.lib
except:
self.lib = self.getVar({})
self.keys = list(self.lib.keys())
self.tt = []
# ---- Solution I was looking for --------------------------------
def TruthTable(self,keys): # generates truth table considering all vars and entered expression
if keys == []: # condition to insert n-tupel as row in lib-string
dummy = []
for key in self.lib:
dummy += [self.lib[key]] + ["\t| "]
dummy += [self.eval(self.lib)] + ["\n"]
self.tt += [dummy]
return
for keyN in range(len(keys)):
for i in [True,False]:
self.lib[keys[keyN]] = i
self.TruthTable(keys[1:])
break
# ----------------------------------------------------------------
def make_tt(self): # prints a truth table
self.init_Lib_Keys()
for key in self.keys:
print("{}\t\t| ".format(key),end="") # header of table with
print(self) # expression in last column
if not self.tt:
self.TruthTable(self.keys) # generates truth table
for row in self.tt: # prints row after row
for col in row:
print(col,end="")
def isTauto(self): # checks if expression is tautology (always True)
self.init_Lib_Keys()
if not self.tt:
self.TruthTable(self.keys)
for row in self.tt:
if not row[-2]: # in row[-2] is evaluated expression
print(row[-2])
return
print(True)
class Not(ExprBoolean):
prec = 3 # precedence rank for brackets
def __init__(self,arg):
self.arg = arg
def str_aux(self,prec):
return "!" + self.arg.str_aux(self.prec)
def getVar(self,env):
return self.arg.getVar(env)
def eval(self,env):
return not self.arg.eval(env)
class BooOp(ExprBoolean):
def __init__(self,x,y):
self.x = x
self.y = y
def str_aux(self,prec):
s = self.x.str_aux(self.prec) + self.op + self.y.str_aux(self.prec)
if self.prec < prec:
return "(" + s + ")"
else:
return s
def getVar(self,env):
new_env = self.x.getVar(env)
new_env = self.y.getVar(new_env)
return new_env
def eval(self,env):
return self.fun(self.x.eval(env),self.y.eval(env))
class And(BooOp):
prec = 2
op = "&"
def fun(self,x,y):
return x & y
class Or(BooOp):
prec = 1
op = "|"
def fun(self,x,y):
return x | y
class Eq(BooOp):
prec = 0
op = "=="
def fun(self,x,y):
return x == y
class Var(ExprBoolean):
def __init__(self,name):
self.name = name
def str_aux(self,prec):
return self.name
def getVar(self,env):
env[self.name] = 0
return env
def eval(self,env):
return env[self.name]
# examples to test class
lib = {"x":True, "y":False, "z":True}
e1 = Or(Var("x"),Not(Var("x")))
e2 = Eq(Var("x"),Not(Not(Var("x"))))
e3 = Eq(Not(And(Var("x"),Var("y"))),Or(Not(Var("x")),Not(Var("y"))))
e4 = Eq(Not(And(Var("x"),Var("y"))),And(Not(Var("x")),Not(Var("y"))))
e5 = Eq(Eq(Eq(Var("p"),Var("q")),Var("r")),Eq(Var("p"),Eq(Var("q"),Var("r"))))
e6 = And(Or(Var("x"),Var("y")),Eq(Var("x"),Var("y")))
e4.make_tt()
e4.isTauto()
If I understand the problem, this is one way to help solve it without using eval() or having to parse the expression yourself. (You will have to implement the other Boolean functions yourself.)
def AND(a, b):
return a and b
def OR(a, b):
return a or b
def NOT(a):
return not a
x = True
y = False
z = True
print(AND(x, y))
# False
print(NOT(AND(x, y)))
# True
print(OR(x, y))
# True
print(AND(AND(x, z), NOT(y)))
# True
Even though python has classes, that doesn't mean you should treat every problem as an invitation to write a class hierarchy.
Evaluating expression is a verb, not a noun.
First off, you don't always have to write an expression evaluator. Python has eval and exec, and boolean and arithmetic expressions in Python are pretty much standard:
>>> eval("3+5")
8
>>> eval("x=3+5")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1
x=3+5
^
SyntaxError: invalid syntax
>>> exec("x=3+5")
>>> x
8
If that is enough, look at the ast module in the standard library.
Even if you want to write it yourself, programming is often more about creating smart data that writing lots of code.
Below is a small implementation of a postfix expression parser (from one of my github repos).
import operator
import math
# Global constants {{{1
_add, _sub, _mul = operator.add, operator.sub, operator.mul
_truediv, _pow, _sqrt = operator.truediv, operator.pow, math.sqrt
_sin, _cos, _tan, _radians = math.sin, math.cos, math.tan, math.radians
_asin, _acos, _atan = math.asin, math.acos, math.atan
_degrees, _log, _log10 = math.degrees, math.log, math.log10
_e, _pi = math.e, math.pi
_ops = {
"+": (2, _add),
"-": (2, _sub),
"*": (2, _mul),
"/": (2, _truediv),
"**": (2, _pow),
"sin": (1, _sin),
"cos": (1, _cos),
"tan": (1, _tan),
"asin": (1, _asin),
"acos": (1, _acos),
"atan": (1, _atan),
"sqrt": (1, _sqrt),
"rad": (1, _radians),
"deg": (1, _degrees),
"ln": (1, _log),
"log": (1, _log10),
}
_okeys = tuple(_ops.keys())
_consts = {"e": _e, "pi": _pi}
_ckeys = tuple(_consts.keys())
def postfix(expression): # {{{1
"""
Evaluate a postfix expression.
Arguments:
expression: The expression to evaluate. Should be a string or a
sequence of strings. In a string numbers and operators
should be separated by whitespace
Returns:
The result of the expression.
"""
if isinstance(expression, str):
expression = expression.split()
stack = []
for val in expression:
if val in _okeys:
n, op = _ops[val]
if n > len(stack):
raise ValueError("not enough data on the stack")
args = stack[-n:]
stack[-n:] = [op(*args)]
elif val in _ckeys:
stack.append(_consts[val])
else:
stack.append(float(val))
return stack[-1]
The "intelligence" of this is in the _ops dictionary, which links an operator to a two-tuple of a number of arguments and an operator function.
Because of that, the evaluator itself is only 14 lines of code.

Wrapper with keyword-argument change between RegulaFalsi and Newton-Raphson in Python

I want to write a code with kwargs and the possibility to change between Regula Falsi and Newton-Raphson to get the zero of a function.
My problem is that I do not know how I can integrate the newton step into this because I have other arguments like the starting point, not an interval like it is with at RegulaFalsi.
I intentionally let the space free after the Newton parts btw.
Thanks in advance! Best, Robin.
The beginning:
import numpy
def Solve(interval, **kwargs):
if len(kwargs) == 0:
print("Methode is not defined")
return 0
elif len(kwargs) > 1:
print("Too much optional parameters")
return 0
for key in kwargs:
if kwargs[key] == "Newton":
elif kwargs[key] == "Regulafalsi":
for i in range(25):
interval = regulafalsi_step(interval)
fx = f(interval[0])
fy = f(interval[1])
z = (interval[0] * fy - interval[1] * fx)/(fy - fx)
else:
print("Solver: unknown parameter", kwargs.values())
return 0
print(f"Zero at: {z}")
def f(x):
return numpy.exp(x)-x-2
def newton_step(i):
def regulafalsi_step(i):
fx = f(i[0])
fy = f(i[1])
z = (i[0] * fy - i[1] * fx)/(fy - fx)
if ((fx >= 0) == (f(z) >= 0)):
return (z, i[1])
return (i[0], z)
Solve((0, 3), methode="Regulafalsi")
Solve((0, 3), methode="Newton")
You can use *args to accept unnamed parameters :
def Solve(*args, **kwargs):
method = kwargs['methode']
if method == "Newton":
starting_point = args[0]
print(f"solving with {method} using {starting_point=!r} ...")
# do something here ...
z = 14
elif method == "Regulafalsi":
interval = args[0]
print(f"solving with {method} using {interval=!r} ...")
# do something here ...
z = 29
else:
raise NotImplementedError(f"{kwargs.get('methode')=!r}")
print(f"Zero at: {z}")
Solve((0, 3), methode="Regulafalsi")
# solving with Regulafalsi using interval=(0, 3) ...
# Zero at: 29
Solve(14.7, methode="Newton")
# solving with Newton using starting_point=14.7 ...
# Zero at: 14
The Solve function expects that the first argument passed to it will be an interval when it is using the Regulafalsi method, while it will have to be a starting point when using the Newton method. You can expect more parameters, just get them from args[1], args[2], ...
But I would suggest to instead define two different functions :
def Solve_with_Newton(starting_point):
print(f"solving with Newton using {starting_point=!r} ...")
# do something here ...
z = 14
print(f"Zero at: {z}")
def Solve_with_Regulafalsi(interval):
print(f"solving with Regulafalsi using {interval=!r} ...")
# do something here ...
z = 29
print(f"Zero at: {z}")
Solve_with_Regulafalsi((0, 3))
# solving with Regulafalsi using interval=(0, 3) ...
# Zero at: 29
Solve_with_Newton(14.7)
# solving with Newton using starting_point=14.7 ...
# Zero at: 14
It has the same output than before, the code is shorter, there is no need to handle the case where the method's name is incorrect, the arguments are explicit, ...
Although it is possible to do it with kwargs, I suggest against it.

How to update a jitclass variable with its string name by passing a setter function to the jitclass itself?

I'm seeking a way to modify a jitclass variable with its name as a string. I tried to write a setter and getter function (get_A and set_A), and I got the method outside the jitclass. I would like then to pass that method to a jitclass method to update the value of the variable. However I get and error saying:
numba.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
non-precise type pyobject
[1] During: typing of argument at <string> (3)
File "<string>", line 3:
<source missing, REPL/exec in use?>
This error may have been caused by the following argument(s):
- argument 1: cannot determine Numba type of <class 'tuple'>
Here the minimal example:
from numba import float64
from numba.experimental import jitclass
spec = [('A', float64), ('B', float64), ('C', float64)]
#jitclass(spec)
class maClass:
def __init__(self):
self.A = 0
def get_A(self):
return self.A
def set_A(self, x):
self.A = x
def update(self, func, val):
func(val)
C1 = maClass()
print('A', C1.A)
Names= ['A']
vals= [1.]
func = getattr(C1, 'set_' + Names[0])
print(func)
C1.update(func, vals[0])
print('A', C1.A)
Here is a solution I came up with.
I first wrote a function that return a list of the variable names I can update
def get_Variable_Names():
return ['A','B','C']
then, in the jitclass, I wrote a function that can update variable according to an index based on the order of get_Variable_Names.
def update_val(self, i, var):
if i == 0:
self.A = var
elif i == 1:
self.B = var
elif i == 2:
self.C = var
To use this function, I search for the index of a variable name in the list returned by get_Variable_Names and then call the update_val function of the jitclass.
listnames = get_Variable_Names()
val = 1.
name = 'A'
index = listnames.index(name)
C1.update_val(index,val)
here's all the code.
from numba import jitclass, int32, float64
def get_Variable_Names():
return ['A','B','C']
spec = [('A' ,float64),('B' ,float64),('C' ,float64)]
#jitclass(spec)
class maClass():
def __init__(self,):
self.A = 0.
self.B = 0.
self.C = 0.
def update_val(self, i, var):
if i == 0:
self.A = var
elif i == 1:
self.B = var
elif i == 2:
self.C = var
C1 = maClass()
listnames = get_Variable_Names()
val = 1.
name = 'A'
index = listnames.index(name)
C1.update_val(index,val)
val = 2.
name = 'B'
index = listnames.index(name)
C1.update_val(index,val)
val = 3.
name = 'C'
index = listnames.index(name)
C1.update_val(index,val)
print('A',C1.A)
print('B',C1.B)
print('C',C1.C)
It is not was I was looking for, but it is find for now.
I'm still waiting for something better.
I even wrote a script to write the if else update_val function, because It can be tedious when we have a large amount of variables to modify
listnames= ['A','B','C']
for i, n in enumerate(listnames):
if i == 0:
print('if i == ' + str(i)+ ' :\n\tself.' + n + ' = var')
else:
print('elif i == ' + str(i) + ' :\n\tself.' + n + ' = var')
With a couple of small changes, you can in fact use function pointers in numba. I came up with the following based on a whole bunch of experimentation, and the first item on the FAQ.
The first thing to notice is that getattr(C1, 'set_A') returns a Python method object. This is not something that is directly supported in numba at time of writing (v0.53.1). Instead, you have to get a pointer to the actual jitted function, which you can do through the relatively undocumented _numba_type_ attribute. This contains a bunch of descriptors of the methods and attributes of your object that are quite useful. Specifically, your func object is given by
func = C1._numba_type_.jit_methods['set_A']
The second thing to note is that the compiled function func requires an explicit self argument that is a subtype of maClass.class_type.instance_type. Calling it with only one argument will cause a compilation error in update:
func(self, val)
Here is the complete example (including the fixed jitclass import):
from numba import float64
from numba.experimental import jitclass
spec = [('A', float64), ('B', float64), ('C', float64)]
#jitclass(spec)
class maClass:
def __init__(self):
self.A = self.B = self.C = 0
def get_A(self):
return self.A
def set_A(self, x):
self.A = x
def update(self, func, val):
func(self, val)
C1 = maClass()
print('A', C1.A)
Names = ['A']
vals = [1.]
func = C1._numba_type_.jit_methods['set_' + Names[0]]
print(func)
C1.update(func, float64(vals[0]))
print('A', C1.A)

OpenMaya API (python): print more useful info from MObjects? E.g. MFloatVector(x, y, z)

With regular python objects, it is easy to see their details.
E.g.
vec = (1, 2, 3)
print vec
=>
(1, 2, 3)
When working with equivalent OpenMaya (OM) objects, all print or str() shows is the type of object:
vec = OpenMaya.MFloatVector(1,2,3)
print vec
=>
<maya.OpenMaya.MFloatVector; proxy of <Swig Object of type 'MFloatVector *' at 0x000000002A346060> >
Is there a general way to ask an MObject to provide more details?
I'd like a result something like:
MFloatVector(1, 2, 3)
----- EDIT -----
From C++ documentation, I see that the information I want is available in C++ via the ostream << operator. E.g. for MFloatVector's << it says:
The format used is [x, y, z]
So another way to ask my question is: In python, how do I create an ostream in memory, send the object to it, then get the result as a string?
----- EDIT #2 -----
My import statement is:
import maya.OpenMaya as OpenMaya
This means I am using Version 1 of Maya Python API. (Because some of the Version 2 stuff is stubs, such as MGlobal. Examples I am looking at use these Version 1 features, so I stayed with Version 1.)
I've posted my own answer, which is to use Version 2, to get the desired behavior. TBD whether Version 2 has everything needed, and what is required to convert Version 1 examples to Version 2. For now, I'm sticking to Version 1. If anyone has a way to get Version 1 to provide more useful print details, that is what I would accept as an answer.
Version 2 of Maya Python API is more python-friendly.
To access Version 2, change the import statement from
import maya.OpenMaya as OpenMaya
to
import maya.api.OpenMaya as OpenMaya
This will require changes to the script, as many methods are tweaked to be more python friendly.
Once this is done, "print vec" and "str(vec)" and "len(vec)" all become useful operations:
vec = OpenMaya.MFloatVector(1, 2, 3)
print vec
print str(vec)
print len(vec)
=>
(1, 2, 3)
(1, 2, 3)
3
Well it is certainly possible to wrap the repr function with your own; something along the lines of:
import maya.OpenMaya as om
def repr_MfloatVector(self):
n = self.__class__.__name__
return "%s(%r, %r, %r)"%(n, self[0], self[1], self[2])
om.MFloatVector.__repr__ = repr_MfloatVector
vec = om.MFloatVector(1,2,3)
print vec
This would affect all future (but not past) MFloatVectors. But why you would go trough such a trouble is another matter. As for working in general for MObjects too much work on this level. However you could try to ask obj.length() of each object to determine if its iterable etc etc. so you could get a quite good spread still too much work for not much gain.
Here is code that defines "Repr(self)". A global function that returns "repr(self)" for most objects. But for objects that return a representation starting with "<", the result is a list of member values that (1) aren't internal (don't start with '__'), and (2) aren't methods.
E.g. an OpenMaya MFloatVector instance "OpenMaya.MFloatVector( 1, 2, 3)" gives result:
#MFloatVector( x: 1.0, y: 2.0, z: 3.0)
The code:
# ==================== AttributeAccess.py ====================
import maya.cmds as cmds
import maya.mel as mel
import sys
import maya.OpenMaya as OM # Version 1
import math
import inspect
import types
# ---------- Common Stuff ----------
# "something" can be any Python object.
def Exists(something):
return something is not None
def printElements(ob):
print '----- Elements: -----'
i = 0
for x in ob:
print ' [' + str(i) + ']: ' + repr(x)
i += 1
print '---------------------'
def printDictElements(ob):
print ''
print '-----------------------'
for x in ob: print repr(x) + ': ' + repr(ob[x])
print '-----------------------'
# ---------- inspect Attributes ----------
# NOTE: ob is an instance, NOT a type object.
def TypeName(ob):
return ob.__class__ .__name__
# Excludes 'internal' names (start with '__').
def Public(name):
return not name.startswith('__')
# member is element of inspect.getmembers:
# a two-element tuple.
def MemberWithType(member):
return ( member[0], TypeName(member[1]), member[1] )
#print MemberWithType( (1.1, 2) )
def Members(ob):
return inspect.getmembers(ob)
# True for Maya Python's 'this' member.
# member [1] is attribute value.
def SwigThis(member):
return (member[0] == 'this') and (TypeName(member[1]) == 'SwigPyObject')
# HACK: "not SwigThis": omit Maya Python's 'this' member.
def PublicMembers(ob):
members = filter(lambda member: Public(member[0]) and not SwigThis(member), Members(ob))
return map(MemberWithType, members)
# Excludes 'internal' names (start with '__').
def Dir(ob):
return filter(Public, dir(ob))
def _Type_And_Features(ob, names):
return '{0}.({1})'.format(TypeName(ob), ', '.join(names))
def MemberName(member):
return member[0]
# member with typename inserted as [1]. So descriptor is [2].
# member type-name is [1].
def CallableMember(member):
#return (member[2].__class__ is types.MethodType)
return inspect.isroutine(member[2])
def MemberNames(members):
return map(MemberName, members)
def Features(ob):
return _Type_And_Features(ob, MemberNames(PublicMembers(ob)) )
#return _Type_And_Features(ob, Dir(ob))
def Callable(ob):
return _Type_And_Features(ob, MemberNames(filter(lambda a: CallableMember(a), PublicMembers(ob))))
#return _Type_And_Features(ob, filter(lambda a: callable(a), Dir(ob)))
def IsClassVar(self, attrName):
return hasattr(self.__class__, attrName)
# REQUIRE attrName already known to be supported by self.
# But just in case, return False if exception, so will be skipped.
def IsNotSameAsClassVar(self, attrName):
try:
if not IsClassVar(self, attrName):
return True
# If it has different value than class' attribute, it is on the instance.
return getattr(self, attrName) is not getattr(self.__class__, attrName)
except:
return False
# ---------- _MayaValues ----------
# NOTE: 'ob' is an instance, not the class (type) itself.
def _ClassVars(ob):
attributes = filter(lambda a: not CallableMember(a), PublicMembers(ob))
# Keep class variables.
# "not IsProperty": HACK: Skip Maya/Swig 'property' class variables.
classVars = filter(lambda desc: IsClassVar(ob, desc[0]) and not IsProperty(getattr(ob.__class__, desc[0])), attributes)
return MemberNames(classVars)
# NOTE: 'ob' is an instance, not the class (type) itself.
def ClassVars(ob):
return _Header_And_Values(TypeName(ob) + ' Class_Variables',
map(lambda attr: attr + ': ' + Repr(getattr(ob, attr)), _ClassVars(ob)),
0
)
# If it is invocable without parameters, return (attrName, typename, result of invocation).
# if Not reportExceptions, return None for Exception.
def CallAttribute_AsTriple(self, attrName, reportExceptions=False):
try:
expressionString = 'self.{0}()'.format(attrName)
result = eval(expressionString)
typename = TypeName(result)
except Exception as e:
if reportExceptions:
result = e
typename = '*** Exception'
else:
return None
return (attrName, typename, result)
# member is tuple (attrName, typeName, value)
# If it is invocable without parameters, return (attrName, typename, result of invocation).
# if Not reportExceptions, return None for Exception.
def CallMember_AsTriple(self, member, reportExceptions=False):
return CallAttribute_AsTriple(self, member[0], reportExceptions)
# If it is invocable without parameters, return string: pretty-printed result of invocation.
# if Not reportExceptions, return None for Exception.
def CallAttribute(self, attrName, reportExceptions=False):
try:
#printElements(locals())
expressionString = 'self.{0}()'.format(attrName)
#print Eval(expressionString, locals())
result = eval(expressionString)
resultString = Repr(result)
typename = TypeName(result)
except Exception as e:
if reportExceptions:
#result = '*** Exception ' + str(e)
result = e
resultString = str(e)
typename = '*** Exception'
else:
return None
return ' .{0} {{{1}}}= {2}'.format(attrName, typename, resultString)
# member is tuple (attrName, typeName, value)
# If it is invocable without parameters, return string: pretty-printed result of invocation.
# if Not reportExceptions, return None for Exception.
def CallMemberRepr(self, member, reportExceptions=False):
return CallAttribute(self, member[0], reportExceptions)
def FirstLine(string):
lines = string.split('\n')
if len(lines) > 1:
return lines[0] + '...'
return string
def ArgLessRoutines_AsTriples(ob):
members = PublicMembers(ob)
members_WithNones = map(lambda member: CallMember_AsTriple(ob, member), members)
# member is tuple (attrName, typeName, value)
members = filter(Exists, members_WithNones)
return members
def ArgLessRoutines(ob):
members = PublicMembers(ob)
members_WithNones = map(lambda member: CallMember_AsTriple(ob, member), members)
# member is tuple (attrName, typeName, value)
members = filter(Exists, members_WithNones)
resultStrings = map(lambda string: FirstLine(string), resultStrings)
return _Header_And_Values(TypeName(ob) + ' ArgLessRoutines', resultStrings)
def _MayaCallables_Common(mayaType):
try:
typeName = mayaType.__name__
if typeName == 'MDagPath':
return ['fullPathName']
if typeName == 'MTypeId':
return ['id']
if typeName == 'MFnMesh':
return ['numPolygons', 'numVertices', 'numEdges', 'numFaceVertices']
if typeName == 'MDagPath':
return ['fullPathName']
except Exception as e:
print e
return []
def _MayaCallables_Version1(mayaType):
return _MayaCallables_Common(mayaType)
def _MayaCallables_Version2(mayaType):
return _MayaCallables_Common(mayaType)
# Names of callable attributes to include in Repr of 'ob'.
# For instances of types in 'maya.OpenMaya'.
def MayaCallables(ob):
try:
typ = ob.__class__
if typ == type:
return []
if typ.__module__ == 'maya.OpenMaya':
return _MayaCallables_Version1(typ)
if typ.__module__ == 'OpenMaya':
return _MayaCallables_Version2(typ)
except Exception as e:
print e
return []
# Return (name, typename, value) per maya callable.
def _MayaValues(ob):
callables = MayaCallables(ob)
members_WithNones = map(lambda attrName: CallAttribute_AsTriple(ob, attrName), callables)
members = filter(Exists, members_WithNones)
return members
# TODO: If all results fit on single line, remove "{typename}" so is more readable.
#def MayaValues(ob):
# resultStrings = _MayaValues(ob)
# return _Header_And_Values(TypeName(ob) + ' MayaValues', resultStrings)
# ---------- Attributes ----------
def _AttributeNames(ob):
attributes = filter(lambda a: not CallableMember(a), PublicMembers(ob))
# Omit class variables.
attributes = filter(lambda desc: IsNotSameAsClassVar(ob, desc[0]), attributes)
return MemberNames(attributes)
def AttributeNames(ob):
return _Type_And_Features(ob, _AttributeNames(ob))
#return _Type_And_Features(ob, filter(lambda a: not callable(a), Dir(ob)))
def _Header_And_Values(headerString, valueStrings, maxWidth=100):
if sum(map(len, valueStrings)) > maxWidth:
# pretty print, with one value per line.
return '{0}(\n {1}\n)'.format(headerString, '\n '.join(valueStrings))
return '{0}({1})'.format(headerString, ', '.join(valueStrings))
def _Type_And_Values(ob, valueStrings, maxWidth=100):
return _Header_And_Values(TypeName(ob), valueStrings, maxWidth)
def AttributeValues(ob):
return _Type_And_Values(ob, map(lambda attr: str(getattr(ob, attr)), _AttributeNames(ob)))
def Attributes(ob, depth=0):
# Limit recursion.
# If deep, don't include MayaValues.
if depth >= 2:
return _Type_And_Values(ob, map(lambda attr: attr + ': ' + str(getattr(ob, attr)), _AttributeNames(ob)))
attributes = map(lambda attr: attr + ': ' + Repr(getattr(ob, attr), depth + 1), _AttributeNames(ob))
if depth == 0:
mayaValues = _MayaValues(ob)
if len(mayaValues) > 0:
for mayaValue in mayaValues:
attribute = mayaValue[0] + ': ' + Repr(mayaValue[2])
attributes.append(attribute)
return _Type_And_Values(ob, attributes)
def IsProperty(ob):
return (TypeName(ob) == 'property')
# ---------- Repr ----------
def Repr(ob, depth=0):
r = repr(ob)
# Helps avoid undesired recursion.
if ob.__class__ == type:
return r
if (r.__class__ == types.StringType) and (len(r) > 0) and (r.find('<') <> 0):
# Has a good repr.
return r
# Doesn't have a good repr; inspect it instead.
return '#' + Attributes(ob, depth)
def Eval(expressionString, _locals=locals(), _globals=globals()):
return str(expressionString) + "= " + str(Repr(eval(expressionString, _globals, _locals)))
# ---------- Testing ----------
# ---------- class Vector ----------
class Vector(object):
def __init__(self, x=0.0, y=0.0, z=0.0):
self.x, self.y, self.z = x, y, z
# Provide useful info for 'repr(self)', 'str(self)', and 'print self'.
def __repr__(self):
return 'Vector({0}, {1}, {2})'.format(self.x, self.y, self.z)
# math operators
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
# ==
def __eq__(self, other):
return (self.__class__ == other.__class__) and \
(self.x == other.x) and \
(self.y == other.y) and \
(self.z == other.z)
# a simple method
def ApproximateLength(self):
return self.x + self.y + self.z
# list/sequence/iterator support.
def tolist(self):
return [self.x, self.y, self.z]
def __len__(self):
return 3
# No need for "next(self)", because we create a list, use its iterator.
def __iter__(self):
return iter(self.tolist())
# class variable
Vector.Zero = Vector()
# ---------- inspecting Vector ----------
def Testing_Vector_Attributes():
#vec = (1, 2, 3)
#vec = [1, 2, 3]
#vec = Vector(1.0, 2.0, 3.0)
vec = OM.MFloatVector(1, 2, 3)
print vec
#for x in vec: print x
print dir(vec)
print TypeName(vec)
print Dir(vec)
print Features(vec)
print Callable(vec)
print '-----------------------'
printElements(PublicMembers(vec))
print '-----------------------'
print AttributeNames(vec)
#print vec.x
#print eval('vec.x')
#print getattr(vec, 'x')
print AttributeValues(vec)
print Attributes(vec)
vec = OM.MFloatVector(1, 2, 3)
#print repr(vec)
#print Repr('Hi')
print Repr( (1,2,3) )
print Repr(vec)
print ClassVars( Vector(1.0, 2.0, 3.0) )
print ClassVars( OM.MFloatVector(1, 2, 3) )
print Eval('OM.MMatrix()')
print Eval('OM.MMatrix().matrix')
if __name__ == "__main__":
Testing_Vector_Attributes()

Python -- polynomials in finite fields. Why does only __add__() work with super() in this case?

I'm trying to use the parent class's __div__() in order to maintain the same type so that many operations can be called at once as in the last example mix1 = bf2/bf4*bf1%bf5 in main() below where multiple arithmetic operations are strung together. For some reason, I can use super() in __add__() but not in __div__(). The error is "IndexError: list index out of range" and I've been going over and over this without any progress. Note that this is all related to polynomial arithmetic within a finite field.
I'm including the parsePolyVariable() and it's dependents (sorry if it looks like there's a bit of code but I assure you it's all for a good cause and builds character), since that's where the list error seems to be stemming from but I can't for the life of me figure out where everything is going very wrong. I'm teaching myself Python, so I'm sure there are some other beginners out there who will see where I'm missing the obvious.
I've been looking over these but they don't seem to be related to this situation:
http://docs.python.org/2/library/functions.html#super
Python super(Class, self).method vs super(Parent, self).method
How can I use Python's super() to update a parent value?
import re
class GF2Polynomial(object): #classes should generally inherit from object
def __init__(self, string):
'''__init__ is a standard special method used to initialize objects.
Here __init__ will initialize a gf2infix object based on a string.'''
self.string = string #basically the initial string (polynomial)
#if self.parsePolyVariable(string) == "0": self.key,self.lst = "0",[0]
#else:
self.key,self.lst = self.parsePolyVariable(string) # key determines polynomial compatibility
self.bin = self.prepBinary(string) #main value used in operations
def id(self,lst):
"""returns modulus 2 (1,0,0,1,1,....) for input lists"""
return [int(lst[i])%2 for i in range(len(lst))]
def listToInt(self,lst):
"""converts list to integer for later use"""
result = self.id(lst)
return int(''.join(map(str,result)))
def parsePolyToListInput(self,poly):
"""
replaced by parsePolyVariable. still functional but not needed.
performs regex on raw string and converts to list
"""
c = [int(i.group(0)) for i in re.finditer(r'\d+', poly)]
return [1 if x in c else 0 for x in xrange(max(c), -1, -1)]
def parsePolyVariable(self,poly):
"""
performs regex on raw string, converts to list.
also determines key (main variable used) in each polynomial on intake
"""
c = [int(m.group(0)) for m in re.finditer(r'\d+', poly)] #re.finditer returns an iterator
if sum(c) == 0: return "0",[0]
letter = [str(m.group(0)) for m in re.finditer(r'[a-z]', poly)]
degree = max(c); varmatch = True; key = letter[0]
for i in range(len(letter)):
if letter[i] != key: varmatch = False
else: varmatch = True
if varmatch == False: return "error: not all variables in %s are the same"%a
lst = [1 if x in c else (1 if x==0 else (1 if x=='x' else 0)) for x in xrange(degree, -1, -1)]
return key,lst
def polyVariableCheck(self,other):
return self.key == other.key
def prepBinary(self,poly):
"""converts to base 2; bina,binb are binary values like 110100101100....."""
x = self.lst; a = self.listToInt(x)
return int(str(a),2)
def __add__(self,other):
"""
__add__ is another special method, and is used to override the + operator. This will only
work for instances of gf2pim and its subclasses.
self,other are gf2infix instances; returns GF(2) polynomial in string format
"""
if self.polyVariableCheck(other) == False:
return "error: variables of %s and %s do not match"%(self.string,other.string)
return GF2Polynomial(self.outFormat(self.bin^other.bin))
def __sub__(self,other):
"""
__sub__ is the special method for overriding the - operator
same as addition in GF(2)
"""
return self.__add__(other)
def __mul__(self,other):
"""
__mul__ is the special method for overriding the * operator
returns product of 2 polynomials in gf2; self,other are values 10110011...
"""
if self.polyVariableCheck(other) == False:
return "error: variables of %s and %s do not match"%(self.string,other.string)
bitsa = reversed("{0:b}".format(self.bin))
g = [(other.bin<<i)*int(bit) for i,bit in enumerate(bitsa)]
return GF2Polynomial(self.outFormat(reduce(lambda x,y: x^y,g)))
def __div__(self,other):
"""
__div__ is the special method for overriding the / operator
returns quotient formatted as polynomial
"""
if self.polyVariableCheck(other) == False:
return "error: variables of %s and %s do not match"%(self.string,other.string)
if self.bin == other.bin: return 1
return GF2Polynomial(self.outFormat(self.bin/other.bin))
def __mod__(self,other):
"""
__mod__ is the special method for overriding the % operator
returns remainder formatted as polynomial
"""
if self.polyVariableCheck(other) == False:
return "error: variables of %s and %s do not match"%(self.string,other.string)
if self.bin == other.bin: return 0
return GF2Polynomial(self.outFormat(self.bin%other.bin))
def __str__(self):
return self.string
def outFormat(self,raw):
"""process resulting values into polynomial format"""
raw = "{0:b}".format(raw); raw = str(raw[::-1]); g = [] #reverse binary string for enumeration
g = [i for i,c in enumerate(raw) if c == '1']
processed = "x**"+" + x**".join(map(str, g[::-1]))
proc1 = processed.replace("x**1","x"); proc2 = proc1.replace("x**0","1")
if len(g) == 0: return 0 #return 0 if list empty
return proc2 #returns result in gf(2) polynomial form
class BinaryField(GF2Polynomial):
def __init__(self, poly, mod):
if mod == "0": self.string = "Error: modulus division by 0"
elif mod == "0": self.string = "%s is 0 so resulting mod is 0"%(poly)
fieldPoly = GF2Polynomial(poly) % mod
if fieldPoly == 0: self.string = "%s and %s are the same so resulting mod is 0"%(poly,mod)
else: super(BinaryField, self).__init__(fieldPoly.string)
#self.degree = len(str(fieldPoly))
def polyFieldCheck(self,other):
return self.degree() == other.degree()
def __add__(self, other):
"""
inherited from GF2Polynomial
"""
return super(BinaryField, self).__add__(other) % min(other,self)
def __sub__(self,other):
"""
inherited from GF2Polynomial
"""
return self.__add__(other)
def __mul__(self, other):
"""
special method of BinaryField, needed for format adjustments between classes
"""
#print "self = %s,%s other = %s,%s "%(self.degree(),type(self.degree()),other.degree(),type(other.degree()))
if self.polyVariableCheck(other) == False:
return "error: variables of %s and %s do not match"%(self.string,other.string)
if self.polyFieldCheck(other) == False:
return "error: fields of %s and %s do not match"%(self.string,other.string)
else: print "Operation will proceed: fields of %s and %s match"%(self.string,other.string)
bitsa = reversed("{0:b}".format(self.bin))
g = [(other.bin<<i)*int(bit) for i,bit in enumerate(bitsa)]
result = reduce(lambda x,y: x^y,g)%min(self.bin,other.bin)
return GF2Polynomial(self.outFormat(result))
def __div__(self, other):
"""
special method of BinaryField, needed for format adjustments between classes
"""
if self.polyVariableCheck(other) == False:
return "error: variables of %s and %s do not match"%(self.string,other.string)
if self.polyFieldCheck(other) == False:
return "error: fields of %s and %s do not match"%(self.string,other.string)
else: print "Operation will proceed: fields of %s and %s match"%(self.string,other.string)
if self.bin == other.bin: return 1
result = self.bin/other.bin
#return self.outFormat(result)
return super(BinaryField, self).__div__(other) #% min(other,self)
def degree(self):
return len(self.lst)-1
And here's the main():
if __name__ == '__main__':
## "x**1 + x**0" polynomial string style input
poly1 = "x**14 + x**1 + x**0"; poly2 = "x**6 + x**2 + x**1"; poly3 = "y**6 + y**2 + y**1"
a = GF2Polynomial(poly1); b = GF2Polynomial(poly2); c = GF2Polynomial(poly3)
## "x+1" polynomial string style input
poly4 = "x**14 + x + 1"; poly5 = "x**6 + x**2 + x"; poly6 = "x**8 + x**3 + 1"
d = GF2Polynomial(poly4); e = GF2Polynomial(poly5); f = GF2Polynomial(poly6)
poly7 = "x**9 + x**5 + 1"; poly8 = "x**11 + x**7 + x**4 + 1"; poly9 = "x**5 + x**4 + x**2 + x"
g = GF2Polynomial(poly7); h = GF2Polynomial(poly8); i = GF2Polynomial(poly9)
## g = GF2Polynomial("x**5 + x**4 + x**3 + 1"); h = GF2Polynomial("x**5 + x"); print "(g*h)%b = ",(g*h)%b
## dd = GF2Polynomial("x**0"); print "dd -- ",dd
## ee = GF2Polynomial("0"); print "ee -- ",ee
bf1 = BinaryField(poly1,b); print bf1; print "degree bf1 = ",bf1.degree()
bf2 = BinaryField(poly4,e); print "bf2 ",bf2; bf3 = BinaryField(poly4,d); print "bf3 ",bf3,type(bf3)
bf4 = BinaryField(poly4,h); bf5 = BinaryField(poly9,e); bf6 = BinaryField(poly8,i)
add1 = bf1+bf2
print "add1 ",add1
div1 = bf1/bf2
print "div1 ",div1,type(div1)
mix1 = bf2*bf1%bf5
print "mix1 ",mix1,type(mix1)
EDIT:
The full traceback --
Message File Name Line Position
Traceback
<module> C:\Users\win7pro-vm\Desktop\crypto\GF2BinaryField.py 233
__div__ C:\Users\win7pro-vm\Desktop\crypto\GF2BinaryField.py 197
__div__ C:\Users\win7pro-vm\Desktop\crypto\GF2BinaryField.py 100
__init__ C:\Users\win7pro-vm\Desktop\crypto\GF2BinaryField.py 20
parsePolyVariable C:\Users\win7pro-vm\Desktop\crypto\GF2BinaryField.py 48
IndexError: list index out of range
For reference line 48 is degree = max(c); varmatch = True; key = letter[0].
Personal notes and information were removed, adjusting the line numbers.
Your return GF2Polynomial(self.outFormat(self.bin/other.bin)) line results in the string 1, which is then passed to the GF2Polynomial.parsePolyVariable() method.
This value has no letters, so the line:
letter = [str(m.group(0)) for m in re.finditer(r'[a-z]', poly)]
returns an empty list. The next line:
degree = max(c); varmatch = True; key = letter[0]
then fails because key = letter[0] gives a IndexError exception.
Your code is hard to read because you use one-letter variables and put multiple statements on one line, so it is hard to make out what your expectations are in that function.
The exception has otherwise nothing to do with super(). There is a simple bug in your own code somewhere.

Categories

Resources