summaryrefslogtreecommitdiff
path: root/bin/analyze-archive.py
diff options
context:
space:
mode:
Diffstat (limited to 'bin/analyze-archive.py')
-rwxr-xr-xbin/analyze-archive.py135
1 files changed, 84 insertions, 51 deletions
diff --git a/bin/analyze-archive.py b/bin/analyze-archive.py
index 4531d86..8311f5c 100755
--- a/bin/analyze-archive.py
+++ b/bin/analyze-archive.py
@@ -61,18 +61,25 @@ Options:
Specify traces which should be ignored due to bogus data. 1 is the first
trace, 2 the second, and so on.
---discard-outliers=
- not supported at the moment
-
--cross-validate=<method>:<count>
Perform cross validation when computing model quality.
Only works with --show-quality=table at the moment.
+
If <method> is "montecarlo": Randomly divide data into 2/3 training and 1/3
validation, <count> times. Reported model quality is the average of all
validation runs. Data is partitioned without regard for parameter values,
so a specific parameter combination may be present in both training and
validation sets or just one of them.
+ If <method> is "kfold": Perform k-fold cross validation with k=<count>.
+ Divide data into 1-1/k training and 1/k validation, <count> times.
+ In the first set, items 0, k, 2k, ... ard used for validation, in the
+ second set, items 1, k+1, 2k+1, ... and so on.
+ validation, <count> times. Reported model quality is the average of all
+ validation runs. Data is partitioned without regard for parameter values,
+ so a specific parameter combination may be present in both training and
+ validation sets or just one of them.
+
--function-override=<name attribute function>[;<name> <attribute> <function>;...]
Manually specify the function to fit for <name> <attribute>. A function
specified this way bypasses parameter detection: It is always assigned,
@@ -94,17 +101,22 @@ Options:
--export-energymodel=<model.json>
Export energy model. Works out of the box for v1 and v2 logfiles. Requires --hwmodel for v0 logfiles.
+
+--no-cache
+ Do not load cached measurement results
"""
import getopt
import json
+import logging
import random
import re
import sys
from dfatool import plotter
-from dfatool.dfatool import PTAModel, RawData, pta_trace_to_aggregate
-from dfatool.dfatool import gplearn_to_function
-from dfatool.dfatool import CrossValidator
+from dfatool.loader import RawData, pta_trace_to_aggregate
+from dfatool.functions import gplearn_to_function
+from dfatool.model import PTAModel
+from dfatool.validation import CrossValidator
from dfatool.utils import filter_aggregate_by_param
from dfatool.automata import PTA
@@ -133,6 +145,15 @@ def format_quality_measures(result):
def model_quality_table(result_lists, info_list):
+ print(
+ "{:20s} {:15s} {:19s} {:19s} {:19s}".format(
+ "key",
+ "attribute",
+ "static".center(19),
+ "LUT".center(19),
+ "parameterized".center(19),
+ )
+ )
for state_or_tran in result_lists[0]["by_name"].keys():
for key in result_lists[0]["by_name"][state_or_tran].keys():
buf = "{:20s} {:15s}".format(state_or_tran, key)
@@ -143,7 +164,7 @@ def model_quality_table(result_lists, info_list):
result = results["by_name"][state_or_tran][key]
buf += format_quality_measures(result)
else:
- buf += "{:6}----{:9}".format("", "")
+ buf += "{:7}----{:8}".format("", "")
print(buf)
@@ -279,7 +300,6 @@ def print_html_model_data(model, pm, pq, lm, lq, am, ai, aq):
if __name__ == "__main__":
ignored_trace_indexes = []
- discard_outliers = None
safe_functions_enabled = False
function_override = {}
show_models = []
@@ -292,11 +312,12 @@ if __name__ == "__main__":
try:
optspec = (
- "info "
+ "info no-cache "
"plot-unparam= plot-param= plot-traces= show-models= show-quality= "
- "ignored-trace-indexes= discard-outliers= function-override= "
+ "ignored-trace-indexes= function-override= "
"export-traces= "
"filter-param= "
+ "log-level= "
"cross-validate= "
"with-safe-functions hwmodel= export-energymodel="
)
@@ -313,9 +334,6 @@ if __name__ == "__main__":
if 0 in ignored_trace_indexes:
print("[E] arguments to --ignored-trace-indexes start from 1")
- if "discard-outliers" in opt:
- discard_outliers = float(opt["discard-outliers"])
-
if "function-override" in opt:
for function_desc in opt["function-override"].split(";"):
state_or_tran, attribute, *function_str = function_desc.split(" ")
@@ -344,12 +362,21 @@ if __name__ == "__main__":
if "hwmodel" in opt:
pta = PTA.from_file(opt["hwmodel"])
+ if "log-level" in opt:
+ numeric_level = getattr(logging, opt["log-level"].upper(), None)
+ if not isinstance(numeric_level, int):
+ print(f"Invalid log level: {loglevel}", file=sys.stderr)
+ sys.exit(1)
+ logging.basicConfig(level=numeric_level)
+
except getopt.GetoptError as err:
print(err, file=sys.stderr)
sys.exit(2)
raw_data = RawData(
- args, with_traces=("export-traces" in opt or "plot-traces" in opt)
+ args,
+ with_traces=("export-traces" in opt or "plot-traces" in opt),
+ skip_cache=("no-cache" in opt),
)
if "info" in opt:
@@ -357,9 +384,12 @@ if __name__ == "__main__":
if raw_data.version <= 1:
data_source = "MIMOSA"
elif raw_data.version == 2:
- data_source = "MSP430 EnergyTrace"
- else:
- data_source = "UNKNOWN"
+ if raw_data.ptalog and "sync" in raw_data.ptalog["opt"]["energytrace"]:
+ data_source = "MSP430 EnergyTrace, sync={}".format(
+ raw_data.ptalog["opt"]["energytrace"]["sync"]
+ )
+ else:
+ data_source = "MSP430 EnergyTrace"
print(f" Data source ID: {raw_data.version} ({data_source})")
preprocessed_data = raw_data.get_preprocessed_data()
@@ -434,7 +464,6 @@ if __name__ == "__main__":
parameters,
arg_count,
traces=preprocessed_data,
- discard_outliers=discard_outliers,
function_override=function_override,
pta=pta,
)
@@ -495,21 +524,6 @@ if __name__ == "__main__":
model.stats.param_dependence_ratio(state, "power", param),
)
)
- if model.stats.has_codependent_parameters(state, "power", param):
- print(
- "{:24s} co-dependencies: {:s}".format(
- "",
- ", ".join(
- model.stats.codependent_parameters(
- state, "power", param
- )
- ),
- )
- )
- for param_dict in model.stats.codependent_parameter_value_dicts(
- state, "power", param
- ):
- print("{:24s} parameter-aware for {}".format("", param_dict))
for trans in model.transitions():
# Mean power is not a typical transition attribute, but may be present for debugging or analysis purposes
@@ -551,6 +565,8 @@ if __name__ == "__main__":
if xv_method == "montecarlo":
static_quality = xv.montecarlo(lambda m: m.get_static(), xv_count)
+ elif xv_method == "kfold":
+ static_quality = xv.kfold(lambda m: m.get_static(), xv_count)
else:
static_quality = model.assess(static_model)
@@ -560,6 +576,8 @@ if __name__ == "__main__":
if xv_method == "montecarlo":
lut_quality = xv.montecarlo(lambda m: m.get_param_lut(fallback=True), xv_count)
+ elif xv_method == "kfold":
+ lut_quality = xv.kfold(lambda m: m.get_param_lut(fallback=True), xv_count)
else:
lut_quality = model.assess(lut_model)
@@ -616,21 +634,21 @@ if __name__ == "__main__":
if "param" in show_models or "all" in show_models:
if not model.stats.can_be_fitted():
- print(
- "[!] measurements have insufficient distinct numeric parameters for fitting. A parameter-aware model is not available."
+ logging.warning(
+ "measurements have insufficient distinct numeric parameters for fitting. A parameter-aware model is not available."
)
for state in model.states():
for attribute in model.attributes(state):
if param_info(state, attribute):
print(
"{:10s}: {}".format(
- state, param_info(state, attribute)["function"]._model_str
+ state,
+ param_info(state, attribute)["function"].model_function,
)
)
print(
"{:10s} {}".format(
- "",
- param_info(state, attribute)["function"]._regression_args,
+ "", param_info(state, attribute)["function"].model_args
)
)
for trans in model.transitions():
@@ -640,19 +658,19 @@ if __name__ == "__main__":
"{:10s}: {:10s}: {}".format(
trans,
attribute,
- param_info(trans, attribute)["function"]._model_str,
+ param_info(trans, attribute)["function"].model_function,
)
)
print(
"{:10s} {:10s} {}".format(
- "",
- "",
- param_info(trans, attribute)["function"]._regression_args,
+ "", "", param_info(trans, attribute)["function"].model_args
)
)
if xv_method == "montecarlo":
analytic_quality = xv.montecarlo(lambda m: m.get_fitted()[0], xv_count)
+ elif xv_method == "kfold":
+ analytic_quality = xv.kfold(lambda m: m.get_fitted()[0], xv_count)
else:
analytic_quality = model.assess(param_model)
@@ -686,7 +704,7 @@ if __name__ == "__main__":
)
if "overall" in show_quality or "all" in show_quality:
- print("overall static/param/lut MAE assuming equal state distribution:")
+ print("overall state static/param/lut MAE assuming equal state distribution:")
print(
" {:6.1f} / {:6.1f} / {:6.1f} µW".format(
model.assess_states(static_model),
@@ -694,15 +712,30 @@ if __name__ == "__main__":
model.assess_states(lut_model),
)
)
- print("overall static/param/lut MAE assuming 95% STANDBY1:")
- distrib = {"STANDBY1": 0.95, "POWERDOWN": 0.03, "TX": 0.01, "RX": 0.01}
- print(
- " {:6.1f} / {:6.1f} / {:6.1f} µW".format(
- model.assess_states(static_model, distribution=distrib),
- model.assess_states(param_model, distribution=distrib),
- model.assess_states(lut_model, distribution=distrib),
+ distrib = dict()
+ num_states = len(model.states())
+ p95_state = None
+ for state in model.states():
+ distrib[state] = 1.0 / num_states
+
+ if "STANDBY1" in model.states():
+ p95_state = "STANDBY1"
+ elif "SLEEP" in model.states():
+ p95_state = "SLEEP"
+
+ if p95_state is not None:
+ for state in distrib.keys():
+ distrib[state] = 0.05 / (num_states - 1)
+ distrib[p95_state] = 0.95
+
+ print(f"overall state static/param/lut MAE assuming 95% {p95_state}:")
+ print(
+ " {:6.1f} / {:6.1f} / {:6.1f} µW".format(
+ model.assess_states(static_model, distribution=distrib),
+ model.assess_states(param_model, distribution=distrib),
+ model.assess_states(lut_model, distribution=distrib),
+ )
)
- )
if "summary" in show_quality or "all" in show_quality:
model_summary_table(