Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POEM_096 Research and Pseudocode #196

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions POEM_096/POEM_096.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
POEM ID: 096

Title: Research and Pseudocode of Manual Reverse Mode differentiation

authors: jsrogan, coleyoung5

Competing POEMs: N/A

Related POEMs: N/A

Associated implementation PR: N/A


Status:

- [x] Active
- [ ] Requesting decision
- [ ] Accepted
- [ ] Rejected
- [ ] Integrated



## Motivation
Considering the fact that private optimization libraries such as SNOPT offer the ability to find a feasible starting point, it makes sense that OPENMdao should also have that functionality. Aditionally OPENMdao's current components do not calculate reverse derivatives, so the implementation of this is also necessary (and still ongoing).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenMDAO computes partials at the component level, either through specification of a jacobian matrix - or through a "matrix-free" API (the component.compute_jacvec_prod method) in it will compute a forward JVP or a reverse VJP. The framework then assembles these calculations into a graph for efficient computation and assembly of the total derivatives. The notion of reverse derivatives just aren't relevant to the implementation of a feasible starting point.




## Description
In order to implement this we researched helpful libraries to implement a tree like structure which would allow us to perform reverse mode differentation while backtracking, and wrote pseudocode which explains our choices and our insights into the problem. While not a full solution by any means, hopefully this provides a strong starting point for others who are looking to implement this problem.




81 changes: 81 additions & 0 deletions POEM_096/poem096.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#we went with anytree because we saw feasible start as a backtracking problem, and anytree seemed like it would be an effective
#method of constructing a tree to backtrack through, well documented, high functionality, etc.
from anytree import Node, RenderTree
from sympy import symbols, diff
import re

def problem_to_tree(problem):
"""
Based on this line of code from parabaloid example:
prob.model.add_subsystem('paraboloid', om.ExecComp('f = (x-3)**2 + x*y + (y+4)**2 - 3'))

We assume 'problem' as it is passed is an OpenMDAO problem, and we constructing the tree from the equation stored in the subsystem


Level 1 is the initial problem (represented by a function) and the left hand sign of the equation, in this case single variable Y

1 Y = A*B + C*D
/ \
/ \
2 AB CD
/ \ / \
/ \ / \
3 A B C D

Gradients: the gradient list ends up being all leaf nodes at the end of the tree's formation

Partial Dervivative: Compute the partial derivative of each parent with respect to all it's children
eg, the PARTIAL of A would be partial_derivative(parent, child) or partial of AB with respect to A, which is B

All nodes link to both their parent and all children (lead nodes and root nodes only link 1 way)


per prob.model.add_subsystem('paraboloid', om.ExecComp('f = (x-3)**2 + x*y + (y+4)**2 - 3')),
subsystem was assumed to be tuple of name and om.ExecComp and that ExecComp was stored as a string
"""
equation = problem.model.subsystem[1] #abstraction, unsure how to access the equation string stored in subsystem

equation = equation.split('=')

lhs = equation[0]
lhs = lhs.strip(' ')
root = Node(lhs, gradients=[]) #gradients starts empty, and is filled during back propagation

rhs = equation[1]
terms_list = re.split(r'\D', rhs) #rough idea, does not take into account full order of operations

for each term in terms_list:
"""
construct an anytree node (as outlined for the root) and
store which operation (+, -, /, *, etc.) took you from parent to child

Example Node AB would be (from level 2 in diagram above):
node_ab = Node(AB, parent=root, gradients=[], operation='*')

- AB is the node's name
- the parent node is the root of the tree in this case
- gradients are empty until back propagation
- the operation which forms AB's children is multiplication. Operation of leaf node is null
"""


return RenderTree(root)


def find_feasible_start(problem):
tree = problem_to_tree(problem)

equation = problem.model.subsystem[1] #abstraction, unsure how to access the equation string stored in subsystem
equation = equation.split('=')
rhs = equation[1]
exclude = {'*', '/', '+', '-', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9'} #characters we don't differentiate with respect to

symbols = [] #each symbol (eg variable) we want to differentiate with respect to
for char in rhs:
if char not in exclude:
symbols.append(char)




Loading