Home Reference Source

src/cengines/twodmapper.js

/*
Mapper for a quantum circuit to a 2D square grid.

    Input: Quantum circuit with 1 and 2 qubit gates on n qubits. Gates are assumed
to be applied in parallel if they act on disjoint qubit(s) and any pair
of qubits can perform a 2 qubit gate (all-to-all connectivity)
Output: Quantum circuit in which qubits are placed in 2-D square grid in which
only nearest neighbour qubits can perform a 2 qubit gate. The mapper
uses Swap gates in order to move qubits next to each other.
*/
import assert from 'assert'
import math from 'mathjs'
import {permutations} from 'itertools'
import BasicMapperEngine from './basicmapper';
import {return_swap_depth} from './linearmapper'
import NativeImpl from '../backends/simulators/cppsim'
import {
  arrayFromRange, len, randomSample, setDifference, setEqual, setFromRange
} from '../libs/polyfill';
import LinearMapper from './linearmapper';
import {
  AllocateQubitGate, DeallocateQubitGate, FlushGate, Swap
} from '../ops';
import {tuple} from '../libs/util';
import Command from '../ops/command';
import {BasicQubit} from '../types/qubit';
import {LogicalQubitIDTag} from '../meta';

/**
 * @class Position
 */
export class Position {
  /**
   * @constructor
   * @param {number} current_row
   * @param {number} current_column
   * @param {number} final_row
   * @param {number} final_column
   * @param {number} row_after_step_1
   */
  constructor(current_row, current_column, final_row, final_column, row_after_step_1) {
    this.current_row = current_row
    this.current_column = current_column
    this.final_row = final_row
    this.final_column = final_column
    this.row_after_step_1 = row_after_step_1
  }
}

/**
 * @class GridMapper
 * @desc
Mapper to a 2-D grid graph.

    Mapped qubits on the grid are numbered in row-major order. E.g. for
3 rows and 2 columns:
  ```js
    0 - 1
    |   |
    2 - 3
    |   |
    4 - 5
  ```
The numbers are the mapped qubit ids. The backend might number
the qubits on the grid differently (e.g. not row-major), we call these
backend qubit ids. If the backend qubit ids are not row-major, one can
pass a dictionary translating from our row-major mapped ids to these
backend ids.

    Note: The algorithm sorts twice inside each column and once inside each
row.

 @property current_mapping Stores the mapping: key is logical qubit id, value is backend qubit id.
storage(int): Number of gate it caches before mapping.
num_rows(int): Number of rows in the grid
num_columns(int): Number of columns in the grid
num_qubits(int): num_rows x num_columns = number of qubits
num_mappings (int): Number of times the mapper changed the mapping
depth_of_swaps (dict): Key are circuit depth of swaps, value is the
number of such mappings which have been
applied
num_of_swaps_per_mapping (dict): Key are the number of swaps per
mapping, value is the number of such
mappings which have been applied
 */
export default class GridMapper extends BasicMapperEngine {
  /**
   * @constructor
  Initialize a GridMapper compiler engine.

    @param {{num_rows: number, num_columns: number, mapped_ids_to_backend_ids: ?Object, storage: ?number, optimization_function: ?function, num_optimization_steps: ?number}} args
    @throws {Error} if incorrect `mapped_ids_to_backend_ids` parameter
   */
  constructor(args) {
    super()
    /**
     * @type {Object}
     * @property {number} args.num_rows
     */
    const {
      num_rows, num_columns, mapped_ids_to_backend_ids,
      storage = 1000,
      optimization_function = x => return_swap_depth(x),
      num_optimization_steps = 50
    } = args

    /**
     * @property {number} this.num_rows Number of rows in the grid
     * @property {number} this.num_columns Number of columns in the grid.
     * @property {number} this.num_qubits
     * @property {Object<number, number>} this._mapped_ids_to_backend_ids
     * @property {Object<number, number>} this._backend_ids_to_mapped_ids
     *      Stores a mapping from mapped ids which are 0,...,this.num_qubits-1 in row-major order
     *      on the grid to the corresponding qubit ids of the backend. Key: mapped id. Value: corresponding backend id.
     *      Default is None which means backend ids are identical to mapped ids.
     * @property {Object<number, number>} this._current_row_major_mapping
     * @property {number} this.storage Number of gates to temporarily store
     * @property {function} this.optimization_function
     *      Function which takes a list of swaps and returns a cost value. Mapper chooses a permutation
     *      which minimizes this cost. Default optimizes for circuit depth.
     * @property {number} this.num_optimization_steps
     *      Number of different permutations to of the matching to try and minimize the cost.
     * @property {number} this.num_mappings
     * @property {Object<number, number>} this.depth_of_swaps
     * @property {Object<number, number>} this.num_of_swaps_per_mapping
     */
    this.num_rows = num_rows
    this.num_columns = num_columns
    this.num_qubits = num_rows * num_columns
    // Internally we use the mapped ids until sending a command.
    // Before sending we use this map to translate to backend ids:
    this._mapped_ids_to_backend_ids = mapped_ids_to_backend_ids
    if (typeof this._mapped_ids_to_backend_ids === 'undefined' || this._mapped_ids_to_backend_ids === null) {
      this._mapped_ids_to_backend_ids = {}
      for (let i = 0; i < this.num_qubits; ++i) {
        this._mapped_ids_to_backend_ids[i] = i
      }
    }

    const f1 = setEqual(new Set(Object.keys(this._mapped_ids_to_backend_ids).map(k => parseInt(k, 10))), setFromRange(this.num_qubits))
    const f2 = new Set(Object.values(this._mapped_ids_to_backend_ids)).size === this.num_qubits
    if (!f1 || !f2) {
      throw new Error('Incorrect mapped_ids_to_backend_ids parameter')
    }
    this._backend_ids_to_mapped_ids = {}

    Object.keys(this._mapped_ids_to_backend_ids).forEach((mapped_id) => {
      const backend_id = this._mapped_ids_to_backend_ids[mapped_id]
      this._backend_ids_to_mapped_ids[backend_id] = mapped_id
    })
    // As we use internally the mapped ids which are in row-major order,
    // we have an internal current mapping which maps from logical ids to
    // these mapped ids:
    this._current_row_major_mapping = Object.assign({}, this._currentMapping)
    this.storage = storage
    this.optimization_function = optimization_function
    this.num_optimization_steps = num_optimization_steps
    // Randomness to pick permutations if there are too many.
    // This creates an own instance of Random in order to not influence
    // the bound methods of the random module which might be used in other
    // places.
    // TODO
    // this._rng = random.Random(11)
    /**
     * Storing commands
     * @property {Command[]} this._stored_commands
     */
    this._stored_commands = []
    /** Logical qubit ids for which the Allocate gate has already been
      processed and sent to the next engine but which are not yet deallocated:
      @property {Set<number>} this._currently_allocated_ids
     */
    this._currently_allocated_ids = new Set()
    /**
     * Change between 2D and 1D mappings (2D is a snake like 1D chain)
        Note it translates to our mapped ids in row major order and not
        backend ids which might be different.
     * @property {Object<number, number>} this._map_2d_to_1d
     * @property {Object<number, number>} this._map_1d_to_2d
    */
    this._map_2d_to_1d = {}
    this._map_1d_to_2d = {}
    for (let row_index = 0; row_index < this.num_rows; ++row_index) {
      for (let column_index = 0; column_index < this.num_columns; ++column_index) {
        if (row_index % 2 === 0) {
          const mapped_id = row_index * this.num_columns + column_index
          this._map_2d_to_1d[mapped_id] = mapped_id
          this._map_1d_to_2d[mapped_id] = mapped_id
        } else {
          const mapped_id_2d = row_index * this.num_columns + column_index
          const mapped_id_1d = ((row_index + 1) * this.num_columns - column_index - 1)
          this._map_2d_to_1d[mapped_id_2d] = mapped_id_1d
          this._map_1d_to_2d[mapped_id_1d] = mapped_id_2d
        }
      }
    }

    /**
     * Statistics
      */
    this.num_mappings = 0
    this.depth_of_swaps = {}
    this.num_of_swaps_per_mapping = {}
  }

  get currentMapping() {
    return super.currentMapping
  }

  set currentMapping(newMapping) {
    this._currentMapping = newMapping
    if (typeof newMapping === 'undefined' || newMapping === null) {
      this._current_row_major_mapping = newMapping
    } else {
      this._current_row_major_mapping = {}

      Object.keys(newMapping).forEach((logical_id) => {
        const backend_id = newMapping[logical_id]
        const value = this._backend_ids_to_mapped_ids[backend_id]
        this._current_row_major_mapping[logical_id] = parseInt(value, 10)
      })
    }
  }

  // Only allows 1 || two qubit gates.
  isAvailable(cmd) {
    let num_qubits = 0
    cmd.allQubits.forEach(qureg => num_qubits += qureg.length)
    return num_qubits <= 2
  }

  /**
  Returns a new mapping of the qubits.

    It goes through this._saved_commands and tries to find a
mapping to apply these gates on a first come first served basis.
    It reuses the function of a 1D mapper and creates a mapping for a
  1D linear chain and then wraps it like a snake onto the square grid.

    One might create better mappings by specializing this function for a
  square grid.

    @return {Object} A new mapping as a dict. key is logical qubit id, value is mapped id
   */
  returnNewMapping() {
    // Change old mapping to 1D in order to use LinearChain heuristic
    let old_mapping_1d
    if (this._current_row_major_mapping) {
      old_mapping_1d = {}
      Object.keys(this._current_row_major_mapping).forEach((logical_id) => {
        const mapped_id = this._current_row_major_mapping[logical_id]
        old_mapping_1d[logical_id] = this._map_2d_to_1d[mapped_id]
      })
    } else {
      old_mapping_1d = this._current_row_major_mapping
    }

    const new_mapping_1d = LinearMapper.returnNewMapping(
      this.num_qubits,
      false,
      this._currently_allocated_ids,
      this._stored_commands,
      old_mapping_1d
    )

    const new_mapping_2d = {}
    Object.keys(new_mapping_1d).forEach((logical_id) => {
      const mapped_id = new_mapping_1d[logical_id]
      new_mapping_2d[logical_id] = this._map_1d_to_2d[mapped_id]
    })

    return new_mapping_2d
  }

  /**
   * If swapped (inplace), then return swap operation so that `key(element0) < key(element1)`
   @param {Position} element0
   @param {Position} element1
   @param {function(arg: Position): number} key
   @return {Array<number>|undefined}
   */
  _compareAndSwap(element0, element1, key) {
    if (key(element0) > key(element1)) {
      const mapped_id0 = (element0.current_column + element0.current_row * this.num_columns)
      const mapped_id1 = (element1.current_column + element1.current_row * this.num_columns)
      const swap_operation = [mapped_id0, mapped_id1]
      // swap elements but update also current position:
      const tmp_0 = element0.final_row
      const tmp_1 = element0.final_column
      const tmp_2 = element0.row_after_step_1
      element0.final_row = element1.final_row
      element0.final_column = element1.final_column
      element0.row_after_step_1 = element1.row_after_step_1
      element1.final_row = tmp_0
      element1.final_column = tmp_1
      element1.row_after_step_1 = tmp_2
      return swap_operation
    }
    return undefined
  }

  /**
   * @param {Array<Position[]>} final_positions
   * @param {function(arg: Position): number} key
   * @return {Array<number[]>}
   * @private
   */
  _sortWithinRows(final_positions, key) {
    const swap_operations = []
    for (let row = 0; row < this.num_rows; ++row) {
      let finished_sorting = false
      while (!finished_sorting) {
        finished_sorting = true
        for (let column = 1; column < this.num_columns - 1; column += 2) {
          const element0 = final_positions[row][column]
          const element1 = final_positions[row][column + 1]
          const swap = this._compareAndSwap(element0, element1, key)
          if (typeof swap !== 'undefined') {
            finished_sorting = false
            swap_operations.push(swap)
          }
        }

        for (let column = 0; column < this.num_columns - 1; column += 2) {
          const element0 = final_positions[row][column]
          const element1 = final_positions[row][column + 1]
          const swap = this._compareAndSwap(element0, element1, key)
          if (typeof swap !== 'undefined') {
            finished_sorting = false
            swap_operations.push(swap)
          }
        }
      }
    }
    return swap_operations
  }

  /**
   * @param {Array<Position[]>} final_positions
   * @param {function(arg: Position): number} key
   * @return {Array<number[]>}
   * @private
   */
  _sortWithinColumns(final_positions, key) {
    const swap_operations = []
    for (let column = 0; column < this.num_columns; ++column) {
      let finished_sorting = false
      while (!finished_sorting) {
        finished_sorting = true
        for (let row = 1; row < this.num_rows - 1; row += 2) {
          const element0 = final_positions[row][column]
          const element1 = final_positions[row + 1][column]
          const swap = this._compareAndSwap(element0, element1, key)
          if (typeof swap !== 'undefined') {
            finished_sorting = false
            swap_operations.push(swap)
          }
        }

        for (let row = 0; row < this.num_rows - 1; row += 2) {
          const element0 = final_positions[row][column]
          const element1 = final_positions[row + 1][column]
          const swap = this._compareAndSwap(element0, element1, key)
          if (typeof swap !== 'undefined') {
            finished_sorting = false
            swap_operations.push(swap)
          }
        }
      }
    }
    return swap_operations
  }

  /**
  Creates a new mapping and executes possible gates.

    It first allocates all 0, ..., this.num_qubits-1 mapped qubit ids, if
    they are not already used because we might need them all for the
  swaps. Then it creates a new map, swaps all the qubits to the new map,
    executes all possible gates, and finally deallocates mapped qubit ids
which don't store any information.
   */
  _run() {
    const num_of_stored_commands_before = len(this._stored_commands)
    if (!this._currentMapping) {
      this.currentMapping = {}
    } else {
      this._sendPossibleCommands()
      if (len(this._stored_commands) === 0) {
        return
      }
    }

    const new_row_major_mapping = this.returnNewMapping()
    // Find permutation of matchings with lowest cost
    let swaps
    let lowest_cost
    const matchings_numbers = arrayFromRange(this.num_rows)
    const ps = []
    if (this.num_optimization_steps <= math.factorial(this.num_rows)) {
      for (const looper of permutations(matchings_numbers, this.num_rows)) {
        ps.push(looper)
      }
    } else {
      for (let i = 0; i < this.num_optimization_steps; ++i) {
        ps.push(randomSample(matchings_numbers, this.num_rows))
      }
    }

    ps.forEach((permutation) => {
      const trial_swaps = this.returnSwaps(
        this._current_row_major_mapping,
        new_row_major_mapping,
        permutation
      )
      if (typeof swaps === 'undefined') {
        swaps = trial_swaps
        lowest_cost = this.optimization_function(trial_swaps)
      } else if (lowest_cost > this.optimization_function(trial_swaps)) {
        swaps = trial_swaps
        lowest_cost = this.optimization_function(trial_swaps)
      }
    })
    if (swaps.length > 0) { // first mapping requires no swaps
      // Allocate all mapped qubit ids (which are not already allocated,
      // i.e., contained in this._currently_allocated_ids)
      let mapped_ids_used = new Set()
      for (const logical_id of this._currently_allocated_ids) {
        mapped_ids_used.add(this._current_row_major_mapping[logical_id])
      }
      const not_allocated_ids = setDifference(setFromRange(this.num_qubits), mapped_ids_used)
      for (const mapped_id of not_allocated_ids) {
        const qb = new BasicQubit(this, this._mapped_ids_to_backend_ids[mapped_id])
        const cmd = new Command(this, new AllocateQubitGate(), tuple([qb]))
        this.send([cmd])
      }


      // Send swap operations to arrive at new_mapping:
      swaps.forEach(([qubit_id0, qubit_id1]) => {
        const q0 = new BasicQubit(this, this._mapped_ids_to_backend_ids[qubit_id0])
        const q1 = new BasicQubit(this, this._mapped_ids_to_backend_ids[qubit_id1])
        const cmd = new Command(this, Swap, tuple([q0], [q1]))
        this.send([cmd])
      })
      // Register statistics:
      this.num_mappings += 1
      const depth = return_swap_depth(swaps)
      if (!(depth in this.depth_of_swaps)) {
        this.depth_of_swaps[depth] = 1
      } else {
        this.depth_of_swaps[depth] += 1
      }
      if (!(len(swaps) in this.num_of_swaps_per_mapping)) {
        this.num_of_swaps_per_mapping[len(swaps)] = 1
      } else {
        this.num_of_swaps_per_mapping[len(swaps)] += 1
      }
      // Deallocate all previously mapped ids which we only needed for the
      // swaps:
      mapped_ids_used = new Set()
      for (const logical_id of this._currently_allocated_ids) {
        mapped_ids_used.add(new_row_major_mapping[logical_id])
      }
      const not_needed_anymore = setDifference(setFromRange(this.num_qubits), mapped_ids_used)
      for (const mapped_id of not_needed_anymore) {
        const qb = new BasicQubit(this, this._mapped_ids_to_backend_ids[mapped_id])
        const cmd = new Command(this, new DeallocateQubitGate(), tuple([qb]))
        this.send([cmd])
      }
    }

    // Change to new map:
    this._current_row_major_mapping = new_row_major_mapping
    const new_mapping = {}
    Object.keys(new_row_major_mapping).forEach((logical_id) => {
      const mapped_id = new_row_major_mapping[logical_id]
      new_mapping[logical_id] = this._mapped_ids_to_backend_ids[mapped_id]
    })

    this.currentMapping = new_mapping
    // Send possible gates
    this._sendPossibleCommands()
    // Check that mapper actually made progress
    if (len(this._stored_commands) === num_of_stored_commands_before) {
      throw new Error('Mapper is potentially in an infinite loop. '
          + 'It is likely that the algorithm requires '
          + 'too many qubits. Increase the number of '
          + 'qubits for this mapper.')
    }
  }

  /**
  Sends the stored commands possible without changing the mapping.
    Note: this._current_row_major_mapping (hence also this.currentMapping) must exist already
   */
  _sendPossibleCommands() {
    const active_ids = new Set(Array.from(this._currently_allocated_ids).map(k => parseInt(k, 10)))
    Object.keys(this._current_row_major_mapping).forEach(logical_id => active_ids.add(parseInt(logical_id, 10)))

    let new_stored_commands = []
    for (let i = 0; i < this._stored_commands.length; ++i) {
      const cmd = this._stored_commands[i]
      if (len(active_ids) === 0) {
        new_stored_commands = new_stored_commands.concat(this._stored_commands.slice(i))
        break
      }
      if (cmd.gate instanceof AllocateQubitGate) {
        const qid = cmd.qubits[0][0].id
        if (qid in this._current_row_major_mapping) {
          this._currently_allocated_ids.add(qid)
          const mapped_id = this._current_row_major_mapping[qid]
          const qb = new BasicQubit(this, this._mapped_ids_to_backend_ids[mapped_id])
          const new_cmd = new Command(this, new AllocateQubitGate(), tuple([qb]), [], [new LogicalQubitIDTag(qid)])
          this.send([new_cmd])
        } else {
          new_stored_commands.push(cmd)
        }
      } else if (cmd.gate instanceof DeallocateQubitGate) {
        const qid = cmd.qubits[0][0].id
        if (active_ids.has(qid)) {
          const mapped_id = this._current_row_major_mapping[qid]
          const qb = new BasicQubit(this, this._mapped_ids_to_backend_ids[mapped_id])
          const new_cmd = new Command(this, new DeallocateQubitGate(), tuple([qb]), [], [new LogicalQubitIDTag(qid)])
          this._currently_allocated_ids.delete(qid)
          active_ids.delete(qid)
          delete this._current_row_major_mapping[qid]
          delete this._currentMapping[qid]
          this.send([new_cmd])
        } else {
          new_stored_commands.push(cmd)
        }
      } else {
        let send_gate = true
        const mapped_ids = new Set()

        for (let k = 0; k < cmd.allQubits.length; ++k) {
          const qureg = cmd.allQubits[k]
          for (let j = 0; j < qureg.length; ++j) {
            const qubit = qureg[j]
            if (!active_ids.has(qubit.id)) {
              send_gate = false
              break
            }
            mapped_ids.add(this._current_row_major_mapping[qubit.id])
          }
        }


        // Check that mapped ids are nearest neighbour on 2D grid
        if (len(mapped_ids) === 2) {
          const [qb0, qb1] = Array.from(mapped_ids).sort((a, b) => a - b)
          send_gate = false
          if (qb1 - qb0 === this.num_columns) {
            send_gate = true
          } else if (qb1 - qb0 === 1 && (qb1 % this.num_columns !== 0)) {
            send_gate = true
          }
        }
        if (send_gate) {
          // Note: This sends the cmd correctly with the backend ids
          //       as it looks up the mapping in this.currentMapping
          //       and not our internal mapping
          //       this._current_row_major_mapping
          this.sendCMDWithMappedIDs(cmd)
        } else {
          cmd.allQubits.forEach(qureg => qureg.forEach(qubit => active_ids.delete(qubit.id)))
          new_stored_commands.push(cmd)
        }
      }
    }

    this._stored_commands = new_stored_commands
  }

  /**
  Receives a command list and, for each command, stores it until
  we do a mapping (FlushGate || Cache of stored commands is full).
   @param {Command[]} command_list  list of commands to receive.
  */
  receive(command_list) {
    command_list.forEach((cmd) => {
      if (cmd.gate instanceof FlushGate) {
        while (this._stored_commands.length > 0) {
          this._run()
        }
        this.send([cmd])
      } else {
        this._stored_commands.push(cmd)
      }

      if (this._stored_commands.length >= this.storage) {
        this._run()
      }
    })
  }

  /**
  Returns the swap operation to change mapping

    @param {Object} old_mapping dict keys are logical ids and values are mapped qubit ids
    @param {Object} new_mapping dict keys are logical ids and values are mapped qubit ids
    @param {Array.<number[]>} permutation list of int from 0, 1, ..., this.num_rows-1. It is
      used to permute the found perfect matchings. Default is None which keeps the original order.
    @return {Array.<number[]>} List of tuples. Each tuple is a swap operation which needs to be
      applied. Tuple contains the two mapped qubit ids for the Swap.
   */
  returnSwaps(old_mapping, new_mapping, permutation) {
    if (typeof permutation === 'undefined') {
      permutation = arrayFromRange(this.num_rows)
    }
    let swap_operations = []

    // final_positions contains info containers
    // final_position[i][j] contains info container with
    // current_row == i and current_column == j
    const final_positions = new Array(this.num_rows)
    for (let i = 0; i < this.num_rows; ++i) {
      final_positions[i] = new Array(this.num_columns)
    }

    // move qubits which are in both mappings
    const used_mapped_ids = new Set()

    Object.keys(old_mapping).forEach((logical_id) => {
      if (logical_id in new_mapping) {
        used_mapped_ids.add(new_mapping[logical_id])
        const old_column = old_mapping[logical_id] % this.num_columns
        const old_row = Math.floor(old_mapping[logical_id] / this.num_columns)
        const new_column = new_mapping[logical_id] % this.num_columns
        const new_row = Math.floor(new_mapping[logical_id] / this.num_columns)
        const info_container = new Position(old_row,
          old_column,
          new_row,
          new_column)
        final_positions[old_row][old_column] = info_container
      }
    })
    // exchange all remaining None with the not yet used mapped ids
    const all_ids = setFromRange(this.num_qubits)
    let not_used_mapped_ids = Array.from(setDifference(all_ids, used_mapped_ids))
    not_used_mapped_ids = not_used_mapped_ids.sort((a, b) => b - a)

    for (let row = 0; row < this.num_rows; ++row) {
      for (let column = 0; column < this.num_columns; ++column) {
        if (typeof final_positions[row][column] === 'undefined') {
          const mapped_id = not_used_mapped_ids.pop()
          const new_column = mapped_id % this.num_columns
          const new_row = Math.floor(mapped_id / this.num_columns)
          const info_container = new Position(row, column, new_row, new_column)
          final_positions[row][column] = info_container
        }
      }
    }

    assert(len(not_used_mapped_ids) === 0)
    const positions = []
    final_positions.forEach(row => positions.push(row.map(item => item.final_column)))
    const matchings = NativeImpl.returnNewSwap(this.num_rows, this.num_columns, positions)
    const offset = this.num_columns
    // permute the matchings
    const tmp = matchings.map(looper => Object.assign({}, looper))
    for (let i = 0; i < this.num_rows; ++i) {
      matchings[i] = tmp[permutation[i]]
    }
    // Assign row_after_step_1
    for (let column = 0; column < this.num_columns; ++column) {
      for (let row_after_step_1 = 0; row_after_step_1 < this.num_rows; ++row_after_step_1) {
        const dest_column = matchings[row_after_step_1][column] - offset
        let best_element
        for (let row = 0; row < this.num_rows; ++row) {
          const element = final_positions[row][column]
          if (typeof element.row_after_step_1 !== 'undefined') {
            continue
          } else if (element.final_column === dest_column) {
            if (typeof best_element === 'undefined') {
              best_element = element
            } else if (best_element.final_row > element.final_row) {
              best_element = element
            }
          }
        }
        best_element.row_after_step_1 = row_after_step_1
      }
    }

    // 2. Sort inside all the rows
    let swaps = this._sortWithinColumns(final_positions, x => x.row_after_step_1)
    swap_operations = swap_operations.concat(swaps)
    // 3. Sort inside all the columns
    swaps = this._sortWithinRows(final_positions, x => x.final_column)
    swap_operations = swap_operations.concat(swaps)
    // 4. Sort inside all the rows
    swaps = this._sortWithinColumns(final_positions, x => x.final_row)
    swap_operations = swap_operations.concat(swaps)
    return swap_operations
  }
}