Initial chekin post-discontinuity.
This commit is contained in:
commit
6556164879
24 files changed
+1063
No files matched your search
+166
@@ -0,0 +1,166 @@
|
||||
#
|
||||
# carkov markov chain library
|
||||
# © Copyright 2021 by Aldercone Studio <alderconestudio@gmail.com>
|
||||
# This is free software, see the included LICENSE for terms and conditions.
|
||||
#
|
||||
|
||||
"""
|
||||
This module defines a chainer class which can process a count dictionary from an analyzer
|
||||
and provides convenience functions for walking the chain.
|
||||
"""
|
||||
|
||||
|
||||
from collections import UserDict, deque
|
||||
from typing import Any, Dict, Tuple, Optional
|
||||
|
||||
from .abstracts import TERMINAL
|
||||
from .analyze.abstract import AbstractAnalyzer
|
||||
from .utils import merge_stats, convert_counts, weighted_stat_choice
|
||||
|
||||
|
||||
ChainType = Dict[Tuple[Any], Any]
|
||||
|
||||
|
||||
def from_analyzer(analyzer: AbstractAnalyzer):
|
||||
"""
|
||||
Static initializer: Return a chainer with parameters and contents based on an analyzer instance.
|
||||
"""
|
||||
chainer = Chain(analyzer.order, analyzer.__class__.__name__)
|
||||
chainer.integrate(analyzer.chain_counts)
|
||||
return chainer
|
||||
|
||||
|
||||
class Chain(UserDict):
|
||||
def __init__(self, order: int, analyzer_class: Optional[str] = None):
|
||||
"""
|
||||
Initialize Chain class
|
||||
|
||||
Arguments:
|
||||
order: The window size of this chainer.
|
||||
"""
|
||||
self.order = order
|
||||
self.data: ChainType = {}
|
||||
self.start_token = (None, ) * self.order
|
||||
self.analyzer_class = analyzer_class
|
||||
|
||||
def integrate(self, counts: ChainType):
|
||||
"""
|
||||
Accept a counts dictionary and merge it with local data and recalculate statistical relationships between
|
||||
outcomes. The counts must be from an analyzer of the same order.
|
||||
|
||||
Arguments:
|
||||
counts: A counts dictionary as contained in the analyzer's chain_counts
|
||||
"""
|
||||
for key, count in counts.items():
|
||||
stat = convert_counts(count)
|
||||
if key in self.data:
|
||||
merge_stats(self.data[key], stat)
|
||||
else:
|
||||
self.data[key] = stat
|
||||
self.update_stats()
|
||||
|
||||
def merge(self, other):
|
||||
"""
|
||||
Merge a separate chainer's data into this chainer. They must be of the same order.
|
||||
|
||||
Arguments:
|
||||
other (Chain): Another chain of the same order.
|
||||
"""
|
||||
for key, stat in other.items():
|
||||
if key in self.data:
|
||||
merge_stats(self.data[key], stat)
|
||||
else:
|
||||
self.data[key] = stat
|
||||
self.update_stats()
|
||||
|
||||
def update_stats(self):
|
||||
"""
|
||||
Update all of the statistical ratios in the chain.
|
||||
"""
|
||||
for token in self.data:
|
||||
self.update_stat(token)
|
||||
|
||||
def update_stat(self, parent_token):
|
||||
"""
|
||||
Update one specific set of statistical ratios in the chain.
|
||||
|
||||
Arguments:
|
||||
parent_token: A windowed token tuple which points at the part of the chain to update
|
||||
"""
|
||||
stat = self.data[parent_token]
|
||||
total = sum([s[0] for s in stat.values()])
|
||||
for it in stat.values():
|
||||
it[1] = int((it[0] / total) * 100)
|
||||
|
||||
def add(self, parent_token, token):
|
||||
"""
|
||||
Add a new count to the chain.
|
||||
|
||||
Arguments:
|
||||
parent_token: A windowed token tuple which points to the location to add the new token.
|
||||
token: The token to add.
|
||||
"""
|
||||
if parent_token not in self.data:
|
||||
self.data[parent_token] = {}
|
||||
|
||||
if token in self.data[parent_token]:
|
||||
self.data[parent_token][token][0] += 1
|
||||
self.update_stat(parent_token)
|
||||
else:
|
||||
self.data[parent_token][token] = [1, 100]
|
||||
|
||||
def select(self, parent_token, random_generator, weighted=False):
|
||||
"""
|
||||
Select a token from a given parent token.
|
||||
|
||||
Arguments:
|
||||
parent_token: A windowed token tuple
|
||||
random_generator: A random.Random instance
|
||||
weighted (bool, default=False): Whether to do a weighted select or a random select.
|
||||
|
||||
Returns:
|
||||
A token
|
||||
"""
|
||||
if parent_token not in self.data:
|
||||
return None
|
||||
if weighted:
|
||||
return weighted_stat_choice(random_generator, self.data[parent_token])
|
||||
else:
|
||||
return random_generator.choice(list(self.data[parent_token].keys()))
|
||||
|
||||
def walk(self, random_generator, weighted=False, maximum=1000):
|
||||
"""
|
||||
Return a list of tokens by walking the chain.
|
||||
|
||||
Arguments:
|
||||
random_generator: A random.Random instance
|
||||
weighted: Whether to do a weighted select at each step.
|
||||
maximum: THe maximum number of walks to perform.
|
||||
|
||||
Returns:
|
||||
A list of tokens
|
||||
"""
|
||||
token = self.start_token
|
||||
item = None
|
||||
output = []
|
||||
while (len(output) < maximum):
|
||||
item = self.select(token, random_generator, weighted)
|
||||
if item == TERMINAL:
|
||||
return output
|
||||
output.append(item)
|
||||
token = self.next_token(token, item)
|
||||
|
||||
def next_token(self, parent_token, token):
|
||||
"""
|
||||
Given a windowed token tuple and a token, return the next windowed token tuple.
|
||||
|
||||
Arguments:
|
||||
parent_token: A windowed token tuple
|
||||
token: A token
|
||||
|
||||
Returns:
|
||||
A windowed token tuple which would be the next step in the chain after the token.
|
||||
"""
|
||||
q = deque(parent_token, self.order)
|
||||
q.append(token)
|
||||
return tuple(q)
|
||||
Reference in new issue
Block a user