bitten: update bitten.py

This commit is contained in:
Connor Olding 2022-06-19 01:03:14 -07:00
parent f6c6610aad
commit c66e25da24

View File

@ -20,6 +20,12 @@ def pack(*args):
return args return args
def lament(*args, **kwargs):
from sys import stderr
print(*args, **kwargs, file=stderr)
def _is_in(needle, *haystack): def _is_in(needle, *haystack):
# like the "in" keyword, but uses "is" instead of "==". # like the "in" keyword, but uses "is" instead of "==".
for thing in haystack: for thing in haystack:
@ -125,83 +131,95 @@ def _flatten(it):
return res return res
def _penalize(constraints, *x, tol=1e-5, scale=1e10, growth=4.0): def _penalize_v2021_23(penalties, tol=1e-5, scale=1e10):
# NOTE: maybe growth shouldn't be configurable unless
# it also affects the order of the penalty equation (currently 3).
# NOTE: different ordering of constraints can result in different solutions.
# NOTE: this function doesn't use numpy, so you can copypaste it elsewhere.
# growth = growth ** (1.0 / len(constraints))
# penalties = [cons(*x) for cons in constraints]
penalties = _flatten(cons(*x) for cons in constraints)
growth = growth ** (1.0 / len(penalties))
unsat = sum(p > tol for p in penalties)
# cubic = [p + p * p + p * p * p for p in penalties]
penalsum = 0.0
for p in penalties:
penalsum *= growth # always happens so each penalty gets its own stratum
if p > tol:
penalsum += p + p * p + p * p * p
return scale * (unsat + penalsum)
def _penalize2(constraints, *x, tol=1e-5, scale=1e10, growth=3.0):
# updated from upstream (v2022.11)
penalties = _flatten(cons(*x) for cons in constraints)
growth = growth ** (1.0 / len(penalties))
unsat = sum(p > tol for p in penalties)
penalsum = 0.0
for p in penalties:
penalsum *= growth # always happens so each penalty gets its own stratum
if p > tol:
penalsum += p + p * p * p
return scale * (unsat + penalsum)
def _penalize3(constraints, *x, tol=1e-5, scale=1e10, growth=3.0):
# updated from upstream (v2022.19)
penalties = _flatten(cons(*x) for cons in constraints)
n_con = len(penalties) n_con = len(penalties)
growth = growth ** (1.0 / n_con) # "ps" unmet = sum(p > 0.0 for p in penalties)
increment = n_con**-0.5 # "pnsi" growth = 4.0 ** (1.0 / n_con) # "ps"
penalty, nominal = 0.0, 0.0 # "pns", "pnsm" penalty = 0.0
for p in penalties: for p in penalties:
p = max(p - tol, 0.0) p = max(p, 0.0)
penalty = penalty * growth + increment + p + p * p * p squared = p * p
nominal = nominal * growth + increment penalty = growth * penalty + p + squared + p * squared
return scale * (penalty - nominal + 1.0) return scale * (unmet + penalty)
def _penalize4(constraints, *x, tol=1e-5, scale=1e9): def _penalize_v2022_11(penalties, tol=1e-5, scale=1e10):
# updated from upstream (v2022.23) n_con = len(penalties)
# NOTE: i've reduced `scale` by a factor of ten unmet = sum(p > 0.0 for p in penalties)
# out of concern for numeric precision. growth = 3.0 ** (1.0 / n_con) # "ps"
# also, i've removed the growth keyword, since it very likely penalty = 0.0
# needs to coincide with the order of the polynomial (always 3). for p in penalties:
penalties = _flatten(cons(*x) for cons in constraints) p = max(p, 0.0)
penalty = growth * penalty + p + p * p * p
return scale * (unmet + penalty)
def _penalize_v2022_19(penalties, tol=1e-5, scale=1e10):
n_con = len(penalties) n_con = len(penalties)
growth = 3.0 ** (1.0 / n_con) # "ps" growth = 3.0 ** (1.0 / n_con) # "ps"
increment = n_con**-0.5 # "pnsi" increment = n_con**-0.5 # "pnsi"
# coeff = increment**3.0 # "pnm"
penalty, nominal = 0.0, 0.0 # "pns", "pnsm" penalty, nominal = 0.0, 0.0 # "pns", "pnsm"
for p in penalties: for p in penalties:
p = max(p - tol, 0.0) p = max(p, 0.0)
v = p # * coeff # 2022.25 drops the coeff penalty = growth * penalty + increment + p + p * p * p
v2 = v * v nominal = growth * nominal + increment
poly = v + v2 + v * v2
penalty = penalty * growth + increment + poly
nominal = nominal * growth + increment
return scale * (penalty - nominal + 1.0) return scale * (penalty - nominal + 1.0)
def _penalize_v2022_22(penalties, tol=1e-5, scale=1e10):
n_con = len(penalties)
growth = 3.0 ** (1.0 / n_con) # "ps"
increment = n_con**-0.5 # "pnsi"
coeff = increment**3.0 # "pnm"
penalty, nominal = 0.0, 0.0 # "pns", "pnsm"
for p in penalties:
p = coeff * max(p, 0.0)
squared = p * p
penalty = growth * penalty + increment + p + squared + p * squared
nominal = growth * nominal + increment
return scale * (penalty - nominal + 1.0)
def _penalize_v2022_25(penalties, tol=1e-5, scale=1e10):
n_con = len(penalties)
growth = 3.0 ** (1.0 / n_con) # "ps"
increment = n_con**-0.5 # "pnsi"
penalty, nominal = 0.0, 0.0 # "pns", "pnsm"
for p in penalties:
p = max(p, 0.0)
squared = p * p
penalty = growth * penalty + increment + p + squared + p * squared
nominal = growth * nominal + increment
return scale * (penalty - nominal + 1.0)
def _penalize_v2022_25_1(penalties, tol=1e-5, scale=1e10):
n_con = len(penalties)
growth = 3.0 ** (1.0 / n_con) # "ps"
increment = n_con**-0.5 # "pnsi"
penalty = 0.0 # "pns"
for p in penalties:
p = max(p, 0.0)
penalty = growth * penalty + increment + p + p * p
return scale * (1.0 + penalty + penalty * penalty)
penalizers = {
1: _penalize_v2021_23,
2: _penalize_v2022_11,
3: _penalize_v2022_19,
4: _penalize_v2022_22,
5: _penalize_v2022_25,
6: _penalize_v2022_25_1,
}
def penalize(x, constraints, tol=1e-5, *, scale=1e10, growth=4.0): def penalize(x, constraints, tol=1e-5, *, scale=1e10, growth=4.0):
# DEPRECATED assert False, "deprecated; use _penalize_v2021_23 instead"
return _penalize(constraints, x, tol=tol, scale=scale, growth=growth)
def count_unsat(x, constraints, tol=1e-5): def count_unsat(x, constraints, tol=1e-5):
# DEPRECATED assert False, "deprecated; do it yourself"
penalties = [cons(*x) for cons in constraints]
return sum(p > tol for p in penalties)
class Impure: # TODO: rename? volatile? aaa the word is on the tip of my tongue class Impure: # TODO: rename? volatile? aaa the word is on the tip of my tongue
@ -303,7 +321,7 @@ class Crossentropy(AbstractError):
class Constrain(Objective): class Constrain(Objective):
def __init__(self, *constraints, tol=1e-5, version=1): def __init__(self, *constraints, tol=1e-5, version=5):
for cons in constraints: for cons in constraints:
assert callable(cons) assert callable(cons)
self.constraints = constraints self.constraints = constraints
@ -312,17 +330,19 @@ class Constrain(Objective):
@property @property
def version(self): def version(self):
return self._version + 1 return self._version
@version.setter @version.setter
def version(self, version): def version(self, version):
penalizers = [_penalize, _penalize2, _penalize3, _penalize4] assert version in penalizers, f"unknown version of penalty function: {version}"
assert 1 <= version <= len(penalizers), version self._version = version
self._version = version - 1
self._penalize = penalizers[self._version] self._penalize = penalizers[self._version]
def penalize(self, *args): def penalize(self, *x):
return self._penalize(self.constraints, *args, tol=self.tol) penalties = _flatten(cons(*x) for cons in self.constraints)
if not any(p > 0.0 for p in penalties):
return 0.0
return self._penalize(penalties, tol=self.tol)
def compute_with(self, fun, **kw_hypers): def compute_with(self, fun, **kw_hypers):
return self.penalize(*kw_hypers.values()) return self.penalize(*kw_hypers.values())
@ -737,12 +757,12 @@ def _bite(
cache.hash(h) cache.hash(h)
cached = cache.get() cached = cache.get()
if _debug: if _debug:
print("HASH:", cache.hashed, sep="\n") lament("HASH:", cache.hashed, sep="\n")
if cached is None: if cached is None:
if _debug: # dirty test for exceptions if _debug: # dirty test for exceptions
center = np.mean(linear_bounds, axis=-1) if hypers else () center = np.mean(linear_bounds, axis=-1) if hypers else ()
print("FIRST VALUE:", objective(center), sep="\n") lament("FIRST VALUE:", objective(center), sep="\n")
if hypers: if hypers:
res = _biteopt(objective, linear_bounds, iters=budget, **optimizer_kwargs) res = _biteopt(objective, linear_bounds, iters=budget, **optimizer_kwargs)
@ -753,7 +773,7 @@ def _bite(
res = _evaluator(objective, budget) res = _evaluator(objective, budget)
if _debug: if _debug:
print("RES:", res, sep="\n") lament("RES:", res, sep="\n")
optimized = res.x optimized = res.x
else: else: