# Copyright (c) 2017-2019 The University of Manchester
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import OrderedDict
from six import itervalues
import logging
from spinn_utilities.ordered_set import OrderedSet
from pacman.model.constraints.key_allocator_constraints import (
FixedKeyFieldConstraint, FlexiKeyFieldConstraint,
ContiguousKeyRangeContraint, FixedMaskConstraint,
FixedKeyAndMaskConstraint, ShareKeyConstraint)
from pacman.utilities.utility_calls import locate_constraints_of_type
from pacman.exceptions import (
PacmanValueError, PacmanConfigurationException,
PacmanInvalidParameterException, PacmanRouteInfoAllocationException)
logger = logging.getLogger(__name__)
[docs]class ConstraintGroup(list):
def __init__(self, values):
super(ConstraintGroup, self).__init__(values)
self._constraint = None
self._n_keys = None
@property
def constraint(self):
return self._constraint
def _set_constraint(self, constraint):
self._constraint = constraint
def __hash__(self):
return id(self).__hash__()
def __eq__(self, other):
return id(other) == id(self)
def __ne__(self, other):
return id(other) != id(self)
[docs]def get_edge_groups(machine_graph, traffic_type):
""" Utility method to get groups of edges using any\
:py:class:`~pacman.model.constraints.key_allocator_constraints.KeyAllocatorSameKeyConstraint`\
constraints. Note that no checking is done here about conflicts\
related to other constraints.
:param machine_graph: the machine graph
:param traffic_type: the traffic type to group
"""
# mapping between partition and shared key group it is in
partition_groups = OrderedDict()
# process each partition one by one in a bubble sort kinda way
for vertex in machine_graph.vertices:
for partition in machine_graph.\
get_outgoing_edge_partitions_starting_at_vertex(vertex):
# only process partitions of the correct traffic type
if partition.traffic_type == traffic_type:
# Get a set of partitions that should be grouped together
shared_key_constraints = locate_constraints_of_type(
partition.constraints, ShareKeyConstraint)
partitions_to_group = [partition]
for constraint in shared_key_constraints:
partitions_to_group.extend(constraint.other_partitions)
# Get a set of groups that should be grouped
groups_to_group = [
partition_groups.get(part_to_group, [part_to_group])
for part_to_group in partitions_to_group]
# Group the groups
new_group = ConstraintGroup(
part for group in groups_to_group for part in group)
partition_groups.update(
{part: new_group for part in new_group})
# Keep track of groups
fixed_key_groups = list()
shared_key_groups = list()
fixed_mask_groups = list()
fixed_field_groups = list()
flexi_field_groups = list()
continuous_groups = list()
noncontinuous_groups = list()
groups_by_type = {
FixedKeyAndMaskConstraint: fixed_key_groups,
FixedMaskConstraint: fixed_mask_groups,
FixedKeyFieldConstraint: fixed_field_groups,
FlexiKeyFieldConstraint: flexi_field_groups,
}
groups = OrderedSet(itervalues(partition_groups))
for group in groups:
# Get all expected constraints in the group
constraints = [
constraint for partition in group
for constraint in locate_constraints_of_type(
partition.constraints,
(FixedKeyAndMaskConstraint, FixedMaskConstraint,
FlexiKeyFieldConstraint, FixedKeyFieldConstraint))]
# Check that the possibly conflicting constraints are equal
if constraints and not all(
constraint_a == constraint_b for constraint_a in constraints
for constraint_b in constraints):
raise PacmanRouteInfoAllocationException(
"The group of partitions {} have conflicting constraints"
.format(constraints))
# If no constraints, must be one of the non-specific groups
if not constraints:
# If the group has only one item, it is not shared
if len(group) == 1:
continuous_constraints = [
constraint for partition in group
for constraint in locate_constraints_of_type(
constraints, ContiguousKeyRangeContraint)]
if continuous_constraints:
continuous_groups.append(group)
else:
noncontinuous_groups.append(group)
# If the group has more than one partition, it must be shared
else:
shared_key_groups.append(group)
# If constraints found, put the group in the appropriate constraint
# group
else:
group._set_constraint(constraints[0])
constraint_type = type(constraints[0])
groups_by_type[constraint_type].append(group)
# return the set of groups
return (fixed_key_groups, shared_key_groups,
fixed_mask_groups, fixed_field_groups, flexi_field_groups,
continuous_groups, noncontinuous_groups)
[docs]def check_types_of_edge_constraint(machine_graph):
""" Go through the graph for operations and checks that the constraints\
are compatible.
:param machine_graph: the graph to search through
:rtype: None:
"""
for partition in machine_graph.outgoing_edge_partitions:
fixed_key = locate_constraints_of_type(
partition.constraints, FixedKeyAndMaskConstraint)
fixed_mask = locate_constraints_of_type(
partition.constraints, FixedMaskConstraint)
fixed_field = locate_constraints_of_type(
partition.constraints, FixedKeyFieldConstraint)
flexi_field = locate_constraints_of_type(
partition.constraints, FlexiKeyFieldConstraint)
if (len(fixed_key) > 1 or len(fixed_field) > 1 or
len(fixed_mask) > 1 or len(flexi_field) > 1):
raise PacmanConfigurationException(
"There are more than one of the same constraint type on "
"the partition {} starting at {}. Please fix and try again."
.format(partition.identifier, partition.pre_vertex))
fixed_key = len(fixed_key) == 1
fixed_mask = len(fixed_mask) == 1
fixed_field = len(fixed_field) == 1
flexi_field = len(flexi_field) == 1
# check for fixed key and a fixed mask. as these should have been
# merged before now
if fixed_key and fixed_mask:
raise PacmanConfigurationException(
"The partition {} starting at {} has a fixed key and fixed "
"mask constraint. These can be merged together, but is "
"deemed an error here"
.format(partition.identifer, partition.pre_vertex))
# check for a fixed key and fixed field, as these are incompatible
if fixed_key and fixed_field:
raise PacmanConfigurationException(
"The partition {} starting at {} has a fixed key and fixed "
"field constraint. These may be merge-able together, but "
"is deemed an error here"
.format(partition.identifer, partition.pre_vertex))
# check that a fixed mask and fixed field have compatible masks
if fixed_mask and fixed_field:
_check_masks_are_correct(partition)
# check that if there's a flexible field, and something else, throw
# error
if flexi_field and (fixed_mask or fixed_key or fixed_field):
raise PacmanConfigurationException(
"The partition {} starting at {} has a flexible field and "
"another fixed constraint. These maybe be merge-able, but "
"is deemed an error here"
.format(partition.identifer, partition.pre_vertex))
def _check_masks_are_correct(partition):
""" Check that the masks between a fixed mask constraint\
and a fixed_field constraint. completes if its correct, raises error\
otherwise
:param partition: \
the outgoing_edge_partition to search for these constraints
:rtype: None:
"""
fixed_mask = locate_constraints_of_type(
partition.constraints, FixedMaskConstraint)[0]
fixed_field = locate_constraints_of_type(
partition.constraints, FixedKeyFieldConstraint)[0]
mask = fixed_mask.mask
for field in fixed_field.fields:
if field.mask & mask != field.mask:
raise PacmanInvalidParameterException(
"field.mask, mask",
"The field mask {} is outside of the mask {}".format(
field.mask, mask),
"{}:{}".format(field.mask, mask))
for other_field in fixed_field.fields:
if other_field != field and other_field.mask & field.mask != 0:
raise PacmanInvalidParameterException(
"field.mask, mask",
"Field masks {} and {} overlap".format(
field.mask, other_field.mask),
"{}:{}".format(field.mask, mask))
[docs]def get_fixed_mask(same_key_group):
""" Get a fixed mask from a group of edges if a\
:py:class:`~pacman.model.constraints.key_allocator_constraints.FixedMaskConstraint`\
constraint exists in any of the edges in the group.
:param same_key_group: \
Set of edges that are to be assigned the same keys and masks
:type same_key_group: \
iterable(:py:class:`pacman.model.graphs.machine.MachineEdge`)
:return: The fixed mask if found, or None
:raise PacmanValueError: If two edges conflict in their requirements
"""
mask = None
fields = None
edge_with_mask = None
for edge in same_key_group:
fixed_mask_constraints = locate_constraints_of_type(
edge.constraints, FixedMaskConstraint)
for fixed_mask_constraint in fixed_mask_constraints:
if mask is not None and mask != fixed_mask_constraint.mask:
raise PacmanValueError(
"Two Edges {} and {} must have the same"
" key and mask, but have different fixed masks,"
" {} and {}".format(edge, edge_with_mask, mask,
fixed_mask_constraint.mask))
if (fields is not None and
fixed_mask_constraint.fields is not None and
fields != fixed_mask_constraint.fields):
raise PacmanValueError(
"Two Edges {} and {} must have the same"
" key and mask, but have different field ranges"
.format(edge, edge_with_mask))
mask = fixed_mask_constraint.mask
edge_with_mask = edge
if fixed_mask_constraint.fields is not None:
fields = fixed_mask_constraint.fields
return mask, fields