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

Metadata fbc3 group #988

Open
wants to merge 81 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
782b645
added annotation class
Hemant27031999 Jun 4, 2020
9709974
made some modifications
Hemant27031999 Jun 4, 2020
a7af772
made the complete metadata package
Hemant27031999 Jun 9, 2020
a191413
modified meta_data classes
Hemant27031999 Jun 10, 2020
4ea5b3e
modified classes to look exactly like dictionaries and lists
Hemant27031999 Jun 11, 2020
7795ea6
modified imports and incorporated SBO term in metadata
Hemant27031999 Jun 11, 2020
02345bb
made the classed to inherit from MutableSequence and MutableMapping
Hemant27031999 Jun 13, 2020
4c6c1ca
adding backward compatibility
Hemant27031999 Jun 15, 2020
ff72c55
added instance
Hemant27031999 Jun 15, 2020
989ffe4
work on annotation structure
matthiaskoenig Jun 15, 2020
a4eb840
adding json example
matthiaskoenig Jun 15, 2020
cf38e02
fixed instance2
Hemant27031999 Jun 15, 2020
7d490a4
work on cvterms
matthiaskoenig Jun 15, 2020
024c980
made backward compatible
Hemant27031999 Jun 18, 2020
ff440fb
code review metadata
matthiaskoenig Jun 18, 2020
e4ad484
cleaned the metadata class by putting code in respective classes
Hemant27031999 Jun 21, 2020
2508ac3
new annotation format supported for SBML to cobra model
Hemant27031999 Jun 23, 2020
d5fc54c
added io for json and other formats
Hemant27031999 Jun 24, 2020
c9e904b
added tests for new annotation format
Hemant27031999 Jun 25, 2020
a6e2f8a
updated history
matthiaskoenig Jun 25, 2020
23f9699
fixing broken tests
Hemant27031999 Jun 28, 2020
2f55bbf
commented a few methods
Hemant27031999 Jun 28, 2020
5aae1b0
added equal method inisde metadata classes
Hemant27031999 Jun 29, 2020
d467a14
modified directories paths
Hemant27031999 Jun 30, 2020
e4cc72b
fixed imports and tox tests
Hemant27031999 Jun 30, 2020
32a81da
solved the problem of annotation copy
Hemant27031999 Jul 1, 2020
a07ccaf
new notes format
Hemant27031999 Jul 2, 2020
f69a55c
code refactoring
matthiaskoenig Jul 2, 2020
e4ab40b
modified history, keyvaluepair and notes
Hemant27031999 Jul 4, 2020
8dc96cf
modified tests and imports
Hemant27031999 Jul 5, 2020
a3685d4
modified cvterm class
Hemant27031999 Jul 6, 2020
991123b
small fixes
matthiaskoenig Jul 9, 2020
70b33db
refactored a few names
Hemant27031999 Jul 12, 2020
0e20a91
modified notes documentation
Hemant27031999 Jul 15, 2020
eeee56a
added user defined constraint class
Hemant27031999 Jul 16, 2020
f93723b
example test cases
matthiaskoenig Jul 16, 2020
efcf064
added tests for UserDefinedConstraints
Hemant27031999 Jul 23, 2020
3039344
modified tests
Hemant27031999 Jul 23, 2020
d5dedc9
small modifications
Hemant27031999 Jul 26, 2020
f839755
moved cobra directory to src directory
Hemant27031999 Jul 26, 2020
9704a4b
solved conflicts
Hemant27031999 Jul 26, 2020
211bf48
fixed some tests
Hemant27031999 Jul 26, 2020
1dba21e
fixed conflicts and tests
Hemant27031999 Jul 26, 2020
90c156f
fixed tests
Hemant27031999 Jul 27, 2020
91177cd
added support of group to JSON
Hemant27031999 Jul 27, 2020
1fc7ab2
added support for optional ids in UserDefinedConstraint class
Hemant27031999 Jul 28, 2020
de2398d
added test for json validation function
Hemant27031999 Jul 30, 2020
54809ad
added json schema import requirement
Hemant27031999 Jul 30, 2020
c758bc5
added helper function for UserDefinedConstraint
Hemant27031999 Jul 30, 2020
5c5e2c2
added validate function for json models
Hemant27031999 Jul 30, 2020
0884f4a
added ast tree for parsing constraint expression
Hemant27031999 Aug 5, 2020
0f93cdd
modified json validation function
Hemant27031999 Aug 6, 2020
3da58be
removed python 2 support
Hemant27031999 Aug 6, 2020
8ec5ef2
modified ids in json
Hemant27031999 Aug 6, 2020
d2d8ee9
code refactoring
matthiaskoenig Aug 6, 2020
14a68f5
reformatted code to python 3
Hemant27031999 Aug 9, 2020
80fdcaa
added datetime validation for py3.6
Hemant27031999 Aug 15, 2020
06cb989
modified dict.py
Hemant27031999 Aug 15, 2020
c10f3fa
modified xfail
Hemant27031999 Aug 15, 2020
1747a7b
removed .idea files
Hemant27031999 Aug 15, 2020
14a1415
removed .idea
Hemant27031999 Aug 15, 2020
ba2990c
merged with devel
Hemant27031999 Aug 16, 2020
b04b81a
removed idea
Hemant27031999 Aug 16, 2020
6bae4c0
modified notes string passing
Hemant27031999 Aug 21, 2020
66dd463
modified notes return type
Hemant27031999 Aug 26, 2020
479c2b2
adding dependency for json validation
matthiaskoenig Aug 26, 2020
543d1f8
added .idea files
matthiaskoenig Aug 26, 2020
65e48f1
merged latest devel
matthiaskoenig Aug 26, 2020
564a51a
refactored and cleanup of history
matthiaskoenig Aug 26, 2020
a6aefc5
Intermediate refactoring, breaking changes
matthiaskoenig Aug 26, 2020
42f4838
refactored keyvaluepairs
matthiaskoenig Aug 26, 2020
b3e26cc
cleanup of datetimes
matthiaskoenig Aug 26, 2020
ca9ef48
cleanup metadata
matthiaskoenig Aug 26, 2020
093f799
cleanup dict methods
matthiaskoenig Aug 26, 2020
b5f7516
added to_dict for CVTerm and fixed models
Hemant27031999 Aug 27, 2020
c8b850a
added metadata.ipynb
Hemant27031999 Aug 27, 2020
67d3af1
fixed imports
Hemant27031999 Aug 27, 2020
b951dbd
Merge branch 'devel' into metadata_fbc3_group
Hemant27031999 Aug 27, 2020
e5b3fba
smaller fix to_dict usage
matthiaskoenig Aug 27, 2020
8b2d6dc
modified to_dict for cvterms
Hemant27031999 Aug 28, 2020
010bb44
separated UserDefinedConstraint
Hemant27031999 Aug 28, 2020
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
304 changes: 215 additions & 89 deletions src/cobra/core/user_defined_constraints.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# -*- coding: utf-8 -*-

import ast
from ast import (
Add,
BinOp,
Div,
Expression,
Mod,
Mult,
Name,
NodeTransformer,
Num,
Sub,
UAdd,
UnaryOp,
USub,
copy_location,
)
from warnings import warn

from cobra.core import DictList
Expand Down Expand Up @@ -44,6 +61,186 @@ def __init__(self, id=None, name=None, lower_bound: [int, float]=None,
if const_comps is not None:
self.add_constraint_comps(const_comps)

class ComputeNumericNodes(NodeTransformer):
""" Compute the value of nodes which are solvable i.e
node containing numeric value on both sides.
"""

def visit_BinOp(self, node: BinOp):
node.left = self.visit(node.left)
node.right = self.visit(node.right)
if isinstance(node.left, Num) and isinstance(node.right, Num):
if isinstance(node.op, Add):
result = Num(n=node.left.n + node.right.n)
return copy_location(result, node)
elif isinstance(node.op, Sub):
result = Num(n=node.left.n - node.right.n)
return copy_location(result, node)
elif isinstance(node.op, Mult):
result = Num(n=node.left.n * node.right.n)
return copy_location(result, node)
elif isinstance(node.op, Div):
result = Num(n=node.left.n / node.right.n)
return copy_location(result, node)
elif isinstance(node.op, Mod):
result = Num(n=node.left.n % node.right.n)
Copy link

Choose a reason for hiding this comment

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

Please add blanks between variables and operators, e.g., change n=xyz to n = xyz.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The python code format tool "black" doesn't allow spaces between the variable and operator when passed as arguments.

return copy_location(result, node)
return node

def add_single_node(self, ast_node, negate=False):
"""
The final node to add constraint component inside constraint.

Parameters
----------
ast_node : AST Node classes
Final node representation of a constraint component
negate : bool
Whether to add a negative sign before this component

"""

if isinstance(ast_node, Name):
coeff = 1
if negate:
coeff = -1
self.add_constraint_comps([ConstraintComponent(coefficient=coeff,
variable=ast_node.id,
variable_type='linear')])
elif isinstance(ast_node, UnaryOp):
if isinstance(ast_node.op, UAdd):
coeff = 1
elif isinstance(ast_node.op, USub):
coeff = -1
else:
raise ValueError("Unsupported Unary Operand: {}".format(ast_node.op))
if negate:
coeff = -1 * coeff
self.add_constraint_comps([ConstraintComponent(coefficient=coeff,
variable=ast_node.operand.id,
variable_type='linear')])
elif isinstance(ast_node, BinOp):
if not isinstance(ast_node.op, Mult):
raise ValueError("Unsupported operand type between the variables:"
"{} and {}".format(ast_node.left, ast_node.right))
if not isinstance(ast_node.right, Name):
raise ValueError("The second argument must be a single variable"
": {}".format(ast_node.right))
if isinstance(ast_node.left, Num):
coeff = ast_node.left.n
if negate:
coeff = -1 * coeff
var = ast_node.right.id
self.add_constraint_comps([ConstraintComponent(coefficient=coeff,
variable=var,
variable_type='linear')])
elif isinstance(ast_node.left, Name):
if ast_node.left.id != ast_node.right.id:
raise ValueError("Multiplication of two different variables is not "
"allowed as per SBML FBC-V3: {} and {}"
"".format(ast_node.left.id, ast_node.right.id))
coeff = 1
if negate:
coeff = -1
self.add_constraint_comps(
[ConstraintComponent(coefficient=coeff,
variable=ast_node.right.id,
variable_type='quadratic')])
elif isinstance(ast_node.left, UnaryOp):
if isinstance(ast_node.left.operand, Num):
if isinstance(ast_node.left.op, UAdd):
coeff = 1
elif isinstance(ast_node.left.op, USub):
coeff = -1
else:
raise ValueError(
"Unsupported Unary Operand: {}".format(ast_node.left.op))
coeff *= ast_node.left.operand.n
if negate:
coeff = -1 * coeff
comp = ConstraintComponent(coefficient=coeff,
variable=ast_node.right.id,
variable_type='linear')
self.add_constraint_comps([comp])
elif isinstance(ast_node.left.operand, Name):
if ast_node.left.operand.id != ast_node.right.id:
raise ValueError("Multiplication of two different variables is "
"not allowed as per SBML FBC-V3: {} and {}"
"".format(ast_node.left.operand.id,
ast_node.right.id))
if isinstance(ast_node.left.op, UAdd):
coeff = 1
elif isinstance(ast_node.left.op, USub):
coeff = -1
else:
raise ValueError(
"Unsupported Unary Operand: {}".format(ast_node.left.op))
if negate:
coeff = -1 * coeff
comp = ConstraintComponent(coefficient=coeff,
variable=ast_node.left.operand.id,
variable_type='quadratic')
self.add_constraint_comps([comp])
elif isinstance(ast_node.left, BinOp):
if ast_node.left.right.id != ast_node.right.id:
raise ValueError("Multiplication of two different variables is not "
"allowed as per SBML FBC-V3: {} and {}".format(
ast_node.left.name.id, ast_node.right.id))
if isinstance(ast_node.left.left, UnaryOp):
if isinstance(ast_node.left.left.op, USub):
coeff = -1
elif isinstance(ast_node.left.left.op, UAdd):
coeff = 1
else:
raise ValueError("Invalid expression.")
coeff = coeff*ast_node.left.left.operand.n
elif isinstance(ast_node.left.left, Num):
coeff = ast_node.left.left.n
else:
raise ValueError("Invalid expression.")
if negate:
coeff = -1*coeff
comp = ConstraintComponent(coefficient=coeff,
variable=ast_node.right.id,
variable_type='quadratic')
self.add_constraint_comps([comp])

def add_components_via_ast_nodes(self, ast_node, negate=False):
"""
Add the constraint components to model via ast node
representation of constraint expression.

Parameters
----------
ast_node : Expression
The ast of the constraint expression
negate : bool
Whether to add a '-' sign before this node

"""

if isinstance(ast_node, BinOp):
if isinstance(ast_node.op, Add):
self.add_components_via_ast_nodes(ast_node.left, negate)
self.add_components_via_ast_nodes(ast_node.right, negate)
elif isinstance(ast_node.op, Sub):
self.add_components_via_ast_nodes(ast_node.left, negate)
negate = not negate
self.add_components_via_ast_nodes(ast_node.right, negate)
elif isinstance(ast_node.op, Mult):
self.add_single_node(ast_node, negate)
else:
raise ValueError("Unsupported operation between variables"
": ".format(ast_node))
elif isinstance(ast_node, Name):
self.add_single_node(ast_node, negate)
elif isinstance(ast_node, UnaryOp):
self.add_single_node(ast_node, negate)
elif isinstance(ast_node, Expression):
self.add_components_via_ast_nodes(ast_node.body, negate)
else:
raise ValueError("Unsupported variable type: ".format(ast_node))

@staticmethod
def constraint_from_expression(id=None, expression: 'str' = '',
lower_bound=None, upper_bound=None):
Expand All @@ -52,47 +249,26 @@ def constraint_from_expression(id=None, expression: 'str' = '',
type string. Following rules must be followed while making the
expression:

1. The coefficient must be added before the variable without
parentheses and must have a multiplicative sign (*) only
between itself and the variable (without any whitespaces
around). The coefficient itself can be an integer, float
or a number in fractional form (a/b). Coefficient with any
other form is not allowed. For instance:
1. The coefficient must be added before the variable and must
have a multiplicative sign (*) only between itself and the
variable. It can be an expression containing numbers:

variable : Allowed
1*variable : Allowed
2*variable : Allowed
2 * variable : Not allowed
2 * variable : Allowed
2.variable : Not Allowed
2.0*variable : Allowed
2/3*variable : Allowed
2*2*variable : Not allowed
(2/4)*variable : Not allowed
(4/2+5%2)*variable : Allowed
(2/4)*variable : Allowed
variable*2 : Not Allowed

2. The exponent for the variable must be set via the 'caret'
operator (^) only. It must also not have whitespaces around
it. Also, since only linear and quadratic variables are all-
wed, the exponent can be 1 or 2 only. For instance:
2. The 'caret' operator must not be used to set the exponent.
Use multiplicative sign instead. Also, since only 'linear' and
'quadratic' variables are allowed, we will need multiplication
of variable at most one (in quadratic case).

variable : Allowed
variable^1 : Allowed
variable^2 : Allowed
variable^3 : Not allowed
variable ^ 2 : Not allowed
variable*variable : Not allowed

3. The only possible sign between two or more variables are '+'
and '-' (addition and subtraction). These signs must have a
single whitespace character on both of its side, with only
exception that if the sign comes before very first variable,
it may drop the whitespace before it. For instance:

variable1 - variable2 : Allowed
variable1+variable2 : Not allowed
- variable1 + variable2 : Allowed
+ variable1 - variable2 : Allowed
variable1 * variable2 : Not allowed

variable^2 : Not allowed
variable * variable : Allowed

Parameters
----------
Expand All @@ -116,61 +292,11 @@ def constraint_from_expression(id=None, expression: 'str' = '',

expression = expression.strip()

expression = expression.replace('+ ', '+')
expression = expression.replace('- ', '-')

variables = expression.split(' ')
for variable in variables:
ind = variable.find('*')
if ind == -1:
if variable[0] == '-':
coefficient = -1
else:
coefficient = 1
else:
parts = variable.split('*')

def convert_to_float(frac_str):
"""
Function to convert a numeric string value
to float.
"""
try:
return float(frac_str)
except ValueError:
num, denom = frac_str.split('/')
try:
leading, num = num.split(' ')
whole = float(leading)
except ValueError:
whole = 0
frac = float(num) / float(denom)
return whole - frac if whole < 0 else whole + frac

coefficient = convert_to_float(parts[0])
variable = parts[1]
ind = variable.find('^')
if ind == -1:
exponent = '1'
else:
parts = variable.split('^')
exponent = parts[1]
variable = parts[0]
variable = variable.replace('+', '')
variable = variable.replace('-', '')
if exponent == '1':
variable_type = 'linear'
elif exponent == '2':
variable_type = 'quadratic'
else:
raise ValueError("Only 'linear' or 'quadratic' "
"variables are allowed. Variable"
" {} is raised to exponent {}"
".".format(variable, exponent))
const_comp = ConstraintComponent(coefficient=coefficient,
variable=variable,
variable_type=variable_type)
constraint.add_constraint_comps([const_comp])
tree = ast.parse(source=expression, mode='eval')
compute_nodes = UserDefinedConstraint.ComputeNumericNodes()
tree = compute_nodes.visit(tree)
print(ast.dump(tree))
constraint.add_components_via_ast_nodes(ast_node=tree)

return constraint

Expand Down
Loading