introduce ResultStore to keep track of game sessions

simu_flags
agp8x 2017-09-18 15:08:38 +02:00
parent c25deecf0b
commit 70f72e6162
8 changed files with 172 additions and 71 deletions

View File

@ -1,15 +1,17 @@
from typing import List from typing import List
from .analyzer import Analyzer, Result from .analyzer import Analyzer, Result
from .analyzer.biogames import BoardDurationAnalyzer, SimulationRoundsAnalyzer, ActivationSequenceAnalyzer from .analyzer.biogames import BoardDurationAnalyzer, SimulationRoundsAnalyzer, ActivationSequenceAnalyzer, \
from .analyzer.default import LogEntryCountAnalyzer, LocationAnalyzer, LogEntrySequenceAnalyzer, ActionSequenceAnalyzer BiogamesCategorizer
from .analyzer.default import LogEntryCountAnalyzer, LocationAnalyzer, LogEntrySequenceAnalyzer, ActionSequenceAnalyzer, \
CategorizerStub
from .analyzer.locomotion import LocomotionActionAnalyzer, CacheSequenceAnalyzer from .analyzer.locomotion import LocomotionActionAnalyzer, CacheSequenceAnalyzer
from .analyzer.mask import MaskSpatials from .analyzer.mask import MaskSpatials
from .render import Render from .render import Render
from .render.biogames import SimulationRoundsRender, BoardDurationHistRender, BoardDurationBoxRender
from .render.default import PrintRender, JSONRender, TrackRender, HeatMapRender from .render.default import PrintRender, JSONRender, TrackRender, HeatMapRender
from .render.locomotion import LocomotionActionRelativeRender, LocomotionActionAbsoluteRender, \ from .render.locomotion import LocomotionActionRelativeRender, LocomotionActionAbsoluteRender, \
LocomotionActionRatioRender LocomotionActionRatioRender
from .render.biogames import SimulationRoundsRender, BoardDurationHistRender, BoardDurationBoxRender
__FALLBACK__ = PrintRender __FALLBACK__ = PrintRender
__MAPPING__ = { __MAPPING__ = {

View File

@ -1,11 +1,14 @@
from analyzers.settings import LogSettings
import logging import logging
from collections import KeysView
from typing import Type, Sized, Collection
log = logging.getLogger(__name__) from analyzers.settings import LogSettings
log: logging.Logger = logging.getLogger(__name__)
class Result: class Result:
def __init__(self, analysis, result): def __init__(self, analysis: Type, result: Sized):
self.result = result self.result = result
self.__analysis__ = analysis self.__analysis__ = analysis
log.debug("set" + str(len(self.result))) log.debug("set" + str(len(self.result)))
@ -18,18 +21,70 @@ class Result:
return self.result return self.result
def __repr__(self): def __repr__(self):
return "<Result " + str(self.__analysis__) + ": " + str(type(self.result)) + " "+str(len(self.result))+">" return "<Result " + str(self.__analysis__) + ": " + str(type(self.result)) + " " + str(len(self.result)) + ">"
class ResultStore:
"""Store Results"""
def __init__(self, store_entry: Type[Collection] = list, store_action: callable = list.append) -> None:
self.store = {}
self.category = None
self.entry: Type[Collection] = store_entry
self.action: callable = store_action
def new_category(self, key) -> None:
self.category = key
if not key in self.store:
self.store[key] = self.entry()
def add(self, entry: Result) -> None:
self.action(self.store[self.category], entry)
def get_store(self) -> dict:
return dict(self.store)
def get_all(self) -> list:
"""
Throw all categories together
:return:
"""
result = []
for key in self.store:
result += self.store[key]
return result
def get_categories(self) -> KeysView:
return self.store.keys()
def get_category(self, key):
if key not in self.store:
return self.entry
return self.store[key]
def serializable(self):
values = {}
for key in self.store:
values[key] = [{"analysis": str(result.analysis()), "result": result.get()} for result in self.store[key]]
return values
class Analyzer: class Analyzer:
def __init__(self, settings: LogSettings): """Operate on log entries, one at a time"""
self.settings = settings
def __init__(self, settings: LogSettings) -> None:
self.settings: LogSettings = settings
def process(self, entry: dict) -> bool: def process(self, entry: dict) -> bool:
"""
Process an entry
:param entry: Entry to process
:return: True if consumed, False for further analysis
"""
raise NotImplementedError() raise NotImplementedError()
def result(self) -> Result: def result(self, store: ResultStore) -> None:
raise NotImplementedError() raise NotImplementedError()
def name(self): def name(self) -> str:
return self.__name__ return self.__name__

View File

@ -1,7 +1,8 @@
import logging import logging
from collections import defaultdict from collections import defaultdict
from . import Result, LogSettings, Analyzer from . import Result, LogSettings, Analyzer, ResultStore
from .default import CategorizerStub
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -15,7 +16,7 @@ class BoardDurationAnalyzer(Analyzer):
def render(self) -> str: def render(self) -> str:
return "\n".join(["{}\t{}".format(entry["active"], entry["id"]) for entry in self.result().get()]) return "\n".join(["{}\t{}".format(entry["active"], entry["id"]) for entry in self.result().get()])
def result(self) -> Result: def result(self, store: ResultStore) -> None:
result = [] result = []
last_timestamp = None last_timestamp = None
last_board = None last_board = None
@ -27,7 +28,7 @@ class BoardDurationAnalyzer(Analyzer):
last_timestamp = timestamp last_timestamp = timestamp
last_board = board_id last_board = board_id
# TODO: last board? # TODO: last board?
return Result(type(self), result) store.add(Result(type(self), result))
def process(self, entry: dict) -> bool: def process(self, entry: dict) -> bool:
entry_type = entry[self.settings.type_field] entry_type = entry[self.settings.type_field]
@ -54,8 +55,8 @@ class SimulationRoundsAnalyzer(Analyzer):
super().__init__(settings) super().__init__(settings)
self.store = defaultdict(lambda: -1) # TODO verify self.store = defaultdict(lambda: -1) # TODO verify
def result(self) -> Result: def result(self, store: ResultStore) -> None:
return Result(type(self), dict(self.store)) store.add(Result(type(self), dict(self.store)))
def process(self, entry: dict) -> bool: def process(self, entry: dict) -> bool:
entry_type = entry[self.settings.type_field] entry_type = entry[self.settings.type_field]
@ -73,8 +74,8 @@ class ActivationSequenceAnalyzer(Analyzer):
super().__init__(settings) super().__init__(settings)
self.store = [] self.store = []
def result(self) -> Result: def result(self, store: ResultStore) -> None:
return Result(type(self), self.store) store.add(Result(type(self), self.store))
def process(self, entry: dict) -> bool: def process(self, entry: dict) -> bool:
if entry[self.settings.type_field] in self.settings.sequences['start']: if entry[self.settings.type_field] in self.settings.sequences['start']:
@ -82,3 +83,15 @@ class ActivationSequenceAnalyzer(Analyzer):
self.store.append(entry['cache']['@id']) self.store.append(entry['cache']['@id'])
else: else:
logger.error("null cache") logger.error("null cache")
return False
class BiogamesCategorizer(CategorizerStub):
def __init__(self, settings: LogSettings):
super().__init__(settings)
def process(self, entry: dict) -> bool:
if self.key is "default":
if entry[self.settings.type_field] in self.settings.custom['instance_start']:
self.key = entry[self.settings.custom['instance_id']]
return False

View File

@ -1,7 +1,7 @@
import logging import logging
from collections import defaultdict from collections import defaultdict
from . import Result, LogSettings, Analyzer from . import Result, LogSettings, Analyzer, ResultStore
class LocationAnalyzer(Analyzer): class LocationAnalyzer(Analyzer):
@ -15,14 +15,14 @@ class LocationAnalyzer(Analyzer):
super().__init__(settings) super().__init__(settings)
self.entries = [] self.entries = []
def result(self) -> Result: def result(self, store: ResultStore) -> None:
self.log.debug(len(self.entries)) self.log.debug(len(self.entries))
return Result(type(self), list(self.entries)) store.add(Result(type(self), list(self.entries)))
def process(self, entry: dict) -> bool: def process(self, entry: dict) -> bool:
if entry[self.settings.type_field] in self.settings.spatials: if entry[self.settings.type_field] in self.settings.spatials:
self.entries.append(entry) self.entries.append(entry)
#self.log.debug(len(self.entries)) # self.log.debug(len(self.entries))
return False return False
@ -32,8 +32,8 @@ class LogEntryCountAnalyzer(Analyzer):
""" """
__name__ = "LogEntryCount" __name__ = "LogEntryCount"
def result(self) -> Result: def result(self, store: ResultStore) -> None:
return Result(type(self), dict(self.store)) store.add(Result(type(self), dict(self.store)))
def process(self, entry: dict) -> bool: def process(self, entry: dict) -> bool:
self.store[entry[self.settings.type_field]] += 1 self.store[entry[self.settings.type_field]] += 1
@ -50,8 +50,8 @@ class LogEntrySequenceAnalyzer(Analyzer):
""" """
__name__ = "LogEntrySequence" __name__ = "LogEntrySequence"
def result(self) -> Result: def result(self, store: ResultStore) -> None:
return Result(type(self), list(self.store)) store.add(Result(type(self), list(self.store)))
def process(self, entry: dict) -> bool: def process(self, entry: dict) -> bool:
entry_type = entry[self.settings.type_field] entry_type = entry[self.settings.type_field]
@ -75,3 +75,21 @@ class ActionSequenceAnalyzer(LogEntrySequenceAnalyzer):
return False return False
self.store.append(entry_type) self.store.append(entry_type)
return False return False
class CategorizerStub(Analyzer):
"""
generate a new Category in a ResultStore
"""
def process(self, entry: dict) -> bool:
raise NotImplementedError()
__name__ = "Categorizer"
def result(self, store: ResultStore) -> None:
store.new_category(self.key)
def __init__(self, settings: LogSettings):
super().__init__(settings)
self.key = "default"

View File

@ -1,5 +1,5 @@
import util import util
from . import Analyzer, LogSettings, Result from . import Analyzer, LogSettings, Result, ResultStore
def init_filter(settings: LogSettings, state: str) -> callable: def init_filter(settings: LogSettings, state: str) -> callable:
@ -41,7 +41,7 @@ class LocomotionActionAnalyzer(Analyzer):
self.current_cache = None self.current_cache = None
self.last = None self.last = None
def result(self) -> Result: def result(self, store: ResultStore) -> None:
if self.last is not None: if self.last is not None:
if self.current_cache is None: if self.current_cache is None:
self.locomotion.append(self.last - self.cache_time) self.locomotion.append(self.last - self.cache_time)
@ -51,7 +51,7 @@ class LocomotionActionAnalyzer(Analyzer):
locomotion = sum(self.locomotion) locomotion = sum(self.locomotion)
action = sum(self.actions) action = sum(self.actions)
total = locomotion + action total = locomotion + action
return Result(type(self), { store.add(Result(type(self), {
'locomotion_sum': locomotion, 'locomotion_sum': locomotion,
'action_sum': action, 'action_sum': action,
'locomotion': self.locomotion, 'locomotion': self.locomotion,
@ -60,7 +60,7 @@ class LocomotionActionAnalyzer(Analyzer):
'locomotion_relative': locomotion / total, 'locomotion_relative': locomotion / total,
'action_relative': action / total, 'action_relative': action / total,
'locomotion_action_ratio': locomotion / action, 'locomotion_action_ratio': locomotion / action,
}) }))
def __init__(self, settings: LogSettings): def __init__(self, settings: LogSettings):
super().__init__(settings) super().__init__(settings)
@ -86,8 +86,8 @@ class CacheSequenceAnalyzer(Analyzer):
self.store.append((entry['timestamp'], entry['cache'])) self.store.append((entry['timestamp'], entry['cache']))
return False return False
def result(self) -> Result: def result(self, store: ResultStore) -> None:
return Result(type(self), list(self.store)) store.add(Result(type(self), list(self.store)))
def __init__(self, settings: LogSettings): def __init__(self, settings: LogSettings):
super().__init__(settings) super().__init__(settings)

View File

@ -1,4 +1,4 @@
from . import Analyzer from . import Analyzer, Result, ResultStore
class MaskSpatials(Analyzer): class MaskSpatials(Analyzer):
@ -11,5 +11,5 @@ class MaskSpatials(Analyzer):
return True return True
return False return False
def result(self) -> int: def result(self, store: ResultStore) -> None:
return self.masked store.add(Result(type(self), {"masked": self.masked}))

View File

@ -13,18 +13,22 @@
], ],
"analyzers": { "analyzers": {
"analyzers": [ "analyzers": [
"LocationAnalyzer" "BiogamesCategorizer",
"LocomotionActionAnalyzer",
"LogEntryCountAnalyzer"
] ]
}, },
"disabled_analyzers":[ "disabled_analyzers": [
"LogEntryCountAnalyzer", "LocationAnalyzer",
"LogEntrySequenceAnalyzer", "LogEntryCountAnalyzer",
"ActionSequenceAnalyzer", "LogEntrySequenceAnalyzer",
"BoardDurationAnalyzer", "ActionSequenceAnalyzer",
"LocomotionActionAnalyzer", "BoardDurationAnalyzer",
"CacheSequenceAnalyzer", "LocomotionActionAnalyzer",
"SimulationRoundsAnalyzer", "CacheSequenceAnalyzer",
"ActivationSequenceAnalyzer"], "SimulationRoundsAnalyzer",
"ActivationSequenceAnalyzer"
],
"sequences": { "sequences": {
"start": "de.findevielfalt.games.game2.instance.log.entry.LogEntryCache", "start": "de.findevielfalt.games.game2.instance.log.entry.LogEntryCache",
"end": { "end": {
@ -32,8 +36,14 @@
"action.@class": "de.findevielfalt.games.game2.instance.action.CacheEnableAction" "action.@class": "de.findevielfalt.games.game2.instance.action.CacheEnableAction"
} }
}, },
"custom":{ "custom": {
"simulation_rounds": ["de.findevielfalt.games.game2.instance.log.entry.LogEntryQuestion"], "simulation_rounds": [
"simu_data": ["de.findevielfalt.games.game2.instance.data.sequence.simulation.SimulationBoardData"] "de.findevielfalt.games.game2.instance.log.entry.LogEntryQuestion"
],
"simu_data": [
"de.findevielfalt.games.game2.instance.data.sequence.simulation.SimulationBoardData"
],
"instance_start": "de.findevielfalt.games.game2.instance.log.entry.LogEntryStartInstance",
"instance_id": "instance_id"
} }
} }

View File

@ -1,23 +1,25 @@
import json
import logging import logging
from load import LOADERS
from typing import List from typing import List
from analyzers import get_renderer, Analyzer, render
from analyzers.settings import LogSettings, load_settings
import analyzers
import analyzers
from analyzers import get_renderer, Analyzer, render
from analyzers.analyzer import ResultStore
from analyzers.settings import LogSettings, load_settings
from load import LOADERS
logging.basicConfig(format='%(levelname)s %(name)s:%(message)s', level=logging.DEBUG) logging.basicConfig(format='%(levelname)s %(name)s:%(message)s', level=logging.DEBUG)
log = logging.getLogger(__name__) log: logging.Logger = logging.getLogger(__name__)
def process_log(log_id: str, settings: LogSettings) -> List[Analyzer]: def process_log(log_id: str, settings: LogSettings) -> List[Analyzer]:
logfile = "data/inst_{id}.{format}".format(id=log_id, format=settings.log_format) logfile: str = "data/inst_{id}.{format}".format(id=log_id, format=settings.log_format)
loader = LOADERS[settings.log_format]() loader = LOADERS[settings.log_format]()
try: try:
loader.load(logfile) loader.load(logfile)
except BaseException as e: except BaseException as e:
raise RuntimeError(e) raise RuntimeError(e)
analyzers = [] analyzers: List[Analyzer] = []
log.debug("build analyzers") log.debug("build analyzers")
for analyzer in settings.analyzers: for analyzer in settings.analyzers:
analyzers.append(analyzer(settings)) analyzers.append(analyzer(settings))
@ -30,36 +32,37 @@ def process_log(log_id: str, settings: LogSettings) -> List[Analyzer]:
if __name__ == '__main__': if __name__ == '__main__':
settings = load_settings("biogames2.json") settings: LogSettings = load_settings("biogames2.json")
log_ids = [ log_ids: List[str] = [
"20d4244719404ffab0ca386c76e4b112", "20d4244719404ffab0ca386c76e4b112",
"56d9b64144ab44e7b90bf766f3be32e3", "56d9b64144ab44e7b90bf766f3be32e3",
"dc2cdc28ca074715b905e4aa5badff10", "dc2cdc28ca074715b905e4aa5badff10",
"e32b16998440475b994ab46d481d3e0c", "e32b16998440475b994ab46d481d3e0c",
] ]
log_ids = [ log_ids: List[str] = [
"34fecf49dbaca3401d745fb467", "34fecf49dbaca3401d745fb467",
"44ea194de594cd8d63ac0314be", # "44ea194de594cd8d63ac0314be",
"57c444470dbf88605433ca935c", # "57c444470dbf88605433ca935c",
"78e0c545b594e82edfad55bd7f", # "78e0c545b594e82edfad55bd7f",
"91abfd4b31a5562b1c66be37d9", # "91abfd4b31a5562b1c66be37d9",
"597b704fe9ace475316c345903", # "597b704fe9ace475316c345903",
"e01a684aa29dff9ddd9705edf8", # "e01a684aa29dff9ddd9705edf8",
"fbf9d64ae0bdad0de7efa3eec6", # "fbf9d64ae0bdad0de7efa3eec6",
"fe1331481f85560681f86827ec", # "fe1331481f85560681f86827ec",
"fec57041458e6cef98652df625", ] "fec57041458e6cef98652df625", ]
results = [] store: ResultStore = ResultStore()
#TODO: capture session ID, dict
for log_id in log_ids: for log_id in log_ids:
for analysis in process_log(log_id, settings): for analysis in process_log(log_id, settings):
log.info("* Result for " + analysis.name()) log.info("* Result for " + analysis.name())
# print(analysis.result()) # print(analysis.result())
# print(analysis.render()) # print(analysis.render())
results.append(analysis.result()) analysis.result(store)
if False: if False:
for r in get_renderer(analyzers.LocomotionActionAnalyzer): for r in get_renderer(analyzers.LocomotionActionAnalyzer):
r().render(results) r().render(store.get_all())
render(analyzers.LocationAnalyzer, results) if False:
render(analyzers.LocationAnalyzer, store.get_all())
print(json.dumps(store.serializable(), indent=1))
# for analyzers in analyzers: # for analyzers in analyzers:
# if analyzers.name() in ["LogEntryCount", "ActionSequenceAnalyzer"]: # if analyzers.name() in ["LogEntryCount", "ActionSequenceAnalyzer"]: