Skip to content

Commit 794bc35

Browse files
authored
Merge pull request #2 from nivmoti/master
Adding visualizations to convex optimization problems, from the cvxpy library
2 parents 8506add + 5797d42 commit 794bc35

11 files changed

+1313
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,4 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
.idea/

README.md

+101
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,104 @@ from analyzer import tech_support
1212
# Analyze the problem.
1313
tech_support(problem)
1414
```
15+
16+
17+
# Visual:
18+
The Purpose of our project is to visualize the Problem expressions which can be found in CVXPY library. <br />
19+
During the development process, we have created four different functions that allow to visualize the Problem expressions in a more accessible and clear way for library users. <br />
20+
As you can see in the file: ``examples.py`` <br />
21+
You can see several examples of how to run expressions using the different functions we created.
22+
23+
## v.draw_graph(xmin, xmax):
24+
A function that graphically displays all the solutions of a specific expression where the variable is represented as X and the equation as Y.
25+
If a function is called without any arguments, the default value will be xmin=-10 xmax=10.
26+
27+
Here you can see the results:
28+
As you can see for the phrase: `Minimize(-1 * (y2) ** 2 + 2 * y2)` <br />
29+
with the constraints `[y2 >= 1]` <br />
30+
31+
```
32+
33+
y2 = Variable()
34+
35+
objective = Minimize(-1 * (y2) ** 2 + 2 * y2)
36+
constraints = [y2 >= 1]
37+
v = Visual(objective)
38+
print(v.expr)
39+
v.draw_graph()
40+
```
41+
by running `v.draw_graph()` you will get the following graph:<br />
42+
![3](https://user-images.githubusercontent.com/93201414/229357160-6517dcca-fcb7-4257-b145-400c084bf853.png)
43+
44+
It displays all the solutions of the equation that we want to see when substituting variable values between -10 to 10.
45+
46+
## v.show_and_save(file_name):
47+
We created this function to enable viewing the expression in a graph format with nodes, which will allow for visual and clear representation. <br />
48+
For each expression you choose, the function's output is a PDF file named "file_name.pdf", which is very similar to the DCP Analyzer. https://dcp.stanford.edu/analyzer <br />
49+
50+
Here too it was important for us to present for all: Parameters , Variables <br />
51+
Which `Sing` is of a type of `Positive` , `Negative`, `Unknown` and Which `Curvature` is of a type of `Constant`,`Affine`,`Convex`,`Concave`,`Unknown`. <br />
52+
53+
In the ``example.py`` file you can run several expressions on this function.
54+
55+
```
56+
x2 = Variable()
57+
y2 = Variable()
58+
objective1 = Minimize((x2 - y2) ** 2)
59+
v = Visual(objective1)
60+
v.show_and_save("file_name")
61+
```
62+
63+
Minimize((x2 - y2) ** 2) <br />
64+
65+
You will get the following graph: <br />
66+
67+
<img width="300" alt="4" src="https://user-images.githubusercontent.com/93201414/229358362-0c92af7f-3ebc-4dd5-85d1-46b82e1aafcd.PNG">
68+
<br />
69+
70+
**Please note that you can run only one example at a time and not all of them together. If you run all of them together, the output file will show only the graph of the last example that was executed** <br />
71+
72+
73+
## v.print_expr():
74+
This function prints the expression in the structure of a tree in the RUN window.
75+
76+
```
77+
x2 = Variable()
78+
y2 = Variable()
79+
80+
objective1 = Minimize((x2 - y2) ** 2)
81+
82+
# examples for print_tree:
83+
84+
print("---objective 1 ---")
85+
v = Visual(objective1)
86+
print(v.curvature_sign_list)
87+
print("---objective 1 ---")
88+
89+
v.print_expr()
90+
```
91+
For each expression, you can run the following function,<br />
92+
This will produce a graph that will be displayed in the runtime window.
93+
94+
For example **for the first expression**
95+
1. Minimize((x2 - y2) ** 2) <br />
96+
97+
you will get :
98+
![image](https://user-images.githubusercontent.com/93201414/229359112-bc30b68a-bcd8-4739-b302-0d96932c2b8f.png)
99+
100+
## v.show():
101+
The function uses the library tkinter <br />
102+
The output shows the tree in a way that opens and closes according to the user <br />
103+
Unlike show_and_save() the output is not saved but opens in a pop-up window <br />
104+
105+
```
106+
x2 = Variable()
107+
y2 = Variable()
108+
objective1 = Minimize((x2 - y2) ** 2)
109+
v = Visual(objective1)
110+
v.show()
111+
```
112+
113+
<img width="158" alt="two" src="https://user-images.githubusercontent.com/93201414/237039372-d6d79c0c-d564-467d-ba21-b5290e7873ba.PNG">
114+
115+

analyzer/solvers.py

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from cvxpy.error import DCPError
1414
import cvxpy as cp
1515

16+
from visual.visual_expression import prob
17+
1618

1719
def get_solvers(problem):
1820
"""Get valid solvers.

analyzer/version.py

+3
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ def check_version():
2727
else:
2828
msg = "The request to pypi returned status code" + str(r.status_code)
2929
raise RuntimeError(msg)
30+
31+
32+
check_version()

requirements.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cvxpy~=1.3.0
2+
numpy~=1.24.2
3+
future~=0.18.3
4+
cvxopt~=1.3.0
5+
requests~=2.28.2
6+
setuptools~=57.0.0
7+
matplotlib~=3.7.1
8+
graphviz~=0.20.1
9+
cp~=2020.12.3
10+
sympy~=1.11.1
11+
pytest~=7.3.1

tests/test_basics.py

+5
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ def test_solvers():
2626
prob = cp.Problem(cp.Minimize(cp.sum_squares(x)), [-1 <= x, x <= 1])
2727
solvers = get_solvers(prob)
2828
assert len(solvers) > 0
29+
30+
31+
test_solvers()
32+
test_checker()
33+
test_version()

visual/examples.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import cvxpy as cp
2+
import cvxopt
3+
from cvxpy import Minimize, Variable, quad_form
4+
from visual.visual_expression import Visual
5+
6+
if __name__ == '__main__':
7+
n = 3
8+
P = cvxopt.matrix([13, 12, -2,
9+
12, 17, 6,
10+
-2, 6, 12], (n, n))
11+
q = cvxopt.matrix([-22, -14.5, 13], (n, 1))
12+
r = 1
13+
x_star = cvxopt.matrix([1, 1 / 2, -1], (n, 1))
14+
15+
x1 = Variable(n)
16+
y1 = Variable()
17+
x2 = Variable()
18+
y2 = Variable()
19+
z1 = Variable(n)
20+
21+
objective = Minimize(-1 * y2 ** 2 + 2 * y2)
22+
objective1 = Minimize((x2 - y2) ** 2)
23+
objective2 = Minimize(0.5 * quad_form(x1, P) - cp.sum_squares(x1) + q.T @ x1 + r + y1)
24+
25+
print("----objective----")
26+
v = Visual(objective)
27+
v.show()
28+
v.show_and_save("file_name")
29+
v.draw_graph()
30+
v.print_expr()
31+
print("----objective----")
32+
33+
print("----objective1----")
34+
v = Visual(objective1)
35+
v.show()
36+
v.show_and_save("file_name1")
37+
v.draw_graph()
38+
v.print_expr()
39+
print("----objective1----")
40+
41+
print("----objective2----")
42+
v = Visual(objective2)
43+
v.show()
44+
v.show_and_save("file_name2")
45+
v.draw_graph()
46+
v.print_expr()
47+
print("----objective2----")

visual/expression_tree.py

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import re
2+
3+
4+
class Node:
5+
# Each node has a unique name because an operator can appear more than once
6+
uniqNameForTree = 0
7+
count_curvature = 0
8+
count_sign = 0
9+
10+
def __init__(self, node, expr: str, flag=0):
11+
# The father of this node - for every operator the father is of type expression
12+
# and for every expression the father is of type operator
13+
self.father = node
14+
# A list of the children of the node -
15+
# each expression has a single child (the operator with the highest priority)
16+
# and each operator has several children
17+
self.sons = []
18+
# the value of the node
19+
self.expr = expr
20+
# flag = 1 ->operator , flag = 0 ->expression
21+
self.flag = flag
22+
self.name = self.uniqNameForTree
23+
Node.uniqNameForTree += 1
24+
self.curvature = None
25+
self.sign = None
26+
27+
self.c_curvature = self.count_curvature
28+
Node.count_curvature += 1
29+
30+
self.c_sign = self.count_sign
31+
Node.count_sign += 1
32+
33+
# ---for test---
34+
def checkin_sons(self, expr: str):
35+
for s in self.sons:
36+
expression = expr.split(' ')
37+
bool = True
38+
for e in expression:
39+
if not s.expr.__contains__(e):
40+
bool = False
41+
break
42+
if bool:
43+
return True
44+
return False
45+
46+
def node_son(self, expr: str):
47+
if not self.sons:
48+
return None
49+
for s in self.sons:
50+
if s.expr == expr:
51+
return s
52+
return None
53+
54+
# ---for test---
55+
56+
def insert(self, op: str):
57+
"""
58+
This function get an operator and uses it to split the expression and create new nodes
59+
60+
"""
61+
# create a new node of type operator
62+
if op not in self.expr:
63+
return
64+
node = Node(self, op, 1)
65+
self.sons.append(node)
66+
# We will look for the first position of the operator that is not inside parentheses and inside a matrix
67+
count = -1
68+
check1 = False
69+
check2 = False
70+
for i in self.expr:
71+
count += 1
72+
if i == '[':
73+
check2 = True
74+
continue
75+
if i == ']':
76+
check2 = False
77+
continue
78+
if i == '(':
79+
check1 = True
80+
continue
81+
if i == ')':
82+
check1 = False
83+
continue
84+
if i == op and not check1 and not check2:
85+
break
86+
87+
ans = [self.expr[:count], self.expr[count + 1:]]
88+
for i in range(len(ans)):
89+
node.sons.append(Node(node, ans[i], 0))
90+
91+
def insert_func(self, op: str):
92+
"""
93+
This function inserts a function type operator and
94+
this means that the expression should be split according to the number of parameters the function accepts
95+
"""
96+
index_first = op.index('(')
97+
index_sec = op.rfind(')')
98+
newExpr = op[index_first + 1: index_sec]
99+
# the operator is the name of the function
100+
node = Node(self, op.split('(')[0], 1)
101+
self.sons.append(node)
102+
param = re.split(r',(?![^()]*\))', newExpr)
103+
# The children of a function type operator are the parameters it accepts
104+
for p in param:
105+
node.sons.append(Node(node, p, 0))
106+
107+
def print_tree(self, spaces=0, counter=0):
108+
"""
109+
This function prints the tree recursively
110+
"""
111+
for i in range(spaces):
112+
print(" ", end="")
113+
if self.flag == 0:
114+
# Print the child's number
115+
print(counter, ".expression:", self.expr)
116+
s = spaces + 1
117+
c = 1
118+
for e in self.sons:
119+
e.print_tree(s, c)
120+
c += 1
121+
for i in range(spaces + 1):
122+
print(" ", end="")
123+
print("]")
124+
else:
125+
print("operator:", self.expr)
126+
for i in range(spaces):
127+
print(" ", end="")
128+
print("[")
129+
s = spaces + 1
130+
c = 1
131+
for e in self.sons:
132+
e.print_tree(s, c)
133+
c += 1
134+
s += 1

0 commit comments

Comments
 (0)