## Page Rank with Hadoop and MrJob

PageRank is a link analysis algorithm and it assigns a numerical weight to each element of a hyperlinked set of documents, such as the World Wide Web, with the purpose of "measuring" its relative importance within the set.

Google search uses PageRank algorithm to rank websites in their search engine results. Page rank is one of the algorithms used in search to determine the best search results for a query.

In order to leverage the machinery of Markov Chains to compute the Steady State Distibution, the webgraph needs to be modieifed to ensure irreducibility and aperiodicity.
This is achieved by modifying the page rank formula to following:
using a damping factor and a teleportation factor along with redistributing lost mass 'm' from a dangling node to all of the nodes.

\begin{equation}
PR(n) = \alpha \left(\frac{1}{|G|}\right) + (1-\alpha)\left( \frac{m}{|G|} + \sum_{i \in L(n)} { \frac{PR(i)}{C(i)}} \right)
\end{equation}

##  Section 1: MRJob implementation of a basic PageRank with toy data

A basic MRJob implementation of the iterative PageRank algorithm that takes sparse adjacency lists as input.

The implementation utilizes teleportation (1-damping/the number of nodes in the network), and further, distributes the mass of dangling nodes with each iteration so that the output of each iteration is correctly normalized (sums to 1).

[NOTE: The PageRank algorithm assumes that a random surfer (walker), starting from a random web page, chooses the next page to which it will move by clicking at random, with probability d,one of the hyperlinks in the current page. This probability is represented by a so-called *damping factor* d, where d ∈ (0, 1). Otherwise, with probability (1 − d), the surfer jumps to any web page in the network. If a page is a dangling end, meaning it has no outgoing hyperlinks, the random surfer selects an arbitrary web page from a uniform distribution and “teleports” to that page]

The data is located here :
> Dropbox: https://www.dropbox.com/sh/2c0k5adwz36lkcw/AAAAKsjQfF9uHfv-X9mCqr9wa?dl=0

Or on Amazon:

> s3://ucb-mids-mls-networks/PageRank-test.txt

with teleportation parameter set to 0.15 (1-d, where d, the damping factor is set to 0.85), and crosscheck the work with the true result, displayed in the first image in the [Wikipedia article](https://en.wikipedia.org/wiki/PageRank)
and here for reference are the corresponding PageRank probabilities:

A, 0.033
B, 0.384
C, 0.343
D, 0.039
E, 0.081
F, 0.039
G, 0.016
H, 0.016
I, 0.016
J, 0.016
K, 0.016

### Implementation

```python
%%writefile wikipedia_example.txt
1	{}
2	{'3': 1}
3	{'2': 1}
4	{'1': 1, '2': 1}
5	{'4': 1, '2': 1, '6': 1}
6	{'2': 1, '5': 1}
7	{'2': 1, '5': 1}
8	{'2': 1, '5': 1}
9	{'2': 1, '5': 1}
10	{'5': 1}
11	{'5': 1}
```

    Writing wikipedia_example.txt

```python
# python code to include initial page ranks
# if time permits this can be done using a map reduce job
num_lines = sum(1 for line in open('wikipedia_example.txt'))
with open("wikipedia_example.txt","r") as f:
    with open("wikipedia_example_updated.txt","w+")  as w:
        for line in f.readlines():
            line  = line.strip('\n') + '|' + str(1.0/num_lines) +'\n'
            w.write(line)
```

```python
!cat wikipedia_example.txt
```

    1	{}
    2	{'3': 1}
    3	{'2': 1}
    4	{'1': 1, '2': 1}
    5	{'4': 1, '2': 1, '6': 1}
    6	{'2': 1, '5': 1}
    7	{'2': 1, '5': 1}
    8	{'2': 1, '5': 1}
    9	{'2': 1, '5': 1}
    10	{'5': 1}
    11	{'5': 1}

```python
!cat wikipedia_example_updated.txt
```

    1	{}|0.0909090909091
    2	{'3': 1}|0.0909090909091
    3	{'2': 1}|0.0909090909091
    4	{'1': 1, '2': 1}|0.0909090909091
    5	{'4': 1, '2': 1, '6': 1}|0.0909090909091
    6	{'2': 1, '5': 1}|0.0909090909091
    7	{'2': 1, '5': 1}|0.0909090909091
    8	{'2': 1, '5': 1}|0.0909090909091
    9	{'2': 1, '5': 1}|0.0909090909091
    10	{'5': 1}|0.0909090909091
    11	{'5': 1}|0.0909090909091

```python
%%writefile PageRank.py

from mrjob.job import MRJob
from mrjob.step import MRStep
import ast
import sys

class PageRank(MRJob):

    def steps(self):
        JOBCONF_STEP1 = {
            'mapreduce.job.output.key.comparator.class': 'org.apache.hadoop.mapred.lib.KeyFieldBasedComparator',
            #'mapreduce.text.key.comparator.options': '-k2,2nr -k1,1',
            #'mapred.text.key.comparator.options': '-k2,2nr -k1,1',
            'mapreduce.job.reduces': 16
        }

        return [MRStep(jobconf=JOBCONF_STEP1,
                       mapper=self.mapper,
                       reducer=self.reducer),
                MRStep(reducer=self.reducer_pagerank)
                ]

    def configure_options(self):
        super(PageRank, self).configure_options()
        self.add_passthrough_option('--dampingfactor', default='0.85', type=str, help='damping factor')
        self.add_passthrough_option('--numtotalnodes', default=None, type=str, help='total number of nodes in the graph')

    def mapper(self, _, line):
        node = None
        adjacency_list = None
        initial_page_rank = 0.0
        neighbors = 0
        if line!= "":
            node, graph_pr_out_links = line.strip().split('\t')
            node =  node.replace('"', '')

            adjacency_list, initial_page_rank = map(ast.literal_eval, graph_pr_out_links.strip("\"").strip('"').strip("'").split('|'))
            neighbors = len(adjacency_list)

            if neighbors> 0:
                for key in adjacency_list.keys():
                    yield (key),  (1.0* initial_page_rank /neighbors)

            if neighbors== 0:
                yield '**dangling', (1.0* initial_page_rank)

            yield (node), (adjacency_list)

    def reducer(self, key, values):
        node = None
        adjacency_list = {}
        total = 0.0
        node =  key.strip("'")
        #values = [v  for v in values]
        redistribute_mass = 0.0

        if node == '**dangling':
            redistribute_mass= sum([float(v) for v in values])
            for i in range(1, int(self.options.numtotalnodes) + 1):
                yield str(i), redistribute_mass

        else:
            for pr_out_link in values:
                if type(pr_out_link) == float:
                    total = total + float(pr_out_link)

                elif type(pr_out_link) == dict:
                    adjacency_list = pr_out_link

            yield node , (str(adjacency_list) + '|' + str(total) )

    def reducer_pagerank(self, node, page_rank_adj_lists):
        lost_mass = 0.0
        page_rank = 0.0
        adj_lists = {}

        alpha = 1 - float(self.options.dampingfactor)
        total_nodes = float(self.options.numtotalnodes)

        for page_rank_adj_list in page_rank_adj_lists:
            if type(page_rank_adj_list) == float:
                lost_mass = page_rank_adj_list
            else:
                adj_lists, page_rank = map(ast.literal_eval, page_rank_adj_list.strip("\"").split('|'))

        page_rank_updated = alpha * (1/total_nodes) + (1-alpha) * (float(lost_mass)/total_nodes + float(page_rank))

        yield node, str(adj_lists) + "|" + str(round(page_rank_updated, 6))

if __name__ == '__main__':
    PageRank.run()
```

    Writing PageRank.py

```python
%%writefile PageRank_driver.py
# START DRIVER
from PageRank import PageRank
from mrjob.job import MRJob
from mrjob.step import MRStep
import sys
import ast
import subprocess

INPUT_GRAPH = sys.argv[1]
DAMPING_FACTOR = sys.argv[2]
TOTAL_NODES = sys.argv[3]
MAX_ITERATION = int(sys.argv[4])
MODE = sys.argv[5]

num_interation = 0

if MODE == 'hadoop':
    mr_job = PageRank(args=[INPUT_GRAPH, '--dampingfactor', DAMPING_FACTOR, '--numtotalnodes', TOTAL_NODES,  '-r', 'hadoop',
                             '--no-output', '--cleanup', 'NONE', '--no-check-input-paths', '--output-dir',  '/tmp/msannat/temp-output' ])
    while (num_interation<MAX_ITERATION):
        with mr_job.make_runner() as runner:
            num_interation += 1
            print "Iteration: ", num_interation
            subprocess.Popen(["hadoop", "fs", "-rm", "-r", "/tmp/msannat/temp-output/"], stdout=None)
            runner.run()
            f = open(INPUT_GRAPH, 'w+')
            cat = subprocess.Popen(["hadoop", "fs", "-cat", "/tmp/msannat/temp-output/part-*"], stdout=subprocess.PIPE)
            for line in cat.stdout:
                #print line
                f.writelines(line)

        f.close()
else:
    mr_job = PageRank(args=[INPUT_GRAPH, '--dampingfactor', DAMPING_FACTOR, '--numtotalnodes', TOTAL_NODES,  '-r', 'local' ])
    while (num_interation<MAX_ITERATION):
        with mr_job.make_runner() as runner:
            num_interation += 1
            print "Iteration: ", num_interation
            runner.run()
            f = open(INPUT_GRAPH, 'w+')
            for line in runner.stream_output():
                #print line
                f.writelines(line)

        f.close()

# END DRIVER
```

    Writing PageRank_driver.py

```python
!rm ./wikipedia_example_input.txt
!cp ./wikipedia_example_updated.txt ./wikipedia_example_input.txt
!python ./PageRank_driver.py ./wikipedia_example_input.txt 0.85 11 30 'local'
```

    rm: cannot remove `./wikipedia_example_input.txt': No such file or directory
    Iteration:  1
    Iteration:  2
    Iteration:  3
    Iteration:  4
    Iteration:  5
    Iteration:  6
    Iteration:  7
    Iteration:  8
    Iteration:  9
    Iteration:  10
    Iteration:  11
    Iteration:  12
    Iteration:  13
    Iteration:  14
    Iteration:  15
    Iteration:  16
    Iteration:  17
    Iteration:  18
    Iteration:  19
    Iteration:  20
    Iteration:  21
    Iteration:  22
    Iteration:  23
    Iteration:  24
    Iteration:  25
    Iteration:  26
    Iteration:  27
    Iteration:  28
    Iteration:  29
    Iteration:  30

```python
!cat ./wikipedia_example_input.txt
```

    "5"	"{u'2': 1, u'4': 1, u'6': 1}|0.080888"
    "6"	"{u'2': 1, u'5': 1}|0.039088"
    "7"	"{u'2': 1, u'5': 1}|0.01617"
    "8"	"{u'2': 1, u'5': 1}|0.01617"
    "9"	"{u'2': 1, u'5': 1}|0.01617"
    "1"	"{}|0.032782"
    "10"	"{u'5': 1}|0.01617"
    "11"	"{u'5': 1}|0.01617"
    "2"	"{u'3': 1}|0.383603"
    "3"	"{u'2': 1}|0.34372"
    "4"	"{u'1': 1, u'2': 1}|0.039088"

### Analysis

The MRJob implmentation of the iterative PageRank algorithm, with teleportation factor and distribution of the dangling nodes mass, when executed on the Wikipedia article example dataset for predefined number of iterations, produces similar PageRank probabilities.

## Section 2: Exploring PageRank teleportation and network plots

* In order to overcome  problems such as disconnected components, the damping factor (a typical value for d is 0.85) can be varied.
* Using the graph in above, plot the test graph (using networkx, https://networkx.github.io/) for several values of the damping parameter alpha, so that each nodes radius is proportional to its PageRank score.
* In particular I would do this for the following damping factors: [0,0.25,0.5,0.75, 0.85, 1].
* Note that the plots should look like the following: https://en.wikipedia.org/wiki/PageRank#/media/File:PageRanks-Example.svg

###  Implementation

```python
%%writefile explore_pagerank_teleportation_driver.py
####
# Using section 1 implmentation of the iterative PageRank algorithm, run several MRJobs with different
#damping factors: [0,0.25,0.5,0.75, 0.85, 1] and collect resulting pagerank probablities.
####

from PageRank import PageRank
from mrjob.job import MRJob
from mrjob.step import MRStep
import sys
import ast
import subprocess
from shutil import copyfile
import time

SOURCE_GRAPH = './wikipedia_example_updated.txt'
DAMPING_FACTOR = [0, 0.25, 0.5, 0.75, 0.85, 1]
TOTAL_NODES = 11
MAX_ITERATION = 30
MODE = 'local'

for d in DAMPING_FACTOR:
    print 'Started job with damping factor:', d
    start_time = time.time()
    INPUT_GRAPH = './wikipedia_example_input_with_'+str(d)+'.txt'
    copyfile(SOURCE_GRAPH, INPUT_GRAPH)

    mr_job = PageRank(args=[INPUT_GRAPH, '--dampingfactor', str(d), '--numtotalnodes', str(TOTAL_NODES),  '-r', 'local' ])
    num_interation = 0
    while(num_interation<MAX_ITERATION):

        with mr_job.make_runner() as runner:

            num_interation += 1
            #print "Iteration: ", num_interation
            runner.run()

            f = open(INPUT_GRAPH, 'w+')
            for line in runner.stream_output():
                f.writelines(line)

        f.close()

    end_time = time.time()
    print "Time taken = {:.2f} seconds for d = {}".format(end_time - start_time, d)

```

    Writing explore_pagerank_teleportation_driver.py

```python
!python ./explore_pagerank_teleportation_driver.py
```

    Started job with damping factor: 0
    Time taken = 22.91 seconds for d = 0
    Started job with damping factor: 0.25
    Time taken = 22.08 seconds for d = 0.25
    Started job with damping factor: 0.5
    Time taken = 21.49 seconds for d = 0.5
    Started job with damping factor: 0.75
    Time taken = 23.58 seconds for d = 0.75
    Started job with damping factor: 0.85
    Time taken = 22.41 seconds for d = 0.85
    Started job with damping factor: 1
    Time taken = 22.51 seconds for d = 1

```python
##
# Plot network graph for damping factors: [0,0.25,0.5,0.75, 0.85, 1] with each node's radius proportional to its PageRank score.
##
%matplotlib inline
import networkx as nx
import ast
from matplotlib import pyplot as plt

def plot_graph(graph, pagerank, damping, labels=None):
    plt.figure(figsize=(12, 12))

    Directed_Graph = nx.DiGraph()

    for edge in graph:
        Directed_Graph.add_edge(edge[0], edge[1])

    node_size = [pagerank[n]*50000 for n in Directed_Graph.nodes()]

    graph_pos = nx.circular_layout(Directed_Graph)
    #graph_pos=nx.spring_layout(Directed_Graph)

    labels = {}
    node_colors = []

    print "Network graph with damping factor = {}".format(damping)

    for node in Directed_Graph.nodes():
        labels[node] = '{}\n{}'.format(node, str(round(pagerank[node]*100,1))+'%')
        if pagerank[node] <= 0.017:
            node_colors.append('violet')
        if pagerank[node] > 0.017 and pagerank[node] <= 0.038:
            node_colors.append('blue')
        if pagerank[node] > 0.038 and pagerank[node] <= 0.049:
            node_colors.append('green')
        if pagerank[node] > 0.05 and pagerank[node] <= 0.1:
            node_colors.append('yellow')
        if pagerank[node] > 0.3 and pagerank[node] <= 0.35:
            node_colors.append('orange')
        elif pagerank[node] > 0.1:
            node_colors.append('red')

    nx.draw_networkx_nodes(Directed_Graph, graph_pos, node_size = node_size, alpha = 0.35, node_color = node_colors)
    nx.draw_networkx_edges(Directed_Graph, graph_pos, width = 0.35, alpha = 1, edge_color = 'black')
    nx.draw_networkx_labels(Directed_Graph, graph_pos, labels=labels, font_size = 12)

    plt.axis('off')
    plt.tight_layout()
    plt.show()

DAMPING_FACTOR = [0, 0.25, 0.5, 0.75, 0.85, 1]
graph = []
pagerank = {}
node_dict = {1:'A', 2:'B', 3:'C', 4:'D', 5:'E', 6:'F', 7:'G', 8:'H', 9:'I', 10:'J', 11:'K' }

for damping in DAMPING_FACTOR:
    input_file = "wikipedia_example_input_with_"+str(damping)+".txt"
    with open (input_file, "r") as f:
        for line in f.readlines():
            line  = line.strip()
            node, pagerank_out_links = line.split('\t')
            node = int(node.strip('"'))
            out_links, pr = map(ast.literal_eval, pagerank_out_links.strip('"').split('|'))
            graph.extend([(node_dict[int(node)], node_dict[int(n) ] ) for n in out_links.keys()])
            pagerank[node_dict[int(node)]] = pr
        plot_graph(graph, pagerank, damping)

```

### Analysis

Using the above implmentation of the iterative PageRank algorithm, with teleportation factor and distribution of the dangling nodes mass, several MRJobs were executed on damping factors: [0,0.25,0.5,0.75, 0.85, 1] and resulted pagerank probablities are plotted with each nodes radius proportional to its PageRank score.

Plots look similar to https://en.wikipedia.org/wiki/PageRank#/media/File:PageRanks-Example.svg.

## Section 3: Applying PageRank to the Wikipedia hyperlinks network

* Run the PageRank implementation on the Wikipedia dataset for 5 iterations, and display the top 100 ranked nodes (with alpha = 0.85).
* Run the PageRank implementation on the Wikipedia dataset for 10 iterations, and display the top 100 ranked nodes (with teleportation factor of 0.15).
* Have the top 100 ranked pages changed? Comment on the findings.
* Plot the pagerank values for the top 100 pages resulting from the 5 iterations run. Then plot the pagerank values for the same 100 pages that resulted from the 10 iterations run.

### Section 3 Implementation

###  Added code to assign intial pagerank using map reduce

```python
%%writefile WikiInputFile.py

from mrjob.job import MRJob
from mrjob.step import MRStep
import ast
import sys

class WikiInputFile(MRJob):
    def steps(self):
        return [MRStep(
                       mapper=self.mapper
                       )
                ]

    def configure_options(self):
        super(WikiInputFile, self).configure_options()
        self.add_passthrough_option('--numtotalnodes', default=None, type=str, help='total number of nodes in the graph')

    def mapper(self, _, line):
        node, adj_list = line.split('\t')
        adj_list  = adj_list + '|' + str(1.0/int(self.options.numtotalnodes))
        yield node, adj_list

if __name__ == '__main__':
    WikiInputFile.run()

```

    Overwriting WikiInputFile.py

```python
%%writefile WikiInputFile_driver.py

from WikiInputFile import WikiInputFile
from mrjob.job import MRJob
from mrjob.step import MRStep
import sys
import ast
import subprocess

INPUT_GRAPH = sys.argv[1]
TOTAL_NODES = sys.argv[2]

mr_job = WikiInputFile(args=[INPUT_GRAPH,  '--numtotalnodes', TOTAL_NODES,  '-r', 'local' ])
with mr_job.make_runner() as runner:
    runner.run()
    f = open(INPUT_GRAPH, 'w+')
    for line in runner.stream_output():
        #print line
        f.writelines(line)

f.close()

```

    Overwriting WikiInputFile_driver.py

```python
#!hdfs dfs -cat hdfs:///tmp/hw9/wiki_out.txt | head
!hdfs dfs -cat hdfs:/tmp/hw7/wiki_out.txt | wc -l
```

    5781290

```python
!rm ./wiki_out_input_five.txt
!hdfs dfs -copyToLocal hdfs:/tmp/hw7/wiki_out.txt ./wiki_out_input_five.txt
!python ./WikiInputFile_driver.py ./wiki_out_input_five.txt 5781290
```

```python
!cat ./wiki_out_input_five.txt | head
```

    "13876063"	"{'8037968': 1, '4582331': 2, '5011905': 1, '5118655': 1, '4730166': 2, '2138559': 1, '1895648': 4, '13841238': 1, '4270538': 1, '10969310': 1, '13704868': 1, '13841200': 1, '10970804': 4, '8039196': 1, '3205568': 2}|1.72971776195e-07"
    "13876441"	"{'2437837': 1, '12906477': 1, '3975017': 1, '2964011': 1, '14870214': 1, '28113': 1, '13876253': 1, '7430574': 1, '1477521': 1, '834022': 1, '862916': 1, '7967978': 1, '6150551': 1, '14594286': 1, '14767686': 1, '14787537': 1, '11409216': 1, '14867267': 1, '14704369': 1, '10403495': 1, '15180113': 1, '4537912': 1, '14634719': 1, '9386580': 1, '14768176': 1, '9269949': 1, '4501915': 1, '13450173': 1, '13686273': 1, '8019937': 1, '13620040': 1, '2598156': 1, '15024128': 1, '5981154': 1, '14637725': 1, '13455888': 1, '6126757': 2, '810917': 1, '10739568': 1, '8178977': 1, '15088153': 1, '15137458': 1, '13876366': 1, '15077614': 1, '10333866': 1, '14838238': 1, '15131676': 2, '3385863': 1, '3808015': 2, '2618628': 1, '15161714': 1, '14751635': 1, '2368971': 1, '14859183': 1, '9518594': 1, '14504966': 1, '6536965': 1, '9885640': 1, '10609871': 1, '15029814': 1, '14685101': 1, '15149496': 1, '14749647': 1, '14494673': 1, '14900344': 1, '4448873': 1, '15044301': 2, '15179225': 1, '14551634': 1, '10011744': 1, '14805663': 1, '10232589': 1, '13169790': 2, '12057876': 1, '12952914': 1, '12906458': 1, '4783981': 1, '5982249': 1, '13506834': 1, '11845948': 1, '13922921': 1, '14641208': 1, '14757038': 2, '5572514': 1, '15165886': 1, '13876656': 1, '9281447': 1, '12300847': 1}|1.72971776195e-07"
    "13876564"	"{'13807607': 2, '661952': 2, '4196067': 1, '9659868': 1, '8669516': 1, '12116945': 1, '1399640': 1, '8692179': 1, '8669518': 1, '11913838': 1}|1.72971776195e-07"
    "13876685"	"{'14547566': 1, '5280792': 1, '15052829': 2, '12381075': 1, '12938027': 1, '15098863': 1, '10027008': 1, '8058770': 1, '3945111': 2, '15149519': 1, '12120948': 1, '12938025': 1, '12998066': 1, '14651818': 1, '14320307': 1}|1.72971776195e-07"
    "13876854"	"{'13455888': 1, '15151734': 1, '5252086': 1}|1.72971776195e-07"
    "13877228"	"{'6277256': 3, '3088697': 1, '13776970': 1, '13776953': 1, '13776975': 1, '2336615': 1, '14494996': 1, '13776982': 1, '8756748': 1, '13776945': 1, '13776981': 1, '6277265': 1, '6438609': 1, '13776951': 1, '5123366': 1, '8758030': 1, '14917958': 1, '13877642': 1, '13776972': 1, '12372822': 1, '14055009': 2, '6438294': 1}|1.72971776195e-07"
    "13877255"	"{'8168009': 2, '6148038': 1, '15174760': 1, '11938038': 1, '707745': 1, '5195418': 1, '6148061': 1, '8757448': 2, '14745041': 2, '14246033': 1, '3304909': 1, '13877662': 1, '5195405': 1, '11597335': 2, '7946950': 1, '13877054': 1, '14281138': 1, '8857570': 1, '8758030': 7, '9050641': 1, '9050634': 1, '1070778': 1, '9050690': 4, '7645886': 2, '11613815': 1, '9750926': 1, '5262712': 1, '10712900': 1}|1.72971776195e-07"
    "13877424"	"{'13877423': 1}|1.72971776195e-07"
    "13877475"	"{'13877476': 1}|1.72971776195e-07"
    "13877832"	"{'14772686': 1, '8757112': 2, '11320167': 1, '13877821': 2, '10553430': 1, '4431400': 2, '14055853': 2, '5195982': 1, '14922939': 1, '14773080': 2, '14655338': 1, '3866642': 2, '9620370': 2, '9353381': 1, '5195507': 1, '1156262': 2, '1040205': 2, '9579009': 1, '4555715': 1}|1.72971776195e-07"
    cat: write error: Broken pipe

```python
!python ./PageRank_driver.py ./wiki_out_input_five.txt 0.85 5781290 5 'hadoop'
```

    Iteration:  1
    rm: `/tmp/msannat/temp-output/': No such file or directory
    Iteration:  2
    17/03/24 03:01:15 INFO fs.TrashPolicyDefault: Namenode trash configuration: Deletion interval = 5760 minutes, Emptier interval = 360 minutes.
    Moved: 'hdfs://nn-ia.s3s.altiscale.com:8020/tmp/msannat/temp-output' to trash at: hdfs://nn-ia.s3s.altiscale.com:8020/user/manishsannat/.Trash/Current
    Iteration:  3
    17/03/24 04:13:06 INFO fs.TrashPolicyDefault: Namenode trash configuration: Deletion interval = 5760 minutes, Emptier interval = 360 minutes.
    Moved: 'hdfs://nn-ia.s3s.altiscale.com:8020/tmp/msannat/temp-output' to trash at: hdfs://nn-ia.s3s.altiscale.com:8020/user/manishsannat/.Trash/Current
    Iteration:  4
    17/03/24 05:27:17 INFO fs.TrashPolicyDefault: Namenode trash configuration: Deletion interval = 5760 minutes, Emptier interval = 360 minutes.
    Moved: 'hdfs://nn-ia.s3s.altiscale.com:8020/tmp/msannat/temp-output' to trash at: hdfs://nn-ia.s3s.altiscale.com:8020/user/manishsannat/.Trash/Current
    Iteration:  5
    17/03/24 06:24:23 INFO fs.TrashPolicyDefault: Namenode trash configuration: Deletion interval = 5760 minutes, Emptier interval = 360 minutes.
    Moved: 'hdfs://nn-ia.s3s.altiscale.com:8020/tmp/msannat/temp-output' to trash at: hdfs://nn-ia.s3s.altiscale.com:8020/user/manishsannat/.Trash/Current

```python
# python code to include initial page ranks
# if time permits this can be done using a map reduce job
num_lines = sum(1 for line in open('wiki_out.txt'))
with open("wiki_out.txt","r") as f:
    with open("wiki_out_input_ten.txt","w+")  as w:
        for line in f.readlines():
            line  = line.strip('\n') + '|' + str(1.0/num_lines) +'\n'
            w.write(line)
```

```python
!python ./PageRank_driver.py ./wiki_out_input_ten.txt 0.85 5781290 10 'hadoop'
```

    Iteration:  1
    17/03/24 00:58:49 INFO fs.TrashPolicyDefault: Namenode trash configuration: Deletion interval = 5760 minutes, Emptier interval = 360 minutes.
    Moved: 'hdfs://nn-ia.s3s.altiscale.com:8020/tmp/msannat/temp-output' to trash at: hdfs://nn-ia.s3s.altiscale.com:8020/user/manishsannat/.Trash/Current

```python
%%writefile TopPageRank.py

from mrjob.job import MRJob
from mrjob.step import MRStep
import itertools
from mrjob.protocol import RawProtocol
import re
import math
import ast

class TopPageRank(MRJob):

    MRJob.SORT_VALUES = True

    def mapper(self, _, line):

        node, pageranke_out_links = line.split("\t")
        node =  node.strip('"').strip("'").strip("\"")
        node = re.sub(r'^"|"$', '', node)
        pageranke_out_links = pageranke_out_links.replace('"', '')
        out_links, page_rank =  pageranke_out_links.strip("\"").split('|')
        page_rank =  page_rank.replace('"', '')
        page_rank = ast.literal_eval(page_rank)

        #out_links, page_rank = map(ast.literal_eval, pageranke_out_links.strip("\"").strip('"').strip("'").split('|'))
        yield node, float(page_rank)

    def reducer(self, node, pageranks):
        pagerank = sum(i for i in pageranks)
        yield node, pagerank

    def steps(self):

        JOBCONF_STEP = {
            'mapreduce.job.output.key.comparator.class': 'org.apache.hadoop.mapred.lib.KeyFieldBasedComparator',
            #'mapreduce.text.key.comparator.options': '-k2,2nr -k1,1',
            'mapred.text.key.comparator.options': '-k2,2nr -k1,1',
            'mapreduce.job.reduces': 1
        }

        return [
              MRStep(jobconf=JOBCONF_STEP,
                    mapper=self.mapper
                    ,reducer=self.reducer)
              ]

if __name__ == '__main__':
    TopPageRank.run()

```

```python
# RUNNER
from TopPageRank import TopPageRank
import subprocess
results = {}
mr_job = TopPageRank(args=['-r', 'hadoop', 'hdfs:///tmp/msannat/wiki_out_input_five.txt','--no-output', '--cleanup', 'NONE', '--output-dir',  '/tmp/msannat/temp-output-rank' ])

with mr_job.make_runner() as runner:
    counter = 0
    subprocess.Popen(["hadoop", "fs", "-rm", "-r", "/tmp/msannat/temp-output-rank/"], stdout=None)
    runner.run()
    cat = subprocess.Popen(["hadoop", "fs", "-cat", "/tmp/msannat/temp-output-rank/part-*"], stdout=subprocess.PIPE)
    f = open('./wiki_out_input_five_top_page_rank.txt', 'w+')
    for line in cat.stdout:
        key, value = mr_job.parse_output_line(line)
        #if counter<1000:
        f.writelines(line)
        #counter+=1
    f.close()

#END RUNNER
```

```python
#RUNNER
from TopPageRank import TopPageRank
import subprocess
results = {}
mr_job = TopPageRank(args=['-r', 'hadoop', './wiki_out_input_ten_iteration.txt','--no-output', '--cleanup', 'NONE', '--output-dir',  '/tmp/msannat/temp-output-rank' ])

with mr_job.make_runner() as runner:
    counter = 0
    subprocess.Popen(["hadoop", "fs", "-rm", "-r", "/tmp/msannat/temp-output-rank/"], stdout=None)
    runner.run()
    cat = subprocess.Popen(["hadoop", "fs", "-cat", "/tmp/msannat/temp-output-rank/part-*"], stdout=subprocess.PIPE)
    f = open('./wiki_out_input_ten_top_page_rank.txt', 'w+')
    for line in cat.stdout:
        key, value = mr_job.parse_output_line(line)
        if counter<1000:
            f.writelines(line)
            counter+=1
    f.close()

#END RUNNER
```

```python
import subprocess
from collections import defaultdict
from collections import OrderedDict
def ind_lookup(path):
    node_words = {}
    cat = subprocess.Popen(["hadoop", "fs", "-cat", "hdfs:/tmp/hw7/wiki_indices.txt"], stdout=subprocess.PIPE)
    for line in cat.stdout:
        word,index,temp1,temp2 = line.strip().split("\t")
        if int(index) in path:
            node_words[index] = word
    return node_words

input_file = './wiki_out_input_five_top_page_rank.txt'
global node_pr
global path

path= []
node_pr = {}
node_articles = {}
with open (input_file, "r") as f:
    for line in f.readlines():
        #print line
        line  = line.strip()
        pagerank, node  = line.split('\t')
        pagerank = pagerank.replace('"', '')
        pagerank = float(pagerank)
        node = node.replace('"', '')
        node = int(node.strip("'").strip('"'))

        path.append(node)
        node_pr[node] = pagerank
#print type(path)

node_articles =  ind_lookup(path)

print '*******************************************************************'
print 'Top 100 ranked nodes (with alpha = 0.85) for 5 iterations'
print '*******************************************************************'
print 'Node\t\tPageRank\t\tArticle'
for node in path:
    print('%d \t%f \t%s ' % (node, node_pr[node], node_articles[str(node)]))
print '*******************************************************************'

```

    *******************************************************************
    Top 100 ranked nodes (with alpha = 0.85) for 5 iterations
    *******************************************************************
    Node		PageRank		Article
    13455888 	0.000022 	United States
    12836211 	0.000015 	The New York Times
    6172466 	0.000010 	Iran
    13425865 	0.000009 	United Kingdom
    4198751 	0.000009 	English language
    7467127 	0.000008 	Latin
    2797855 	0.000008 	China
    2578813 	0.000007 	Catholic Church
    14112583 	0.000007 	World War II
    1300106 	0.000007 	Arabic
    9386580 	0.000007 	New York City
    13853369 	0.000006 	Washington, D.C.
    13432150 	0.000006 	United States Census Bureau
    11253108 	0.000006 	Russia
    12067030 	0.000006 	Soviet Union
    4695850 	0.000006 	France
    5051368 	0.000005 	Germany
    14112408 	0.000005 	World War I
    13429704 	0.000005 	United Nations
    2437837 	0.000005 	Canada
    4350909 	0.000005 	European Union
    4344962 	0.000005 	Europe
    12905382 	0.000005 	The Washington Post
    11197411 	0.000005 	Rovi
    10390714 	0.000005 	Poland
    6076759 	0.000005 	India
    6525240 	0.000004 	Jews
    6237129 	0.000004 	Italy
    2252643 	0.000004 	Buddhism
    1575979 	0.000004 	BBC
    13280859 	0.000004 	Turkey
    7990491 	0.000004 	London
    4502744 	0.000004 	Federal Information Processing Standards
    4783981 	0.000004 	French language
    4624519 	0.000004 	Flowering plant
    9861483 	0.000004 	Oxford University Press
    9391762 	0.000004 	New York
    915245 	0.000004 	Alexa Internet
    11449900 	0.000004 	San Francisco
    10495214 	0.000004 	President of the United States
    4185021 	0.000004 	Encyclopædia Britannica
    1300461 	0.000004 	Arabs
    12530354 	0.000004 	Taiwan
    12860433 	0.000004 	The Renaissance
    13031052 	0.000004 	Time (magazine)
    12430985 	0.000003 	Sweden
    12395575 	0.000003 	Supreme Court of the United States
    12077147 	0.000003 	Spanish language
    12074312 	0.000003 	Spain
    12785678 	0.000003 	The Guardian
    12447593 	0.000003 	Switzerland
    5045896 	0.000003 	German language
    4978429 	0.000003 	Geographic Names Information System
    4739291 	0.000003 	Franklin D. Roosevelt
    1575228 	0.000003 	BBC News
    1516699 	0.000003 	Australia
    1478749 	0.000003 	Atlantic Ocean
    1440095 	0.000003 	Associated Press
    13450173 	0.000003 	United States dollar
    13449048 	0.000003 	United States Senate
    13432523 	0.000003 	United States Congress
    13312776 	0.000003 	Tz database
    662282 	0.000003 	Academy Awards
    6222591 	0.000003 	Israel
    6206562 	0.000003 	Islam
    6147866 	0.000003 	Internet Assigned Numbers Authority
    724519 	0.000003 	Adolf Hitler
    2853994 	0.000003 	Christianity
    2778099 	0.000003 	Chicago
    2396749 	0.000003 	California
    2155467 	0.000003 	Brazil
    1813634 	0.000003 	Belgium
    1686442 	0.000003 	Barack Obama
    8019937 	0.000003 	Los Angeles
    8019717 	0.000003 	Los Angeles Times
    7902219 	0.000003 	List of sovereign states
    4196067 	0.000003 	England
    3138976 	0.000003 	Coordinated Universal Time
    9302154 	0.000003 	Nazi Germany
    9269949 	0.000003 	National Oceanic and Atmospheric Administration
    9174135 	0.000003 	NPR
    9132002 	0.000003 	Myanmar
    8697871 	0.000003 	Mexico
    9997298 	0.000003 	Paris
    9908632 	0.000003 	Pacific Ocean
    9877983 	0.000003 	PBS
    9838325 	0.000003 	Ottoman Empire
    981395 	0.000003 	AllMusic
    975413 	0.000003 	All Media Network
    9534683 	0.000003 	North America
    9355455 	0.000003 	Netherlands
    10345830 	0.000003 	Plant
    10232589 	0.000003 	Philadelphia
    11143007 	0.000003 	Roman Empire
    1105602 	0.000003 	Anatolia
    10440929 	0.000003 	Portable Document Format
    1658241 	0.000002 	Baltimore
    1523975 	0.000002 	Austria
    15127872 	0.000002 	time zone
    14900494 	0.000002 	newspaper
    *******************************************************************

### Section 3 Analysis

The altiscale job for 10 iterations couldn't finish with repeated interruptins (mostly loss of innternet connection)  but comparing output from different iterations it seems that top 100 ranked nodes remained same with some change in their ranks after each iteration.

## Section 4: Topic-specific PageRank implementation using MRJob

Modify the PageRank implementation to produce a topic specific PageRank implementation, as described in:

http://www-cs-students.stanford.edu/~taherh/papers/topic-sensitive-pagerank.pdf

Note in this article that there is a special caveat to ensure that the transition matrix is irreducible.
This caveat lies in footnote 3 on page 3:
```
	A minor caveat: to ensure that M is irreducible when p
	contains any 0 entries, nodes not reachable from nonzero
	nodes in p should be removed. In practice this is not problematic.
```
and must be adhered to for convergence to be guaranteed.

Run topic specific PageRank on the following randomly generated network of 100 nodes:

> s3://ucb-mids-mls-networks/randNet.txt (also available on Dropbox)
> `wget http://ucb-mids-mls-networks.s3.amazonaws.com/randNet.txt`

which are organized into ten topics, as described in the file:

> s3://ucb-mids-mls-networks/randNet_topics.txt  (also available on Dropbox)
> `wget http://ucb-mids-mls-networks.s3.amazonaws.com/randNet_topics.txt`

Since there are 10 topics, the result should be 11 PageRank vectors (one for the vanilla PageRank implementation in 9.1, and one for each topic with the topic specific implementation). Print out the top ten ranking nodes and their topics for each of the 11 versions, and comment on the result. Assume a teleportation factor of 0.15 in all the analyses.

One final and important comment here:  please consider the requirements for irreducibility with topic-specific PageRank. In particular, the literature ensures irreducibility by requiring that nodes not reachable from in-topic nodes be removed from the network.

This is not a small task, especially as it it must be performed separately for each of the (10) topics.

So, instead of using this method for irreducibility, please comment on why the literature's method is difficult to implement, and what what extra computation it will require.

Then for code, please use the alternative, non-uniform damping vector:

```
vji = beta*(1/|Tj|); if node i lies in topic Tj

vji = (1-beta)*(1/(N - |Tj|)); if node i lies outside of topic Tj
```
for beta in (0,1) close to 1.

With this approach, I will not have to delete any nodes. If beta > 0.5, PageRank is topic-sensitive, and if beta < 0.5, the PageRank is anti-topic-sensitive. For any value of beta irreducibility should hold, so please try beta=0.99, and perhaps some other values locally, on the smaller networks.

### Section 4 Implementation

###  Added code to assign intial pagerank using map reduce

```python
%%writefile WikiInputFile94.py

from mrjob.job import MRJob
from mrjob.step import MRStep
import ast
import sys

class WikiInputFile94(MRJob):
    def steps(self):
        return [MRStep( mapper=self.mapper )
                ]

    def configure_options(self):
        super(WikiInputFile94, self).configure_options()
        self.add_passthrough_option('--numtotalnodes', default=None, type=str, help='total number of nodes in the graph')
        self.add_passthrough_option('--numtotaltopics', default=None, type=str, help='total number of nodes in the graph')

    def mapper(self, _, line):
        node, adj_list = line.split('\t')
        adj_list  = adj_list + '|' + str([1.0/int(self.options.numtotalnodes)] * (int(self.options.numtotaltopics) + 1))
        yield node, adj_list

if __name__ == '__main__':
    WikiInputFile94.run()

```

    Overwriting WikiInputFile94.py

```python
%%writefile WikiInputFile94_driver.py

from WikiInputFile94 import WikiInputFile94
from mrjob.job import MRJob
from mrjob.step import MRStep
import sys
import ast
import subprocess

INPUT_GRAPH = sys.argv[1]
TOTAL_NODES = sys.argv[2]
TOTAL_TOPICS = sys.argv[3]

mr_job = WikiInputFile94(args=[INPUT_GRAPH,  '--numtotalnodes', TOTAL_NODES, '--numtotaltopics', TOTAL_TOPICS,  '-r', 'local' ])
with mr_job.make_runner() as runner:
    runner.run()
    f = open(INPUT_GRAPH, 'w+')
    for line in runner.stream_output():
        #print line
        f.writelines(line)

f.close()

```

    Overwriting WikiInputFile94_driver.py

```python
!cat ./randNet_topics_updated.txt | wc -l
```

    100

```python
!rm ./randNet_topics_updated_topics.txt.txt
!cp ./randNet_topics_updated.txt ./randNet_topics_updated_topics.txt.txt
!python ./WikiInputFile94_driver.py ./randNet_topics_updated_topics.txt 100 10
```

    rm: cannot remove `./randNet_topics_updated_topics.txt.txt': No such file or directory

```python
!cat ./randNet_topics_updated_topics.txt
```

    "55"	"{u'77': 1, u'12': 1, u'32': 1, u'34': 1, u'1': 1, u'3': 1, u'47': 1, u'84': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "56"	"{u'27': 1, u'15': 1, u'46': 1, u'32': 1, u'31': 1, u'52': 1, u'68': 1, u'84': 1}|6|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "57"	"{u'43': 1, u'52': 1, u'61': 1, u'63': 1, u'80': 1, u'78': 1}|9|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "58"	"{u'21': 1, u'33': 1, u'56': 1, u'28': 1, u'43': 1, u'52': 1, u'2': 1, u'6': 1, u'83': 1, u'94': 1, u'74': 1}|2|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "59"	"{u'25': 1, u'17': 1, u'23': 1, u'55': 1, u'44': 1, u'50': 1, u'40': 1, u'52': 1, u'73': 1, u'35': 1, u'70': 1, u'69': 1, u'93': 1}|2|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "6"	"{u'21': 1, u'19': 1, u'30': 1, u'76': 1, u'2': 1, u'100': 1, u'81': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "60"	"{u'14': 1, u'17': 1, u'76': 1, u'29': 1, u'1': 1, u'98': 1, u'62': 1, u'63': 1, u'6': 1, u'94': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "61"	"{u'25': 1, u'14': 1, u'35': 1, u'63': 1, u'72': 1, u'9': 1, u'86': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "62"	"{u'11': 1, u'14': 1, u'16': 1, u'28': 1, u'43': 1, u'52': 1, u'99': 1, u'89': 1, u'72': 1, u'67': 1, u'100': 1, u'81': 1, u'95': 1, u'97': 1, u'96': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "63"	"{u'11': 1, u'25': 1, u'27': 1, u'21': 1, u'17': 1, u'54': 1, u'44': 1, u'28': 1, u'36': 1, u'37': 1, u'7': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "64"	"{u'26': 1, u'3': 1, u'54': 1, u'42': 1, u'62': 1, u'41': 1, u'60': 1, u'98': 1, u'75': 1, u'67': 1, u'82': 1, u'100': 1, u'87': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "65"	"{u'39': 1, u'45': 1, u'17': 1, u'19': 1, u'56': 1, u'76': 1, u'7': 1, u'92': 1, u'86': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "66"	"{u'28': 1, u'42': 1, u'77': 1, u'74': 1, u'68': 1, u'100': 1, u'79': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "67"	"{u'59': 1, u'48': 1, u'56': 1, u'51': 1, u'61': 1, u'5': 1, u'71': 1, u'6': 1, u'94': 1, u'96': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "68"	"{u'24': 1, u'49': 1, u'28': 1, u'34': 1, u'75': 1, u'90': 1, u'92': 1, u'97': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "69"	"{u'49': 1, u'32': 1, u'53': 1, u'60': 1, u'73': 1, u'82': 1, u'64': 1, u'80': 1}|6|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "7"	"{u'12': 1, u'16': 1, u'44': 1, u'53': 1, u'98': 1, u'63': 1, u'8': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "70"	"{u'10': 1, u'13': 1, u'15': 1, u'23': 1, u'46': 1, u'88': 1, u'62': 1, u'73': 1, u'66': 1, u'85': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "71"	"{u'11': 1, u'13': 1, u'38': 1, u'32': 1, u'62': 1, u'1': 1, u'3': 1, u'63': 1, u'64': 1, u'65': 1, u'81': 1, u'4': 1}|2|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "72"	"{u'59': 1, u'45': 1, u'53': 1, u'1': 1, u'88': 1, u'78': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "73"	"{u'17': 1, u'52': 1, u'77': 1, u'62': 1, u'63': 1, u'4': 1, u'90': 1}|2|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "74"	"{u'10': 1, u'20': 1, u'33': 1, u'57': 1, u'51': 1, u'40': 1, u'41': 1, u'99': 1, u'61': 1, u'35': 1, u'15': 1, u'100': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "75"	"{u'39': 1, u'12': 1, u'22': 1, u'17': 1, u'45': 1, u'91': 1, u'89': 1, u'82': 1, u'83': 1}|2|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "76"	"{u'12': 1, u'15': 1, u'14': 1, u'48': 1, u'51': 1, u'45': 1, u'42': 1, u'98': 1, u'9': 1, u'92': 1, u'97': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "77"	"{u'11': 1, u'25': 1, u'39': 1, u'19': 1, u'54': 1, u'36': 1, u'41': 1, u'91': 1, u'71': 1, u'70': 1, u'9': 1, u'80': 1, u'85': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "78"	"{u'54': 1, u'44': 1, u'100': 1, u'25': 1, u'69': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "79"	"{u'26': 1, u'30': 1, u'45': 1, u'29': 1, u'40': 1, u'36': 1, u'60': 1, u'98': 1, u'88': 1, u'63': 1, u'2': 1, u'81': 1, u'74': 1, u'85': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "8"	"{u'27': 1, u'100': 1, u'56': 1, u'99': 1, u'64': 1, u'30': 1, u'9': 1, u'90': 1, u'93': 1, u'92': 1, u'86': 1, u'87': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "80"	"{u'91': 1, u'8': 1, u'3': 1, u'58': 1, u'50': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "81"	"{u'58': 1, u'43': 1, u'41': 1, u'98': 1, u'94': 1, u'21': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "82"	"{u'13': 1, u'42': 1, u'40': 1, u'76': 1, u'89': 1, u'69': 1, u'93': 1, u'95': 1, u'100': 1}|2|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "83"	"{u'58': 1, u'46': 1, u'32': 1, u'44': 1, u'52': 1, u'63': 1, u'4': 1, u'68': 1, u'80': 1, u'95': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "84"	"{u'10': 1, u'16': 1, u'99': 1, u'88': 1, u'65': 1, u'71': 1, u'85': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "85"	"{u'10': 1, u'38': 1, u'58': 1, u'32': 1, u'31': 1, u'56': 1, u'37': 1, u'53': 1, u'2': 1, u'7': 1, u'70': 1, u'96': 1, u'86': 1, u'78': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "86"	"{u'9': 1, u'90': 1, u'46': 1, u'84': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "87"	"{u'13': 1, u'58': 1, u'49': 1, u'47': 1, u'53': 1, u'75': 1, u'7': 1, u'91': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "88"	"{u'24': 1, u'15': 1, u'71': 1, u'32': 1, u'87': 1, u'66': 1, u'83': 1, u'94': 1, u'97': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "89"	"{u'26': 1, u'45': 1, u'9': 1, u'88': 1, u'2': 1, u'73': 1, u'4': 1, u'68': 1, u'83': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "9"	"{u'25': 1, u'14': 1, u'23': 1, u'55': 1, u'43': 1, u'40': 1, u'62': 1, u'66': 1, u'70': 1, u'80': 1}|2|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "90"	"{u'10': 1, u'38': 1, u'58': 1, u'47': 1, u'70': 1, u'51': 1, u'53': 1, u'41': 1, u'60': 1, u'61': 1, u'5': 1, u'99': 1, u'67': 1, u'69': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "91"	"{u'77': 1, u'73': 1, u'30': 1, u'86': 1, u'72': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "92"	"{u'38': 1, u'20': 1, u'22': 1, u'55': 1, u'47': 1, u'28': 1, u'41': 1, u'18': 1, u'91': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "93"	"{u'38': 1, u'19': 1, u'36': 1, u'53': 1, u'60': 1, u'98': 1, u'88': 1, u'90': 1, u'83': 1, u'92': 1, u'86': 1, u'94': 1, u'85': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "94"	"{u'49': 1, u'77': 1, u'61': 1, u'88': 1, u'66': 1, u'6': 1, u'79': 1}|9|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "95"	"{u'59': 1, u'13': 1, u'56': 1, u'15': 1, u'29': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "96"	"{u'45': 1, u'43': 1, u'75': 1, u'73': 1, u'72': 1, u'93': 1, u'92': 1}|9|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "97"	"{u'48': 1, u'31': 1, u'51': 1, u'34': 1, u'63': 1, u'5': 1, u'4': 1, u'93': 1, u'81': 1, u'74': 1, u'96': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "98"	"{u'24': 1, u'25': 1, u'14': 1, u'49': 1, u'32': 1, u'56': 1, u'51': 1, u'91': 1, u'74': 1, u'6': 1, u'9': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "99"	"{u'58': 1, u'29': 1, u'61': 1, u'2': 1, u'73': 1, u'71': 1, u'90': 1, u'93': 1, u'5': 1, u'97': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "1"	"{u'11': 1, u'27': 1, u'46': 1, u'47': 1, u'35': 1, u'63': 1, u'89': 1, u'5': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "10"	"{u'38': 1, u'49': 1, u'46': 1, u'47': 1, u'28': 1, u'53': 1, u'61': 1, u'89': 1, u'64': 1, u'80': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "100"	"{u'10': 1, u'39': 1, u'48': 1, u'33': 1, u'51': 1, u'53': 1, u'52': 1, u'67': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "11"	"{u'39': 1, u'12': 1, u'21': 1, u'55': 1, u'43': 1, u'46': 1, u'89': 1, u'7': 1, u'84': 1}|6|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "12"	"{u'26': 1, u'59': 1, u'16': 1, u'33': 1, u'29': 1, u'99': 1, u'88': 1, u'2': 1, u'100': 1, u'95': 1}|2|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "13"	"{u'54': 1, u'52': 1, u'77': 1, u'65': 1, u'92': 1, u'85': 1}|6|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "14"	"{u'11': 1, u'34': 1, u'3': 1, u'64': 1, u'65': 1, u'91': 1, u'86': 1}|9|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "15"	"{u'13': 1, u'21': 1, u'49': 1, u'45': 1, u'37': 1, u'99': 1, u'74': 1, u'92': 1, u'87': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "16"	"{u'24': 1, u'10': 1, u'58': 1, u'57': 1, u'65': 1, u'9': 1, u'85': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "17"	"{u'27': 1, u'48': 1, u'35': 1, u'61': 1, u'75': 1, u'71': 1, u'70': 1, u'92': 1, u'78': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "18"	"{u'8': 1, u'57': 1, u'2': 1, u'79': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "19"	"{u'13': 1, u'16': 1, u'31': 1, u'77': 1, u'8': 1, u'44': 1, u'84': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "2"	"{u'26': 1, u'69': 1, u'32': 1, u'67': 1, u'37': 1, u'29': 1, u'5': 1, u'72': 1, u'6': 1, u'8': 1, u'95': 1, u'94': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "20"	"{u'24': 1, u'15': 1, u'48': 1, u'49': 1, u'16': 1, u'75': 1, u'53': 1, u'52': 1, u'62': 1, u'67': 1, u'68': 1, u'59': 1, u'100': 1, u'95': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "21"	"{u'26': 1, u'15': 1, u'48': 1, u'17': 1, u'32': 1, u'31': 1, u'53': 1, u'34': 1, u'67': 1, u'9': 1, u'8': 1, u'97': 1}|9|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "22"	"{u'21': 1, u'45': 1, u'4': 1, u'71': 1, u'86': 1, u'94': 1, u'79': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "23"	"{u'10': 1, u'26': 1, u'15': 1, u'55': 1, u'57': 1, u'35': 1, u'87': 1, u'75': 1, u'74': 1, u'94': 1}|6|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "24"	"{u'14': 1, u'32': 1, u'42': 1, u'50': 1, u'53': 1, u'29': 1, u'74': 1, u'66': 1, u'54': 1, u'83': 1, u'100': 1, u'92': 1, u'94': 1, u'96': 1}|9|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "25"	"{u'95': 1, u'81': 1, u'15': 1, u'17': 1, u'70': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "26"	"{u'11': 1, u'27': 1, u'7': 1, u'42': 1, u'34': 1, u'71': 1, u'6': 1, u'9': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "27"	"{u'12': 1, u'45': 1, u'35': 1, u'1': 1, u'74': 1, u'94': 1, u'85': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "28"	"{u'13': 1, u'58': 1, u'49': 1, u'30': 1, u'42': 1, u'88': 1, u'4': 1, u'95': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "29"	"{u'33': 1, u'83': 1, u'42': 1, u'65': 1, u'70': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "3"	"{u'39': 1, u'22': 1, u'28': 1, u'61': 1, u'73': 1, u'90': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "30"	"{u'10': 1, u'26': 1, u'48': 1, u'47': 1, u'57': 1, u'13': 1, u'36': 1, u'77': 1, u'61': 1, u'72': 1, u'93': 1, u'78': 1, u'50': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "31"	"{u'51': 1, u'26': 1, u'32': 1, u'57': 1, u'37': 1, u'52': 1, u'63': 1, u'64': 1, u'8': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "32"	"{u'39': 1, u'57': 1, u'99': 1, u'74': 1, u'71': 1, u'6': 1, u'84': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "33"	"{u'24': 1, u'25': 1, u'39': 1, u'14': 1, u'55': 1, u'18': 1, u'45': 1, u'98': 1, u'65': 1, u'85': 1, u'78': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "34"	"{u'1': 1, u'45': 1, u'49': 1, u'32': 1, u'31': 1, u'56': 1, u'60': 1, u'99': 1, u'63': 1, u'66': 1, u'100': 1, u'97': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "35"	"{u'25': 1, u'59': 1, u'58': 1, u'49': 1, u'52': 1, u'74': 1, u'90': 1, u'68': 1, u'69': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "36"	"{u'24': 1, u'15': 1, u'22': 1, u'55': 1, u'32': 1, u'28': 1, u'3': 1, u'5': 1, u'72': 1, u'70': 1, u'68': 1, u'92': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "37"	"{u'26': 1, u'12': 1, u'15': 1, u'58': 1, u'55': 1, u'53': 1, u'61': 1, u'75': 1, u'2': 1, u'68': 1, u'9': 1, u'63': 1, u'97': 1, u'85': 1}|6|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "38"	"{u'11': 1, u'15': 1, u'63': 1, u'18': 1, u'88': 1, u'2': 1, u'71': 1, u'91': 1, u'8': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "39"	"{u'17': 1, u'55': 1, u'50': 1, u'77': 1, u'62': 1, u'65': 1, u'68': 1, u'69': 1, u'100': 1, u'87': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "4"	"{u'15': 1, u'16': 1, u'37': 1, u'88': 1, u'35': 1, u'75': 1, u'74': 1, u'73': 1, u'83': 1, u'100': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "40"	"{u'58': 1, u'56': 1, u'61': 1, u'88': 1, u'4': 1, u'90': 1, u'92': 1, u'97': 1}|3|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "41"	"{u'39': 1, u'12': 1, u'15': 1, u'18': 1, u'56': 1, u'37': 1, u'71': 1, u'83': 1}|4|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "42"	"{u'10': 1, u'25': 1, u'30': 1, u'51': 1, u'75': 1, u'35': 1, u'88': 1, u'63': 1, u'84': 1, u'95': 1, u'97': 1}|9|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "43"	"{u'27': 1, u'55': 1, u'31': 1, u'62': 1, u'74': 1, u'91': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "44"	"{u'85': 1, u'51': 1, u'40': 1, u'41': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "45"	"{u'13': 1, u'27': 1, u'15': 1, u'17': 1, u'49': 1, u'19': 1, u'54': 1, u'44': 1, u'30': 1, u'37': 1, u'50': 1, u'18': 1, u'76': 1, u'6': 1, u'68': 1, u'59': 1, u'93': 1, u'95': 1, u'31': 1, u'79': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "46"	"{u'13': 1, u'27': 1, u'14': 1, u'55': 1, u'70': 1, u'9': 1, u'100': 1, u'78': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "47"	"{u'20': 1, u'17': 1, u'40': 1, u'77': 1, u'66': 1, u'9': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "48"	"{u'13': 1, u'12': 1, u'71': 1, u'61': 1, u'18': 1, u'57': 1, u'76': 1, u'89': 1, u'7': 1, u'87': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "49"	"{u'39': 1, u'44': 1, u'30': 1, u'50': 1, u'94': 1, u'98': 1, u'63': 1, u'70': 1, u'67': 1, u'9': 1, u'90': 1, u'92': 1, u'31': 1, u'85': 1}|10|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "5"	"{u'38': 1, u'54': 1, u'29': 1, u'35': 1, u'41': 1, u'62': 1, u'73': 1, u'83': 1, u'100': 1, u'95': 1, u'84': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "50"	"{u'11': 1, u'60': 1, u'27': 1, u'21': 1, u'17': 1, u'28': 1, u'51': 1, u'29': 1, u'1': 1, u'61': 1, u'89': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "51"	"{u'25': 1, u'14': 1, u'23': 1, u'46': 1, u'42': 1, u'36': 1, u'34': 1, u'99': 1, u'8': 1, u'100': 1, u'78': 1}|5|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "52"	"{u'58': 1, u'43': 1, u'41': 1, u'98': 1, u'74': 1, u'80': 1, u'79': 1, u'85': 1}|1|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "53"	"{u'7': 1, u'34': 1, u'3': 1, u'73': 1, u'71': 1, u'67': 1, u'82': 1, u'96': 1, u'87': 1, u'97': 1, u'78': 1}|7|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"
    "54"	"{u'24': 1, u'86': 1, u'16': 1, u'42': 1, u'23': 1, u'28': 1, u'35': 1, u'98': 1, u'67': 1, u'90': 1, u'92': 1, u'95': 1}|8|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]|[0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]"

```python
%%writefile PageRankTopics.py

from mrjob.job import MRJob
from mrjob.step import MRStep
import ast
import sys

class PageRankTopics(MRJob):

    def steps(self):
        JOBCONF_STEP1 = {
            'mapreduce.job.output.key.comparator.class': 'org.apache.hadoop.mapred.lib.KeyFieldBasedComparator',
            #'mapreduce.text.key.comparator.options': '-k2,2nr -k1,1',
            #'mapred.text.key.comparator.options': '-k2,2nr -k1,1',
            'mapreduce.job.reduces': 1
        }

        return [MRStep(jobconf=JOBCONF_STEP1,
                       mapper=self.mapper,
                       reducer=self.reducer),
                MRStep(reducer_init=self.reducer_pagerank_init,
                       reducer=self.reducer_pagerank)
                ]

    def configure_options(self):
        super(PageRankTopics, self).configure_options()
        self.add_passthrough_option('--dampingfactor', default='0.85', type=str, help='damping factor')
        self.add_passthrough_option('--numtotalnodes', default=None, type=str, help='total number of nodes in the graph')
        self.add_passthrough_option('--numtopics', default='10', type=str, help='total number of topics')
        self.add_passthrough_option('--beta', default='0.98', type=str, help='beta factor')
        self.add_file_option('--topic_counts', help='topic counts file')

    def mapper(self, _, line):
        pass

        node = None
        adjacency_list = None
        initial_page_rank = 0.0
        neighbors = 0
        page_rank_list = []

        topic = ""

        if line!= "":
            node, graph_pr_out_links = line.strip().split('\t')
            node =  node.replace('"', '')

            adjacency_list, topic, page_rank_list, = map(ast.literal_eval, graph_pr_out_links.strip("\"").strip('"').strip("'").split('|'))
            neighbors = len(adjacency_list)

            if neighbors> 0:
                page_rank_updated =  [page_rank*1.0/neighbors for page_rank in page_rank_list]
                for key in adjacency_list.keys():
                    yield (key),  (page_rank_updated)

            if neighbors== 0:
                yield '**dangling', (page_rank_list)

            yield (node), (graph_pr_out_links)

    def reducer(self, key, values):
        node = None
        adjacency_list = {}
        total = 0.0
        node =  key.strip("'")
        lost_mass = [0] * (int(self.options.numtopics) + 1)
        updated_page_rank = [0] * (int(self.options.numtopics) + 1)

        if key == '**dangling':
            for value in values:
                for j in range(len(value)):
                    lost_mass[j]+= float(value[j])

            for i in range(1, int(self.options.numtotalnodes) + 1):
                yield str(i), lost_mass

        else:
            for value in values:
                if type(value) == list:
                    for k in range(len(value)):
                        updated_page_rank[k]+= float(value[k])
                else:
                    adjacency_list, topic, page_rank_ori = map(ast.literal_eval, value.strip("\"").strip('"').strip("'").split('|'))

            yield node , (str(adjacency_list) + '|' + str(topic)  + '|' + str(updated_page_rank) )

    def reducer_pagerank_init(self):
        self.topic_counts = {}
        with open('topic_counts.txt', 'r') as topic_counts:
            for line in topic_counts.readlines():
                count, topic  = line.strip().split("\t")
                self.topic_counts[int(topic)] = int(count)

    def reducer_pagerank(self, node, pagerank_adj_list):
        lost_mass = [0] * (int(self.options.numtopics) + 1)

        page_rank_updated = []
        adj_list = {}
        num_topics = int(self.options.numtopics) + 1

        alpha = 1 - float(self.options.dampingfactor)
        total_nodes = float(self.options.numtotalnodes)
        beta = float(self.options.beta)

        for pagerank_link in pagerank_adj_list:
            if type(pagerank_link) == list:
                lost_mass = pagerank_link
            else:
                adj_list, topic, pagerank = map(ast.literal_eval, pagerank_link.strip("\"").split('|'))

        for topic_num in range(1, num_topics):
            curr_topic = self.topic_counts[topic_num]
            #yield node, self.topic_counts[topic_num]

            if (topic_num) == int(topic):
                #pass
                page_rank_updated.append(alpha * (beta/curr_topic) + (1-alpha) * (lost_mass[topic_num-1]/total_nodes + float(pagerank[topic_num-1])))
            else:
                page_rank_updated.append(alpha * ((1-beta)/(total_nodes-curr_topic)) + (1-alpha) * (lost_mass[topic_num-1]/total_nodes + float(pagerank[topic_num-1])))

        page_rank_updated.append(alpha * (1/total_nodes) + (1-alpha) * (lost_mass[topic_num-1]/total_nodes + float(pagerank[topic_num-1])))

        yield node, str(adj_list) + "|" + str(topic) + "|" + str(page_rank_updated)

if __name__ == '__main__':
    PageRankTopics.run()

```

```python
%%writefile PageRankTopics_driver.py

from PageRankTopics import PageRankTopics
from mrjob.job import MRJob
from mrjob.step import MRStep
import sys
import ast
import subprocess

INPUT_GRAPH = sys.argv[1]
DAMPING_FACTOR = sys.argv[2]
TOTAL_NODES = sys.argv[3]
MAX_ITERATION = int(sys.argv[4])
MODE = sys.argv[5]

num_interation = 0

if MODE == 'hadoop':
    mr_job = PageRankTopics(args=[INPUT_GRAPH, '--dampingfactor', DAMPING_FACTOR, '--numtotalnodes', TOTAL_NODES,  '-r', 'hadoop',
                                  '--topic_counts', 'topic_counts.txt', '--no-output', '--cleanup', 'NONE', '--no-check-input-paths', '--output-dir',  '/tmp/msannat/temp-output' ])
    while (num_interation<MAX_ITERATION):
        with mr_job.make_runner() as runner:
            num_interation += 1
            print "Iteration: ", num_interation
            subprocess.Popen(["hadoop", "fs", "-rm", "-r", "/tmp/msannat/temp-output/"], stdout=None)
            runner.run()
            f = open(INPUT_GRAPH, 'w+')
            cat = subprocess.Popen(["hadoop", "fs", "-cat", "/tmp/msannat/temp-output/part-*"], stdout=subprocess.PIPE)
            for line in cat.stdout:
                #print line
                f.writelines(line)

        f.close()

else:
    mr_job = PageRankTopics(args=[INPUT_GRAPH, '--dampingfactor', DAMPING_FACTOR, '--numtotalnodes', TOTAL_NODES,
                                  '--topic_counts', './topic_counts.txt',  '-r', 'local' ])
    while (num_interation<MAX_ITERATION):
        with mr_job.make_runner() as runner:
            num_interation += 1
            print "Iteration: ", num_interation
            runner.run()
            f = open(INPUT_GRAPH, 'w+')
            for line in runner.stream_output():
                #print line
                f.writelines(line)

        f.close()

```

```python
!rm ./randNet_topics_input.txt
!cp ./randNet_topics_updated_topics.txt ./randNet_topics_input.txt
!python ./PageRankTopics_driver.py ./randNet_topics_input.txt 0.85 100 100 'local'
```

```python
import ast
import sys

topic_page_ranks = []
i = 0

with open('./randNet_topics_input.txt', 'r') as topics_pr_file:
    for line in topics_pr_file.readlines():
        node, pr_out_links = line.split('\t')
        pr_out_links =  pr_out_links.replace('"', '')
        node = node.replace('"', '')
        out_links, topic, pr = map(ast.literal_eval, pr_out_links.split('|'))
        top_pages_topics = []
        top_pages_topics.extend([int(node), topic])
        for i in range (0, 11):
            top_pages_topics.append(float(pr[i]))
        topic_page_ranks.append(top_pages_topics)

print '*************************************'
print "Top 10 pages for Global Page rank"
print '**************************************'

for pr in sorted(topic_page_ranks, key=lambda x: x[12], reverse=True)[:10]:
    print "{0: <4} {1: <4} {2}".format(pr[0], pr[1], round(pr[12], 4))

for i in range(10):
    print '************************************'
    print "Top 10 pages for Topic {}".format(i+1)
    print '************************************'
    for pr in sorted(topic_page_ranks, key=lambda x: x[i+2], reverse=True)[:10]:
        print "{0: <4} {1: <4} {2}".format(pr[0], pr[1], round(pr[i+2], 4))

```

    *************************************
    Top 10 pages for Global Page rank
    **************************************
    63   4    0.0184
    61   8    0.0172
    15   3    0.0165
    74   10   0.0155
    92   1    0.0149
    100  8    0.0149
    90   5    0.0147
    13   6    0.0145
    85   7    0.0143
    71   2    0.0141
    ************************************
    Top 10 pages for Topic 1
    ************************************
    32   1    0.0206
    77   1    0.0205
    52   1    0.0197
    92   1    0.0195
    10   1    0.0185
    27   1    0.0184
    85   7    0.0178
    98   1    0.0176
    46   1    0.0174
    74   10   0.016
    ************************************
    Top 10 pages for Topic 2
    ************************************
    58   2    0.0307
    71   2    0.0295
    9    2    0.0291
    73   2    0.0287
    12   2    0.0267
    59   2    0.0256
    75   2    0.0247
    82   2    0.0227
    52   1    0.0163
    17   10   0.0151
    ************************************
    Top 10 pages for Topic 3
    ************************************
    15   3    0.0314
    70   3    0.0269
    86   3    0.0264
    91   3    0.0243
    66   3    0.024
    2    3    0.0236
    31   3    0.0226
    40   3    0.022
    20   3    0.0196
    74   10   0.0159
    ************************************
    Top 10 pages for Topic 4
    ************************************
    63   4    0.0261
    83   4    0.0216
    65   4    0.0205
    78   4    0.0201
    41   4    0.0198
    84   4    0.0194
    79   4    0.0183
    38   4    0.0174
    15   3    0.0167
    72   4    0.0166
    ************************************
    Top 10 pages for Topic 5
    ************************************
    99   5    0.0288
    90   5    0.0282
    88   5    0.027
    51   5    0.0267
    45   5    0.0254
    34   5    0.0237
    5    5    0.0237
    4    5    0.0232
    80   5    0.0227
    100  8    0.0167
    ************************************
    Top 10 pages for Topic 6
    ************************************
    13   6    0.0343
    56   6    0.0326
    37   6    0.0315
    11   6    0.0311
    69   6    0.0299
    23   6    0.0281
    15   3    0.0172
    85   7    0.017
    52   1    0.0166
    74   10   0.0155
    ************************************
    Top 10 pages for Topic 7
    ************************************
    85   7    0.0267
    25   7    0.0265
    28   7    0.0247
    53   7    0.0246
    35   7    0.0241
    97   7    0.0232
    47   7    0.0227
    55   7    0.0224
    30   7    0.022
    50   7    0.0199
    ************************************
    Top 10 pages for Topic 8
    ************************************
    100  8    0.0327
    61   8    0.0277
    39   8    0.027
    8    8    0.027
    62   8    0.0252
    87   8    0.0251
    6    8    0.0234
    54   8    0.0227
    18   8    0.0205
    9    2    0.0154
    ************************************
    Top 10 pages for Topic 9
    ************************************
    94   9    0.03
    14   9    0.0293
    42   9    0.029
    21   9    0.0282
    57   9    0.0273
    96   9    0.026
    24   9    0.0256
    63   4    0.0171
    61   8    0.0164
    74   10   0.0143
    ************************************
    Top 10 pages for Topic 10
    ************************************
    74   10   0.0262
    17   10   0.0235
    49   10   0.0234
    95   10   0.0205
    7    10   0.0198
    43   10   0.0193
    68   10   0.0189
    48   10   0.0189
    1    10   0.0189
    3    10   0.0185

### Section 4 Analysis

As expected, pages assoaciated with a specifc topic ranks higher and the vanila global page ranks are different than the page ranks for topics.

Literature's method for irreducibility is difficult to implement with topic-specific PageRank as it will required to be performed for each topic making it very computationally expesive.

-------  END OF HWK 9 --------


```python
from logicqubit.logic import *
from cmath import *
import numpy as np
import sympy as sp
import scipy
from scipy.optimize import *
import matplotlib.pyplot as plt
```

    Cuda is not available!
    logicqubit version 1.5.8

https://arxiv.org/pdf/1907.09386.pdf

```python
def commutator(A, B):
    C = A*B-B*A
    return np.all(C.get() == 0)
```

```python
gates = Gates(1)

ID = gates.ID()
X = gates.X()
Y = gates.Y()
Z = gates.Z()
```

```python
III = ID.kron(ID).kron(ID)
XXX = X.kron(X).kron(X)
XIZ = X.kron(ID).kron(Z)
XYI = X.kron(Y).kron(ID)
XIZ = X.kron(ID).kron(Z)
YYY = Y.kron(Y).kron(Y)
ZZZ = Z.kron(Z).kron(Z)
IZZ = ID.kron(Z).kron(Z)
ZZI = ID.kron(Z).kron(ID)
```

```python
commutator(XYI, XIZ)
```

    True

```python
# autovalores de X,Y,Z: [1, -1] e de I: [1, 1]
sig_iss = np.kron(np.kron([1, 1], [1, -1]), [1, -1])
sig_ssi = np.kron(np.kron([1, -1], [1, -1]), [1, 1])
sig_sis = np.kron(np.kron([1, -1], [1, 1]), [1, -1])
sig_sis
```

    array([ 1, -1,  1, -1, -1,  1, -1,  1])

```python
H = III + XYI*3 + XIZ*10

min(scipy.linalg.eig(H.get())[0])
```

    (-12+4.1241847957050005e-32j)

```python
def ansatz(reg, params):
    n_qubits = len(reg)
    depth = n_qubits
    for i in range(depth):
        for j in range(n_qubits):
            if(j < n_qubits-1):
                reg[j+1].CNOT(reg[j])
            reg[i].RY(params[j])

def ansatz_3q(q1, q2, q3, params):
    q1.RY(params[0])
    q2.RY(params[1])
    q3.RY(params[2])
    q2.CNOT(q1)
    q3.CNOT(q2)
    q1.RX(params[3])
    q2.RX(params[4])
    q3.RX(params[5])
    q2.CNOT(q1)
    q3.CNOT(q2)
    q1.RY(params[6])
    q2.RY(params[7])
    q3.RY(params[8])
    q2.CNOT(q1)
    q3.CNOT(q2)
    q1.RY(params[9])
    q2.RY(params[10])
    q3.RY(params[11])
    q2.CNOT(q1)
    q3.CNOT(q2)
```

```python
def expectation_3q(params):
    logicQuBit  = LogicQuBit(3)
    q1 = Qubit()
    q2 = Qubit()
    q3 = Qubit()

    ansatz_3q(q1,q2,q3,params)
    #ansatz([q1,q2,q3],params)
    psi = logicQuBit.getPsi()

    return (psi.adjoint()*H*psi).get()[0][0]

minimum = minimize(expectation_3q, [0,0,0,0,0,0,0,0,0,0,0,0], method='Nelder-Mead', options={'xtol': 1e-10, 'ftol': 1e-10})
print(minimum)
```

    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:586: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[k] = func(sim[k])
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:611: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxe
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:618: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxr
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:614: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxr
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:637: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxcc
    /home/cleoner/anaconda3/lib/python3.8/site-packages/scipy/optimize/optimize.py:627: ComplexWarning: Casting complex values to real discards the imaginary part
      fsim[-1] = fxc

     final_simplex: (array([[-1.70536841e+00, -1.00539050e-01,  1.55861477e-01,
             1.43285592e+00,  7.21512712e-01,  4.20845303e-02,
             1.09009478e-01,  3.22594690e-02, -1.58316966e-01,
            -4.84445090e-01,  2.47706805e-02, -7.58020841e-01],
           [-1.72932470e+00, -1.11875984e-01,  1.48727714e-01,
             1.45605885e+00,  7.19279604e-01,  4.18493832e-03,
             1.38757640e-01,  1.35012392e-02, -1.66798557e-01,
            -4.85511796e-01,  4.19130555e-02, -7.24793089e-01],
           [-1.71587546e+00, -1.16578201e-01,  1.31684364e-01,
             1.43959437e+00,  6.84974479e-01,  2.05536298e-02,
             1.07468660e-01,  5.54096191e-02, -1.57343206e-01,
            -4.69880836e-01,  2.61660302e-02, -7.27302825e-01],
           [-1.68926830e+00, -1.28022390e-01,  1.27347166e-01,
             1.44220497e+00,  6.59737147e-01,  2.54488244e-03,
             1.23687602e-01,  4.34666389e-02, -1.59325585e-01,
            -4.65406496e-01,  2.60530944e-02, -7.03618705e-01],
           [-1.70178393e+00, -1.39039820e-01,  1.20098364e-01,
             1.46687376e+00,  6.60005588e-01,  4.85023745e-03,
             1.16261792e-01,  4.51160055e-02, -1.61960754e-01,
            -4.66235936e-01,  2.65815805e-02, -7.01327612e-01],
           [-1.73136172e+00, -1.11889428e-01,  1.59835631e-01,
             1.46858747e+00,  7.28620542e-01,  4.16095665e-03,
             1.53245444e-01,  2.97244816e-02, -1.66764106e-01,
            -4.99738716e-01,  1.22139212e-02, -7.66691092e-01],
           [-1.73840294e+00, -1.13737680e-01,  1.41138938e-01,
             1.44895040e+00,  7.07565019e-01, -8.14463757e-03,
             1.44689394e-01,  5.13248256e-02, -1.63342433e-01,
            -4.83494763e-01,  1.92743377e-02, -7.39749231e-01],
           [-1.71499356e+00, -1.31262845e-01,  1.24501410e-01,
             1.46647094e+00,  6.29315690e-01, -2.20381711e-03,
             1.21485857e-01,  1.38805128e-02, -1.60239951e-01,
            -4.59377891e-01,  6.55797083e-02, -6.64585991e-01],
           [-1.72540044e+00, -1.12603409e-01,  1.52325874e-01,
             1.45508295e+00,  6.96674358e-01, -9.04517768e-03,
             1.57193835e-01,  3.31089829e-02, -1.63566403e-01,
            -4.88639375e-01,  2.08999611e-02, -7.41616163e-01],
           [-1.72900108e+00, -1.11681325e-01,  1.42217524e-01,
             1.44594979e+00,  6.61918175e-01, -4.74549668e-03,
             1.39870817e-01,  3.63766965e-02, -1.58061382e-01,
            -4.73907336e-01,  3.99762905e-02, -7.14487289e-01],
           [-1.74028153e+00, -1.21196218e-01,  1.36415820e-01,
             1.47201071e+00,  6.60963881e-01,  7.77681127e-03,
             1.23284726e-01,  3.28353691e-02, -1.59678663e-01,
            -4.73931256e-01,  4.59205514e-02, -7.11721910e-01],
           [-1.72392396e+00, -1.09011353e-01,  1.37529234e-01,
             1.43165597e+00,  6.51721018e-01,  1.54026079e-04,
             1.29483205e-01,  4.70746703e-02, -1.54331254e-01,
            -4.66927057e-01,  4.01151916e-02, -7.10547945e-01],
           [-1.74655332e+00, -1.05512430e-01,  1.40324864e-01,
             1.43440322e+00,  6.82607623e-01, -1.15001155e-02,
             1.43130562e-01,  5.36610163e-02, -1.58344148e-01,
            -4.75522403e-01,  3.32044358e-02, -7.25480480e-01]]), array([-11.90465103, -11.89738534, -11.89714667, -11.89611876,
           -11.89543048, -11.89459568, -11.89119465, -11.88976402,
           -11.88961561, -11.88756703, -11.88544989, -11.8848997 ,
           -11.88311624]))
               fun: -11.904651028973685
           message: 'Maximum number of function evaluations has been exceeded.'
              nfev: 2401
               nit: 1787
            status: 1
           success: False
                 x: array([-1.70536841, -0.10053905,  0.15586148,  1.43285592,  0.72151271,
            0.04208453,  0.10900948,  0.03225947, -0.15831697, -0.48444509,
            0.02477068, -0.75802084])

```python
def expectation_value(measurements, base = np.array([1,-1,-1,1,-1,1,1,-1])):
    probabilities = np.array(measurements)
    expectation = np.sum(base * probabilities)
    return expectation

def sigma_xxx(params):
    logicQuBit  = LogicQuBit(3, first_left = False)
    q1 = Qubit()
    q2 = Qubit()
    q3 = Qubit()

    ansatz_3q(q1,q2,q3,params)

    # medidas em XX
    q1.RY(-pi/2)
    q2.RY(-pi/2)
    q3.RY(-pi/2)

    result = logicQuBit.Measure([q1,q2,q3])
    result = expectation_value(result)
    return result

# [xyi, xiz]=0 precisamos de apenas uma medida no eixo XYZ
def sigma_xyz(params):
    logicQuBit  = LogicQuBit(3, first_left = False)
    q1 = Qubit()
    q2 = Qubit()
    q3 = Qubit()

    ansatz_3q(q1,q2,q3,params)

    # medidas em XYZ
    q1.RY(-pi/2)
    q2.RX(pi/2)

    result = logicQuBit.Measure([q1,q2,q3])
    xyi = expectation_value(result, sig_ssi)
    xiz = expectation_value(result, sig_sis)
    return xyi, xiz

def sigma_yyy(params):
    logicQuBit  = LogicQuBit(3, first_left = False)
    q1 = Qubit()
    q2 = Qubit()
    q3 = Qubit()

    ansatz_3q(q1,q2,q3,params)

    # medidas em YY
    q1.RX(pi/2)
    q2.RX(pi/2)
    q3.RX(pi/2)

    result = logicQuBit.Measure([q1,q2,q3])
    result = expectation_value(result)
    return result

def sigma_zzz(params):
    logicQuBit  = LogicQuBit(3, first_left = False)
    q1 = Qubit()
    q2 = Qubit()
    q3 = Qubit()

    ansatz_3q(q1,q2,q3,params)

    result = logicQuBit.Measure([q1,q2,q3])
    zzz = expectation_value(result)
    izz = expectation_value(result, sig_iss) # [zzz, izz] = 0
    return zzz, izz

def expectation_energy(params):
    xyi, xiz =  sigma_xyz(params)

    result = 1 + 3*xyi + 10*xiz
    return result
```

```python
minimum = minimize(expectation_energy, [0,0,0,0,0,0,0,0,0,0,0,0], method='Nelder-Mead', options={'xtol': 1e-10, 'ftol': 1e-10})
print(minimum)
```

     final_simplex: (array([[-1.59861417,  0.67851413,  0.10077095, -0.92422256, -0.11976661,
             0.00917929,  1.13129902,  0.47232269, -0.86804904,  0.11303789,
             0.21045843, -0.08121447],
           [-1.59895826,  0.67857134,  0.10084076, -0.92411586, -0.11940067,
             0.00888377,  1.1313394 ,  0.4721589 , -0.86791851,  0.11316486,
             0.21029665, -0.08118751],
           [-1.59883373,  0.67864737,  0.10089682, -0.9241835 , -0.11958172,
             0.00921565,  1.13123184,  0.47222441, -0.86796823,  0.11303257,
             0.21034507, -0.08105163],
           [-1.59863681,  0.67853123,  0.10099701, -0.92431596, -0.11956898,
             0.00894363,  1.13132499,  0.47220322, -0.8681013 ,  0.11301176,
             0.21051405, -0.0810089 ],
           [-1.59891672,  0.67803267,  0.10070373, -0.92361073, -0.11958812,
             0.00895073,  1.13074031,  0.47220744, -0.86746476,  0.1132327 ,
             0.21086732, -0.08111697],
           [-1.59895044,  0.67867105,  0.10066558, -0.9243782 , -0.11958008,
             0.00869684,  1.1320279 ,  0.47227541, -0.86868568,  0.11321458,
             0.21042249, -0.08120846],
           [-1.59856045,  0.67853487,  0.10067208, -0.92413636, -0.1196517 ,
             0.00900177,  1.13148039,  0.4722272 , -0.86819776,  0.11311357,
             0.21032051, -0.0812351 ],
           [-1.59852924,  0.67891918,  0.10089009, -0.92418457, -0.11907734,
             0.00869892,  1.13176505,  0.47184014, -0.86839749,  0.11311502,
             0.20968821, -0.08100081],
           [-1.59874767,  0.6787171 ,  0.10058647, -0.92410889, -0.11957377,
             0.00890507,  1.13195571,  0.47213675, -0.86882512,  0.11317587,
             0.2103274 , -0.08093424],
           [-1.59865491,  0.67862233,  0.10095037, -0.92422868, -0.11957092,
             0.00900781,  1.13149926,  0.47213535, -0.86845405,  0.11300785,
             0.21054798, -0.08073757],
           [-1.59905849,  0.67804611,  0.10083859, -0.92379546, -0.11947886,
             0.00876768,  1.1307453 ,  0.47223251, -0.86730022,  0.11324809,
             0.21083986, -0.08125908],
           [-1.59884236,  0.67867333,  0.1009312 , -0.92416094, -0.11935983,
             0.0089217 ,  1.13139659,  0.47208309, -0.86808683,  0.11309664,
             0.21024271, -0.08099302],
           [-1.59858964,  0.6790594 ,  0.10085633, -0.92449436, -0.11950178,
             0.00915556,  1.13185601,  0.47214315, -0.86857939,  0.11298531,
             0.2098386 , -0.08108023]]), array([-10.62601164, -10.62600831, -10.62600783, -10.62600772,
           -10.62600634, -10.62600632, -10.62600568, -10.62600563,
           -10.62600488, -10.62600456, -10.62600423, -10.62600401,
           -10.62600391]))
               fun: -10.626011641573301
           message: 'Maximum number of function evaluations has been exceeded.'
              nfev: 2400
               nit: 1779
            status: 1
           success: False
                 x: array([-1.59861417,  0.67851413,  0.10077095, -0.92422256, -0.11976661,
            0.00917929,  1.13129902,  0.47232269, -0.86804904,  0.11303789,
            0.21045843, -0.08121447])

```python
def gradient(params, evaluate, shift=pi/2):
    n_params = params.shape[0]
    gradients = np.zeros(n_params)

    for i in range(n_params):
        #parameter shift rule
        shift_vect = np.array([shift if j==i else 0 for j in range(n_params)])
        shift_right = params + shift_vect
        shift_left = params - shift_vect

        expectation_right = evaluate(shift_right)
        expectation_left = evaluate(shift_left)

        gradients[i] = expectation_right - expectation_left

    return gradients
```

```python
params = np.random.uniform(-np.pi, np.pi, 12)
last_params = np.zeros(12)
```

```python
lr = 0.1
err = 1
while err > 1e-5:
    grad = gradient(params, expectation_energy, pi/1.1)
    params = params - lr*grad
    err = abs(sum(params - last_params))
    last_params = np.array(params)
    print(err)
```

    /tmp/ipykernel_11017/3485820173.py:14: ComplexWarning: Casting complex values to real discards the imaginary part
      gradients[i] = expectation_right - expectation_left

    0.8184556076668226
    0.38752317161238425
    0.31702405800521294
    0.2114378341002251
    0.12622217096968308
    0.06988522765929
    0.036757536629974275
    0.017942924481501726
    0.0070756162271398415
    0.0005119468375004432
    0.003698812536389179
    0.006590165046403695
    0.008719561745389709
    0.010398725049715174
    0.01181080663570816
    0.01306999962503802
    0.014252341439315447
    0.015412060931818933
    0.01659058414055703
    0.01782174325629804
    0.019134978012696813
    0.020557450358407475
    0.022115558994343143
    0.02383611987328671
    0.02574736439465522
    0.02787984548498948
    0.03026730594273791
    0.032947538679256816
    0.03596324558849784
    0.03936287309745537
    0.04320135940688802
    0.04754065935909127
    0.05244980096077244
    0.058004049267572466
    0.06428247867973151
    0.0713628543250511
    0.07931219144133625
    0.08817077437715488
    0.09792705008058987
    0.1084813340462448
    0.11959893426643273
    0.1308598055205104
    0.1416230660430793
    0.1510374797164561
    0.1581322214338978
    0.1619990907937687
    0.16202305509789933
    0.15806412646508625
    0.15049679275216576
    0.14009020314283258
    0.12780088054536423
    0.11457517377189513
    0.10121894277157381
    0.08834047896872375
    0.07634638312104697
    0.06546741553043031
    0.055797160058966844
    0.04733230115173521
    0.040007978616722985
    0.033725345147359545
    0.028371057127321797
    0.023829887247373782
    0.019992151752861266
    0.01675756492657432
    0.014036805316610978
    0.011751712947697434
    0.009834729013108823
    0.00822796294721231
    0.0068821177964697
    0.0057554055839963425
    0.004812522852923217
    0.004023719654357505
    0.003363973798020758
    0.0028122702278157163
    0.0023509791336336237
    0.0019653235189718513
    0.0016429259807656082
    0.0013734245613781226
    0.00114814818950284
    0.0009598431357881853
    0.0008024428977797093
    0.0006708749017532157
    0.0005608983165898752
    0.00046896809468264253
    0.0003921210799300301
    0.000327880655312196
    0.0002741769480434586
    0.00022928007796427783
    0.00019174433302846517
    0.00016036149368594543
    0.0001341218137406175
    0.00011218140624817962
    9.383498593305007e-05
    7.849309026288775e-05
    6.566304432198411e-05
    5.493305488530886e-05
    4.595891956085119e-05
    3.8452921250931034e-05
    3.217454859367752e-05
    2.6922742115287334e-05
    2.2529415194871838e-05
    1.8854040046867304e-05
    1.5779123653603123e-05
    1.3206427151390976e-05
    1.1053806463134917e-05
    9.25257191770612e-06

```python
expectation_energy(params)
```

    (-11.99999999921216+0j)

```python

```

```python

```


```python
from Exercise import Exercise, MarkdownBlock
from config import URL, TOKEN
import json

import numpy as np
import sympy as sp

Exercise.URL = URL
Exercise.TOKEN = TOKEN
```

```python
from sympy import Rational, Symbol, latex, UnevaluatedExpr

u = lambda x : UnevaluatedExpr(x)

# Helper functions
def explain_add(a, b):
    assert(np.shape(a) == np.shape(b))
    rows, columns = np.shape(a)
    return sp.Matrix([[Symbol(f"({latex(u(a[i,j]))} + {latex(u(b[i,j]))})") for j in range(columns)] for i in range(rows)])

def symbolic_matrix(character, rows, columns):
    return sp.Matrix([[Symbol(f"{{{character}}}_{{{i+1}, {j+1}}}") for j in range(columns)] for i in range(rows)])

def explain_multiply(a, b):
    # #rows in b == #columns in a
    assert(np.shape(a)[1] == np.shape(b)[0])
    rows = np.shape(a)[0]
    columns = np.shape(b)[1]
    result = np.empty(shape=(rows, columns), dtype=object)
    for i in range(rows):
        row = a[i,:]
        for j in range(columns):
            column = b[:,j]
            zipped = zip(row, column)
            mapped = list(map(lambda t: f"{latex(u(t[0]))} \cdot {latex(u(t[1]))}", zipped))
            s = Symbol("")
            result[i, j] = Symbol(" + ".join(mapped), evaluate=False)

    return sp.Matrix(result)

# TODO: fixme for >= 3 dim matrices
# TODO: print intermediate steps at each return
def explain_det(a):
    # square matrix
    assert(np.shape(a)[0] == np.shape(a)[1])
    if np.shape(a)[0] == 1:
        return a[0,0]
    if np.shape(a)[0] == 2:
        return f"{a[0,0]} \cdot {a[1,1]} - {a[0,1]} \cdot {a[0,1]}"
    else:
        return f"{a[0,0]} \cdot {explain_det(a[1:,1:])} - {a[0,1]} \cdot {[[a[1,0], a[1,2]], [a[2,0], a[2,2]]]} + {a[0,2]} \cdot {[[a[1,0], a[1,2]], [a[1,1], a[2,1]]]}"

# A = symbolic_matrix("a", 3, 3)
# e = Exercise(f"${explain_det(A)}$")
# e.display()
```

```python
def generator():
    length = np.random.randint(1, 7)
    v1 = sp.randMatrix(r=length, c=1, min=0, max=10)
    v2 = sp.randMatrix(r=length, c=1, min=0, max=10)

    s = "What is $@v1 + @v2$?"

    params = {}
    params["v1"] = v1
    params["v2"] = v2
    e = Exercise(MarkdownBlock(s, params))

    params["v3"] = explain_add(v1,v2)
    params["v4"] = v1 + v2
    s1 = "Yes, $@v1 + @v2 = @v3 = @v4$!"
    e.add_answer(v1 + v2, True, MarkdownBlock(s1, params))

    a = symbolic_matrix("a", length, 1)
    b = symbolic_matrix("b", length, 1)
    ab = explain_add(a, b)
    default_feedback = "Remember the definition of matrix addition: $@a + @b = @ab$"
    e.add_default_feedback(MarkdownBlock(default_feedback, dict(a=a, b=b, ab=ab)))

    return e

Exercise.write_multiple(generator, 100, "vector_add_2")
```

```python
s = "What is $@a @b$?"

rows = np.random.randint(1, 4)
columns = np.random.randint(1, 4)

params = {}
params["a"] = sp.randMatrix(r=rows, c=columns, min=0, max=10)
params["b"] = sp.randMatrix(r=columns, c=rows+2, min=0, max=10)
ans = params["a"] * params["b"]

e = Exercise(MarkdownBlock(s, params))

e.add_answer(ans, True, "That's right!")

e.play()
```

```python
def generator():
    s = "What is $@a @b$?"

    rows = np.random.randint(1, 4)
    columns = np.random.randint(1, 4)

    params = {}
    params["a"] = sp.randMatrix(r=rows, c=columns, min=0, max=10)
    params["b"] = sp.randMatrix(r=columns, c=rows+2, min=0, max=10)
    ans = params["a"] * params["b"]

    e = Exercise(MarkdownBlock(s, params))

    e.add_answer(ans, True, "That's right!")

    return e

Exercise.write_multiple(generator, 10, "mat_multiply")
```

```python
def generator():
    s = "What is $@a \cdot @b$?"

    rows = np.random.randint(1, 4)
    columns = np.random.randint(1, 4)

    params = {}
    params["a"] = sp.Matrix(np.random.randint(5, size=rows*columns).reshape((rows,columns)))
    params["b"] = sp.Matrix(np.random.randint(5, size=(2+rows)*columns).reshape((columns,rows+2)))

    e = Exercise(MarkdownBlock(s, params))

    ans = params["a"] * params["b"]
    e.add_answer(params["a"] * params["b"], True, "That's right!")

    params = {}
    params["x"] = symbolic_matrix("a", rows, columns)
    params["y"] = symbolic_matrix("b", columns, rows+2)
    params["z"] = explain_multiply(params["x"], params["y"])
    f = """Remember the definition of matrix multiplication: \n $@x \cdot @y = @z$"""
    e.add_default_feedback(MarkdownBlock(f, params))

    return e

Exercise.write_multiple(generator, 10, "mat_multiply")
```

```python
def explain_multiply(a, b):
    # #rows in b == #columns in a
    assert(np.shape(a)[1] == np.shape(b)[0])
    rows = np.shape(a)[0]
    columns = np.shape(b)[1]
    result = np.empty(shape=(rows, columns), dtype=object)
    for i in range(rows):
        row = a[i,:]
        for j in range(columns):
            column = b[:,j]
            zipped = zip(row, column)
            mapped = list(map(lambda t: f"{latex(u(t[0]))} \cdot {latex(u(t[1]))}", zipped))
            s = Symbol("")
            result[i, j] = Symbol(" + ".join(mapped), evaluate=False)

    return sp.Matrix(result)

# explain_multiply(np.arange(4).reshape((2,2)), np.arange(4).reshape((2,2)))
```

```python
v = symbolic_matrix("a", 3, 1)
display(3*u(v))
display(3*v)

```

# TODO: meaningfull, contextualized exercises for each of these:
- scalar def.
- vector def.
- matrix def.
- vector indexing
- matrix indexing
- total values in a matrix
- vector dimensions
- matrix dimensions
- special matrices
- transpose
- operations (+, *), linear combinations
- dot product, cross product, norm
- cosine similarity

## Matrix Transpose

```python
m = "Determine $@a^\intercal$?"

a = np.arange(6).reshape((2, 3))

params = {}
params["a"] = sp.Matrix(a)

e = Exercise(MarkdownBlock(m, params))
e.display()

e.add_answer(params["a"].T, True, "Correct!")

e.write()
e.publish()
```

## Matrix Inverse

```python
m = "Determine $@a^{-1}$?"

a = np.arange(4).reshape((2, 2))

params = {}
params["a"] = sp.Matrix(a)

e = Exercise(MarkdownBlock(m, params))
e.display()

e.add_answer(params["a"].inv(), True, "Correct!")

e.write()
e.publish()
```

```python
m = "Given $A = @a$, what is $det(A)$?"

a = np.arange(4).reshape((2, 2))
params = {}
params["a"] = sp.Matrix(a)

e = Exercise(MarkdownBlock(m, params))
e.display()

e.add_answer(params["a"].det(), True, "Correct!")

e.write()
e.publish()
```

```python

```