Skip to content

Commit

Permalink
python3 compatible parse_variables (#1428)
Browse files Browse the repository at this point in the history
* note: in py3 pickles have to be read as binary files
* fancier py3 printing
  • Loading branch information
bqpd authored Sep 1, 2019
1 parent 4207f38 commit a96e0a0
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 112 deletions.
2 changes: 1 addition & 1 deletion docs/source/examples/beam.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class Beam(Model):
w_tip
"""
@parse_variables(__doc__, globals())
def setup(self, N=4):
exec(parse_variables(self.__doc__))
# minimize tip displacement (the last w)
self.cost = self.w_tip = w[-1]
return {
Expand Down
15 changes: 8 additions & 7 deletions docs/source/examples/boundschecking.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class BoundsChecking(Model):
D
"""
@parse_variables(__doc__, globals())
def setup(self):
exec(parse_variables(BoundsChecking.__doc__))
self.cost = F
return [
F >= D + T,
Expand All @@ -50,9 +50,10 @@ def setup(self):
pass
gp = m.gp(allow_missingbounds=True)

bplate = ", but would gain it from any of these sets of bounds: "
assert {(m.D.key, 'lower'): bplate + "[(%s, 'lower')]" % m.Ap,
(m.Ap.key, 'lower'): bplate + ("[(%s, 'lower')]"
" or [(%s, 'lower')]" % (m.D, m.nu)),
(m.nu.key, 'lower'): bplate + "[(%s, 'lower')]" % m.Ap
} == gp.missingbounds
bpl = ", but would gain it from any of these sets of bounds: "
assert gp.missingbounds[(m.D.key, 'lower')] == bpl + "[(%s, 'lower')]" % m.Ap
assert gp.missingbounds[(m.nu.key, 'lower')] == bpl + "[(%s, 'lower')]" % m.Ap
# ordering is arbitrary:
assert gp.missingbounds[(m.Ap.key, 'lower')] in (
bpl + ("[(%s, 'lower')] or [(%s, 'lower')]" % (m.D, m.nu)),
bpl + ("[(%s, 'lower')] or [(%s, 'lower')]" % (m.nu, m.D)))
5 changes: 3 additions & 2 deletions docs/source/examples/docstringparsing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"Docstring parsing example"
from gpkit import Model, parse_variables
from gpkit.tools.docstring import parse_varstring


class Cube(Model):
Expand Down Expand Up @@ -33,15 +34,15 @@ class Cube(Model):
The ordering of these blocks doesn't affect anything; order them in the
way that makes the most sense to someone else reading your model.
"""
@parse_variables(__doc__, globals())
def setup(self):
exec(parse_variables(Cube.__doc__))

return [A >= 2*(s[0]*s[1] + s[1]*s[2] + s[2]*s[0]),
s.prod() >= V,
s[2] >= h]


print(parse_variables(Cube.__doc__))
print(parse_varstring(Cube.__doc__))
c = Cube()
c.cost = c.A
print(c.solve(verbosity=0).table())
56 changes: 30 additions & 26 deletions docs/source/examples/docstringparsing_output.txt
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
from gpkit import Variable, VectorVariable

try:
A = self.A = Variable('A', 'm^2', 'surface area')
except Exception as e:
raise ValueError("`"+e.__class__.__name__+": "+str(e)+"` was raised"
" while executing the parsed line `A = self.A = Variable('A', 'm^2', 'surface area')`. Is this line following the format `Name (optional Value) [Units] (Optional Description)` without any whitespace in the Name or Value fields?")

try:
V = self.V = Variable('V', 100, 'L', 'minimum volume')
except Exception as e:
raise ValueError("`"+e.__class__.__name__+": "+str(e)+"` was raised"
" while executing the parsed line `V = self.V = Variable('V', 100, 'L', 'minimum volume')`. Is this line following the format `Name (optional Value) [Units] (Optional Description)` without any whitespace in the Name or Value fields?")

try:
h = self.h = Variable('h', 1, 'm', 'minimum height')
except Exception as e:
raise ValueError("`"+e.__class__.__name__+": "+str(e)+"` was raised"
" while executing the parsed line `h = self.h = Variable('h', 1, 'm', 'minimum height')`. Is this line following the format `Name (optional Value) [Units] (Optional Description)` without any whitespace in the Name or Value fields?")

try:
s = self.s = VectorVariable(3, 's', 'm', 'side length')
except Exception as e:
raise ValueError("`"+e.__class__.__name__+": "+str(e)+"` was raised"
" while executing the parsed line `s = self.s = VectorVariable(3, 's', 'm', 'side length')`. Is this line following the format `Name (optional Value) [Units] (Optional Description)` without any whitespace in the Name or Value fields?")

from gpkit import Variable, VectorVariable # Demonstration of nomenclature syntax
#
# Lines that end in "Variables" will be parsed as a scalar variable table
# until the next blank line.
#
# Variables
# ---------
A = self.A = Variable('A', 'm^2', 'surface area') # from 'A [m^2] surface area'
V = self.V = Variable('V', 100, 'L', 'minimum volume') # from 'V 100 [L] minimum volume'
#
# Lines that end in "Variables of length $N" will be parsed as vector
# variables of length $N until the next blank line.
#
# Variables of length 3
# ---------------------
s = self.s = VectorVariable(3, 's', 'm', 'side length') # from 's [m] side length'
#
# Let's introduce more variables: (any line ending in "Variables" is parsed)
#
# Zoning Variables
# ----------------
h = self.h = Variable('h', 1, 'm', 'minimum height') # from 'h 1 [m] minimum height'
#
# Upper Unbounded
# ---------------
# A
#
# The ordering of these blocks doesn't affect anything; order them in the
# way that makes the most sense to someone else reading your model.
#

Cost
----
Expand Down
16 changes: 9 additions & 7 deletions docs/source/examples/performance_modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ class AircraftP(Model):
Wfuel, aircraft.W, state.mu
"""
@parse_variables(__doc__, globals())
def setup(self, aircraft, state):
self.aircraft = aircraft
self.state = state
exec(parse_variables(AircraftP.__doc__))

self.wing_aero = aircraft.wing.dynamic(aircraft.wing, state)
self.perf_models = [self.wing_aero]
Expand Down Expand Up @@ -62,8 +62,8 @@ class Aircraft(Model):
---------------
wing.c, wing.S
"""
@parse_variables(__doc__, globals())
def setup(self):
exec(parse_variables(Aircraft.__doc__))
self.fuse = Fuselage()
self.wing = Wing()
self.components = [self.fuse, self.wing]
Expand All @@ -87,8 +87,9 @@ class FlightState(Model):
rho 0.74 [kg/m^3] air density
"""
@parse_variables(__doc__, globals())
def setup(self):
exec(parse_variables(FlightState.__doc__))
pass


class FlightSegment(Model):
Expand Down Expand Up @@ -165,10 +166,10 @@ class WingAero(Model):
---------------
CL, wing.S, state.mu, state.rho, state.V
"""
@parse_variables(__doc__, globals())
def setup(self, wing, state):
self.wing = wing
self.state = state
exec(parse_variables(WingAero.__doc__))

c = wing.c
A = wing.A
Expand Down Expand Up @@ -205,8 +206,8 @@ class Wing(Model):
---------------
c, S
"""
@parse_variables(__doc__, globals())
def setup(self):
exec(parse_variables(Wing.__doc__))
return {"parametrization of wing weight":
W >= S*rho,
"definition of mean chord":
Expand All @@ -225,8 +226,9 @@ class Fuselage(Model):
W 100 [lbf] weight
"""
@parse_variables(__doc__, globals())
def setup(self):
exec(parse_variables(Fuselage.__doc__))
pass

AC = Aircraft()
MISSION = Mission(AC)
Expand All @@ -239,7 +241,7 @@ def setup(self):
sol.savetxt()
sol.save("solution.pkl")
# retrieve solution from a file
sol_loaded = pickle.load(open("solution.pkl"))
sol_loaded = pickle.load(open("solution.pkl", "rb"))

vars_of_interest = set(AC.varkeys)
# note that there's two ways to access submodels
Expand Down
1 change: 1 addition & 0 deletions gpkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .constraints.model import Model
from .tools.docstring import parse_variables


GPBLU = "#59ade4"
GPCOLORS = ["#59ade4", "#FA3333"]

Expand Down
2 changes: 2 additions & 0 deletions gpkit/constraints/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ def process_result(self, result):

def __repr__(self):
"Returns namespaced string."
if not self:
return "<gpkit.%s object>" % self.__class__.__name__
return ("<gpkit.%s object containing %i top-level constraint(s)"
" and %i variable(s)>" % (self.__class__.__name__,
len(self), len(self.varkeys)))
Expand Down
12 changes: 7 additions & 5 deletions gpkit/repr_conventions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@

if sys.version_info >= (3, 0):
unichr = chr # pylint: disable=redefined-builtin,invalid-name


PI_STR = "PI" # fails on some external models if it's "π"
UNICODE_EXPONENTS = False
UNIT_FORMATTING = ":~" # ":P~" for unicode exponents in units
PI_STR = "π" # fails on some external models if it's "π"
UNICODE_EXPONENTS = True
UNIT_FORMATTING = ":P~" # ":P~" for unicode exponents in units
else:
PI_STR = "PI" # fails on some external models if it's "π"
UNICODE_EXPONENTS = False
UNIT_FORMATTING = ":~" # ":P~" for unicode exponents in units


def lineagestr(lineage, modelnums=True):
Expand Down
18 changes: 10 additions & 8 deletions gpkit/solution_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def tight_table(self, _, ntightconstrs=5, tight_senss=1e-2, **kwargs):
return []
title = "Tightest Constraints"
data = [((-float("%+6.2g" % c.relax_sensitivity), str(c)),
"%+6.2g" % c.relax_sensitivity, c)
"%+6.2g" % c.relax_sensitivity, id(c), c)
for c in self.model.flat() if c.relax_sensitivity >= tight_senss]
if not data:
lines = ["No constraints had a sensitivity above %+5.1g."
Expand Down Expand Up @@ -106,7 +106,7 @@ def constraint_table(data, sortbymodel=True, showmodels=True, **_):
if not showmodels:
excluded = ("units", "lineage") # hide all of it
models, decorated = {}, []
for sortby, openingstr, constraint in sorted(data):
for sortby, openingstr, _, constraint in sorted(data):
model = lineagestr(constraint) if sortbymodel else ""
if model not in models:
models[model] = len(models)
Expand Down Expand Up @@ -176,11 +176,13 @@ def warnings_table(self, _, **kwargs):
lines += ["| for sweep %i |" % i]
if wtype == "Unexpectedly Tight Constraints" and data[0][1]:
data = [(-int(1e5*c.relax_sensitivity),
"%+6.2g" % c.relax_sensitivity, c) for _, c in data]
"%+6.2g" % c.relax_sensitivity, id(c), c)
for _, c in data]
lines += constraint_table(data, **kwargs)
elif wtype == "Unexpectedly Loose Constraints" and data[0][1]:
data = [(-int(1e5*c.rel_diff),
"%.4g %s %.4g" % c.tightvalues, c) for _, c in data]
"%.4g %s %.4g" % c.tightvalues, id(c), c)
for _, c in data]
lines += constraint_table(data, **kwargs)
else:
for msg, _ in data:
Expand Down Expand Up @@ -507,8 +509,8 @@ def savetxt(self, filename="solution.txt", printmodel=True, **kwargs):
"Saves solution table as a text file"
with open(filename, "w") as f:
if printmodel and self.model:
f.write(str(self.model).encode("utf-8"))
f.write(self.table(**kwargs).encode("utf-8"))
f.write(str(self.model))
f.write(self.table(**kwargs))

def savecsv(self, showvars=None, filename="solution.csv", valcols=5,
**kwargs):
Expand Down Expand Up @@ -547,7 +549,7 @@ def savecsv(self, showvars=None, filename="solution.csv", valcols=5,
f.write(el + ",")
f.write(","*(valcols - len(vals.split())))
f.write((line[2].replace("[", "").replace("]", "").strip()
+ ",").encode("utf8"))
+ ","))
f.write(line[3].strip() + "\n")

def subinto(self, posy):
Expand Down Expand Up @@ -789,7 +791,7 @@ def var_table(data, title, printunits=True, latex=False, rawlines=False,
valstr = valstr.replace(before, after)
if values_remaining <= 0:
spaces = (-values_remaining
* len(valstr)/(values_remaining + ncols))
* len(valstr)//(values_remaining + ncols))
valstr = valstr + " ]" + " "*spaces
lines.append(["", valstr, "", ""])
else:
Expand Down
4 changes: 2 additions & 2 deletions gpkit/tests/t_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ def test_performance_modeling(self, example):
sol.table()
sol.save("solution.pkl")
sol.table()
sol_loaded = pickle.load(open("solution.pkl"))
sol_loaded = pickle.load(open("solution.pkl", "rb"))
sol_loaded.table()

sweepsol = m.sweep({example.AC.fuse.W: (50, 100, 150)}, verbosity=0)
sweepsol.table()
sweepsol.save("sweepsolution.pkl")
sweepsol.table()
sol_loaded = pickle.load(open("sweepsolution.pkl"))
sol_loaded = pickle.load(open("sweepsolution.pkl", "rb"))
sol_loaded.table()

def test_sp_to_gp_sweep(self, example):
Expand Down
16 changes: 6 additions & 10 deletions gpkit/tests/t_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,8 +702,8 @@ class Box(Model):
---------------
w, d, h
"""
@parse_variables(__doc__, globals())
def setup(self):
exec(parse_variables(Box.__doc__))
return [V == h*w*d]


Expand Down Expand Up @@ -740,15 +740,11 @@ def test_modelcontainmentprinting(self):
def test_no_naming_on_var_access(self):
# make sure that analysis models don't add their names to
# variables looked up from other models
if sys.version_info >= (3, 0):
with self.assertRaises(FutureWarning):
box = Box()
else:
box = Box()
area_bounds = BoxAreaBounds(box)
M = Model(box["V"], [box, area_bounds])
for var in ("h", "w", "d"):
self.assertEqual(len(M.variables_byname(var)), 1)
box = Box()
area_bounds = BoxAreaBounds(box)
M = Model(box["V"], [box, area_bounds])
for var in ("h", "w", "d"):
self.assertEqual(len(M.variables_byname(var)), 1)


TESTS = [TestModelSolverSpecific, TestModelNoSolve]
Expand Down
Loading

0 comments on commit a96e0a0

Please sign in to comment.