0

I am working on a project using PuLP and I am trying to create a terminal prompt to allow users to input their data and then my Python program will change the input to code readable by PuLP. To do this I must allow users to input:

2*a + 3*b <= c

and my code will eval() this code as well as creating variables a, b and c such

a = LpVariable("a",None,None,LpContinuous)
b = LpVariable("b",None,None,LpContinuous)
c = LpVariable("c",None,None,LpContinuous)

any ideas? I've tried using exec() but it does not seem to like this much.

at the moment I'm getting the input via:

print "Please enter your constraints 1 at a time and enter '#' when done:"
control = True
while (control):
    entry = raw_input('-->')
    entryS = ""
    entryS += entry

so the string 2*a+3*B <= c is stored as entryS

dassouki
  • 6,286
  • 7
  • 51
  • 81
  • 1
    how are you getting the input from the terminal now? – munk Dec 06 '12 at 20:30
  • i would first input the whole thing as string, then you have to check each "letter" to see if it's a char, int oder operator and then declare a matching variable. – Lotzki Dec 06 '12 at 20:35
  • @usmcs updated the question, sorry. – user1883573 Dec 06 '12 at 20:39
  • That will delete enstryS every time. – Trufa Dec 06 '12 at 20:41
  • @user1880863 how do I declare a matching variable if I don't know what is going to be said it could be 2*bla + 3*anystring <= jkl – user1883573 Dec 06 '12 at 20:41
  • @Trufa I want it to do that because the user can have more than one input. – user1883573 Dec 06 '12 at 20:41
  • @user1883573 ok, I don't really understand the question then :) – Trufa Dec 06 '12 at 20:43
  • The user is going to input a set of constraints for PuLP (a linear programming package for python) one at a time. However, PuLP needs you to declare the variables seperately so I want to iterate through the input to try find the variables and put them into the second type of code. Sorry if I'm not explaining very well, I'm new to this. – user1883573 Dec 06 '12 at 20:46
  • @user1883573 sorry man, still don't really get it, lets start little by little, what do you expect the user to input and in which way? – Trufa Dec 06 '12 at 20:58
  • the user will input some linear inequality such as: a*x_1 + b*x_2 + c*x_3 <= d via the command prompt – user1883573 Dec 06 '12 at 21:03

2 Answers2

1

Using eval() might not be such a good idea, but if you insist (Python 3):

call = lambda f: lambda *args: f(*args)
flip = lambda f: lambda *args: f(*reversed(args))


class Expression:

    def __repr__(self):
        return '{}({})'.format(type(self).__name__, self)


class BinaryExpression(Expression):

    def __init__(self, left, right):
        self.left = promote(left)
        self.right = promote(right)

    def __str__(self):
        return '({} {} {})'.format(self.op, self.left, self.right)


class Variable(Expression):

    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name


class Number(Expression):

    def __init__(self, value):
        self.value = int(value)

    def __str__(self):
        return str(self.value)


class Multiplication(BinaryExpression):
    op = '*'


class Addition(BinaryExpression):
    op = '+'


class Smaller(BinaryExpression):
    op = '<'


class Greater(BinaryExpression):
    op = '>'


class SmallerOrEqual(BinaryExpression):
    op = '<='


class GreaterOrEqual(BinaryExpression):
    op = '>='


Expression.__mul__ = call(Multiplication)
Expression.__rmul__ = flip(Multiplication)
Expression.__add__ = call(Addition)
Expression.__radd__ = flip(Addition)
Expression.__lt__ = call(Smaller)
Expression.__gt__ = call(Greater)
Expression.__le__ = call(SmallerOrEqual)
Expression.__ge__ = call(GreaterOrEqual)


def promote(item):
    if isinstance(item, str):
        return Variable(item)
    elif isinstance(item, int):
        return Number(item)
    else:
        return item


class LpVariable:

    def __init__(self, name, x, y, z):
        self.name = name
        self.x = x
        self.y = y
        self.z = z

    def __str__(self):
        return 'LpVariable({}, {}, {}, {})'.format(
            self.name,
            self.x,
            self.y,
            self.z,
        )

    __repr__ = __str__


LpContinuous = 'LpContinuous'


class ExpressionVisitor:

    def visit(self, node):
        return getattr(self, 'visit_' + type(node).__name__)(node)


class LpTransformer(ExpressionVisitor):

    def visit_Variable(self, node):
        return LpVariable(node.name, None, None, LpContinuous)

    def visit_Number(self, node):
        return node.value

    def visit_Multiplication(self, node):
        return [node.op, self.visit(node.left), self.visit(node.right)]

    def visit_Addition(self, node):
        return [node.op, self.visit(node.left), self.visit(node.right)]

    def visit_Smaller(self, node):
        return [node.op, self.visit(node.left), self.visit(node.right)]

    def visit_Greater(self, node):
        return [node.op, self.visit(node.left), self.visit(node.right)]

    def visit_SmallerOrEqual(self, node):
        return [node.op, self.visit(node.left), self.visit(node.right)]

    def visit_GreaterOrEqual(self, node):
        return [node.op, self.visit(node.left), self.visit(node.right)]


class Evaluator(ExpressionVisitor):

    def __init__(self, **env):
        self.env = env

    def visit_Variable(self, node):
        return self.env[node.name]

    def visit_Number(self, node):
        return node.value

    def visit_Multiplication(self, node):
        return self.visit(node.left) * self.visit(node.right)

    def visit_Addition(self, node):
        return self.visit(node.left) + self.visit(node.right)

    def visit_Smaller(self, node):
        return self.visit(node.left) < self.visit(node.right)

    def visit_Greater(self, node):
        return self.visit(node.left) > self.visit(node.right)

    def visit_SmallerOrEqual(self, node):
        return self.visit(node.left) <= self.visit(node.right)

    def visit_GreaterOrEqual(self, node):
        return self.visit(node.left) >= self.visit(node.right)


class Namespace(dict):

    def __missing__(self, key):
        value = self[key] = Variable(key)
        return value


def main():
    constraints = '2*a + 3*b <= c'
    namespace = Namespace()
    tree = eval(constraints, {}, namespace)
    print('AST in prefix notation:', tree)
    print()
    print('Namespace:', namespace)
    print()
    print('LP-Transformed tree:')
    import pprint
    pprint.pprint(LpTransformer().visit(tree))
    print()
    print('Evaluated with a=3, b=5, c=10:')
    pprint.pprint(Evaluator(a=3, b=5, c=10).visit(tree))
    print()
    print('Evaluated with a=3, b=5, c=100:')
    pprint.pprint(Evaluator(a=3, b=5, c=100).visit(tree))


if __name__ == '__main__':
    main()

Result:

AST in prefix notation: (<= (+ (* 2 a) (* 3 b)) c)    

Namespace: {'a': Variable(a), 'c': Variable(c), 'b': Variable(b)}

LP-Transformed tree:
['<=',
 ['+',
  ['*', 2, LpVariable(a, None, None, LpContinuous)],
  ['*', 3, LpVariable(b, None, None, LpContinuous)]],
 LpVariable(c, None, None, LpContinuous)]

Evaluated with a=3, b=5, c=10:
False

Evaluated with a=3, b=5, c=100:
True

The LpVariable class is obviously a mockup. Also, the LpTransformer class should produce something that's usable by pulp. Just change the visit_* methods accordingly.

Numbers are all ints, which you might not want. You should probably add floats and/or convert all numbers to decimal.Decimal.

Instead of using eval() I'd probably write a real parser, maybe with pyparsing, or, my favorite for stuff like that, Parcon.

pillmuncher
  • 10,094
  • 2
  • 35
  • 33
0

Put

entryS = "" 

before the while loop.

VoronoiPotato
  • 3,113
  • 20
  • 30
  • The user is going to input a set of constraints for PuLP (a linear programming package for python) one at a time. However, PuLP needs you to declare the variables seperately so I want to iterate through the input to try find the variables and put them into the second type of code. Sorry if I'm not explaining very well, I'm new to this. so entryS has to be reset for each input. – user1883573 Dec 06 '12 at 20:47