def __repr__(self): return '<%s: %s>' % ( type(self).__name__, ' * '.join( str(sequence_tools.get_length(sequence)) for sequence in self.sequences), )
def _enumerate(iterable, reverse_index): if reverse_index is False: return __builtin__.enumerate(iterable) else: from combi._python_toolbox import sequence_tools try: length = sequence_tools.get_length(iterable) except AttributeError: iterable = nifty_collections.LazyTuple(iterable) length = len(iterable) return itertools.izip(range(length - 1, -1, -1), iterable)
def accumulated_lengths(self): ''' A sequence of the accumulated length as every sequence is added. For example, if this chain space has sequences with lengths of 10, 100 and 1000, this would be `[0, 10, 110, 1110]`. ''' total = 0 yield 0 for sequence in self.sequences: total += sequence_tools.get_length(sequence) yield total
class MapSpace(sequence_tools.CuteSequenceMixin, collections.Sequence): ''' A space of a function applied to a sequence. This is similar to Python's builtin `map`, except that it behaves like a sequence rather than an iterable. (Though it's also iterable.) You can access any item by its index number. Example: >>> map_space = MapSpace(lambda x: x ** 2, range(7)) >>> map_space MapSpace(<function <lambda> at 0x00000000030C1510>, range(0, 7)) >>> len(map_space) 7 >>> map_space[3] 9 >>> tuple(map_space) (0, 1, 4, 9, 16, 25, 36) ''' def __init__(self, function, sequence): self.function = function self.sequence = sequence_tools.ensure_iterable_is_immutable_sequence( sequence, default_type=nifty_collections.LazyTuple) length = caching.CachedProperty( lambda self: sequence_tools.get_length(self.sequence)) def __repr__(self): return '%s(%s, %s)' % (type(self).__name__, self.function, self.sequence) def __getitem__(self, i): if isinstance(i, slice): return type(self)(self.function, self.sequence[i]) assert isinstance(i, int) return self.function(self.sequence[i]) # Propagating `IndexError`. def __iter__(self): for item in self.sequence: yield self.function(item) _reduced = property(lambda self: (type(self), self.function, self.sequence)) __eq__ = lambda self, other: (isinstance(other, MapSpace) and self._reduced == other._reduced) __hash__ = lambda self: hash(self._reduced) __bool__ = lambda self: bool(self.sequence)
def apply(self, sequence, result_type=None): ''' Apply the perm to a sequence, choosing items from it. This can also be used as `sequence * perm`. Example: >>> perm = PermSpace(5)[10] >>> perm <Perm: (0, 2, 4, 1, 3)> >>> perm.apply('growl') 'golrw' >>> 'growl' * perm 'golrw' Specify `result_type` to determine the type of the result returned. If `result_type=None`, will use `tuple`, except when `other` is a `str` or `Perm`, in which case that same type would be used. ''' sequence = \ sequence_tools.ensure_iterable_is_immutable_sequence(sequence) if sequence_tools.get_length(sequence) < \ sequence_tools.get_length(self): raise Exception("Can't apply permutation on sequence of " "shorter length.") permed_generator = (sequence[i] for i in self) if result_type is not None: if result_type is str: return ''.join(permed_generator) else: return result_type(permed_generator) elif isinstance(sequence, Perm): return type(self)(permed_generator, sequence.nominal_perm_space) elif isinstance(sequence, str): return ''.join(permed_generator) else: return tuple(permed_generator)
def index(self, given_sequence): '''Get the index number of `given_sequence` in this product space.''' if not isinstance(given_sequence, collections.Sequence) or \ not len(given_sequence) == len(self.sequences): raise ValueError current_radix = 1 wip_index = 0 for item, sequence in reversed( tuple(zip(given_sequence, self.sequences))): wip_index += current_radix * sequence.index(item) # (Propagating `ValueError`.) current_radix *= sequence_tools.get_length(sequence) return wip_index
def index(self, given_sequence): '''Get the index number of `given_sequence` in this product space.''' if not isinstance(given_sequence, collections.Sequence) or \ not len(given_sequence) == len(self.sequences): raise ValueError current_radix = 1 wip_index = 0 for item, sequence in reversed(tuple(zip(given_sequence, self.sequences))): wip_index += current_radix * sequence.index(item) # (Propagating `ValueError`.) current_radix *= sequence_tools.get_length(sequence) return wip_index
def __bool__(self): from combi._python_toolbox import sequence_tools return bool(sequence_tools.get_length(self))
def __repr__(self): return '<%s: %s>' % ( type(self).__name__, ' * '.join(str(sequence_tools.get_length(sequence)) for sequence in self.sequences), )
def __getitem__(self, i): if isinstance(i, (slice, sequence_tools.CanonicalSlice)): canonical_slice = sequence_tools.CanonicalSlice( i, self.length, offset=self.canonical_slice.start ) return PermSpace( self.sequence, domain=self.domain, n_elements=self.n_elements, fixed_map=self.fixed_map, degrees=self.degrees, is_combination=self.is_combination, slice_=canonical_slice, perm_type=self.perm_type ) assert isinstance(i, numbers.Integral) if i <= -1: i += self.length if not (0 <= i < self.length): raise IndexError elif self.is_sliced: return self.unsliced[i + self.canonical_slice.start] elif self.is_dapplied: return self.perm_type(self.undapplied[i], perm_space=self) ####################################################################### elif self.is_degreed: if self.is_rapplied: assert not self.is_recurrent and \ not self.is_partial and not self.is_combination and \ not self.is_dapplied and not self.is_sliced return self.perm_type(map(self.sequence.__getitem__, self.unrapplied[i]), perm_space=self) assert not self.is_rapplied and not self.is_recurrent and \ not self.is_partial and not self.is_combination and \ not self.is_dapplied and not self.is_sliced # If that wasn't an example of asserting one's dominance, I don't # know what is. available_values = list(self.free_values) wip_perm_sequence_dict = dict(self.fixed_map) wip_n_cycles_in_fixed_items = \ self._n_cycles_in_fixed_items_of_just_fixed wip_i = i for j in self.sequence: if j in wip_perm_sequence_dict: continue for unused_value in available_values: candidate_perm_sequence_dict = dict(wip_perm_sequence_dict) candidate_perm_sequence_dict[j] = unused_value ### Checking whether we closed a cycle: ################### # # if j == unused_value: closed_cycle = True else: current = j while True: current = candidate_perm_sequence_dict[current] if current == j: closed_cycle = True break elif current not in candidate_perm_sequence_dict: closed_cycle = False break # # ### Finished checking whether we closed a cycle. ########## candidate_n_cycles_in_fixed_items = \ wip_n_cycles_in_fixed_items + closed_cycle candidate_fixed_perm_space_length = sum( math_tools.abs_stirling( self.sequence_length - len(candidate_perm_sequence_dict), self.sequence_length - degree - candidate_n_cycles_in_fixed_items ) for degree in self.degrees ) if wip_i < candidate_fixed_perm_space_length: available_values.remove(unused_value) wip_perm_sequence_dict[j] = unused_value wip_n_cycles_in_fixed_items = \ candidate_n_cycles_in_fixed_items break wip_i -= candidate_fixed_perm_space_length else: raise RuntimeError assert wip_i == 0 return self.perm_type((wip_perm_sequence_dict[k] for k in self.domain), self) ####################################################################### elif self.is_recurrent: assert not self.is_dapplied and not self.is_degreed and \ not self.is_sliced available_values = list(self.sequence) reserved_values = nifty_collections.Bag(self.fixed_map.values()) wip_perm_sequence_dict = dict(self.fixed_map) wip_i = i shit_set = set() for j in range(self.n_elements): if j in self.fixed_map: available_values.remove(self.fixed_map[j]) reserved_values[self.fixed_map[j]] -= 1 continue unused_values = [ item for item in nifty_collections.OrderedBag(available_values) - reserved_values if item not in shit_set ] for unused_value in unused_values: wip_perm_sequence_dict[j] = unused_value candidate_sub_perm_space = \ PermSpace._create_with_cut_prefix( self.sequence, n_elements=self.n_elements, fixed_map=wip_perm_sequence_dict, is_combination=self.is_combination, shit_set=shit_set, perm_type=self.perm_type ) if wip_i < candidate_sub_perm_space.length: available_values.remove(unused_value) break else: wip_i -= candidate_sub_perm_space.length if self.is_combination: shit_set.add(wip_perm_sequence_dict[j]) del wip_perm_sequence_dict[j] else: raise RuntimeError assert wip_i == 0 return self.perm_type( dict_tools.get_tuple(wip_perm_sequence_dict, self.domain), self ) ####################################################################### elif self.is_fixed: free_values_perm = self._free_values_unsliced_perm_space[i] free_values_perm_iterator = iter(free_values_perm) return self.perm_type( tuple( (self._undapplied_fixed_map[m] if (m in self.fixed_indices) else next(free_values_perm_iterator)) for m in range(self.sequence_length) ), self ) ####################################################################### elif self.is_combination: wip_number = self.length - 1 - i wip_perm_sequence = [] for i in range(self.n_elements, 0, -1): for j in range(self.sequence_length, i - 2, -1): candidate = math_tools.binomial(j, i) if candidate <= wip_number: wip_perm_sequence.append( self.sequence[-(j+1)] ) wip_number -= candidate break else: raise RuntimeError result = tuple(wip_perm_sequence) assert len(result) == self.n_elements return self.perm_type(result, self) ####################################################################### else: factoradic_number = math_tools.to_factoradic( i * math.factorial( self.n_unused_elements), n_digits_pad=self.sequence_length ) if self.is_partial: factoradic_number = factoradic_number[:-self.n_unused_elements] unused_numbers = list(self.sequence) result = tuple(unused_numbers.pop(factoradic_digit) for factoradic_digit in factoradic_number) assert sequence_tools.get_length(result) == self.n_elements return self.perm_type(result, self)
def __init__(self, slice_, iterable_or_length=None, offset=0): from combi._python_toolbox import sequence_tools from combi._python_toolbox import cute_iter_tools if isinstance(slice_, CanonicalSlice): slice_ = slice(slice_.start, slice_.stop, slice_.step) assert isinstance(slice_, slice) self.given_slice = slice_ if iterable_or_length is not None: if isinstance(iterable_or_length, math_tools.PossiblyInfiniteIntegral): self.length = iterable_or_length elif isinstance(iterable_or_length, collections.Sequence): self.length = sequence_tools.get_length(iterable_or_length) else: assert isinstance(iterable_or_length, collections.Iterable) self.length = cute_iter_tools.get_length(iterable_or_length) else: self.length = None self.offset = offset ### Parsing `step`: ################################################### # # assert slice_.step != 0 if slice_.step is None: self.step = 1 else: self.step = slice_.step # # ### Finished parsing `step`. ########################################## ### Parsing `start`: ################################################# # # if slice_.start is None: if self.step > 0: self.start = 0 + self.offset else: assert self.step < 0 self.start = (self.length + self.offset) if \ (self.length is not None) else infinity else: # s.start is not None if self.length is not None: if slice_.start < 0: self.start = \ max(slice_.start + self.length, 0) + self.offset else: self.start = min(slice_.start, self.length) + self.offset else: self.start = slice_.start + self.offset # # ### Finished parsing `start`. ######################################### ### Parsing `stop`: ################################################### # # if slice_.stop is None: if self.step > 0: self.stop = (self.length + self.offset) if \ (self.length is not None) else infinity else: assert self.step < 0 self.stop = -infinity else: # slice_.stop is not None if self.length is not None: if slice_.stop < 0: self.stop = max(slice_.stop + self.length, 0) + self.offset else: # slice_.stop >= 0 self.stop = min(slice_.stop, self.length) + self.offset else: self.stop = slice_.stop + self.offset # # ### Finished parsing `stop`. ########################################## if (self.step > 0 and self.start >= self.stop >= 0) or \ (self.step < 0 and self.stop >= self.start): # We have a case of an empty slice. self.start = self.stop = 0 self.slice_ = slice( *((item if item not in math_tools.infinities else None) for item in self)) ### Doing sanity checks: ############################################## # # if self.length: if self.step > 0: assert 0 <= self.start <= \ self.stop <= self.length + self.offset else: assert self.step < 0 assert 0 <= self.stop <= \ self.start <= self.length + self.offset
def __init__(self, slice_, iterable_or_length=None, offset=0): from combi._python_toolbox import sequence_tools from combi._python_toolbox import cute_iter_tools if isinstance(slice_, CanonicalSlice): slice_ = slice(slice_.start, slice_.stop, slice_.step) assert isinstance(slice_, slice) self.given_slice = slice_ if iterable_or_length is not None: if isinstance(iterable_or_length, math_tools.PossiblyInfiniteIntegral): self.length = iterable_or_length elif isinstance(iterable_or_length, collections.Sequence): self.length = sequence_tools.get_length(iterable_or_length) else: assert isinstance(iterable_or_length, collections.Iterable) self.length = cute_iter_tools.get_length(iterable_or_length) else: self.length = None self.offset = offset ### Parsing `step`: ################################################### # # assert slice_.step != 0 if slice_.step is None: self.step = 1 else: self.step = slice_.step # # ### Finished parsing `step`. ########################################## ### Parsing `start`: ################################################# # # if slice_.start is None: if self.step > 0: self.start = 0 + self.offset else: assert self.step < 0 self.start = (self.length + self.offset) if \ (self.length is not None) else infinity else: # s.start is not None if self.length is not None: if slice_.start < 0: self.start = \ max(slice_.start + self.length, 0) + self.offset else: self.start = min(slice_.start, self.length) + self.offset else: self.start = slice_.start + self.offset # # ### Finished parsing `start`. ######################################### ### Parsing `stop`: ################################################### # # if slice_.stop is None: if self.step > 0: self.stop = (self.length + self.offset) if \ (self.length is not None) else infinity else: assert self.step < 0 self.stop = -infinity else: # slice_.stop is not None if self.length is not None: if slice_.stop < 0: self.stop = max(slice_.stop + self.length, 0) + self.offset else: # slice_.stop >= 0 self.stop = min(slice_.stop, self.length) + self.offset else: self.stop = slice_.stop + self.offset # # ### Finished parsing `stop`. ########################################## if (self.step > 0 and self.start >= self.stop >= 0) or \ (self.step < 0 and self.stop >= self.start): # We have a case of an empty slice. self.start = self.stop = 0 self.slice_ = slice(*((item if item not in math_tools.infinities else None) for item in self)) ### Doing sanity checks: ############################################## # # if self.length: if self.step > 0: assert 0 <= self.start <= \ self.stop <= self.length + self.offset else: assert self.step < 0 assert 0 <= self.stop <= \ self.start <= self.length + self.offset
def index(self, perm): '''Get the index number of permutation `perm` in this space.''' if not isinstance(perm, collections.Iterable): raise ValueError try: perm = sequence_tools.ensure_iterable_is_immutable_sequence( perm, allow_unordered=False ) except sequence_tools.UnorderedIterableException: raise ValueError('An unordered iterable is never contained in a ' '`PermSpace`. Try an ordered one.') perm_set = set(perm) if not isinstance(perm, UnrecurrentedPerm) \ else set(perm._perm_sequence) if not (perm_set <= set(self.sequence)): raise ValueError if sequence_tools.get_length(perm) != self.n_elements: raise ValueError if not isinstance(perm, self.perm_type): perm = self.perm_type(perm, self) if self.sequence != perm.nominal_perm_space.sequence: # (This also covers `self.rapplied != perm.rapplied`) raise ValueError if self.domain != perm.domain: # (This also covers `self.dapplied != perm.dapplied`) raise ValueError if self.is_degreed and (perm.degree not in self.degrees): raise ValueError # At this point we know the permutation contains the correct items, and # has the correct degree. if perm.is_dapplied: return self.undapplied.index(perm.undapplied) ####################################################################### elif self.is_degreed: if perm.is_rapplied: return self.unrapplied.index(perm.unrapplied) wip_perm_number = 0 wip_perm_sequence_dict = dict(self.fixed_map) unused_values = list(self.free_values) for i, value in enumerate(perm._perm_sequence): if i in self.fixed_indices: continue unused_values.remove(value) lower_values = [j for j in unused_values if j < value] for lower_value in lower_values: temp_fixed_map = dict(wip_perm_sequence_dict) temp_fixed_map[i] = lower_value wip_perm_number += PermSpace( self.sequence_length, degrees=self.degrees, fixed_map=temp_fixed_map ).length wip_perm_sequence_dict[self.domain[i]] = value perm_number = wip_perm_number ####################################################################### elif self.is_recurrent: assert not self.is_degreed and not self.is_dapplied wip_perm_number = 0 unused_values = list(self.sequence) reserved_values = list(self.fixed_map.values()) perm_sequence_list = list(perm._perm_sequence) shit_set = set() for i, value in enumerate(perm._perm_sequence): if i in self.fixed_map: if self.fixed_map[i] == value: unused_values.remove(value) reserved_values.remove(value) continue else: raise ValueError lower_values = [ thing for thing in nifty_collections.OrderedSet(unused_values) if (thing not in reserved_values or unused_values.count(thing) > reserved_values.count(thing)) and unused_values.index(thing) < unused_values.index(value) and thing not in shit_set ] unused_values.remove(value) for lower_value in lower_values: temp_fixed_map = dict( enumerate(perm_sequence_list[:i] + [lower_value]) ) temp_fixed_map.update(self.fixed_map) candidate_sub_perm_space = \ PermSpace._create_with_cut_prefix( self.sequence, n_elements=self.n_elements, fixed_map=temp_fixed_map, is_combination=self.is_combination, shit_set=shit_set, perm_type=self.perm_type ) wip_perm_number += candidate_sub_perm_space.length if self.is_combination: shit_set.add(lower_value) perm_number = wip_perm_number ####################################################################### elif self.is_fixed: assert not self.is_degreed and not self.is_recurrent free_values_perm_sequence = [] for i, perm_item in zip(self.domain, perm._perm_sequence): if i in self.fixed_map: if self.fixed_map[i] != perm_item: raise ValueError else: free_values_perm_sequence.append(perm_item) # At this point we know all the items that should be fixed are # fixed. perm_number = self._free_values_unsliced_perm_space.index( free_values_perm_sequence ) ####################################################################### elif self.is_combination: if perm.is_rapplied: return self.unrapplied.index(perm.unrapplied) assert not self.is_rapplied and not self.is_recurrent and \ not self.is_dapplied and not self.is_fixed and \ not self.is_degreed if not cute_iter_tools.is_sorted(perm._perm_sequence): raise ValueError processed_perm_sequence = tuple( self.sequence_length - 1 - item for item in perm._perm_sequence[::-1] ) perm_number = self.unsliced.length - 1 - sum( (math_tools.binomial(item, i) for i, item in enumerate(processed_perm_sequence, start=1)), 0 ) ####################################################################### else: factoradic_number = [] unused_values = list(self.sequence) for i, value in enumerate(perm._perm_sequence): index_of_current_number = unused_values.index(value) factoradic_number.append(index_of_current_number) unused_values.remove(value) perm_number = math_tools.from_factoradic( factoradic_number + [0] * self.n_unused_elements ) // math.factorial(self.n_unused_elements) ####################################################################### if perm_number not in self.canonical_slice: raise ValueError return perm_number - self.canonical_slice.start
def __getitem__(self, i): if isinstance(i, (slice, sequence_tools.CanonicalSlice)): canonical_slice = sequence_tools.CanonicalSlice( i, self.length, offset=self.canonical_slice.start) return PermSpace(self.sequence, domain=self.domain, n_elements=self.n_elements, fixed_map=self.fixed_map, degrees=self.degrees, is_combination=self.is_combination, slice_=canonical_slice, perm_type=self.perm_type) assert isinstance(i, numbers.Integral) if i <= -1: i += self.length if not (0 <= i < self.length): raise IndexError elif self.is_sliced: return self.unsliced[i + self.canonical_slice.start] elif self.is_dapplied: return self.perm_type(self.undapplied[i], perm_space=self) ####################################################################### elif self.is_degreed: if self.is_rapplied: assert not self.is_recurrent and \ not self.is_partial and not self.is_combination and \ not self.is_dapplied and not self.is_sliced return self.perm_type(map(self.sequence.__getitem__, self.unrapplied[i]), perm_space=self) assert not self.is_rapplied and not self.is_recurrent and \ not self.is_partial and not self.is_combination and \ not self.is_dapplied and not self.is_sliced # If that wasn't an example of asserting one's dominance, I don't # know what is. available_values = list(self.free_values) wip_perm_sequence_dict = dict(self.fixed_map) wip_n_cycles_in_fixed_items = \ self._n_cycles_in_fixed_items_of_just_fixed wip_i = i for j in self.sequence: if j in wip_perm_sequence_dict: continue for unused_value in available_values: candidate_perm_sequence_dict = dict(wip_perm_sequence_dict) candidate_perm_sequence_dict[j] = unused_value ### Checking whether we closed a cycle: ################### # # if j == unused_value: closed_cycle = True else: current = j while True: current = candidate_perm_sequence_dict[current] if current == j: closed_cycle = True break elif current not in candidate_perm_sequence_dict: closed_cycle = False break # # ### Finished checking whether we closed a cycle. ########## candidate_n_cycles_in_fixed_items = \ wip_n_cycles_in_fixed_items + closed_cycle candidate_fixed_perm_space_length = sum( math_tools.abs_stirling( self.sequence_length - len(candidate_perm_sequence_dict), self.sequence_length - degree - candidate_n_cycles_in_fixed_items) for degree in self.degrees) if wip_i < candidate_fixed_perm_space_length: available_values.remove(unused_value) wip_perm_sequence_dict[j] = unused_value wip_n_cycles_in_fixed_items = \ candidate_n_cycles_in_fixed_items break wip_i -= candidate_fixed_perm_space_length else: raise RuntimeError assert wip_i == 0 return self.perm_type( (wip_perm_sequence_dict[k] for k in self.domain), self) ####################################################################### elif self.is_recurrent: assert not self.is_dapplied and not self.is_degreed and \ not self.is_sliced available_values = list(self.sequence) reserved_values = nifty_collections.Bag(self.fixed_map.values()) wip_perm_sequence_dict = dict(self.fixed_map) wip_i = i shit_set = set() for j in range(self.n_elements): if j in self.fixed_map: available_values.remove(self.fixed_map[j]) reserved_values[self.fixed_map[j]] -= 1 continue unused_values = [ item for item in nifty_collections.OrderedBag(available_values) - reserved_values if item not in shit_set ] for unused_value in unused_values: wip_perm_sequence_dict[j] = unused_value candidate_sub_perm_space = \ PermSpace._create_with_cut_prefix( self.sequence, n_elements=self.n_elements, fixed_map=wip_perm_sequence_dict, is_combination=self.is_combination, shit_set=shit_set, perm_type=self.perm_type ) if wip_i < candidate_sub_perm_space.length: available_values.remove(unused_value) break else: wip_i -= candidate_sub_perm_space.length if self.is_combination: shit_set.add(wip_perm_sequence_dict[j]) del wip_perm_sequence_dict[j] else: raise RuntimeError assert wip_i == 0 return self.perm_type( dict_tools.get_tuple(wip_perm_sequence_dict, self.domain), self) ####################################################################### elif self.is_fixed: free_values_perm = self._free_values_unsliced_perm_space[i] free_values_perm_iterator = iter(free_values_perm) return self.perm_type( tuple((self._undapplied_fixed_map[m] if ( m in self.fixed_indices ) else next(free_values_perm_iterator)) for m in range(self.sequence_length)), self) ####################################################################### elif self.is_combination: wip_number = self.length - 1 - i wip_perm_sequence = [] for i in range(self.n_elements, 0, -1): for j in range(self.sequence_length, i - 2, -1): candidate = math_tools.binomial(j, i) if candidate <= wip_number: wip_perm_sequence.append(self.sequence[-(j + 1)]) wip_number -= candidate break else: raise RuntimeError result = tuple(wip_perm_sequence) assert len(result) == self.n_elements return self.perm_type(result, self) ####################################################################### else: factoradic_number = math_tools.to_factoradic( i * math.factorial(self.n_unused_elements), n_digits_pad=self.sequence_length) if self.is_partial: factoradic_number = factoradic_number[:-self.n_unused_elements] unused_numbers = list(self.sequence) result = tuple( unused_numbers.pop(factoradic_digit) for factoradic_digit in factoradic_number) assert sequence_tools.get_length(result) == self.n_elements return self.perm_type(result, self)
def index(self, perm): '''Get the index number of permutation `perm` in this space.''' if not isinstance(perm, collections.Iterable): raise ValueError try: perm = sequence_tools.ensure_iterable_is_immutable_sequence( perm, allow_unordered=False) except sequence_tools.UnorderedIterableException: raise ValueError('An unordered iterable is never contained in a ' '`PermSpace`. Try an ordered one.') perm_set = set(perm) if not isinstance(perm, UnrecurrentedPerm) \ else set(perm._perm_sequence) if not (perm_set <= set(self.sequence)): raise ValueError if sequence_tools.get_length(perm) != self.n_elements: raise ValueError if not isinstance(perm, self.perm_type): perm = self.perm_type(perm, self) if self.sequence != perm.nominal_perm_space.sequence: # (This also covers `self.rapplied != perm.rapplied`) raise ValueError if self.domain != perm.domain: # (This also covers `self.dapplied != perm.dapplied`) raise ValueError if self.is_degreed and (perm.degree not in self.degrees): raise ValueError # At this point we know the permutation contains the correct items, and # has the correct degree. if perm.is_dapplied: return self.undapplied.index(perm.undapplied) ####################################################################### elif self.is_degreed: if perm.is_rapplied: return self.unrapplied.index(perm.unrapplied) wip_perm_number = 0 wip_perm_sequence_dict = dict(self.fixed_map) unused_values = list(self.free_values) for i, value in enumerate(perm._perm_sequence): if i in self.fixed_indices: continue unused_values.remove(value) lower_values = [j for j in unused_values if j < value] for lower_value in lower_values: temp_fixed_map = dict(wip_perm_sequence_dict) temp_fixed_map[i] = lower_value wip_perm_number += PermSpace( self.sequence_length, degrees=self.degrees, fixed_map=temp_fixed_map).length wip_perm_sequence_dict[self.domain[i]] = value perm_number = wip_perm_number ####################################################################### elif self.is_recurrent: assert not self.is_degreed and not self.is_dapplied wip_perm_number = 0 unused_values = list(self.sequence) reserved_values = list(self.fixed_map.values()) perm_sequence_list = list(perm._perm_sequence) shit_set = set() for i, value in enumerate(perm._perm_sequence): if i in self.fixed_map: if self.fixed_map[i] == value: unused_values.remove(value) reserved_values.remove(value) continue else: raise ValueError lower_values = [ thing for thing in nifty_collections.OrderedSet(unused_values) if (thing not in reserved_values or unused_values.count( thing) > reserved_values.count(thing)) and unused_values.index(thing) < unused_values.index(value) and thing not in shit_set ] unused_values.remove(value) for lower_value in lower_values: temp_fixed_map = dict( enumerate(perm_sequence_list[:i] + [lower_value])) temp_fixed_map.update(self.fixed_map) candidate_sub_perm_space = \ PermSpace._create_with_cut_prefix( self.sequence, n_elements=self.n_elements, fixed_map=temp_fixed_map, is_combination=self.is_combination, shit_set=shit_set, perm_type=self.perm_type ) wip_perm_number += candidate_sub_perm_space.length if self.is_combination: shit_set.add(lower_value) perm_number = wip_perm_number ####################################################################### elif self.is_fixed: assert not self.is_degreed and not self.is_recurrent free_values_perm_sequence = [] for i, perm_item in zip(self.domain, perm._perm_sequence): if i in self.fixed_map: if self.fixed_map[i] != perm_item: raise ValueError else: free_values_perm_sequence.append(perm_item) # At this point we know all the items that should be fixed are # fixed. perm_number = self._free_values_unsliced_perm_space.index( free_values_perm_sequence) ####################################################################### elif self.is_combination: if perm.is_rapplied: return self.unrapplied.index(perm.unrapplied) assert not self.is_rapplied and not self.is_recurrent and \ not self.is_dapplied and not self.is_fixed and \ not self.is_degreed if not cute_iter_tools.is_sorted(perm._perm_sequence): raise ValueError processed_perm_sequence = tuple( self.sequence_length - 1 - item for item in perm._perm_sequence[::-1]) perm_number = self.unsliced.length - 1 - sum( (math_tools.binomial(item, i) for i, item in enumerate(processed_perm_sequence, start=1)), 0) ####################################################################### else: factoradic_number = [] unused_values = list(self.sequence) for i, value in enumerate(perm._perm_sequence): index_of_current_number = unused_values.index(value) factoradic_number.append(index_of_current_number) unused_values.remove(value) perm_number = math_tools.from_factoradic( factoradic_number + [0] * self.n_unused_elements) // math.factorial( self.n_unused_elements) ####################################################################### if perm_number not in self.canonical_slice: raise ValueError return perm_number - self.canonical_slice.start