Bits of tinkering
[cipher-training.git] / find_best_affine_break_parameters.py
1 import random
2 import collections
3 from cipher import *
4 from cipherbreak import *
5 import itertools
6 import csv
7
8 corpus = sanitise(''.join([open('shakespeare.txt', 'r').read(),
9 open('sherlock-holmes.txt', 'r').read(),
10 open('war-and-peace.txt', 'r').read()]))
11 corpus_length = len(corpus)
12
13 euclidean_scaled_english_counts = norms.euclidean_scale(english_counts)
14
15 metrics = [{'func': norms.l1, 'invert': True, 'name': 'l1'},
16 {'func': norms.l2, 'invert': True, 'name': 'l2'},
17 {'func': norms.l3, 'invert': True, 'name': 'l3'},
18 {'func': norms.cosine_similarity, 'invert': False, 'name': 'cosine_similarity'}]
19 # {'func': norms.harmonic_mean, 'invert': True, 'name': 'harmonic_mean'},
20 # {'func': norms.geometric_mean, 'invert': True, 'name': 'geometric_mean'}]
21 scalings = [{'corpus_frequency': normalised_english_counts,
22 'scaling': norms.normalise,
23 'name': 'normalised'},
24 {'corpus_frequency': euclidean_scaled_english_counts,
25 'scaling': norms.euclidean_scale,
26 'name': 'euclidean_scaled'}]
27 message_lengths = [2000, 1000, 500, 250, 100, 50, 20]
28
29 trials = 5000
30
31 scores = {}
32
33
34 def make_frequency_compare_function(target_frequency, frequency_scaling, metric, invert):
35 def frequency_compare(text):
36 counts = frequency_scaling(frequencies(text))
37 if invert:
38 score = -1 * metric(target_frequency, counts)
39 else:
40 score = metric(target_frequency, counts)
41 return score
42 return frequency_compare
43
44 def scoring_functions():
45 return [{'func': make_frequency_compare_function(s['corpus_frequency'],
46 s['scaling'], m['func'], m['invert']),
47 'name': '{} + {}'.format(m['name'], s['name'])}
48 for m in metrics
49 for s in scalings] + [{'func': Pletters, 'name': 'Pletters'}]
50
51 def eval_scores():
52 [eval_one_score(f, l)
53 for f in scoring_functions()
54 for l in message_lengths]
55
56 def eval_one_score(scoring_function, message_length):
57 print(scoring_function['name'], message_length, ': ', end='', flush=True)
58 if scoring_function['name'] not in scores:
59 scores[scoring_function['name']] = collections.defaultdict(int)
60 for _ in range(trials):
61 sample_start = random.randint(0, corpus_length - message_length)
62 sample = corpus[sample_start:(sample_start + message_length)]
63 multiplier = random.choice([x for x in range(1, 26, 2) if x != 13])
64 adder = random.randint(0, 25)
65 one_based = random.choice([True, False])
66 key = (multiplier, adder, one_based)
67 ciphertext = affine_encipher(sample, multiplier, adder, one_based)
68 found_key, _ = affine_break(ciphertext, scoring_function['func'])
69 if found_key == key:
70 scores[scoring_function['name']][message_length] += 1
71 print(scores[scoring_function['name']][message_length], '/', trials)
72 return scores[scoring_function['name']][message_length]
73
74 def show_results():
75 with open('affine_break_parameter_trials.csv', 'w') as f:
76 writer = csv.DictWriter(f, ['name'] + message_lengths,
77 quoting=csv.QUOTE_NONNUMERIC)
78 writer.writeheader()
79 for scoring in sorted(scores.keys()):
80 scores[scoring]['name'] = scoring
81 writer.writerow(scores[scoring])
82
83 print('Starting...')
84 eval_scores()
85 show_results()