bitten: update bitten.py
This commit is contained in:
parent
f6c6610aad
commit
c66e25da24
1 changed files with 91 additions and 71 deletions
162
bitten/bitten.py
162
bitten/bitten.py
|
@ -20,6 +20,12 @@ def pack(*args):
|
|||
return args
|
||||
|
||||
|
||||
def lament(*args, **kwargs):
|
||||
from sys import stderr
|
||||
|
||||
print(*args, **kwargs, file=stderr)
|
||||
|
||||
|
||||
def _is_in(needle, *haystack):
|
||||
# like the "in" keyword, but uses "is" instead of "==".
|
||||
for thing in haystack:
|
||||
|
@ -125,83 +131,95 @@ def _flatten(it):
|
|||
return res
|
||||
|
||||
|
||||
def _penalize(constraints, *x, tol=1e-5, scale=1e10, growth=4.0):
|
||||
# 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)
|
||||
def _penalize_v2021_23(penalties, tol=1e-5, scale=1e10):
|
||||
n_con = len(penalties)
|
||||
growth = growth ** (1.0 / n_con) # "ps"
|
||||
increment = n_con**-0.5 # "pnsi"
|
||||
penalty, nominal = 0.0, 0.0 # "pns", "pnsm"
|
||||
unmet = sum(p > 0.0 for p in penalties)
|
||||
growth = 4.0 ** (1.0 / n_con) # "ps"
|
||||
penalty = 0.0
|
||||
for p in penalties:
|
||||
p = max(p - tol, 0.0)
|
||||
penalty = penalty * growth + increment + p + p * p * p
|
||||
nominal = nominal * growth + increment
|
||||
return scale * (penalty - nominal + 1.0)
|
||||
p = max(p, 0.0)
|
||||
squared = p * p
|
||||
penalty = growth * penalty + p + squared + p * squared
|
||||
return scale * (unmet + penalty)
|
||||
|
||||
|
||||
def _penalize4(constraints, *x, tol=1e-5, scale=1e9):
|
||||
# updated from upstream (v2022.23)
|
||||
# NOTE: i've reduced `scale` by a factor of ten
|
||||
# out of concern for numeric precision.
|
||||
# also, i've removed the growth keyword, since it very likely
|
||||
# needs to coincide with the order of the polynomial (always 3).
|
||||
penalties = _flatten(cons(*x) for cons in constraints)
|
||||
def _penalize_v2022_11(penalties, tol=1e-5, scale=1e10):
|
||||
n_con = len(penalties)
|
||||
unmet = sum(p > 0.0 for p in penalties)
|
||||
growth = 3.0 ** (1.0 / n_con) # "ps"
|
||||
penalty = 0.0
|
||||
for p in penalties:
|
||||
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)
|
||||
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 = max(p - tol, 0.0)
|
||||
v = p # * coeff # 2022.25 drops the coeff
|
||||
v2 = v * v
|
||||
poly = v + v2 + v * v2
|
||||
penalty = penalty * growth + increment + poly
|
||||
nominal = nominal * growth + increment
|
||||
p = max(p, 0.0)
|
||||
penalty = growth * penalty + increment + p + p * p * p
|
||||
nominal = growth * nominal + increment
|
||||
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):
|
||||
# DEPRECATED
|
||||
return _penalize(constraints, x, tol=tol, scale=scale, growth=growth)
|
||||
assert False, "deprecated; use _penalize_v2021_23 instead"
|
||||
|
||||
|
||||
def count_unsat(x, constraints, tol=1e-5):
|
||||
# DEPRECATED
|
||||
penalties = [cons(*x) for cons in constraints]
|
||||
return sum(p > tol for p in penalties)
|
||||
assert False, "deprecated; do it yourself"
|
||||
|
||||
|
||||
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):
|
||||
def __init__(self, *constraints, tol=1e-5, version=1):
|
||||
def __init__(self, *constraints, tol=1e-5, version=5):
|
||||
for cons in constraints:
|
||||
assert callable(cons)
|
||||
self.constraints = constraints
|
||||
|
@ -312,17 +330,19 @@ class Constrain(Objective):
|
|||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version + 1
|
||||
return self._version
|
||||
|
||||
@version.setter
|
||||
def version(self, version):
|
||||
penalizers = [_penalize, _penalize2, _penalize3, _penalize4]
|
||||
assert 1 <= version <= len(penalizers), version
|
||||
self._version = version - 1
|
||||
assert version in penalizers, f"unknown version of penalty function: {version}"
|
||||
self._version = version
|
||||
self._penalize = penalizers[self._version]
|
||||
|
||||
def penalize(self, *args):
|
||||
return self._penalize(self.constraints, *args, tol=self.tol)
|
||||
def penalize(self, *x):
|
||||
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):
|
||||
return self.penalize(*kw_hypers.values())
|
||||
|
@ -737,12 +757,12 @@ def _bite(
|
|||
cache.hash(h)
|
||||
cached = cache.get()
|
||||
if _debug:
|
||||
print("HASH:", cache.hashed, sep="\n")
|
||||
lament("HASH:", cache.hashed, sep="\n")
|
||||
|
||||
if cached is None:
|
||||
if _debug: # dirty test for exceptions
|
||||
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:
|
||||
res = _biteopt(objective, linear_bounds, iters=budget, **optimizer_kwargs)
|
||||
|
@ -753,7 +773,7 @@ def _bite(
|
|||
res = _evaluator(objective, budget)
|
||||
|
||||
if _debug:
|
||||
print("RES:", res, sep="\n")
|
||||
lament("RES:", res, sep="\n")
|
||||
|
||||
optimized = res.x
|
||||
else:
|
||||
|
|
Loading…
Reference in a new issue