Skip to content

Commit

Permalink
Speed up gpkit algebra (#1196)
Browse files Browse the repository at this point in the history
  • Loading branch information
bqpd authored Nov 2, 2017
1 parent 542d867 commit 029c5df
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 36 deletions.
4 changes: 3 additions & 1 deletion gpkit/constraints/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ def sens_from_dual(self, las, nus):
la = las[offset:offset+n_posys]
nu = nus[offset:offset+n_posys]
v_ss = constr.sens_from_dual(la, nu)
var_senss += v_ss
# not using HashVector addition because we want to preseve zeros
for key, value in v_ss.items():
var_senss[key] = value + var_senss.get(key, 0)
offset += n_posys
return var_senss

Expand Down
78 changes: 46 additions & 32 deletions gpkit/nomials/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,31 +62,23 @@ def __add__(self, other):
other *= float(other.units/self.units)
hmap = HashVector.__add__(self, other)
hmap.units = self.units
hmap.remove_zeros(only_check_cs=True) # pylint: disable=no-member, protected-access
return hmap

def remove_zeros(self, only_check_cs=False):
def remove_zeros(self):
"""Removes zeroed exponents and monomials.
If `only_check_cs` is True, checks only whether any values are zero.
If False also checks whether any exponents in the keys are zero.
"""
# TODO: do this automatically during HashVector operations
im_a_posynomial = (len(self) > 1)
for key in self.keys():
value = self[key]
if value == 0:
for key, value in self.items():
zeroes = set(vk for vk, exp in key.items() if exp == 0)
if zeroes:
# raise ValueError(self)
del self[key]
if not im_a_posynomial:
self[HashVector()] = 0.0 # don't remove 0-monomial's exp
elif not only_check_cs:
zeroes = set(vk for vk, exp in key.items() if exp == 0)
if zeroes:
del self[key]
for vk in zeroes:
del key[vk]
key._hashvalue = None # reset hash # pylint: disable=protected-access
self[key] = value + self.get(key, 0)
for vk in zeroes:
key._hashvalue ^= hash((vk, key[vk]))
del key[vk]
self[key] = value + self.get(key, 0)

def diff(self, varkey):
"Differentiates a NomialMap with respect to a varkey"
Expand Down Expand Up @@ -135,11 +127,12 @@ def sub(self, substitutions, varkeys, parsedsubs=False):

cp = NomialMap()
cp.units = self.units
cp.expmap, cp.csmap = {}, {}
# csmap is modified during substitution, but keeps the same exps
cp.expmap, cp.csmap = {}, self.copy()
varlocs = defaultdict(set)
for exp, c in self.items():
new_exp = HashVector(exp)
cp.expmap[exp] = new_exp
new_exp = exp.copy()
cp.expmap[exp] = new_exp # cp modifies exps, so it needs new ones
cp[new_exp] = c
for vk in new_exp:
varlocs[vk].add((exp, new_exp))
Expand All @@ -163,18 +156,7 @@ def sub(self, substitutions, varkeys, parsedsubs=False):
cval, = cval.values()
exps_covered = set()
for o_exp, exp in exps:
x = exp[vk]
powval = float(cval)**x if cval != 0 or x >= 0 else np.inf
cp.csmap[o_exp] = powval * cp.csmap.get(o_exp, self[o_exp])
if exp in cp and exp not in exps_covered:
c = cp.pop(exp)
del exp[vk]
for key in expval:
exp[key] = expval[key]*x + exp.get(key, 0)
exp._hashvalue = None # reset hash, # pylint: disable=protected-access
cp[exp] = powval * c + cp.get(exp, 0)
exps_covered.add(exp)
cp.remove_zeros(only_check_cs=True) # pylint: disable=protected-access
subinplace(cp, exp, o_exp, vk, cval, expval, exps_covered)
return cp

def mmap(self, orig):
Expand Down Expand Up @@ -204,3 +186,35 @@ def mmap(self, orig):
orig_idx = origexps.index(orig_exp)
pmap[selfexps.index(self_exp)][orig_idx] = fraction
return pmap, m_from_ms


# pylint: disable=invalid-name
def subinplace(cp, exp, o_exp, vk, cval, expval, exps_covered):
"Modifies cp by substituing cval/expval for vk in exp"
x = exp[vk]
powval = float(cval)**x if cval != 0 or x >= 0 else np.inf
cp.csmap[o_exp] *= powval
if exp in cp and exp not in exps_covered:
c = cp.pop(exp)
exp._hashvalue ^= hash((vk, x)) # remove (key, value) from _hashvalue
del exp[vk]
for key in expval:
if key in exp:
exp._hashvalue ^= hash((key, exp[key])) # remove from hash
newval = expval[key]*x + exp[key]
else:
newval = expval[key]*x
exp._hashvalue ^= hash((key, newval)) # add to hash
exp[key] = newval
value = powval * c
if exp in cp:
currentvalue = cp[exp]
if value != -currentvalue:
cp[exp] = value + currentvalue
else:
del cp[exp] # remove zeros created during substitution
elif value:
cp[exp] = value
if not cp: # make sure it's never an empty hmap
cp[HashVector()] = 0.0
exps_covered.add(exp)
7 changes: 5 additions & 2 deletions gpkit/nomials/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,11 @@ def __mul__(self, other):
for exp_s, c_s in self.hmap.items():
for exp_o, c_o in other.hmap.items():
exp = exp_s + exp_o
hmap[exp] = c_s*c_o + hmap.get(exp, 0)
hmap.remove_zeros()
new, accumulated = c_s*c_o, hmap.get(exp, 0)
if new != -accumulated:
hmap[exp] = accumulated + new
elif accumulated:
del hmap[exp] # remove zeros created by multiplication
hmap.units_of_product(self.hmap.units, other.hmap.units)
return Signomial(hmap)
return NotImplemented
Expand Down
9 changes: 8 additions & 1 deletion gpkit/small_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,14 @@ def __add__(self, other):
elif isinstance(other, dict):
sums = self.copy()
for key, value in other.items():
sums[key] = value + sums.get(key, 0)
if key in sums:
svalue = sums[key]
if value == -svalue:
del sums[key] # remove zeros created by addition
else:
sums[key] = value + svalue
else:
sums[key] = value
return sums
return NotImplemented

Expand Down

0 comments on commit 029c5df

Please sign in to comment.