def test_row_isrounds(self): self.assertTrue(Row(0).is_rounds()) self.assertTrue(Row(1).is_rounds()) self.assertTrue(Row(2).is_rounds()) self.assertTrue(Row('12345678').is_rounds()) self.assertFalse(Row('21').is_rounds())
def test_row_conjugator(self): x = Row('2143') y = Row('3412') r = Row.conjugator(x, y) self.assertEqual(y, ~r * x * r) self.assertFalse(Row.conjugator(x, 4))
def test_row_are_conjugate(self): conj_class = list(map(Row, ['2143', '3412', '4321'])) for r1, r2 in itertools.product(conj_class, conj_class): self.assertTrue(Row.are_conjugate(r1, r2)) for r in conj_class: self.assertFalse(Row.are_conjugate(r, 4))
def test_row_equals_string_types(self): self.assertEqual(Row('123456'), '123456') self.assertEqual(Row(b'123456'), b'123456') self.assertEqual(Row(u'123456'), u'123456') self.assertEqual(bytes(Row('123456')), b'123456') try: self.assertEqual(unicode(Row('123456')), u'123456') except NameError: pass
def test_row_block_subscript_bounds(self): rb = RowBlock(*[Change(5, pn) for pn in ['3', '1', '5']]) self.assertRaises(IndexError, lambda: rb[-1]) self.assertEqual(rb[0], '12345') self.assertEqual(rb[3], '32415') self.assertRaises(IndexError, lambda: rb[4]) self.assertRaises(IndexError, lambda: rb.__setitem__(-1, Row(5))) rb[0] = Row(5) # Should succeed rb[3] = Row(5) self.assertRaises(IndexError, lambda: rb.__setitem__(4, Row(5)))
def test_row_divide_row(self): self.assertEqual(Row('642153') / Row('235164'), Row('164325')) self.assertEqual(Row('53678421') / Row('425613'), Row('83456721')) self.assertEqual(Row('51324') / Row('645231'), Row('624135')) r = Row() r = r / '623415' self.assertEqual(r, '523461') r = r / '623415' self.assertEqual(r, '623415') r = r / '87654321' self.assertEqual(r, '87514326') self.assertEqual(r.bells, 8)
def test_row_sign(self): self.assertEqual(Row().sign(), +1) self.assertEqual(Row(1).sign(), +1) self.assertEqual(Row(2).sign(), +1) self.assertEqual(Row(17).sign(), +1) self.assertEqual(Row('234561').sign(), -1) self.assertEqual(Row('234516').sign(), +1) self.assertEqual(Row('352461').sign(), +1) self.assertEqual(Row('312647958').sign(), -1) self.assertEqual(Row('167832495').sign(), +1)
def leads(self): """ A list containing the leads of the composition. """ if self._leads is None: lead_specifications = [] for line in self.configs.composition: if line in self.configs.methods: lead_specifications.append({ 'method_name': line, 'method_object': self.configs.methods[line], 'call_symbol': '', 'call_object': None, }) elif line in self.configs.calls: lead_specifications[-1]['call_symbol'] = line lead_specifications[-1]['call_object'] = \ self.configs.calls[line] self._leads = [] lead_head = Row(self.configs.bells) for lead_specification in lead_specifications: lead_specification['starting_lead_head'] = lead_head self._leads.append(Lead(**lead_specification)) lead_head = self._leads[-1].lead_head return self._leads
def test_row_multiply_change(self): r = Row() r = r * Change(6, 'X') self.assertEqual(r, '214365') r = r * Change(6, '1') self.assertEqual(r, '241635') r = r * Change(8, 'X') self.assertEqual(r, '42615387') r = r * Change(5, '3') self.assertEqual(r, '24651387')
def test_group_named_groups(self): self.assertEqual(list(Group.symmetric_group(0)), [Row()]) self.assertEqual(list(Group.symmetric_group(3)), ['123', '132', '213', '231', '312', '321']) self.assertEqual(list(Group.symmetric_group(3, 1)), ['1234', '1243', '1324', '1342', '1423', '1432']) self.assertEqual(list(Group.symmetric_group( 3, 1, 5)), ['12345', '12435', '13245', '13425', '14235', '14325']) self.assertEqual(list(Group.alternating_group(0)), [Row()]) self.assertEqual(list(Group.alternating_group(3)), ['123', '231', '312']) self.assertEqual(list(Group.alternating_group(3, 1)), ['1234', '1342', '1423']) self.assertEqual(list(Group.alternating_group(3, 1, 5)), ['12345', '13425', '14235']) self.assertRaises(ValueError, lambda: Group.symmetric_group(2, 2, 3)) self.assertRaises(ValueError, lambda: Group.alternating_group(2, 2, 3))
def test_row_cycles(self): self.assertEqual(Row().cycles(), '') self.assertEqual(Row(1).cycles(), '1') self.assertEqual(Row(5).cycles(), '1,2,3,4,5') self.assertEqual(Row('124536').cycles(), '1,2,345,6') self.assertEqual(Row('214563').cycles(), '12,3456') self.assertEqual(Row('2145673').cycles(), '12,34567')
def test_row_order(self): self.assertEqual(Row().order(), 1) self.assertEqual(Row(1).order(), 1) self.assertEqual(Row('21').order(), 2) self.assertEqual(Row('234561').order(), 6) self.assertEqual(Row('21436578').order(), 2) self.assertEqual(Row('2315674').order(), 12)
def is_cyclic(self): """ Whether the composition has a cyclic part end. (Returns False if it's a single-part composition). """ if self._is_cyclic is None: # Rows don't have an is_cyclic method (why not?) # Generate all cyclic part ends and see if ours is one of them. if self.is_treble_fixed: cyclic_part_ends = [ Row.cyclic(self.configs.bells, 1, n) for n in range(self.configs.bells - 1) ] else: cyclic_part_ends = [ Row.cyclic(self.configs.bells, 0, n) for n in range(self.configs.bells) ] self._is_cyclic = self.part_end in cyclic_part_ends return self.parts != 1 and self._is_cyclic
def create_worksheet(self, workbook, name, landscape=False): """ Creates a worksheet and fills it with rows. """ worksheet = workbook.add_worksheet(name) if landscape: worksheet.set_landscape() worksheet.set_paper(9) # A4 worksheet.set_margins(0.4, 0.4, 0.4, 0.4) # 1cm all round worksheet.set_header('', {'margin': 0}) worksheet.set_footer('', {'margin': 0}) worksheet.fit_to_pages(1, 1) # Start with the smallest possible rows (i.e. length of longest method) # and increase it until we pass the required aspect ratio rows = max( [lead.method_object.length for lead in self.composition.leads]) desired_aspect_ratio = 1 / sqrt(2) if landscape else sqrt(2) while self.calculate_aspect_ratio(rows) < desired_aspect_ratio: rows += 2 # Stamp leads all over the worksheet self.row_index = 0 self.col_index = 0 self.lead_head = Row(self.composition.configs.bells) col_index_delta = self.composition.configs.bells + 3 for lead in self.composition.leads: if self.row_index + lead.method_object.length >= rows: self.row_index = 0 self.col_index += col_index_delta self.print_lead(lead, worksheet) # Set print area and column widths worksheet.print_area( 0, 0, rows, self.calculate_columns(rows) * col_index_delta - 2) for i in range(self.calculate_columns(rows)): worksheet.set_column(i * col_index_delta, (i + 1) * col_index_delta - 3, BELL_COLUMN_WIDTH) worksheet.set_column((i + 1) * col_index_delta - 1, (i + 1) * col_index_delta - 1, GUTTER_COLUMN_WIDTH)
def test_group_iterator(self): self.assertEqual(list(Group()), [Row()]) self.assertEqual(list(Group(6)), ['123456']) self.assertEqual(list(Group('134256')), ['123456', '134256', '142356']) self.assertEqual(list(Group('213564', '123546')), [ '123456', '123465', '123546', '123564', '123645', '123654', '213456', '213465', '213546', '213564', '213645', '213654', ]) self.assertEqual(list(Group('654321')), ['123456', '654321'])
def test_group_coset_labels(self): G = Group('2143', '1324', '1243') # S_4 H = Group('2143', '1324') # Rows in plain hunt # A row in G but not H; this must be in a coset of H. # This is the lexicographically lowest row (except rounds), so should be # the label of its coset. g = Row('1243') for h_dash in G: # gH is the left coset of H in G with respect to g lcoset_row = g * h_dash # Hg is the right coset of H in G with respect to g rcoset_row = h_dash * g if h_dash in H: self.assertEqual(H.lcoset_label(lcoset_row), g) self.assertEqual(H.rcoset_label(rcoset_row), g) else: self.assertNotEqual(H.lcoset_label(lcoset_row), g) self.assertNotEqual(H.rcoset_label(rcoset_row), g)
def test_row_find(self): r = Row('615423') self.assertEqual(r.find(0), 1) self.assertEqual(r.find(1), 4) self.assertEqual(r.find(2), 5) self.assertEqual(r.find(3), 3) self.assertEqual(r.find(4), 2) self.assertEqual(r.find(5), 0) self.assertEqual(r.find(6), 6) self.assertRaises(ValueError, lambda: r.find(-1)) r.find(0) r.find(MAX_BELL_NUMBER) self.assertRaises(ValueError, lambda: r.find(MAX_BELL_NUMBER + 1))
def test_row_multiply_row(self): self.assertEqual(Row('4312') * Row('2341'), Row('3124')) self.assertEqual(Row('7631425') * Row('2347165'), Row('6315724')) self.assertEqual(Row('12387654') * Row('631245'), Row('63128754')) self.assertEqual(Row('24531') * Row('57863124'), Row('17865243')) r = Row() r = r * '34512' self.assertEqual(r, '34512') r = r * '14253' self.assertEqual(r, '31425')
def test_row_subscript_bounds(self): r = Row(6) self.assertRaises(IndexError, lambda: r[-1]) self.assertEqual(r[0], 0) self.assertEqual(r[5], 5) self.assertRaises(IndexError, lambda: r[6])
def test_row_inverse_tilde(self): self.assertEqual(~Row('654321'), '654321') self.assertEqual(~Row('312'), '231') self.assertEqual(~Row('18234567'), '13456782')
def test_row_constructor_exceptions(self): self.assertRaises(ValueError, lambda: Row(-1)) Row(0) Row(MAX_BELL_NUMBER) self.assertRaises(ValueError, lambda: Row(MAX_BELL_NUMBER + 1)) self.assertRaises(TypeError, lambda: Row(self))
def bell_number_to_char(num): """ Converts a bell number into the character that represents it. """ return str(Row(num + 1))[num] # Hack this out of a Row string
def test_group_conjugate(self): G = Group('2143', '1324') r = Row('1243') self.assertEqual(sorted([~r * g * r for g in G]), sorted(G.conjugate(r)))
def test_row_bells(self): self.assertEqual(Row().bells, 0) self.assertEqual(Row(7).bells, 7) self.assertEqual(Row('12345').bells, 5)
def test_row_named_rows(self): self.assertEqual(Row.rounds(0), '') self.assertEqual(Row.rounds(1), '1') self.assertEqual(Row.rounds(2), '12') self.assertEqual(Row.rounds(5), '12345') self.assertEqual(Row.rounds(8), '12345678') self.assertEqual(Row.queens(0), '') self.assertEqual(Row.queens(1), '1') self.assertEqual(Row.queens(2), '12') self.assertEqual(Row.queens(5), '13524') self.assertEqual(Row.queens(8), '13572468') self.assertEqual(Row.kings(0), '') self.assertEqual(Row.kings(1), '1') self.assertEqual(Row.kings(2), '12') self.assertEqual(Row.kings(5), '53124') self.assertEqual(Row.kings(8), '75312468') self.assertEqual(Row.tittums(0), '') self.assertEqual(Row.tittums(1), '1') self.assertEqual(Row.tittums(2), '12') self.assertEqual(Row.tittums(5), '14253') self.assertEqual(Row.tittums(8), '15263748') self.assertEqual(Row.reverse_rounds(0), '') self.assertEqual(Row.reverse_rounds(1), '1') self.assertEqual(Row.reverse_rounds(2), '21') self.assertEqual(Row.reverse_rounds(5), '54321') self.assertEqual(Row.reverse_rounds(8), '87654321')
def test_operators_return_not_implemented(self): # Arithmetic operator returns NotImplemented when given unknown types self.assertEqual(Row().__lt__(self), NotImplemented) self.assertEqual(Row().__le__(self), NotImplemented) self.assertEqual(Row().__eq__(self), NotImplemented) self.assertEqual(Row().__ne__(self), NotImplemented) self.assertEqual(Row().__gt__(self), NotImplemented) self.assertEqual(Row().__ge__(self), NotImplemented) self.assertEqual(Row().__mul__(self), NotImplemented) self.assertEqual(Row().__rmul__(self), NotImplemented) self.assertEqual(Row().__truediv__(self), NotImplemented) self.assertEqual(Row().__rtruediv__(self), NotImplemented) # ... but passes through errors parsing known types. self.assertRaises(ValueError, lambda: Row().__lt__('!')) self.assertRaises(ValueError, lambda: Row().__le__('!')) self.assertRaises(ValueError, lambda: Row().__eq__('!')) self.assertRaises(ValueError, lambda: Row().__ne__('!')) self.assertRaises(ValueError, lambda: Row().__gt__('!')) self.assertRaises(ValueError, lambda: Row().__ge__('!')) self.assertRaises(ValueError, lambda: Row().__mul__('!')) self.assertRaises(ValueError, lambda: Row().__rmul__('!')) self.assertRaises(ValueError, lambda: Row().__truediv__('!')) self.assertRaises(ValueError, lambda: Row().__rtruediv__('!')) try: self.assertEqual(Row().__div__(self), NotImplemented) self.assertRaises(ValueError, lambda: Row().__div__('!')) except AttributeError: pass # Ignore (Python 3 doesn't create __div__)
def test_row_find(self): r = Row('615423') self.assertEqual(r.find(0), 1) self.assertEqual(r.find(1), 4) self.assertEqual(r.find(2), 5) self.assertEqual(r.find(3), 3) self.assertEqual(r.find(4), 2) self.assertEqual(r.find(5), 0) self.assertEqual(r.find(6), 6) self.assertRaises(ValueError, lambda: r.find(-1)) r.find(0) r.find(MAX_BELL_NUMBER) self.assertRaises(ValueError, lambda: r.find(MAX_BELL_NUMBER + 1)) self.assertRaises(TypeError, lambda: r.find(self)) self.assertEqual(r.find(Bell(0)), 1) self.assertEqual(r.find(Bell('1')), 1)
def test_row_repr(self): self.assertEqual(repr(Row()), 'Row()') self.assertEqual(repr(Row('123456')), "Row('123456')")
def test_row_print(self): self.assertEqual(str(Row()), '') self.assertEqual(str(Row('123456')), '123456')
def test_row_equals(self): self.assertTrue(Row() == Row('')) self.assertTrue(Row() != Row('1')) self.assertTrue(Row(6) == Row('123456')) self.assertTrue(Row('123456') == Row('123456')) self.assertFalse(Row('123456') != Row('123456')) self.assertTrue(Row('123456') != Row('123465')) self.assertFalse(Row('123456') == Row('123465')) self.assertTrue(Row('123456') != Row('1234')) self.assertFalse(Row('123456') == Row('1234')) self.assertTrue(Row('123456') != Row('12345678')) self.assertFalse(Row('123456') == Row('12345678'))
def execute(self): if self.composition.configs.has_config('calls'): longest_call = max(map( lambda s: len(s), self.composition.configs.calls.keys() )) format_string = ' {0.method_name}\n{0.call_symbol:' + \ str(longest_call) + '} {0.lead_head}' else: format_string = ' {0.method_name}\n{0.lead_head}' file = os.path.join(self.get_output_directory(), 'composition.txt') with open(file, 'w') as file: def output(*args): """Prints a line to the output file without any newline.""" print(*args, end='', file=file) # Title output('{length} {stage}\n'.format( length=self.composition.length, stage=Method.stage_name(self.composition.configs.bells), )) # Number of methods methods = {} for name, length in self.composition.method_balance.items(): # Assemble dict mapping lengths to lists of methods # e.g. {176: ['Slinky'], 880: ['Maypole']} if length not in methods: methods[length] = [] methods[length].append(name) for length in methods.keys(): # Replace each list of methods with string for output # e.g. {176: '176 Slinky', 880: '880 Maypole'} methods[length] = ', '.join(sorted(methods[length])) methods[length] = '{0} {1}'.format(length, methods[length]) methods = [ # Sort entries in reverse order of length # e.g. {880: '880 Maypole', 176: '176 Slinky'} methods[length] for length in reversed(sorted(methods.keys())) ] output('({number}m: {methods})\n'.format( number=len(self.composition.method_balance), methods='; '.join(methods), )) output('\n') # Rows of the composition if self.composition.configs.has_config('calls'): output(' ' * (longest_call + 1)) output(Row(self.composition.configs.bells)) for lead in self.composition.leads: output(format_string.format(lead)) output('\n') output('\n') # Composition statistics output('{0.parts} part. {0.com} com.\n'.format(self.composition))