summaryrefslogtreecommitdiff
path: root/lib/model.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/model.py')
-rw-r--r--lib/model.py188
1 files changed, 152 insertions, 36 deletions
diff --git a/lib/model.py b/lib/model.py
index c74ebd1..348c541 100644
--- a/lib/model.py
+++ b/lib/model.py
@@ -1165,6 +1165,11 @@ class KConfigModel:
self.value = value
self.stddev = stddev
+ @classmethod
+ def from_json(cls, json_node):
+ node = cls(json_node["value"], json_node["stddev"])
+ return node
+
def model(self, kconf):
return self.value
@@ -1174,12 +1179,21 @@ class KConfigModel:
def to_json(self):
return {"value": self.value, "stddev": self.stddev}
- class Node:
+ class BoolNode:
def __init__(self, symbol):
self.symbol = symbol
self.child_n = None
self.child_y = None
+ @classmethod
+ def from_json(cls, outer_cls, json_node):
+ node = cls(json_node["symbol"])
+ if json_node["n"]:
+ node.set_child_n(outer_cls._node_from_json(json_node["n"]))
+ if json_node["y"]:
+ node.set_child_y(outer_cls._node_from_json(json_node["y"]))
+ return node
+
def set_child_n(self, child_node):
self.child_n = child_node
@@ -1194,7 +1208,7 @@ class KConfigModel:
return None
def __repr__(self):
- return f"<Node(n={self.child_n}, y={self.child_y})>"
+ return f"<BoolNode {self.symbol}, n={self.child_n}, y={self.child_y}>"
def to_json(self):
ret = {"symbol": self.symbol}
@@ -1208,9 +1222,48 @@ class KConfigModel:
ret["y"] = None
return ret
- def __init__(self, kconfig_benchmark, attribute):
+ class ChoiceNode:
+ def __init__(self, symbol):
+ self.symbol = symbol
+ self.choice = dict()
+
+ @classmethod
+ def from_json(cls, outer_cls, json_node):
+ node = cls(json_node["symbol"])
+ for choice_name, choice_json in json_node["choice"].items():
+ node.set_child(choice_name, outer_cls._node_from_json(choice_json))
+ return node
+
+ def set_child(self, choice, node):
+ self.choice[choice] = node
+
+ def model(self, kconf):
+ kconf_choice = next(
+ filter(lambda choice: choice.name == self.symbol, kconf.choices)
+ )
+ return self.choice[kconf_choice.selection.name].model(kconf)
+
+ def __repr__(self):
+ choice_names = sorted(self.choice.keys())
+ choice_list = ", ".join(
+ map(lambda choice: f"{choice}={self.choice[choice]}", choice_names)
+ )
+ return f"<ChoiceNode {self.symbol}, {choice_list}>"
+
+ def to_json(self):
+ ret = {"symbol": self.symbol, "choice": dict()}
+ for choice_name, choice_node in self.choice.items():
+ ret["choice"][choice_name] = choice_node.to_json()
+ return ret
+
+ @classmethod
+ def from_benchmark(cls, kconfig_benchmark, attribute):
+ self = cls()
self.data = kconfig_benchmark.data
- self.symbols = kconfig_benchmark.symbols
+ self.symbols = kconfig_benchmark.symbol_names
+ self.choices = kconfig_benchmark.choice_names
+ self.symbol = kconfig_benchmark.symbol
+ self.choice = kconfig_benchmark.choice
self.max_stddev = 10
if callable(attribute):
self.attribute = "custom"
@@ -1223,9 +1276,39 @@ class KConfigModel:
self.attr_function = lambda x: x[1]["total"]["RAM"]
else:
raise ValueError("attribute must be a a function, 'rom', or 'ram'")
+ return self
+
+ @classmethod
+ def from_json(cls, json_input: dict):
+ self = cls()
+ self.attribute = json_input["attribute"]
+ self.symbols = json_input["symbols"]
+ self.model = self._node_from_json(json_input["model"])
+ return self
+
+ @classmethod
+ def _node_from_json(cls, json_node):
+ if "choice" in json_node:
+ return cls.ChoiceNode.from_json(cls, json_node)
+ elif "n" in json_node:
+ return cls.BoolNode.from_json(cls, json_node)
+ return cls.Leaf.from_json(json_node)
def build_tree(self):
- self.model = self._build_tree(self.symbols, self.data, 0)
+ # without ChoiceNode:
+ # self.model = self._build_tree(self.symbols, list(), self.data, 0)
+
+ standalone_symbols = list(
+ filter(
+ lambda sym: self.symbol[sym].choice is None
+ or self.symbol[sym].choice.is_optional,
+ self.symbols,
+ )
+ )
+ tree_choices = list(
+ filter(lambda choice: not self.choice[choice].is_optional, self.choices)
+ )
+ self.model = self._build_tree(standalone_symbols, tree_choices, self.data, 0)
def value_for_config(self, kconf):
return self.model.model(kconf)
@@ -1238,53 +1321,86 @@ class KConfigModel:
}
return output
- def _build_tree(self, this_symbols, this_data, level):
+ def _build_tree(self, this_symbols, this_choices, this_data, level):
rom_sizes = list(map(self.attr_function, this_data))
if np.std(rom_sizes) < self.max_stddev or len(this_symbols) == 0:
return self.Leaf(np.mean(rom_sizes), np.std(rom_sizes))
- mean_stds = list()
- for i, param in enumerate(this_symbols):
- enabled = list(filter(lambda vrr: vrr[0][i] == True, this_data))
- disabled = list(filter(lambda vrr: vrr[0][i] == False, this_data))
+ sym_stds = list()
+ for symbol_name in this_symbols:
+ enabled = list(filter(lambda vrr: vrr[0][symbol_name] == True, this_data))
+ disabled = list(filter(lambda vrr: vrr[0][symbol_name] == False, this_data))
enabled_std_rom = np.std(list(map(self.attr_function, enabled)))
disabled_std_rom = np.std(list(map(self.attr_function, disabled)))
children = [enabled_std_rom, disabled_std_rom]
if np.any(np.isnan(children)):
- mean_stds.append(np.inf)
+ sym_stds.append(np.inf)
else:
- mean_stds.append(np.mean(children))
+ sym_stds.append(np.mean(children))
+
+ choice_stds = list()
+ for choice in this_choices:
+ choice_foo = list()
+ choice_std = list()
+ num_configs = 0
+ for symbol in self.choice[choice].syms:
+ sym_enabled = list(
+ filter(lambda vrr: vrr[0][symbol.name] == True, this_data)
+ )
+ num_configs += len(sym_enabled)
+ choice_foo.append(sym_enabled)
+ choice_std.append(np.std(list(map(self.attr_function, sym_enabled))))
- symbol_index = np.argmin(mean_stds)
- symbol = this_symbols[symbol_index]
- enabled = list(filter(lambda vrr: vrr[0][symbol_index] == True, this_data))
- disabled = list(filter(lambda vrr: vrr[0][symbol_index] == False, this_data))
+ # only split on a choice if it is present in _all_ configurations
+ if np.any(np.isnan(choice_std)) or num_configs != len(this_data):
+ choice_stds.append(np.inf)
+ else:
+ choice_stds.append(np.mean(choice_std))
- node = self.Node(symbol)
+ min_index = np.argmin(sym_stds + choice_stds)
- new_symbols = this_symbols[:symbol_index] + this_symbols[symbol_index + 1 :]
- enabled = list(
- map(
- lambda x: (x[0][:symbol_index] + x[0][symbol_index + 1 :], x[1]),
- enabled,
- )
- )
- disabled = list(
- map(
- lambda x: (x[0][:symbol_index] + x[0][symbol_index + 1 :], x[1]),
- disabled,
+ if min_index < len(sym_stds):
+ symbol_index = min_index
+ symbol = this_symbols[symbol_index]
+
+ node = self.BoolNode(symbol)
+
+ new_symbols = this_symbols[:symbol_index] + this_symbols[symbol_index + 1 :]
+
+ enabled = list(filter(lambda vrr: vrr[0][symbol] == True, this_data))
+ disabled = list(filter(lambda vrr: vrr[0][symbol] == False, this_data))
+
+ logger.debug(
+ f"Level {level} split on {symbol} (mean std={sym_stds[symbol_index]}) has {len(enabled)} children when enabled and {len(disabled)} children when disabled"
)
- )
- logger.debug(
- f"Level {level} split on {symbol} has {len(enabled)} children when enabled and {len(disabled)} children when disabled"
- )
- if len(enabled):
- node.set_child_y(self._build_tree(new_symbols, enabled, level + 1))
- if len(disabled):
- node.set_child_n(self._build_tree(new_symbols, disabled, level + 1))
+ if len(enabled):
+ node.set_child_y(
+ self._build_tree(new_symbols, this_choices, enabled, level + 1)
+ )
+ if len(disabled):
+ node.set_child_n(
+ self._build_tree(new_symbols, this_choices, disabled, level + 1)
+ )
+ else:
+ choice_index = min_index - len(sym_stds)
+ choice = this_choices[choice_index]
+ node = self.ChoiceNode(choice)
+
+ new_choices = this_choices[:choice_index] + this_choices[choice_index + 1 :]
+
+ for sym in self.choice[choice].syms:
+ enabled = list(filter(lambda vrr: vrr[0][sym.name] == True, this_data))
+ logger.debug(
+ f"Level {level} split on {choice} (mean std={choice_stds[choice_index]}) has {len(enabled)} children for {sym.name}"
+ )
+ if len(enabled):
+ node.set_child(
+ sym.name,
+ self._build_tree(this_symbols, new_choices, enabled, level + 1),
+ )
return node