<jupyter_start><jupyter_text>The Kinetic Transition Network A Markov chain, roughly, is a consecutive series of events (equally spaced in time) where the probability of a specific event only depends on the previous one. For example, imagine that you measure an observable of your experiment every hour, writting down in your notebook the sequence of obtained values:<jupyter_code>series=['A','A','B','C','C','A','D','A','C','B','C','C','A','A','D']<jupyter_output><empty_output><jupyter_text>The consecutive pairs of values can be used to calculate the transitions probabilities. In other words, whats the probability of observing the transition to stat 'B' having the system in 'A'. Let see the transitions:<jupyter_code>for current_state, next_state in zip(series[:-1],series[1:]):
    print('{} --> {}'.format(current_state, next_state))<jupyter_output>A --> A
A --> B
B --> C
C --> C
C --> A
A --> D
D --> A
A --> C
C --> B
B --> C
C --> C
C --> A
A --> A
A --> D<jupyter_text>We can count the number of times your system went from 'i' to 'j' successsively. Lets name the net number of times a transition appears in a Markov chains as $m_{ij}$:<jupyter_code>mij={'A':{}, 'B':{}, 'C':{}, 'D':{}}

for current_state, next_state in zip(series[:-1],series[1:]):
    try:
        mij[current_state][next_state]+=1
    except:
        mij[current_state][next_state]=1<jupyter_output><empty_output><jupyter_text>Thus, we have that the number of times 'D' was observed right after 'A' is:<jupyter_code>mij['A']['D']<jupyter_output><empty_output><jupyter_text>We can also count the number of times a value 'i' was observed at the origin of a transition. Lets call this as $m_i$.<jupyter_code>mi={'A':0, 'B':0, 'C':0, 'D':0}

for current_state, next_state in zip(series[:-1],series[1:]):
        mi[current_state]+=1<jupyter_output><empty_output><jupyter_text>The frequence a value 'i' was observed is $m_i$ for all values but the last one in the series. In this case 'D' appears twice in the series but $m_D=1$.<jupyter_code>mi['D']<jupyter_output><empty_output><jupyter_text>Now that we have all $m_i$ and $m_{ij}$ values, the behaviour observed in our system along 15 consecutive measurements can be enconded as a network. To do so, each state 'i' in the Markov chain is a node in the KTN. A link, or edge, from node $i$ to node $j$ is added to the network if the transition $i \rightarrow j$ was observed at least once along the chain. This way the new graph is a **directed network**, between $i$ and $j$ two different edges can be defined: $i \rightarrow j$ and $i \leftarrow j$.Nodes and edges are then clearly defined given a series of events. Now, we can attach to each element, node or edge, a weight. The value $m_i$ defines the weight of the node $i$, and the value $m_{ij}$ defines the weight of the edge $i \rightarrow j$. This way, the resultant graph is a **weighted directed graph** known as the kinetic transition network (KTN) obtained from the events chain.Finnally, we can define the weight of the network $M$ as the sum of all nodes' weights, which is the same as summing the weights of all edges in the graph:\begin{equation}M = \sum_i {m_i} = \sum_{ij} m_{ij}\end{equation}Given that by definition:\begin{equation}{m_i} = \sum_{j} {m_{ij}}\end{equation}<jupyter_code>net = okn.KTN()

for current_state, next_state in zip(series[:-1],series[1:]):
    okn.add_transition(net, origin=current_state, end=next_state, weight=1)
okn.update_microstates_weights(net)
okn.info(net)
okn.info(net, target='microstate')
okn.info(net, target='transition')<jupyter_output><empty_output><jupyter_text>The weights of the KTN can be redefined as probabilities. This way it is more evident that the KTN is nothing but the representation of a Markov model as a graph:\begin{equation}p_i = \frac{m_i}{\sum_j m_j}\end{equation}and\begin{equation}p_{ij} = \frac{m_{ij}}{\sum_k m_{ik}}\end{equation} The weights can be re-written then as $p_{i}$ for the nodes, stationary probabilities for the kinetic model, and as $p_{ij}$ for the edges, transition probabilities.<jupyter_code>okn.update_probabilities(net)
okn.info(net, target='network')
okn.info(net, target='microstate')
okn.info(net, target='transition')<jupyter_output><empty_output><jupyter_text>If our markov chain is long enough to be ergodic, the same number of times the system was observed going from $i$ to $j$, $m_{ij}$, will be the same number of times for the opposite transition $m_{ji}$ -in equilibrium with an infinite chain-. This symmetricity of the network:\begin{equation}m_{ij}=m_{ji}\end{equation}is known as the detailed balance condition in the context of Markov models:\begin{equation}p_{i} p_{ij} = p_{j} p_{ji}\end{equation}Thanks to the reformulation:\begin{equation}m_{ij}=m_{i} p_{ij}=M p_{i} p_{ij}\end{equation}Obviously, because of limited sampling, our network will never be exactly symmetric. But we can imposse symetrizity keeping the sum of $m_{ij}+m_{ji}$ constant. Lets denote the new edges weights as $\hat{m}_{ij}$ :\begin{equation}\hat{m}_{ij}= \frac{m_{ij} + m_{ji}}{2}\end{equation}With the symmetric edges, weights on nodes need to be recalculated. This time the weight of node $i$ is not only equal to the sum of out-going edges, but to the sum of the in-going edges also:\begin{equation}\hat{m}_{i}= \sum_j m_{ij} = \sum_j m_{ji}\end{equation}By definition the weight of network does not change after symmetrization:\begin{equation}\hat{M}= M\end{equation}<jupyter_code>okn.symmetrize(net)
okn.info(net, target='network')
okn.info(net, target='microstate')
okn.info(net, target='transition')<jupyter_output><empty_output><jupyter_text>Check that with good enough statistics, with the symmetric network, the out-degree (number of out-going neighbor nodes) and the in-degree (number of in-going neighbor nodes) are equal. At last, a KTN can be built from more than a single series of events. Lets' take two markov chains with no common events:<jupyter_code>series1=['A','A','B','C','C','A','D','A','C','B','C','C','A','A','D']
series2=['E','F','F','G','F','F','E','E','J','E','J','E','F','G','F']
net = okn.series_to_ktn([series1, series2])
okn.info(net)<jupyter_output><empty_output><jupyter_text>In this last case, in graphs theory it is said that the network has two disconnected components:<jupyter_code>components = okn.components(net)
components
okn.info(net)
okn.info(net, target='microstate')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13591779542269966, 'fraction_numerical': 0.0037365716954694066, 'mean_word_length': 5.762105263157895, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '47254', 'license': 'MIT', 'n_tokens_mistral': 2095, 'n_tokens_neox': 2041, 'n_words': 756, 'path': 'docs/contents/The_Kinetic_Transition_Network.ipynb', 'repo_name': 'uibcdf/OpenKinNet', 'size': '6423'}