Facebook
From Shaikot, 10 Months ago, written in Python.
Embed
Download Paste or View Raw
Hits: 119
  1. import random
  2.  
  3.  
  4. class Space():
  5.  
  6.     def __init__(self, height, width, num_hospitals):
  7.         """Create a new state space with given dimensions."""
  8.         self.height = height
  9.         self.width = width
  10.         self.num_hospitals = num_hospitals
  11.         self.houses = set()
  12.         self.hospitals = set()
  13.  
  14.     def add_house(self, row, col):
  15.         """Add a house at a particular location in state space."""
  16.         self.houses.add((row, col))
  17.  
  18.     def available_spaces(self):
  19.         """Returns all cells not currently used by a house or hospital."""
  20.  
  21.         # Consider all possible cells
  22.         candidates = set(
  23.             (row, col)
  24.             for row in range(self.height)
  25.             for col in range(self.width)
  26.         )
  27.  
  28.         # Remove all houses and hospitals
  29.         for house in self.houses:
  30.             candidates.remove(house)
  31.         for hospital in self.hospitals:
  32.             candidates.remove(hospital)
  33.         return candidates
  34.  
  35.     def hill_climb(self, maximum=None, image_prefix=None, log=False):
  36.         """Performs hill-climbing to find a solution."""
  37.         count = 0
  38.  
  39.         # Start by initializing hospitals randomly
  40.         self.hospitals = set()
  41.         for i in range(self.num_hospitals):
  42.             self.hospitals.add(random.choice(list(self.available_spaces())))
  43.         if log:
  44.             print("Initial state: cost", self.get_cost(self.hospitals))
  45.         if image_prefix:
  46.             self.output_image(f"{image_prefix}{str(count).zfill(3)}.png")
  47.  
  48.         # Continue until we reach maximum number of iterations
  49.         while maximum is None or count < maximum:
  50.             count += 1
  51.             best_neighbors = []
  52.             best_neighbor_cost = None
  53.  
  54.             # Consider all hospitals to move
  55.             for hospital in self.hospitals:
  56.  
  57.                 # Consider all neighbors for that hospital
  58.                 for replacement in self.get_neighbors(*hospital):
  59.  
  60.                     # Generate a neighboring set of hospitals
  61.                     neighbor = self.hospitals.copy()
  62.                     neighbor.remove(hospital)
  63.                     neighbor.add(replacement)
  64.  
  65.                     # Check if neighbor is best so far
  66.                     cost = self.get_cost(neighbor)
  67.                     if best_neighbor_cost is None or cost < best_neighbor_cost:
  68.                         best_neighbor_cost = cost
  69.                         best_neighbors = [neighbor]
  70.                     elif best_neighbor_cost == cost:
  71.                         best_neighbors.append(neighbor)
  72.  
  73.             # None of the neighbors are better than the current state
  74.             if best_neighbor_cost >= self.get_cost(self.hospitals):
  75.                 return self.hospitals
  76.  
  77.             # Move to a highest-valued neighbor
  78.             else:
  79.                 if log:
  80.                     print(f"Found better neighbor: cost {best_neighbor_cost}")
  81.                 self.hospitals = random.choice(best_neighbors)
  82.  
  83.             # Generate image
  84.             if image_prefix:
  85.                 self.output_image(f"{image_prefix}{str(count).zfill(3)}.png")
  86.  
  87.     def random_restart(self, maximum, image_prefix=None, log=False):
  88.         """Repeats hill-climbing multiple times."""
  89.         best_hospitals = None
  90.         best_cost = None
  91.  
  92.         # Repeat hill-climbing a fixed number of times
  93.         for i in range(maximum):
  94.             hospitals = self.hill_climb()
  95.             cost = self.get_cost(hospitals)
  96.             if best_cost is None or cost < best_cost:
  97.                 best_cost = cost
  98.                 best_hospitals = hospitals
  99.                 if log:
  100.                     print(f"{i}: Found new best state: cost {cost}")
  101.             else:
  102.                 if log:
  103.                     print(f"{i}: Found state: cost {cost}")
  104.  
  105.             if image_prefix:
  106.                 self.output_image(f"{image_prefix}{str(i).zfill(3)}.png")
  107.  
  108.         return best_hospitals
  109.  
  110.     def get_cost(self, hospitals):
  111.         """Calculates sum of distances from houses to nearest hospital."""
  112.         cost = 0
  113.         for house in self.houses:
  114.             cost += min(
  115.                 abs(house[0] - hospital[0]) + abs(house[1] - hospital[1])
  116.                 for hospital in hospitals
  117.             )
  118.         return cost
  119.  
  120.     def get_neighbors(self, row, col):
  121.         """Returns neighbors not already containing a house or hospital."""
  122.         candidates = [
  123.             (row - 1, col),
  124.             (row + 1, col),
  125.             (row, col - 1),
  126.             (row, col + 1)
  127.         ]
  128.         neighbors = []
  129.         for r, c in candidates:
  130.             if (r, c) in self.houses or (r, c) in self.hospitals:
  131.                 continue
  132.             if 0 <= r < self.height and 0 <= c < self.width:
  133.                 neighbors.append((r, c))
  134.         return neighbors
  135.  
  136.     def output_image(self, filename):
  137.         """Generates image with all houses and hospitals."""
  138.         from PIL import Image, ImageDraw, ImageFont
  139.         cell_size = 100
  140.         cell_border = 2
  141.         cost_size = 40
  142.         padding = 10
  143.  
  144.         # Create a blank canvas
  145.         img = Image.new(
  146.             "RGBA",
  147.             (self.width * cell_size,
  148.              self.height * cell_size + cost_size + padding * 2),
  149.             "white"
  150.         )
  151.         house = Image.open("assets/images/House.png").resize(
  152.             (cell_size, cell_size)
  153.         )
  154.         hospital = Image.open("assets/images/Hospital.png").resize(
  155.             (cell_size, cell_size)
  156.         )
  157.          f 30)
  158.         draw = ImageDraw.Draw(img)
  159.  
  160.         for i in range(self.height):
  161.             for j in range(self.width):
  162.  
  163.                 # Draw cell
  164.                 rect = [
  165.                     (j * cell_size + cell_border,
  166.                      i * cell_size + cell_border),
  167.                     ((j + 1) * cell_size - cell_border,
  168.                      (i + 1) * cell_size - cell_border)
  169.                 ]
  170.                 draw.rectangle(rect, fill="black")
  171.  
  172.                 if (i, j) in self.houses:
  173.                     img.paste(house, rect[0], house)
  174.                 if (i, j) in self.hospitals:
  175.                     img.paste(hospital, rect[0], hospital)
  176.  
  177.         # Add cost
  178.         draw.rectangle(
  179.             (0, self.height * cell_size, self.width * cell_size,
  180.              self.height * cell_size + cost_size + padding * 2),
  181.             "black"
  182.         )
  183.         draw.text(
  184.             (padding, self.height * cell_size + padding),
  185.             f"Cost: {self.get_cost(self.hospitals)}",
  186.             fill="white",
  187.              f
  188.         )
  189.  
  190.         img.save(filename)
  191.  
  192.  
  193. # Create a new space and add houses randomly
  194. s = Space(height=10, width=20, num_hospitals=3)
  195. for i in range(15):
  196.     s.add_house(random.randrange(s.height), random.randrange(s.width))
  197.  
  198. # Use local search to determine hospital placement
  199. hospitals = s.hill_climb(image_prefix="hospitals", log=True)
  200.