diff --git a/pacman/data/pacman_data_view.py b/pacman/data/pacman_data_view.py index 588cc0a25..fa2a5138f 100644 --- a/pacman/data/pacman_data_view.py +++ b/pacman/data/pacman_data_view.py @@ -562,7 +562,7 @@ def get_all_monitor_sdram(cls) -> AbstractSDRAM: """ # Note the sdram can not be calculated in advance as some Vertices # require the hardware time step not available until simulator run - sdram = ConstantSDRAM(0) + sdram: AbstractSDRAM = ConstantSDRAM(0) for vertex in cls.__pacman_data._all_monitor_vertices: sdram += vertex.sdram_required return sdram @@ -580,7 +580,7 @@ def get_ethernet_monitor_sdram(cls) -> AbstractSDRAM: :rtype: AbstractSDRAM """ - sdram = ConstantSDRAM(0) + sdram: AbstractSDRAM = ConstantSDRAM(0) for vertex in cls.__pacman_data._ethernet_monitor_vertices: sdram += vertex.sdram_required return sdram diff --git a/pacman/model/resources/__init__.py b/pacman/model/resources/__init__.py index c761aae5d..43d0a6796 100644 --- a/pacman/model/resources/__init__.py +++ b/pacman/model/resources/__init__.py @@ -17,8 +17,9 @@ from .iptag_resource import IPtagResource from .multi_region_sdram import MultiRegionSDRAM from .reverse_iptag_resource import ReverseIPtagResource +from .shared_sdram import SharedSDRAM from .variable_sdram import VariableSDRAM __all__ = ["AbstractSDRAM", "ConstantSDRAM", "IPtagResource", "MultiRegionSDRAM", - "ReverseIPtagResource", "VariableSDRAM"] + "ReverseIPtagResource", "SharedSDRAM", "VariableSDRAM"] diff --git a/pacman/model/resources/abstract_sdram.py b/pacman/model/resources/abstract_sdram.py index d605acda5..06db3ad3c 100644 --- a/pacman/model/resources/abstract_sdram.py +++ b/pacman/model/resources/abstract_sdram.py @@ -43,28 +43,6 @@ def __add__(self, other: AbstractSDRAM) -> AbstractSDRAM: """ raise NotImplementedError - @abstractmethod - def __sub__(self, other: AbstractSDRAM) -> AbstractSDRAM: - """ - Creates a new SDRAM which is this one less the other. - - :param AbstractSDRAM other: another SDRAM resource - :return: a New AbstractSDRAM - :rtype: AbstractSDRAM - """ - raise NotImplementedError - - @abstractmethod - def sub_from(self, other: AbstractSDRAM) -> AbstractSDRAM: - """ - Creates a new SDRAM which is the other less this one. - - :param AbstractSDRAM other: another SDRAM resource - :return: a New AbstractSDRAM - :rtype: AbstractSDRAM - """ - raise NotImplementedError - @property @abstractmethod def fixed(self) -> int: @@ -84,12 +62,9 @@ def per_timestep(self) -> float: """ raise NotImplementedError + @abstractmethod def __eq__(self, other: Any) -> bool: - if not isinstance(other, AbstractSDRAM): - return False - if other.fixed != self.fixed: - return False - return other.per_timestep == self.per_timestep + raise NotImplementedError @abstractmethod def report(self, timesteps: Optional[int], indent: str = "", @@ -105,3 +80,16 @@ def report(self, timesteps: Optional[int], indent: str = "", ``None`` is standard print """ raise NotImplementedError + + @property + @abstractmethod + def short_str(self) -> str: + """ + A short string representation of this SDRAM. + + To be used within main str methods + """ + raise NotImplementedError + + def __str__(self): + return f"SDRAM:{self.short_str}" diff --git a/pacman/model/resources/constant_sdram.py b/pacman/model/resources/constant_sdram.py index b89cbaea2..b05006f6f 100644 --- a/pacman/model/resources/constant_sdram.py +++ b/pacman/model/resources/constant_sdram.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, TextIO +from typing import Any, Optional, TextIO from spinn_utilities.overrides import overrides from .abstract_sdram import AbstractSDRAM @@ -49,7 +49,12 @@ def fixed(self) -> int: def per_timestep(self) -> float: return 0 - def __add__(self, other): + def __eq__(self, other: Any) -> bool: + if not isinstance(other, ConstantSDRAM): + return False + return other.fixed == self.fixed + + def __add__(self, other: AbstractSDRAM) -> AbstractSDRAM: if isinstance(other, ConstantSDRAM): return ConstantSDRAM( self._sdram + other.fixed) @@ -57,24 +62,12 @@ def __add__(self, other): # The other is more complex so delegate to it return other.__add__(self) - def __sub__(self, other): - if isinstance(other, ConstantSDRAM): - return ConstantSDRAM( - self._sdram - other.fixed) - else: - # The other is more complex so delegate to it - return other.sub_from(self) - - @overrides(AbstractSDRAM.sub_from) - def sub_from(self, other: AbstractSDRAM) -> AbstractSDRAM: - if isinstance(other, ConstantSDRAM): - return ConstantSDRAM( - other.fixed - self._sdram) - else: - # The other is more complex so delegate to it - return other - self - @overrides(AbstractSDRAM.report) def report(self, timesteps: Optional[int], indent: str = "", preamble: str = "", target: Optional[TextIO] = None): print(indent, preamble, f"Constant {self._sdram} bytes", file=target) + + @property + @overrides(AbstractSDRAM.short_str) + def short_str(self) -> str: + return f"fixed: {self._sdram}" diff --git a/pacman/model/resources/multi_region_sdram.py b/pacman/model/resources/multi_region_sdram.py index 4c546fcc7..89a022412 100644 --- a/pacman/model/resources/multi_region_sdram.py +++ b/pacman/model/resources/multi_region_sdram.py @@ -14,7 +14,7 @@ from __future__ import annotations from enum import Enum import math -from typing import Dict, Optional, TextIO, Union +from typing import Any, Dict, Optional, TextIO, Union import numpy from typing_extensions import TypeAlias @@ -33,7 +33,7 @@ def _ceil(value: _Value) -> int: return math.ceil(value) -class MultiRegionSDRAM(VariableSDRAM): +class MultiRegionSDRAM(AbstractSDRAM): """ A resource for SDRAM that comes in regions. @@ -48,16 +48,18 @@ class MultiRegionSDRAM(VariableSDRAM): __slots__ = ( # The regions of SDRAM, each of which is an AbstractSDRAM - "__regions", ) + "__regions", + # The total cost of all the regions + "_total") def __init__(self) -> None: - super().__init__(0, 0) self.__regions: Dict[_RegionKey, AbstractSDRAM] = {} + self._total: AbstractSDRAM = ConstantSDRAM(0) @property def regions(self): """ - The map from region identifiers to the to the amount of SDRAM required. + The map from region identifiers to the amount of SDRAM required. :rtype: dict(int or str or enum, AbstractSDRAM) """ @@ -75,18 +77,11 @@ def add_cost(self, region: _RegionKey, fixed_sdram: _Value, :param per_timestep_sdram: The variable cost for this region is any :type per_timestep_sdram: int or numpy.integer """ - self._fixed_sdram += _ceil(fixed_sdram) - self._per_timestep_sdram += _ceil(per_timestep_sdram) - sdram: AbstractSDRAM if per_timestep_sdram: - sdram = VariableSDRAM( - _ceil(fixed_sdram), _ceil(per_timestep_sdram)) + self.nest(region, VariableSDRAM( + _ceil(fixed_sdram), _ceil(per_timestep_sdram))) else: - sdram = ConstantSDRAM(_ceil(fixed_sdram)) - if region in self.__regions: - self.__regions[region] += sdram - else: - self.__regions[region] = sdram + self.nest(region, ConstantSDRAM(_ceil(fixed_sdram))) def nest(self, region: _RegionKey, other: AbstractSDRAM): """ @@ -102,15 +97,14 @@ def nest(self, region: _RegionKey, other: AbstractSDRAM): :param AbstractSDRAM other: Another SDRAM model to make combine by nesting """ - self._fixed_sdram += other.fixed - self._per_timestep_sdram += other.per_timestep + self._total += other if region in self.__regions: if isinstance(other, MultiRegionSDRAM): r = self.__regions[region] if isinstance(r, MultiRegionSDRAM): r.merge(other) else: - other.add_cost(region, r.fixed, r.per_timestep) + other.nest(region, r) self.__regions[region] = other else: self.__regions[region] += other @@ -127,8 +121,7 @@ def merge(self, other: MultiRegionSDRAM): :param MultiRegionSDRAM other: Another mapping of costs by region """ - self._fixed_sdram += other.fixed - self._per_timestep_sdram += other.per_timestep + self._total += other for region in other.regions: if region in self.regions: self.__regions[region] += other.regions[region] @@ -138,7 +131,52 @@ def merge(self, other: MultiRegionSDRAM): @overrides(AbstractSDRAM.report) def report(self, timesteps: Optional[int], indent: str = "", preamble: str = "", target: Optional[TextIO] = None): - super().report(timesteps, indent, preamble, target) + self._total.report(timesteps, indent, preamble, target) for region in self.__regions: self.__regions[region].report( timesteps, indent+" ", str(region)+":", target) + + def get_total_sdram(self, n_timesteps: Optional[int]) -> int: + """ + The total SDRAM. + + :param int n_timesteps: number of timesteps to cost for + :return: + """ + return self._total.get_total_sdram(n_timesteps) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, MultiRegionSDRAM): + return self._total == other._total + return self._total == other + + def __add__(self, other: AbstractSDRAM) -> AbstractSDRAM: + """ + Combines this SDRAM resource with the other one and creates a new one. + + :param other: another SDRAM resource + :return: a New AbstractSDRAM + """ + return self._total + other + + @property + def fixed(self) -> int: + """ + The fixed SDRAM cost. + """ + return self._total.fixed + + @property + def per_timestep(self) -> float: + """ + The extra SDRAM cost for each additional timestep. + + .. warning:: + May well be zero. + """ + return self._total.per_timestep + + @property + @overrides(AbstractSDRAM.short_str) + def short_str(self) -> str: + return f"Multi:{self._total.short_str}" diff --git a/pacman/model/resources/shared_sdram.py b/pacman/model/resources/shared_sdram.py new file mode 100644 index 000000000..23966c5c7 --- /dev/null +++ b/pacman/model/resources/shared_sdram.py @@ -0,0 +1,136 @@ +# Copyright (c) 2017 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +from typing import Any, Dict, Optional, TextIO, Union + +import numpy + +from spinn_utilities.overrides import overrides +from pacman.exceptions import PacmanConfigurationException +from .abstract_sdram import AbstractSDRAM +from .constant_sdram import ConstantSDRAM +from .variable_sdram import VariableSDRAM + + +def _ceil(value: Union[int, float, numpy.integer, numpy.floating]) -> int: + return math.ceil(value) + + +class SharedSDRAM(AbstractSDRAM): + """ + Represents an amount of SDRAM used on a chip in the machine. + + This is split into cost for each core and ones for each chip + """ + + __slots__ = ( + # The amount of SDRAM per core + "_per_core", + # Map of extra shared SDRAM per Chip + "_shared" + ) + + def __init__(self, shared: Dict[str, AbstractSDRAM], + per_core: Optional[AbstractSDRAM] = None) -> None: + """ + Creates an SDRAM of both per_core and shared requirements. + + .. note:: + Each key must map GLOBALLY to the same SDRAM (or an equal one) + It is recommended that a vertex use its label at the start the key + + :param shared: + The sdram that will be allocated ONCE per Chip + This is a dict of keys to sdram + :param per_core: + The amount of SDRAM that will be needed on every core. + """ + # create a shallow copy + self._shared = shared.copy() + if per_core is None: + self._per_core: AbstractSDRAM = ConstantSDRAM(0) + else: + self._per_core = per_core + + @overrides(AbstractSDRAM.get_total_sdram) + def get_total_sdram(self, n_timesteps: Optional[int]) -> int: + running = self._per_core.get_total_sdram(n_timesteps) + for sdram in self._shared.values(): + running += sdram.get_total_sdram(n_timesteps) + return running + + @property + @overrides(AbstractSDRAM.fixed) + def fixed(self) -> int: + running = self._per_core.fixed + for sdram in self._shared.values(): + running += sdram.fixed + return running + + @property + @overrides(AbstractSDRAM.per_timestep) + def per_timestep(self) -> float: + running = self._per_core.per_timestep + for sdram in self._shared.values(): + running += sdram.per_timestep + return running + + @overrides(AbstractSDRAM.__eq__) + def __eq__(self, other: Any) -> bool: + if not isinstance(other, SharedSDRAM): + return False + if self._per_core != other._per_core: + return False + return self._shared == other._shared + + @overrides(AbstractSDRAM.__add__) + def __add__(self, other: AbstractSDRAM) -> AbstractSDRAM: + if isinstance(other, (ConstantSDRAM, VariableSDRAM)): + return SharedSDRAM(self._shared, self._per_core + other) + elif isinstance(other, SharedSDRAM): + shared = self._shared.copy() + for key, value in other._shared.items(): + if key in shared: + if value != shared[key]: + raise PacmanConfigurationException( + f"Shared {key} has different values") + else: + shared[key] = value + return SharedSDRAM(shared, self._per_core) + else: + # MultiRegionSDRAM + return other + self + + @overrides(AbstractSDRAM.report) + def report(self, timesteps: Optional[int], indent: str = "", + preamble: str = "", target: Optional[TextIO] = None): + self._per_core.report(timesteps, indent, preamble, target) + for key, sdram in self._shared.items(): + sdram.report(timesteps, indent+" ", key+":", target) + + @property + @overrides(AbstractSDRAM.short_str) + def short_str(self) -> str: + if self._per_core.fixed > 0 or self._per_core.per_timestep > 0: + per_core = f"per-core: {self._per_core.short_str} " + else: + per_core = "" + shared = "" + for key, sdram in self._shared.items(): + if shared == "": + shared = f"shared:{key}: {sdram.short_str}" + else: + shared = f" {key}: {sdram.short_str}" + return per_core + shared diff --git a/pacman/model/resources/variable_sdram.py b/pacman/model/resources/variable_sdram.py index 0dc1419b3..ed874e3da 100644 --- a/pacman/model/resources/variable_sdram.py +++ b/pacman/model/resources/variable_sdram.py @@ -13,13 +13,14 @@ # limitations under the License. import math -from typing import Optional, TextIO, Union +from typing import Any, Optional, TextIO, Union import numpy from spinn_utilities.overrides import overrides from pacman.exceptions import PacmanConfigurationException from .abstract_sdram import AbstractSDRAM +from .constant_sdram import ConstantSDRAM def _ceil(value: Union[int, float, numpy.integer, numpy.floating]) -> int: @@ -72,21 +73,31 @@ def fixed(self) -> int: def per_timestep(self) -> float: return self._per_timestep_sdram - def __add__(self, other: AbstractSDRAM) -> 'VariableSDRAM': - return VariableSDRAM( - self._fixed_sdram + other.fixed, - self._per_timestep_sdram + other.per_timestep) - - def __sub__(self, other: AbstractSDRAM) -> 'VariableSDRAM': - return VariableSDRAM( - self._fixed_sdram - other.fixed, - self._per_timestep_sdram - other.per_timestep) - - @overrides(AbstractSDRAM.sub_from) - def sub_from(self, other: AbstractSDRAM) -> 'VariableSDRAM': - return VariableSDRAM( - other.fixed - self._fixed_sdram, - other.per_timestep - self._per_timestep_sdram) + def __eq__(self, other: Any) -> bool: + if isinstance(other, VariableSDRAM): + if other.fixed != self.fixed: + return False + else: + return other.per_timestep == self.per_timestep + elif isinstance(other, ConstantSDRAM): + if self._per_timestep_sdram != 0: + return False + else: + return other.fixed == self.fixed + else: + return False + + def __add__(self, other: AbstractSDRAM) -> AbstractSDRAM: + if isinstance(other, ConstantSDRAM): + return VariableSDRAM( + self._fixed_sdram + other.fixed, self._per_timestep_sdram) + elif isinstance(other, VariableSDRAM): + return VariableSDRAM( + self._fixed_sdram + other.fixed, + self._per_timestep_sdram + other.per_timestep) + else: + # SharedSDRAM, MultiRegionSDRAM + return other + self @overrides(AbstractSDRAM.report) def report(self, timesteps: Optional[int], indent: str = "", @@ -95,3 +106,9 @@ def report(self, timesteps: Optional[int], indent: str = "", f"Fixed {self._fixed_sdram} bytes " f"Per_timestep {self._per_timestep_sdram} bytes " f"for a total of {self.get_total_sdram(timesteps)}", file=target) + + @property + @overrides(AbstractSDRAM.short_str) + def short_str(self) -> str: + return (f"fixed:{self._fixed_sdram} " + f"per_timestep:{self._per_timestep_sdram}") diff --git a/pacman/operations/placer_algorithms/application_placer.py b/pacman/operations/placer_algorithms/application_placer.py index c88bfbbcc..cbad547a3 100644 --- a/pacman/operations/placer_algorithms/application_placer.py +++ b/pacman/operations/placer_algorithms/application_placer.py @@ -27,7 +27,7 @@ from pacman.model.graphs import AbstractVirtual from pacman.model.graphs.machine import MachineVertex from pacman.model.graphs.application import ApplicationVertex -from pacman.model.resources import AbstractSDRAM +from pacman.model.resources import AbstractSDRAM, ConstantSDRAM from pacman.exceptions import ( PacmanPlaceException, PacmanConfigurationException, PacmanTooBigToPlace) @@ -63,8 +63,8 @@ class ApplicationPlacer(object): "__plan_n_timesteps", # Sdram available on perfect none Ethernet Chip after Monitors placed "__max_sdram", - # Minimum sdram that should be available for a Chip to not be full - "__min_sdram", + # Maximum sdram that should be used for a Chip to not be full + "__cap_sdram", # N Cores free on perfect none Ethernet Chip after Monitors placed "__max_cores", @@ -88,8 +88,8 @@ class ApplicationPlacer(object): "__current_chip", # List of cores available. Included ones for current group until used "__current_cores_free", - # Available sdram after the current group is placed - "__current_sdram_free", + # Used sdram after the current group is placed + "__current_sdram_used", # Data about the neighbouring Chips to ones used # Current board being placed on @@ -116,7 +116,8 @@ def __init__(self, placements: Placements): self.__max_cores = ( version.max_cores_per_chip - version.n_scamp_cores - PacmanDataView.get_all_monitor_cores()) - self.__min_sdram = self.__max_sdram // self.__max_cores + self.__cap_sdram = self.__max_sdram - ( + self.__max_sdram // self.__max_cores) self.__placements = placements self.__chips = self._chip_order() @@ -128,7 +129,7 @@ def __init__(self, placements: Placements): self.__current_chip: Optional[Chip] = None self.__current_cores_free: List[int] = list() - self.__current_sdram_free = 0 + self.__current_sdram_used: AbstractSDRAM = ConstantSDRAM(0) self.__app_vertex_label: Optional[str] = None # Set some value so no Optional needed @@ -239,11 +240,10 @@ def _prepare_placements(self, same_chip_groups: Sequence[ if len(vertices_to_place) == 0: # Either placed (fixed) or virtual so skip group continue - plan_sdram = sdram.get_total_sdram(self.__plan_n_timesteps) n_cores = len(vertices_to_place) # Try to find a chip with space - chip = self._get_next_chip_with_space(n_cores, plan_sdram) + chip = self._get_next_chip_with_space(n_cores, sdram) if chip is None: return None @@ -448,7 +448,7 @@ def _chip_order(self): yield chip def _space_on_chip( - self, chip: Chip, n_cores: int, plan_sdram: int) -> bool: + self, chip: Chip, n_cores: int, sdram: AbstractSDRAM) -> bool: """ Checks if the Chip has enough space for this group, Cache if yes @@ -469,13 +469,13 @@ def _space_on_chip( :param Chip chip: :param int n_cores: number of cores needed - :param int plan_sdram: + :param sdram: :rtype: bool :raises PacmanTooBigToPlace: If the requirements are too big for any chip """ cores_free = list(chip.placable_processors_ids) - sdram_free = chip.sdram + sdram_used: AbstractSDRAM = ConstantSDRAM(0) # remove the already placed for other Application Vertices on_chip = self.__placements.placements_on_chip(chip) @@ -485,10 +485,10 @@ def _space_on_chip( for placement in on_chip: cores_free.remove(placement.p) - sdram_free -= placement.vertex.sdram_required.get_total_sdram( - self.__plan_n_timesteps) + sdram_used += placement.vertex.sdram_required - if sdram_free < self.__min_sdram: + if sdram_used.get_total_sdram( + self.__plan_n_timesteps) > self.__cap_sdram: self.__full_chips.add(chip) return False @@ -496,8 +496,10 @@ def _space_on_chip( # This assumes all groups are the same size so even if too small self.__prepared_chips.add(chip) - if len(cores_free) < n_cores or sdram_free < plan_sdram: - self._check_could_fit(n_cores, plan_sdram) + total_sdram = sdram_used + sdram + plan_sdram = total_sdram.get_total_sdram(self.__plan_n_timesteps) + if len(cores_free) < n_cores or plan_sdram > chip.sdram: + self._check_could_fit(n_cores, sdram) return False # record the current Chip @@ -505,14 +507,14 @@ def _space_on_chip( # cores are popped out later to keep them here for now self.__current_cores_free = cores_free # sdram is the whole group so can be removed now - self.__current_sdram_free = sdram_free - plan_sdram + self.__current_sdram_used = total_sdram # adds the neighbours self._add_neighbours(chip) return True - def _check_could_fit(self, n_cores: int, plan_sdram: int): + def _check_could_fit(self, n_cores: int, sdram: AbstractSDRAM): """ Checks that the cores/SDRAM would fit on a empty perfect Chip @@ -521,6 +523,7 @@ def _check_could_fit(self, n_cores: int, plan_sdram: int): :raises PacmanTooBigToPlace: If the requirements are too big for any chip """ + plan_sdram = sdram.get_total_sdram(self.__plan_n_timesteps) if plan_sdram <= self.__max_sdram and n_cores <= self.__max_cores: # should fit somewhere return @@ -556,14 +559,14 @@ def _check_could_fit(self, n_cores: int, plan_sdram: int): f"are reserved for monitors") raise PacmanTooBigToPlace(message) - def _get_next_start(self, n_cores: int, plan_sdram: int) -> Chip: + def _get_next_start(self, n_cores: int, sdram: AbstractSDRAM) -> Chip: """ Gets the next start Chip Also sets up the current_chip and starts a new neighbourhood :param int n_cores: number of cores needs - :param int plan_sdram: minimum amount of SDRAM needed + :param sdram: minimum amount of SDRAM needed :rtype: Chip :raises PacmanPlaceException: If no new start Chip is available @@ -582,7 +585,7 @@ def _get_next_start(self, n_cores: int, plan_sdram: int) -> Chip: # Set the Ethernet x and y in case space_on_chip adds neighbours self.__ethernet_x = start.nearest_ethernet_x self.__ethernet_y = start.nearest_ethernet_y - if self._space_on_chip(start, n_cores, plan_sdram): + if self._space_on_chip(start, n_cores, sdram): break logger.debug("Starting placement from {}", start) @@ -614,7 +617,7 @@ def _pop_start_chip(self) -> Chip: f"and {len(self.__starts_tried)} tried" f"{PacmanDataView.get_chips_boards_required_str()}") - def _get_next_neighbour(self, n_cores: int, plan_sdram: int): + def _get_next_neighbour(self, n_cores: int, sdram: AbstractSDRAM): """ Gets the next neighbour Chip @@ -623,7 +626,7 @@ def _get_next_neighbour(self, n_cores: int, plan_sdram: int): This will return None if there are no more neighbouring Chip big enough :param int n_cores: number of cores needs - :param int plan_sdram: minimum amount of SDRAM needed + :param sdram: minimum amount of SDRAM needed :rtype: Chip or None :raises PacmanTooBigToPlace: If the requirements are too big for any chip @@ -634,11 +637,11 @@ def _get_next_neighbour(self, n_cores: int, plan_sdram: int): if chip is None: # Sign to consider preparation with this start a failure return None - if self._space_on_chip(chip, n_cores, plan_sdram): + if self._space_on_chip(chip, n_cores, sdram): return chip def _get_next_chip_with_space( - self, n_cores: int, plan_sdram: int) -> Optional[Chip]: + self, n_cores: int, sdram: AbstractSDRAM) -> Optional[Chip]: """ Gets the next Chip with space @@ -646,20 +649,24 @@ def _get_next_chip_with_space( If no neighbouring more Chips available returns None :param int n_cores: number of cores needs - :param int plan_sdram: minimum amount of SDRAM needed + :param sdram: minimum amount of SDRAM needed :raises PacmanPlaceException: If no new start Chip is available :raises PacmanTooBigToPlace: If the requirements are too big for any chip """ if self.__current_chip is None: - return self._get_next_start(n_cores, plan_sdram) - elif (len(self.__current_cores_free) >= n_cores and - self.__current_sdram_free >= plan_sdram): + return self._get_next_start(n_cores, sdram) + + total_sdram = sdram + self.__current_sdram_used + plan_sdram = total_sdram.get_total_sdram( + self.__plan_n_timesteps) + if (len(self.__current_cores_free) >= n_cores and + plan_sdram <= self.__current_chip.sdram): # Cores are popped out later - self.__current_sdram_free -= plan_sdram + self.__current_sdram_used = total_sdram return self.__current_chip else: - return self._get_next_neighbour(n_cores, plan_sdram) + return self._get_next_neighbour(n_cores, sdram) def _add_neighbours(self, chip: Chip): """ diff --git a/pacman/utilities/json_utils.py b/pacman/utilities/json_utils.py index c69794c90..e59f1a186 100644 --- a/pacman/utilities/json_utils.py +++ b/pacman/utilities/json_utils.py @@ -22,10 +22,10 @@ from spinn_utilities.typing.json import JsonArray, JsonObject from pacman.data import PacmanDataView -from pacman.model.graphs.machine import MachineVertex, SimpleMachineVertex +from pacman.model.graphs.machine import SimpleMachineVertex from pacman.model.placements.placement import Placement from pacman.model.resources import ( - IPtagResource, ReverseIPtagResource, VariableSDRAM) + IPtagResource, ReverseIPtagResource) from pacman.model.routing_info import BaseKeyAndMask @@ -185,47 +185,6 @@ def reverse_iptags_from_json( return iptags -def vertex_to_json(vertex: MachineVertex) -> JsonObject: - """ - Converts a Machine Vertex to json. - - :param MachineVertex vertex: - :rtype: dict(str, object) - """ - json_dict: JsonObject = dict() - try: - json_dict["class"] = vertex.__class__.__name__ - json_dict["label"] = vertex.label - json_dict["fixed_sdram"] = int(vertex.sdram_required.fixed) - json_dict["per_timestep_sdram"] = int( - vertex.sdram_required.per_timestep) - json_dict["iptags"] = iptag_resources_to_json(vertex.iptags) - json_dict["reverse_iptags"] = reverse_iptags_to_json( - vertex.reverse_iptags) - except Exception as ex: # pylint: disable=broad-except - json_dict["exception"] = str(ex) - return json_dict - - -def vertex_from_json(json_dict: JsonObject) -> SimpleMachineVertex: - """ - Creates a simple Vertex based on the json - - :param dict(str, object) json_dict: - :rtype: SimpleMachineVertex - """ - sdram = VariableSDRAM( - cast(int, json_dict["fixed_sdram"]), - cast(float, json_dict["per_timestep_sdram"])) - iptags = iptag_resources_from_json( - cast(List[JsonObject], json_dict["iptags"])) - reverse_iptags = reverse_iptags_from_json( - cast(List[JsonObject], json_dict["reverse_iptags"])) - return SimpleMachineVertex( - sdram=sdram, label=json_dict["label"], iptags=iptags, - reverse_iptags=reverse_iptags) - - def placement_to_json(placement: Placement) -> JsonObject: """ Converts a Placement to json diff --git a/unittests/model_tests/resources_tests/test_resources_model.py b/unittests/model_tests/resources_tests/test_resources_model.py index 825b5fd56..22f511969 100644 --- a/unittests/model_tests/resources_tests/test_resources_model.py +++ b/unittests/model_tests/resources_tests/test_resources_model.py @@ -22,7 +22,7 @@ from pacman.exceptions import PacmanConfigurationException from pacman.model.resources import ( ConstantSDRAM, IPtagResource, MultiRegionSDRAM, ReverseIPtagResource, - VariableSDRAM) + SharedSDRAM, VariableSDRAM) class MockEnum(Enum): @@ -48,28 +48,18 @@ def test_sdram(self): const2 = ConstantSDRAM(256) combo = const1 + const2 self.assertEqual(combo.get_total_sdram(None), 128+256) - combo = const1 - const2 - self.assertEqual(combo.get_total_sdram(None), 128-256) combo = const2 + const1 self.assertEqual(combo.get_total_sdram(None), 256+128) - combo = const2 - const1 - self.assertEqual(combo.get_total_sdram(None), 256-128) var1 = VariableSDRAM(124, 8) self.assertEqual(var1.get_total_sdram(100), 124 + 8 * 100) combo = var1 + const1 self.assertEqual(combo.get_total_sdram(100), 124 + 8 * 100 + 128) - combo = var1 - const1 - self.assertEqual(combo.get_total_sdram(100), 124 + 8 * 100 - 128) combo = const1 + var1 self.assertEqual(combo.get_total_sdram(100), 128 + 124 + 8 * 100) - combo = const1 - var1 - self.assertEqual(combo.get_total_sdram(100), 128 - (124 + 8 * 100)) var2 = VariableSDRAM(234, 6) combo = var2 + var1 self.assertEqual(combo.get_total_sdram(150), 234 + 124 + (6 + 8) * 150) - combo = var2 - var1 - self.assertEqual(combo.get_total_sdram(150), 234 - 124 + (6 - 8) * 150) multi1 = MultiRegionSDRAM() multi1.add_cost(1, 100, 4) @@ -154,16 +144,6 @@ def test_tags_resources(self): self.assertEqual(riptr, riptr2) self.assertEqual(hash(riptr), hash(riptr2)) - def test_sub(self): - const1 = ConstantSDRAM(128) - const2 = ConstantSDRAM(28) - self.assertEqual(ConstantSDRAM(100), const1 - const2) - self.assertEqual(ConstantSDRAM(100), const2.sub_from(const1)) - var1 = VariableSDRAM(100, 5) - self.assertEqual(VariableSDRAM(28, -5), const1 - var1) - self.assertEqual(VariableSDRAM(-28, 5), var1 - const1) - self.assertEqual(VariableSDRAM(-28, 5), const1.sub_from(var1)) - def test_total(self): var0 = VariableSDRAM(28, 0) self.assertEqual(28, var0.get_total_sdram(None)) @@ -171,6 +151,81 @@ def test_total(self): with self.assertRaises(PacmanConfigurationException): var4.get_total_sdram(None) + def test_shared(self): + var1 = VariableSDRAM(20, 1) + sh1 = SharedSDRAM({"foo": var1}) + sh1.report(10) + str(sh1) + self.assertEqual(sh1.get_total_sdram(5), 25) + combo1 = sh1 + sh1 + self.assertEqual(combo1.get_total_sdram(5), 25) + self.assertEqual(combo1, sh1) + combo2 = var1 + var1 + self.assertEqual(combo2.get_total_sdram(5), 50) + con1 = ConstantSDRAM(12) + combo3 = sh1 + con1 + self.assertEqual(combo3.get_total_sdram(5), 37) + combo4 = con1 + sh1 + self.assertEqual(combo4.get_total_sdram(5), 37) + self.assertEqual(combo3, combo4) + + def test_sdram_multi(self): + multi1 = MultiRegionSDRAM() + multi1.add_cost(1, 100, 4) + sh1 = SharedSDRAM({"foo": multi1}) + self.assertEqual(sh1.get_total_sdram(10), 100 + 4 * 10) + + multi2 = MultiRegionSDRAM() + var2 = VariableSDRAM(20, 1) + sh2 = SharedSDRAM({"bar": var2}) + multi2.nest(2, sh2) + self.assertEqual(multi2.get_total_sdram(10), 20 + 10) + + combo = sh1 + sh2 + self.assertEqual(combo.get_total_sdram(10), 100 + 4 * 10 + 20 + 10) + + def test_nested_shared(self): + # nested sdram do not make sense but do work + # all but the outer sdram acts like a non shared sdram + c1 = ConstantSDRAM(45) + sh1 = SharedSDRAM({"foo": c1}) + sh2 = SharedSDRAM({"bar": sh1}) + self.assertEqual(sh2.get_total_sdram(None), 45) + + def test_reused_key(self): + var1 = VariableSDRAM(20, 1) + sh1 = SharedSDRAM({"foo": var1}) + var2 = VariableSDRAM(20, 1) + sh2 = SharedSDRAM({"foo": var2}) + + v_sum = var1 + var2 + self.assertEqual(v_sum.get_total_sdram(10), 2 * (20 + 10)) + + # same shared entered more than once is the same as entered once + combo = sh1 + sh2 + self.assertEqual(combo.get_total_sdram(10), 20 + 10) + + # Same share inside a multiple is NOT summed! + multi = MultiRegionSDRAM() + multi.nest(1, sh1) + multi.nest(2, sh1) + self.assertEqual(combo.get_total_sdram(10), 20 + 10) + + var3 = VariableSDRAM(30, 2) + # reusing key with different values is HIGHLy discouraged + sh3 = SharedSDRAM({"foo": var3}) + + # But will go boom it the shared are combined. + # Remember this will happen is placed on same Chip + with self.assertRaises(PacmanConfigurationException): + sh1 + sh3 + + sh4 = SharedSDRAM({"bar": var3}) + multi4 = MultiRegionSDRAM() + multi4.nest(1, sh1) + multi4.nest(2, sh4) + self.assertEqual(multi4.get_total_sdram(10), 20 + 10 + 30 + 2 * 10) + if __name__ == '__main__': unittest.main() diff --git a/unittests/operations_tests/placer_algorithms_tests/test_application_placer.py b/unittests/operations_tests/placer_algorithms_tests/test_application_placer.py index 5532a842b..fc57f7d40 100644 --- a/unittests/operations_tests/placer_algorithms_tests/test_application_placer.py +++ b/unittests/operations_tests/placer_algorithms_tests/test_application_placer.py @@ -236,7 +236,8 @@ def test_sdram_bigger_monitors(): writer.add_sample_monitor_vertex(monitor, True) try: placer = ApplicationPlacer(Placements()) - placer._check_could_fit(1, plan_sdram=max_sdram // 2 + 5) + sdram = ConstantSDRAM(max_sdram // 2 + 5) + placer._check_could_fit(1, sdram=sdram) raise AssertionError("Error not raise") except PacmanTooBigToPlace as ex: assert ("after monitors only" in str(ex)) @@ -278,7 +279,7 @@ def test_more_cores_with_monitor(): many = writer.get_machine_version().max_cores_per_chip - 1 try: placer = ApplicationPlacer(Placements()) - placer._check_could_fit(many, 500000) + placer._check_could_fit(many, ConstantSDRAM(500000)) raise AssertionError("Error not raise") except PacmanTooBigToPlace as ex: assert ("reserved for monitors" in str(ex)) @@ -292,4 +293,4 @@ def test_could_fit(): writer.add_sample_monitor_vertex(monitor, True) placer = ApplicationPlacer(Placements()) many = writer.get_machine_version().max_cores_per_chip - 2 - placer._check_could_fit(many, 500000) + placer._check_could_fit(many, ConstantSDRAM(500000)) diff --git a/unittests/utilities_tests/test_json_utils.py b/unittests/utilities_tests/test_json_utils.py index 95bfc4543..0711555f4 100644 --- a/unittests/utilities_tests/test_json_utils.py +++ b/unittests/utilities_tests/test_json_utils.py @@ -18,7 +18,7 @@ from pacman.model.resources import ( ConstantSDRAM, IPtagResource, ReverseIPtagResource) from pacman.utilities.json_utils import ( - placement_from_json, placement_to_json, vertex_to_json, vertex_from_json) + placement_from_json, placement_to_json) from pacman.model.graphs.machine import SimpleMachineVertex @@ -59,13 +59,6 @@ def _compare_placement(self, p1, p2, seen=None): # Composite JSON round-trip testing schemes # ------------------------------------------------------------------ - def vertex_there_and_back(self, there): - j_object = vertex_to_json(there) - j_str = json.dumps(j_object) - j_object2 = json.loads(j_str) - back = vertex_from_json(j_object2) - self._compare_vertex(there, back) - def placement_there_and_back(self, there): j_object = placement_to_json(there) j_str = json.dumps(j_object) @@ -77,14 +70,6 @@ def placement_there_and_back(self, there): # Test cases # ------------------------------------------------------------------ - def test_vertex(self): - s1 = SimpleMachineVertex( - sdram=ConstantSDRAM(0), - iptags=[IPtagResource("127.0.0.1", port=None, strip_sdp=True)], - reverse_iptags=[ReverseIPtagResource(port=25, sdp_port=2, tag=5)], - label="Vertex") - self.vertex_there_and_back(s1) - def test_placement(self): s1 = SimpleMachineVertex( sdram=ConstantSDRAM(0),