lib/solver/core/distributed.ex

defmodule CPSolver.Distributed do
  require Logger

  def call(node, solver, mod, function, args) do
    :erpc.call(node, mod, function, [solver | args])
  end

  def worker_nodes(node_list \\ [Node.self() | Node.list()]) do
    node_list
  end

  def choose_worker_node(nodes \\ worker_nodes())

  def choose_worker_node(true) do
    worker_nodes()
    |> choose_worker_node()
  end

  def choose_worker_node(distributed?) when not distributed? do
    Node.self()
  end

  def choose_worker_node(node_list) when is_list(node_list) do
    Enum.random(node_list)
  end

  def test() do
    alias CPSolver.Examples.{Sudoku, Queens}

    Logger.configure(level: :info)

    leader_node = :leader
    if Node.self() |> to_string() |> String.starts_with?(to_string(leader_node)) do
      ## cluster is up, skip initialization
      :ok
    else
      Node.start(leader_node, :shortnames)
      Node.set_cookie(:solver)
      # Run the solver with the model that takes noticeable time to complete.
      _worker_nodes = Enum.map(["node1", "node2", "node3"], fn node ->
        {:ok, _pid, node_name} = :peer.start(%{name: node, longnames: false, args: [~c"-setcookie", ~c"solver"]})
        :erpc.call(node_name, :code, :add_paths, [:code.get_path()])
        node_name
      end)
    end

    # Run the solver with the model that takes noticeable time to complete.
    my_pid = self()
    ## Distributed Sudoku
    Logger.notice("Sudoku test: starting")

    five_solutions_sudoku = Sudoku.puzzles().s9x9_5
    {:ok, _res} = CPSolver.solve(Sudoku.model(five_solutions_sudoku),
          solution_handler: fn solution ->
        send(my_pid, {Node.self(), solution})
      end,
      distributed: Enum.shuffle(Node.list())
    )
    verify_solutions(5)
    Logger.notice("Sudoku test: ok")


    ## Distributed Queens
    Logger.notice("Queens test: starting")

    {:ok, _res} = CPSolver.solve(Queens.model(8),
      solution_handler: fn solution ->
        send(my_pid, {Node.self(), solution})
      end,
      distributed: Enum.shuffle(Node.list())
    )
    verify_solutions(92)
    Logger.notice("Queens test: ok")
    true

  end

  def verify_solutions(expected_count) do
    solutions = CPSolver.Utils.flush()
    ^expected_count = length(solutions)
    true =
      solutions
      |> MapSet.new(fn {node, _solution} -> node end)
      |> tap(fn nodes -> Logger.info("Nodes with solutions: #{Enum.join(nodes, "")}") end)
      |> MapSet.subset?(MapSet.new(Node.list()))
  end




end