DiscreteNeuralNets/src/dominion.py
2025-07-20 14:20:25 -06:00

151 lines
5.4 KiB
Python

"""
Dominion
Tools for creating 2-dimensional dominions
"""
import random, pathlib
from matplotlib import pyplot as plt
import output
class Dominion:
"""
A dominion, which is a square array of entries with the property that every
2 by 2 subarray has at most two distinct entries. Higher-dimensional
analogues may be implemented in the future.
Attributes:
labels (frozenset): The labels which may appear as entries in the
dominion.
array (tuple of tuple): The array of entries belonging to the dominion.
"""
def __init__(self, labels, array):
"""
Create a dominion with a given array of labels.
Argument:
labels (iterable): The labels which may appear as entries in the
dominion.
array (iterable of iterable): The array of entries belonging to the
dominion.
"""
self.labels = frozenset(labels)
self.array = tuple(tuple(row) for row in array)
def show(self):
"""
Display a textual representation of the dominion in question.
"""
for row in self.array:
print(row)
def __repr__(self):
return "A Dominion of size {} with {} possible labels.".format(
len(self.array), len(self.labels))
def __str__(self):
labels = '{' + ', '.join(map(str, self.labels)) + '}'
return "A Dominion of size {} with labels from {}.".format(
len(self.array), labels)
def draw(self, color_map, filename):
"""
Render an image from a given dominion and color map.
Arguments:
color_map (string): The name of a color map.
filename (string): The name of the resulting file.
"""
plt.imsave(output.path + '//{}.png'.format(filename), \
self.array, cmap=color_map)
def new_row(row, labels, constraint_graph=None):
"""
Construct a new row for a dominion with a given collection of labels and a
graph constraining which labels can appear together.
Arguments:
row (tuple): A tuple of labels representing a row of a dominion.
labels (iterable): The pixel labels used in the dominion. The entries
of `row` should come from this.
constraint_graph (Graph): The graph determining which labels can appear
next to each other. The vertices of `constraint_graph` should be
the entries of `labels`. The default value `None` behaves as though
the graph is the complete graph on the vertex set whose members are
the entries of `labels'.
Returns:
tuple: A new row which is permitted to follow `row` in a dominion with
the given labels and constraints.
"""
partial_row = []
n = len(row)
for i in range(n):
if i == 0:
left_candidates = frozenset((row[0],))
right_candidates = frozenset((row[0], row[1]))
elif i == n - 1:
left_candidates = frozenset(
(row[n - 2], row[n - 1], partial_row[n - 2]))
right_candidates = frozenset((row[n - 1],))
else:
left_candidates = frozenset(
(row[i - 1], row[i], partial_row[i - 1]))
right_candidates = frozenset((row[i], row[i + 1]))
# If either side already has two candidates, we must choose from the
# intersection of the two sides.
candidates = left_candidates.intersection(right_candidates)
# Otherwise, it must be that both the left and right sides have only a
# single member. In this case, we may also choose an adjacent vertex on
# the constraint graph.
if len(left_candidates) == 1 and len(right_candidates) == 1:
if constraint_graph is None:
candidates = labels
else:
candidates = candidates.union(constraint_graph.neighbors(
tuple(candidates)[0]))
# Add a random candidate.
random_candidate = random.sample(list(candidates), 1)
partial_row += random_candidate
return tuple(partial_row)
def random_dominion(size, labels, constraint_graph=None):
"""
Create a random dominion given a size, collection of labels, and constraint
graph.
Arguments:
size (int): The number of rows (and columns) of the dominion.
labels (iterable): The pixel labels used in the dominion. The entries
of `row` should come from this.
constraint_graph (Graph): The graph determining which labels can appear
next to each other. The vertices of `constraint_graph` should be
the entries of `labels`. The default value `None` behaves as though
the graph is the complete graph on the vertex set whose members are
the entries of `labels'.
Returns:
Dominion: The randomly-generated dominion.
"""
partial_dominion = [[random.choice(labels)]]
for _ in range(size - 1):
if constraint_graph is None:
new_label = random.choice(labels)
else:
new_label = random.choice(
tuple(constraint_graph.neighbors(
partial_dominion[0][-1])) + (partial_dominion[0][-1],))
partial_dominion[0].append(new_label)
for _ in range(size - 1):
next_row = new_row(partial_dominion[-1], labels, constraint_graph)
partial_dominion.append(next_row)
return Dominion(labels, partial_dominion)