Skip to content
This repository was archived by the owner on Mar 24, 2024. It is now read-only.

Commit dc284c0

Browse files
committed
add different complexity measures to glyph-remote
1 parent d22c7ab commit dc284c0

File tree

7 files changed

+73
-67
lines changed

7 files changed

+73
-67
lines changed

glyph/assessment.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,16 @@ def inner(*args, **kwargs):
250250
return timeoutable(default=max_fitness)(f)(*args, timeout=timeout, **kwargs)
251251
return inner
252252
return decorate
253+
254+
255+
def expressional_complexity(ind):
256+
"""Sum of length of all subtrees of the individual."""
257+
return sum(len(ind[ind.searchSubtree(i)]) for i in range(len(ind)))
258+
259+
260+
complexity_measures = {
261+
"ec_genotype": expressional_complexity,
262+
"ec_phenotype": lambda ind: expressional_complexity(ind.resolve_sc()),
263+
"num_nodes_genotype": len,
264+
"num_nodes_phenotype": lambda ind: len(ind.resolve_sc())
265+
}

glyph/cli/_parser.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import numpy as np
66

77
import glyph.application
8+
import glyph.assessment
89
from glyph.utils.argparse import (
910
catch_and_log,
1011
positive_int,
@@ -137,11 +138,10 @@ def get_parser(parser=None):
137138
parser.add_argument(
138139
"--send_meta_data", action="store_true", default=False, help="Send metadata after each generation"
139140
)
140-
# Gooey has a bug when using the action 'count'.
141-
# To work as expected the '-' flag has to be first and the '--' flag has to be second.
142141
parser.add_argument(
143142
"-v",
144143
"--verbose",
144+
type=str.upper,
145145
dest="verbosity",
146146
choices=["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"],
147147
default="INFO",
@@ -221,10 +221,10 @@ def get_parser(parser=None):
221221
help="Simplify expression before sending them. (default: False)",
222222
)
223223
ass_group.add_argument(
224-
"--consider_complexity",
225-
action="store_false",
226-
default=True,
227-
help="Consider the complexity of solutions for MOO (default: True)",
224+
"--complexity_measure",
225+
choices=["None"] + list(glyph.assessment.complexity_measures.keys()),
226+
default=None,
227+
help="Consider the complexity of solutions for MOO (default: None)",
228228
)
229229
ass_group.add_argument(
230230
"--no_caching",
@@ -353,7 +353,6 @@ def get_parser(parser=None):
353353
constraints = parser.add_argument_group("constraints")
354354
glyph.application.ConstraintsFactory.add_options(constraints)
355355

356-
357356
observer = parser.add_argument_group("observer")
358357
observer.add_argument(
359358
"--animate",

glyph/cli/glyph_remote.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@
1717

1818
import deap.gp
1919
import deap.tools
20-
from deprecated import deprecated
21-
import glyph.application
22-
import glyph.gp.individual
23-
import glyph.utils
2420
import sympy
2521
import zmq
22+
from deprecated import deprecated
2623
from cache import DBCache
2724
from scipy.optimize._minimize import _minimize_neldermead as nelder_mead
2825

2926

27+
import glyph.application
28+
import glyph.gp.individual
29+
import glyph.utils
3030
from glyph._version import get_versions
31-
from glyph.assessment import const_opt
31+
from glyph.assessment import const_opt, complexity_measures
3232
from glyph.cli._parser import * # noqa
3333
from glyph.gp.constraints import constrain
3434
from glyph.gp.individual import _constant_normal_form, add_sc, pretty_print, sc_mmqout, simplify_this
@@ -39,7 +39,11 @@
3939
from glyph.utils.logging import print_params, load_config
4040

4141
logger = logging.getLogger(__name__)
42-
version = get_versions()["version"]
42+
43+
44+
def get_version_info():
45+
version = get_versions()["version"]
46+
return f"Version {version}"
4347

4448

4549
class ExperimentProtocol(enum.EnumMeta):
@@ -100,7 +104,7 @@ def from_checkpoint(cls, file_name, com):
100104
gp_runner = cp["runner"]
101105
gp_runner.assessment_runner = RemoteAssessmentRunner(
102106
com,
103-
consider_complexity=args.consider_complexity,
107+
complexity_measure=args.complexity_measure,
104108
method=args.const_opt_method,
105109
options=args.options,
106110
caching=args.caching,
@@ -264,7 +268,7 @@ class RemoteAssessmentRunner:
264268
def __init__(
265269
self,
266270
com,
267-
consider_complexity=True,
271+
complexity_measure=None,
268272
multi_objective=False,
269273
method="Nelder-Mead",
270274
options={"smart_options": {"use": False}},
@@ -277,7 +281,7 @@ def __init__(
277281
):
278282
"""Contains assessment logic. Uses zmq connection to request evaluation."""
279283
self.com = com
280-
self.consider_complexity = consider_complexity
284+
self.complexity_measure = complexity_measures.get(complexity_measure, None)
281285
self.multi_objective = multi_objective
282286
self.caching = caching
283287
self.cache = {} if persistent_caching is None else DBCache("glyph-remote", persistent_caching)
@@ -296,7 +300,7 @@ def __init__(
296300

297301
self.smart_options = options.get("smart_options")
298302
if self.smart_options["use"]:
299-
self.method = glyph.utils.numeric.SmartConstantOptimizer(
303+
self.method = glyph.utils.numeric.SlowConversionTerminator(
300304
glyph.utils.numeric.hill_climb, **self.smart_options["kw"]
301305
)
302306

@@ -345,8 +349,8 @@ def measure(self, individual, meta=None):
345349

346350
self.queue.put(None)
347351
individual.popt = popt
348-
if self.consider_complexity:
349-
fitness = *error, sum(map(len, individual))
352+
if self.complexity_measure:
353+
fitness = *error, sum(map(self.complexity_measure, individual))
350354
else:
351355
fitness = error
352356
return fitness
@@ -469,7 +473,7 @@ def make_remote_app(callbacks=(), callback_factories=(), parser=None):
469473
com,
470474
method=args.const_opt_method,
471475
options=args.options,
472-
consider_complexity=args.consider_complexity,
476+
complexity_measure=args.complexity_measure,
473477
caching=args.caching,
474478
persistent_caching=args.persistent_caching,
475479
simplify=args.simplify,
@@ -513,9 +517,13 @@ def make_remote_app(callbacks=(), callback_factories=(), parser=None):
513517
app = RemoteApp(args, gp_runner, args.checkpoint_file, callbacks=callbacks)
514518

515519
bc = break_condition(ttl=args.ttl, target=args.target, max_iter=args.max_iter_total, error_index=0)
520+
return app, bc, args
521+
522+
523+
def log_info(args):
524+
logger.info(f"Glyph-remote. {get_version_info()}")
516525
logger.debug("Parameters:")
517526
print_params(logger.debug, vars(args))
518-
return app, bc, args
519527

520528

521529
def send_meta_data(app):
@@ -527,8 +535,8 @@ def send_meta_data(app):
527535

528536

529537
def main():
530-
logger.info(f"Glyph-remote: Version {version}")
531538
app, bc, args = make_remote_app()
539+
log_info(args)
532540
app.run(break_condition=bc)
533541

534542

glyph/gp/individual.py

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,6 @@
1717

1818
logger = logging.getLogger(__name__)
1919

20-
#def len_subtree(i):
21-
# sl_left = ind.searchSubtree(i+1)
22-
# len_left = sl_left.stop - sl_left.start
23-
# sl_right = ind.searchSubtree(sl_left.stop)
24-
# len_right = sl_right.stop - sl_right.start
25-
# return len_left, len_right
26-
2720

2821
def sc_qout(x, y):
2922
"""SC is the quotient of the number of nodes of its left and right child-trees x and y"""
@@ -54,25 +47,6 @@ def as_terminal(self, *args):
5447
return deap.gp.Terminal(self.func(*vals), False, deap.gp.__type__)
5548

5649

57-
def resolve_sc(ind):
58-
"""Evaluate StructConst in individual top to bottom."""
59-
nodes = type(ind)(ind[:])
60-
61-
# structure based constants need to be evaluated top to bottom first
62-
is_sc = lambda n: isinstance(n, StructConst)
63-
while any(n for n in nodes if is_sc(n)):
64-
# get the first structure based constant
65-
i, node = next(filter((lambda x: is_sc(x[1])), enumerate(nodes)))
66-
slice_ = nodes.searchSubtree(i)
67-
# find its subtree
68-
subtree = type(ind)(nodes[slice_])
69-
# replace the subtree by a Terminal representing its numeric value
70-
# child_trees helper function yields a list of tree which are used as arguments by the first primitive
71-
args = list(child_trees(subtree))
72-
nodes[slice_] = [node.as_terminal(*args)]
73-
return nodes
74-
75-
7650
def add_sc(pset, func):
7751
"""Adds a structural constant to a given primitive set.
7852
@@ -281,9 +255,27 @@ def __repr__(self):
281255
def __str__(self):
282256
return self.to_polish()
283257

258+
def resolve_sc(self):
259+
"""Evaluate StructConst in individual top to bottom."""
260+
nodes = type(self)(self[:])
261+
262+
# structure based constants need to be evaluated top to bottom first
263+
is_sc = lambda n: isinstance(n, StructConst)
264+
while any(n for n in nodes if is_sc(n)):
265+
# get the first structure based constant
266+
i, node = next(filter((lambda x: is_sc(x[1])), enumerate(nodes)))
267+
slice_ = nodes.searchSubtree(i)
268+
# find its subtree
269+
subtree = type(self)(nodes[slice_])
270+
# replace the subtree by a Terminal representing its numeric value
271+
# child_trees helper function yields a list of tree which are used as arguments by the first primitive
272+
args = list(child_trees(subtree))
273+
nodes[slice_] = [node.as_terminal(*args)]
274+
return nodes
275+
284276
def to_polish(self, for_sympy=False, replace_struct=True):
285277
"""Symbolic representation of the expression tree."""
286-
nodes = resolve_sc(self) if replace_struct else self
278+
nodes = self.resolve_sc() if replace_struct else self
287279
repr = ""
288280
stack = []
289281
for node in nodes:

glyph/utils/numeric.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def f(x):
9191
return res
9292

9393

94-
class SmartConstantOptimizer:
94+
class SlowConversionTerminator:
9595
def __init__(self, method, step_size=10, min_stat=10, threshold=25):
9696
"""Decorate a minimize method used in `scipy.optimize.minimize` to cancel non promising constant optimizations.
9797
@@ -136,7 +136,3 @@ def __call__(self, fun, x0, args, **kwargs):
136136

137137
return res
138138

139-
140-
def expressional_complexity(ind):
141-
"""Sum of length of all subtrees of the individual."""
142-
return sum(len(ind[ind.searchSubtree(i)]) for i in range(len(ind)))

tests/unittest/test_assessment.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ def test_pickle_assessment_runner():
177177

178178
@pytest.mark.parametrize("x", [[np.nan], np.array([np.nan]), np.nan])
179179
def test_replace_nan(x):
180-
print(x)
181180
x_clean = assessment.replace_nan(x)
182181
assert isinstance(x_clean, type(x))
183182
try:
@@ -217,3 +216,14 @@ def g():
217216

218217
assert decorator(f)() == 1
219218
assert decorator(g)() == 2
219+
220+
221+
ec_cases = (
222+
("x_0", 1),
223+
("Add(exp(x_0), x_0)", 8),
224+
)
225+
226+
227+
@pytest.mark.parametrize("expr, res", ec_cases)
228+
def test_expressional_complexity(NumpyIndividual, expr, res):
229+
assert assessment.expressional_complexity(NumpyIndividual.from_string(expr)) == res

tests/unittest/utils/test_numerics.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
11
import pytest
22

33
from glyph.utils.numeric import *
4-
5-
6-
ec_cases = (
7-
("x_0", 1),
8-
("Add(exp(x_0), x_0)", 8),
9-
)
10-
11-
12-
@pytest.mark.parametrize("case", ec_cases)
13-
def test_expressional_complexity(NumpyIndividual, case):
14-
expr, res = case
15-
assert expressional_complexity(NumpyIndividual.from_string(expr)) == res

0 commit comments

Comments
 (0)