예제 #1
0
def _tile_search2(Jobs, X, Y, cfg=config.Config):
  global _CkpointTime, _Placements, _TBestTiling, _TBestScore

  r = random.Random()
  N = len(Jobs)

  # M is the number of jobs that will be placed randomly.
  # N-M is the number of jobs that will be searched exhaustively.
  M = N - config.RandomSearchExhaustiveJobs
  M = max(M,0)

  xspacing = cfg['xspacing']
  yspacing = cfg['yspacing']
  
  # Must escape with Ctrl-C
  while 1:
    T = tiling.Tiling(X,Y)
    joborder = r.sample(range(N), N)

    minInletSize = tiling.minDimension(Jobs)

    for ix in joborder[:M]:
      Xdim,Ydim,job,rjob = Jobs[ix]
      
      T.removeInlets(minInletSize)

      if r.choice([0,1]):
        addpoints = T.validAddPoints(Xdim+xspacing,Ydim+yspacing)
        if not addpoints:
          break

        pt = r.choice(addpoints)
        T.addJob(pt, Xdim+xspacing, Ydim+yspacing, job)
      else:
        addpoints = T.validAddPoints(Ydim+xspacing,Xdim+yspacing)
        if not addpoints:
          break

        pt = r.choice(addpoints)
        T.addJob(pt, Ydim+xspacing, Xdim+yspacing, rjob)
    else:
      # Do exhaustive search on remaining jobs
      if N-M:
        remainingJobs = []
        for ix in joborder[M:]:
          remainingJobs.append(Jobs[ix])

        tilesearch1.initialize(0)
        tilesearch1._tile_search1(remainingJobs, T, 1)
        T = tilesearch1.bestTiling()

      if T:
        score = T.area()

        if score < _TBestScore:
          _TBestTiling,_TBestScore = T,score
        elif score == _TBestScore:
          if T.corners() < _TBestTiling.corners():
            _TBestTiling,_TBestScore = T,score

    _Placements += 1
      
    # If we've been at this for 3 seconds, print some status information
    if time.time() > _CkpointTime:
      printTilingStats()
예제 #2
0
def _tile_search2(Jobs, X, Y, cfg=config.Config):
    global _CkpointTime, _Placements, _TBestTiling, _TBestScore

    r = random.Random()
    N = len(Jobs)

    # M is the number of jobs that will be placed randomly.
    # N-M is the number of jobs that will be searched exhaustively.
    M = N - config.RandomSearchExhaustiveJobs
    M = max(M, 0)

    xspacing = cfg['xspacing']
    yspacing = cfg['yspacing']

    # Must escape with Ctrl-C
    while 1:
        T = tiling.Tiling(X, Y)
        joborder = r.sample(range(N), N)

        minInletSize = tiling.minDimension(Jobs)

        for ix in joborder[:M]:
            Xdim, Ydim, job, rjob = Jobs[ix]

            T.removeInlets(minInletSize)

            if r.choice([0, 1]):
                addpoints = T.validAddPoints(Xdim + xspacing, Ydim + yspacing)
                if not addpoints:
                    break

                pt = r.choice(addpoints)
                T.addJob(pt, Xdim + xspacing, Ydim + yspacing, job)
            else:
                addpoints = T.validAddPoints(Ydim + xspacing, Xdim + yspacing)
                if not addpoints:
                    break

                pt = r.choice(addpoints)
                T.addJob(pt, Ydim + xspacing, Xdim + yspacing, rjob)
        else:
            # Do exhaustive search on remaining jobs
            if N - M:
                remainingJobs = []
                for ix in joborder[M:]:
                    remainingJobs.append(Jobs[ix])

                tilesearch1.initialize(0)
                tilesearch1._tile_search1(remainingJobs, T, 1)
                T = tilesearch1.bestTiling()

            if T:
                score = T.area()

                if score < _TBestScore:
                    _TBestTiling, _TBestScore = T, score
                elif score == _TBestScore:
                    if T.corners() < _TBestTiling.corners():
                        _TBestTiling, _TBestScore = T, score

        _Placements += 1

        # If we've been at this for 3 seconds, print some status information
        if time.time() > _CkpointTime:
            printTilingStats()

            # Check for timeout - changed to file config
            if (config.Config['searchtimeout'] > 0) and (
                (time.time() - _StartTime) > config.Config['searchtimeout']):
                raise KeyboardInterrupt

        gerbmerge.updateGUI("Performing automatic layout...")
예제 #3
0
def _tile_search1(Jobs, TSoFar, firstAddPoint, cfg=config.Config):
  """This recursive function does the following with an existing tiling TSoFar:
     
     * For each 4-tuple (Xdim,Ydim,job,rjob) in Jobs, the non-rotated 'job' is selected
     
     * For the non-rotated job, the list of valid add-points is found

     * For each valid add-point, the job is placed at this point in a new,
       cloned tiling.

     * The function then calls its recursively with the remaining list of
       jobs.

     * The rotated job is then selected and the list of valid add-points is
       found. Again, for each valid add-point the job is placed there in
       a new, cloned tiling.

     * Once again, the function calls itself recursively with the remaining
       list of jobs.

     * The best tiling encountered from all recursive calls is returned.

     If TSoFar is None it means this combination of jobs is not tileable.

     The side-effect of this function is to set _TBestTiling and _TBestScore
     to the best tiling encountered so far. _TBestTiling could be None if
     no valid tilings have been found so far.
  """
  global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore, _Permutations, _PrintStats

  if not TSoFar:
    return (None, float(sys.maxint))

  if not Jobs:
    # Update the best tiling and score. If the new tiling matches
    # the best score so far, compare on number of corners, trying to
    # minimize them.
    score = TSoFar.area()

    if score < _TBestScore:
      _TBestTiling,_TBestScore = TSoFar,score
    elif score == _TBestScore:
      if TSoFar.corners() < _TBestTiling.corners():
        _TBestTiling,_TBestScore = TSoFar,score

    _Placements += 1
    if firstAddPoint:
      _Permutations += 1
    return

  xspacing = cfg['xspacing']
  yspacing = cfg['yspacing']

  minInletSize = tiling.minDimension(Jobs)
  TSoFar.removeInlets(minInletSize)

  for job_ix in range(len(Jobs)):
    # Pop off the next job and construct remaining_jobs, a sub-list
    # of Jobs with the job we've just popped off excluded.
    Xdim,Ydim,job,rjob = Jobs[job_ix]
    remaining_jobs = Jobs[:job_ix]+Jobs[job_ix+1:]

    if 0:
      print "Level %d (%s)" % (level, job.name)
      TSoFar.joblist()
      for J in remaining_jobs:
          print J[2].name, ", ",
      print
      print '-'*75

    # Construct add-points for the non-rotated and rotated job.
    # As an optimization, do not construct add-points for the rotated
    # job if the job is a square (duh).
    addpoints1 = TSoFar.validAddPoints(Xdim+xspacing,Ydim+yspacing)     # unrotated job
    if Xdim != Ydim:
      addpoints2 = TSoFar.validAddPoints(Ydim+xspacing,Xdim+yspacing)   # rotated job
    else:
      addpoints2 = []

    # Recursively construct tilings for the non-rotated job and
    # update the best-tiling-so-far as we do so.
    if addpoints1:
      for ix in addpoints1:
        # Clone the tiling we're starting with and add the job at this
        # add-point.
        T = TSoFar.clone()
        T.addJob(ix, Xdim+xspacing, Ydim+yspacing, job)

        # Recursive call with the remaining jobs and this new tiling. The
        # point behind the last parameter is simply so that _Permutations is
        # only updated once for each permutation, not once per add-point.
        # A permutation is some ordering of jobs (N! choices) and some
        # ordering of non-rotated and rotated within that ordering (2**N
        # possibilities per ordering).
        _tile_search1(remaining_jobs, T, firstAddPoint and ix==addpoints1[0])
    elif firstAddPoint:
      # Premature prune due to not being able to put this job anywhere. We
      # have pruned off 2^M permutations where M is the length of the remaining
      # jobs.
      _Permutations += 2L**len(remaining_jobs)

    if addpoints2:
      for ix in addpoints2:
        # Clone the tiling we're starting with and add the job at this
        # add-point. Remember that the job is rotated so swap X and Y
        # dimensions.
        T = TSoFar.clone()
        T.addJob(ix, Ydim+xspacing, Xdim+yspacing, rjob)

        # Recursive call with the remaining jobs and this new tiling.
        _tile_search1(remaining_jobs, T, firstAddPoint and ix==addpoints2[0])
    elif firstAddPoint:
      # Premature prune due to not being able to put this job anywhere. We
      # have pruned off 2^M permutations where M is the length of the remaining
      # jobs.
      _Permutations += 2L**len(remaining_jobs)

    # If we've been at this for 3 seconds, print some status information
    if _PrintStats and time.time() > _CkpointTime:
      printTilingStats()
      
      # Check for timeout - changed to file config
      if (config.Config['searchtimeout'] > 0) and ((time.time() - _StartTime) > config.Config['searchtimeout']):
        raise KeyboardInterrupt 
        
    gerbmerge.updateGUI("Performing automatic layout...")        
예제 #4
0
def _tile_search1(Jobs, TSoFar, firstAddPoint, cfg=config.Config):
  """This recursive function does the following with an existing tiling TSoFar:
     
     * For each 4-tuple (Xdim,Ydim,job,rjob) in Jobs, the non-rotated 'job' is selected
     
     * For the non-rotated job, the list of valid add-points is found

     * For each valid add-point, the job is placed at this point in a new,
       cloned tiling.

     * The function then calls its recursively with the remaining list of
       jobs.

     * The rotated job is then selected and the list of valid add-points is
       found. Again, for each valid add-point the job is placed there in
       a new, cloned tiling.

     * Once again, the function calls itself recursively with the remaining
       list of jobs.

     * The best tiling encountered from all recursive calls is returned.

     If TSoFar is None it means this combination of jobs is not tileable.

     The side-effect of this function is to set _TBestTiling and _TBestScore
     to the best tiling encountered so far. _TBestTiling could be None if
     no valid tilings have been found so far.
  """
  global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore, _Permutations, _PrintStats

  if not TSoFar:
    return (None, float(sys.maxint))

  if not Jobs:
    # Update the best tiling and score. If the new tiling matches
    # the best score so far, compare on number of corners, trying to
    # minimize them.
    score = TSoFar.area()

    if score < _TBestScore:
      _TBestTiling,_TBestScore = TSoFar,score
    elif score == _TBestScore:
      if TSoFar.corners() < _TBestTiling.corners():
        _TBestTiling,_TBestScore = TSoFar,score

    _Placements += 1
    if firstAddPoint:
      _Permutations += 1
    return

  xspacing = cfg['xspacing']
  yspacing = cfg['yspacing']

  minInletSize = tiling.minDimension(Jobs)
  TSoFar.removeInlets(minInletSize)

  for job_ix in range(len(Jobs)):
    # Pop off the next job and construct remaining_jobs, a sub-list
    # of Jobs with the job we've just popped off excluded.
    Xdim,Ydim,job,rjob = Jobs[job_ix]
    remaining_jobs = Jobs[:job_ix]+Jobs[job_ix+1:]

    if 0:
      print "Level %d (%s)" % (level, job.name)
      TSoFar.joblist()
      for J in remaining_jobs:
          print J[2].name, ", ",
      print
      print '-'*75

    # Construct add-points for the non-rotated and rotated job.
    # As an optimization, do not construct add-points for the rotated
    # job if the job is a square (duh).
    addpoints1 = TSoFar.validAddPoints(Xdim+xspacing,Ydim+yspacing)     # unrotated job
    if Xdim != Ydim:
      addpoints2 = TSoFar.validAddPoints(Ydim+xspacing,Xdim+yspacing)   # rotated job
    else:
      addpoints2 = []

    # Recursively construct tilings for the non-rotated job and
    # update the best-tiling-so-far as we do so.
    if addpoints1:
      for ix in addpoints1:
        # Clone the tiling we're starting with and add the job at this
        # add-point.
        T = TSoFar.clone()
        T.addJob(ix, Xdim+xspacing, Ydim+yspacing, job)

        # Recursive call with the remaining jobs and this new tiling. The
        # point behind the last parameter is simply so that _Permutations is
        # only updated once for each permutation, not once per add-point.
        # A permutation is some ordering of jobs (N! choices) and some
        # ordering of non-rotated and rotated within that ordering (2**N
        # possibilities per ordering).
        _tile_search1(remaining_jobs, T, firstAddPoint and ix==addpoints1[0])
    elif firstAddPoint:
      # Premature prune due to not being able to put this job anywhere. We
      # have pruned off 2^M permutations where M is the length of the remaining
      # jobs.
      _Permutations += 2L**len(remaining_jobs)

    if addpoints2:
      for ix in addpoints2:
        # Clone the tiling we're starting with and add the job at this
        # add-point. Remember that the job is rotated so swap X and Y
        # dimensions.
        T = TSoFar.clone()
        T.addJob(ix, Ydim+xspacing, Xdim+yspacing, rjob)

        # Recursive call with the remaining jobs and this new tiling.
        _tile_search1(remaining_jobs, T, firstAddPoint and ix==addpoints2[0])
    elif firstAddPoint:
      # Premature prune due to not being able to put this job anywhere. We
      # have pruned off 2^M permutations where M is the length of the remaining
      # jobs.
      _Permutations += 2L**len(remaining_jobs)

    # If we've been at this for 3 seconds, print some status information
    if _PrintStats and time.time() > _CkpointTime:
      printTilingStats()
      
      # Check for timeout
      if (config.SearchTimeout > 0) and (time.time() - _StartTime > config.SearchTimeout):
        raise KeyboardInterrupt 
        
    gerbmerge.updateGUI("Performing automatic layout...")        
예제 #5
0
    def run(self, q):
        """Perform a random search through all possible jobs given the provided panel size.
        Only self.placements & lastCheckTime are modified within this method.
        """
        r = random.Random()
        N = len(self.jobs)

        # M is the number of jobs that will be placed randomly.
        # N-M is the number of jobs that will be searched exhaustively.
        M = N - self.RandomSearchExhaustiveJobs
        M = max(M, 0)

        # Track if a new bestTiling has been found
        # Also track how many placements it's been since then
        foundBetter = False
        placementsSinceLastCheck = 0

        # Must escape with Ctrl-C
        while 1:
            currentTiling = self.baseTiling.clone()
            joborder = r.sample(range(N), N)

            minInletSize = tiling.minDimension(self.jobs)

            for ix in joborder[:M]:
                Xdim, Ydim, job, rjob = self.jobs[ix]

                currentTiling.removeInlets(minInletSize)

                if r.choice([0, 1]):
                    addpoints = currentTiling.validAddPoints(Xdim + self.xspacing, Ydim + self.yspacing)
                    if not addpoints:
                        break

                    pt = r.choice(addpoints)
                    currentTiling.addJob(pt, Xdim + self.xspacing, Ydim + self.yspacing, job)
                else:
                    addpoints = currentTiling.validAddPoints(Ydim + self.xspacing, Xdim + self.yspacing)
                    if not addpoints:
                        break

                    pt = r.choice(addpoints)
                    currentTiling.addJob(pt, Ydim + self.xspacing, Xdim + self.yspacing, rjob)
            else:
                # Do exhaustive search on remaining jobs
                if N - M:
                    remainingJobs = []
                    for ix in joborder[M:]:
                        remainingJobs.append(self.jobs[ix])

                    finalSearch = ExhaustiveSearch(remainingJobs, self.x, self.y, self.xspacing, self.yspacing, 0, currentTiling)
                    finalSearch.run(False)

                    if finalSearch.bestScore < self.bestScore:
                        self.bestScore = finalSearch.bestScore
                        self.bestTiling = finalSearch.bestTiling
                        foundBetter = True

            self.placements += 1
            placementsSinceLastCheck += 1

            # If we've been at this for one period and found a better
            # tiling since last checkTime, output the new one
            if time.time() > self.lastCheckTime + self.syncPeriod:
                self.lastCheckTime = time.time()

                # Only output a tiling if it's better. We still report
                # attempled placements though
                # We also reset the placements tried
                if foundBetter is True:
                    q.put((placementsSinceLastCheck, self.bestTiling), block=True)
                    foundBetter = False
                else:
                    q.put((placementsSinceLastCheck, None), block=True)

                placementsSinceLastCheck = 0

                # Check for timeout
                if self.searchTimeout > 0 and ((time.time() - self.startTime) > self.searchTimeout):
                    return
예제 #6
0
    def _run(self, Jobs, tiles, firstAddPoint, printStats):
        """This recursive function does the following with an existing tiling baseTiling:

           * For each 4-tuple (Xdim,Ydim,job,rjob) in Jobs, the non-rotated 'job' is selected

           * For the non-rotated job, the list of valid add-points is found

           * For each valid add-point, the job is placed at this point in a new,
             cloned tiling.

           * The function then calls its recursively with the remaining list of
             jobs.

           * The rotated job is then selected and the list of valid add-points is
             found. Again, for each valid add-point the job is placed there in
             a new, cloned tiling.

           * Once again, the function calls itself recursively with the remaining
             list of jobs.

           * The best tiling encountered from all recursive calls is returned.

           If baseTiling is None it means this combination of jobs is not tileable.

           The side-effect of this function is to set self.bestTiling and self.bestScore
           to the best tiling encountered so far. self.bestTiling could be None if
           no valid tilings have been found so far.
        """
        if not tiles:
            return (None, float("inf"))

        if not Jobs:
            # Update the best tiling and score. If the new tiling matches
            # the best score so far, compare on number of corners, trying to
            # minimize them.
            score = tiles.area()

            if score < self.bestScore or (score == self.bestScore and tiles.corners() < self.bestTiling.corners()):
                self.bestTiling = tiles
                self.bestScore = score

            if firstAddPoint:
                self.permutations += 1
            return

        minInletSize = tiling.minDimension(Jobs)
        tiles.removeInlets(minInletSize)

        for job_ix in range(len(Jobs)):
            # Pop off the next job and construct remaining_jobs, a sub-list
            # of Jobs with the job we've just popped off excluded.
            Xdim, Ydim, job, rjob = Jobs[job_ix]
            remaining_jobs = Jobs[:job_ix] + Jobs[job_ix + 1:]

            # Construct add-points for the non-rotated and rotated job.
            # As an optimization, do not construct add-points for the rotated
            # job if the job is a square (duh).
            addpoints1 = tiles.validAddPoints(Xdim + self.xspacing, Ydim + self.yspacing)     # unrotated job
            if Xdim != Ydim:
                addpoints2 = tiles.validAddPoints(Ydim + self.xspacing, Xdim + self.yspacing)   # rotated job
            else:
                addpoints2 = []

            # Recursively construct tilings for the non-rotated job and
            # update the best-tiling-so-far as we do so.
            if addpoints1:
                for ix in addpoints1:
                    # Clone the tiling we're starting with and add the job at this
                    # add-point.
                    T = tiles.clone()
                    T.addJob(ix, Xdim + self.xspacing, Ydim + self.yspacing, job)

                    # Recursive call with the remaining jobs and this new tiling. The
                    # point behind the last parameter is simply so that self.permutations is
                    # only updated once for each permutation, not once per add-point.
                    # A permutation is some ordering of jobs (N! choices) and some
                    # ordering of non-rotated and rotated within that ordering (2**N
                    # possibilities per ordering).
                    self._run(remaining_jobs, T, firstAddPoint and ix == addpoints1[0], printStats)
            elif firstAddPoint:
                # Premature prune due to not being able to put this job anywhere. We
                # have pruned off 2^M permutations where M is the length of the remaining
                # jobs.
                self.permutations += 2 ** len(remaining_jobs)

            if addpoints2:
                for ix in addpoints2:
                    # Clone the tiling we're starting with and add the job at this
                    # add-point. Remember that the job is rotated so swap X and Y
                    # dimensions.
                    T = tiles.clone()
                    T.addJob(ix, Ydim + self.xspacing, Xdim + self.yspacing, rjob)

                    # Recursive call with the remaining jobs and this new tiling.
                    self._run(remaining_jobs, T, firstAddPoint and ix == addpoints2[0], printStats)
            elif firstAddPoint:
                # Premature prune due to not being able to put this job anywhere. We
                # have pruned off 2^M permutations where M is the length of the remaining
                # jobs.
                self.permutations += 2 ** len(remaining_jobs)

            # If we've been at this for one period, print some status information
            if printStats and time.time() > self.lastCheckTime + self.syncPeriod:
                self.lastCheckTime = time.time()
                print(self)

                # Check for timeout
                if (self.searchTimeout > 0) and ((time.time() - self.startTime) > self.searchTimeout):
                    return
예제 #7
0
    def run(self, q):
        """Perform a random search through all possible jobs given the provided panel size.
        Only self.placements & lastCheckTime are modified within this method.
        """
        r = random.Random()
        N = len(self.jobs)

        # M is the number of jobs that will be placed randomly.
        # N-M is the number of jobs that will be searched exhaustively.
        M = N - self.RandomSearchExhaustiveJobs
        M = max(M, 0)

        # Track if a new bestTiling has been found
        # Also track how many placements it's been since then
        foundBetter = False
        placementsSinceLastCheck = 0

        # Must escape with Ctrl-C
        while 1:
            currentTiling = self.baseTiling.clone()
            joborder = r.sample(range(N), N)

            minInletSize = tiling.minDimension(self.jobs)

            for ix in joborder[:M]:
                Xdim, Ydim, job, rjob = self.jobs[ix]

                currentTiling.removeInlets(minInletSize)

                if r.choice([0, 1]):
                    addpoints = currentTiling.validAddPoints(
                        Xdim + self.xspacing, Ydim + self.yspacing)
                    if not addpoints:
                        break

                    pt = r.choice(addpoints)
                    currentTiling.addJob(pt, Xdim + self.xspacing,
                                         Ydim + self.yspacing, job)
                else:
                    addpoints = currentTiling.validAddPoints(
                        Ydim + self.xspacing, Xdim + self.yspacing)
                    if not addpoints:
                        break

                    pt = r.choice(addpoints)
                    currentTiling.addJob(pt, Ydim + self.xspacing,
                                         Xdim + self.yspacing, rjob)
            else:
                # Do exhaustive search on remaining jobs
                if N - M:
                    remainingJobs = []
                    for ix in joborder[M:]:
                        remainingJobs.append(self.jobs[ix])

                    finalSearch = ExhaustiveSearch(remainingJobs, self.x,
                                                   self.y, self.xspacing,
                                                   self.yspacing, 0,
                                                   currentTiling)
                    finalSearch.run(False)

                    if finalSearch.bestScore < self.bestScore:
                        self.bestScore = finalSearch.bestScore
                        self.bestTiling = finalSearch.bestTiling
                        foundBetter = True

            self.placements += 1
            placementsSinceLastCheck += 1

            # If we've been at this for one period and found a better
            # tiling since last checkTime, output the new one
            if time.time() > self.lastCheckTime + self.syncPeriod:
                self.lastCheckTime = time.time()

                # Only output a tiling if it's better. We still report
                # attempled placements though
                # We also reset the placements tried
                if foundBetter is True:
                    q.put((placementsSinceLastCheck, self.bestTiling),
                          block=True)
                    foundBetter = False
                else:
                    q.put((placementsSinceLastCheck, None), block=True)

                placementsSinceLastCheck = 0

                # Check for timeout
                if self.searchTimeout > 0 and (
                    (time.time() - self.startTime) > self.searchTimeout):
                    return
예제 #8
0
    def _run(self, Jobs, tiles, firstAddPoint, printStats):
        """This recursive function does the following with an existing tiling baseTiling:

           * For each 4-tuple (Xdim,Ydim,job,rjob) in Jobs, the non-rotated 'job' is selected

           * For the non-rotated job, the list of valid add-points is found

           * For each valid add-point, the job is placed at this point in a new,
             cloned tiling.

           * The function then calls its recursively with the remaining list of
             jobs.

           * The rotated job is then selected and the list of valid add-points is
             found. Again, for each valid add-point the job is placed there in
             a new, cloned tiling.

           * Once again, the function calls itself recursively with the remaining
             list of jobs.

           * The best tiling encountered from all recursive calls is returned.

           If baseTiling is None it means this combination of jobs is not tileable.

           The side-effect of this function is to set self.bestTiling and self.bestScore
           to the best tiling encountered so far. self.bestTiling could be None if
           no valid tilings have been found so far.
        """
        if not tiles:
            return (None, float("inf"))

        if not Jobs:
            # Update the best tiling and score. If the new tiling matches
            # the best score so far, compare on number of corners, trying to
            # minimize them.
            score = tiles.area()

            if score < self.bestScore or (
                    score == self.bestScore
                    and tiles.corners() < self.bestTiling.corners()):
                self.bestTiling = tiles
                self.bestScore = score

            if firstAddPoint:
                self.permutations += 1
            return

        minInletSize = tiling.minDimension(Jobs)
        tiles.removeInlets(minInletSize)

        for job_ix in range(len(Jobs)):
            # Pop off the next job and construct remaining_jobs, a sub-list
            # of Jobs with the job we've just popped off excluded.
            Xdim, Ydim, job, rjob = Jobs[job_ix]
            remaining_jobs = Jobs[:job_ix] + Jobs[job_ix + 1:]

            # Construct add-points for the non-rotated and rotated job.
            # As an optimization, do not construct add-points for the rotated
            # job if the job is a square (duh).
            addpoints1 = tiles.validAddPoints(Xdim + self.xspacing, Ydim +
                                              self.yspacing)  # unrotated job
            if Xdim != Ydim:
                addpoints2 = tiles.validAddPoints(Ydim + self.xspacing, Xdim +
                                                  self.yspacing)  # rotated job
            else:
                addpoints2 = []

            # Recursively construct tilings for the non-rotated job and
            # update the best-tiling-so-far as we do so.
            if addpoints1:
                for ix in addpoints1:
                    # Clone the tiling we're starting with and add the job at this
                    # add-point.
                    T = tiles.clone()
                    T.addJob(ix, Xdim + self.xspacing, Ydim + self.yspacing,
                             job)

                    # Recursive call with the remaining jobs and this new tiling. The
                    # point behind the last parameter is simply so that self.permutations is
                    # only updated once for each permutation, not once per add-point.
                    # A permutation is some ordering of jobs (N! choices) and some
                    # ordering of non-rotated and rotated within that ordering (2**N
                    # possibilities per ordering).
                    self._run(remaining_jobs, T, firstAddPoint
                              and ix == addpoints1[0], printStats)
            elif firstAddPoint:
                # Premature prune due to not being able to put this job anywhere. We
                # have pruned off 2^M permutations where M is the length of the remaining
                # jobs.
                self.permutations += 2**len(remaining_jobs)

            if addpoints2:
                for ix in addpoints2:
                    # Clone the tiling we're starting with and add the job at this
                    # add-point. Remember that the job is rotated so swap X and Y
                    # dimensions.
                    T = tiles.clone()
                    T.addJob(ix, Ydim + self.xspacing, Xdim + self.yspacing,
                             rjob)

                    # Recursive call with the remaining jobs and this new tiling.
                    self._run(remaining_jobs, T, firstAddPoint
                              and ix == addpoints2[0], printStats)
            elif firstAddPoint:
                # Premature prune due to not being able to put this job anywhere. We
                # have pruned off 2^M permutations where M is the length of the remaining
                # jobs.
                self.permutations += 2**len(remaining_jobs)

            # If we've been at this for one period, print some status information
            if printStats and time.time(
            ) > self.lastCheckTime + self.syncPeriod:
                self.lastCheckTime = time.time()
                print(self)

                # Check for timeout
                if (self.searchTimeout > 0) and (
                    (time.time() - self.startTime) > self.searchTimeout):
                    return