DemonScript: Размещения ящиков

Рассмотрим мир прямоугольных закрытых ящиков различных размеров. Ящики можно вкладывать друг в друга. Когда ящик X находится внутри ящика Y, считается истинным отношение X.@in.Y. Если информации о размерах ящиков нет, то существует достаточно большее число вариантов размещения ящиков друг в друге. Например, для двух ящиков a,b, возможно три варианта: 1) ящик a находится в ящике b; 2) ящик b находится в a; 3) ящики не находятся друг в друге. Для четырёх ящиков таких вариантов 16 и т.д.:

   1, 3, 16, 125, 1296, 16807, 262144, 4782969, 100000000, 2357947691,..
В общем случае для N ящиков число размещений равно (N+1)(N-1) и образует последовательность A000272. Задача состоит в получении всех этих моделей при помощи логического программирования.

Определим тип отношения и создадим граф с четырмя безымянными узлами:

edges @in                          // объявляем типы рёбер
GRAPH.add_nodes(4)                 // будет 4 узла (ящика)
Теперь добавим в объект Mind аксиомы, которые описывают отношение in:
var X,Y,Z
Mind.add( 
  !X.@in.X,                        // антирефлексивно
   X.@in.Z & Z.@in.Y -> X.@in.Y,   // транзитивно
   Z.@in.X & Z.@in.Y -> X.@in.Y | Y.@in.X | (X==Y), // дерево
)
Антирефлексивность означает, что ящик X сам в себе находиться не может, а транзитивность подразумевает возможность сколь угодно глубокой вложенности ящиков, подобно матрёшкам. Третья аксиома древесности звучит как: если Z находится и в X, и в Y, то либо X внутри Y, либо Y внутри X, либо это один и тот же ящик.

Идея порождения моделей состоит в следующем. Если в графе между двумя узлами (ящиками) нет ребра @in (т.е. оно имеет неопределённое значение Undef), это означает что ящики как могут находится друг в друге (отношение равно True) так могут и не находиться (отношение равно False). Поэтому допустимы две модели со значениями ребра в одной True, а во второй False. Каждая из них, в свою очердь может породить ещё два графа для другого ребра и т.д.

Поэтому, возьмём граф с N узлами без рёбер и поместим его в очередь (массив Open). На каждой итерации из очереди извлекается один граф и в нём ищется неопределённое ребро. Если такого ребра нет, это означает, что сформировалась полностью определённая модель размещения ящиков. Если же ребро найдено, то делаются две копии этого графа. В одном графе ребро помечаем как True, а в другом - как False. Эти графы снова помещаем в очередь и повторяем процедуру до тех пор пока очередь не опустеет.

Перейдём к реализации алгоритма. Создадим двух демонов. Первый будет добавлять в очередь (массив) Open новый граф, устанавливая ребро X.@in.Y в значение Val:

def add_graph(Open, X, Y, Val)
{
   var GV  = GRAPH.copy()          // делаем копию графа
   GRAPH   = GV                    // делаем её активной
   X.@in.Y = Val                   // меняем ребро
   Mind.set_graph(GV)              // запускаем логический вывод
   Open.push(GV)                   // добавляем граф в очередь
}
Второй демон находит первое неопределённое ребро и порождаеи два графа, которые после установки ребра помещаются в очередь Open:
def set_edges(Open)
{
   for X in True :                      // бежим по всем узлам графа
      if exists(Y, X.@in.Y == Undef){   // если есть узел с неопределённым ребром
         add_graph(Open, X, Y, True)    // создаём две копии графа 
         add_graph(Open, X, Y, False)   // в которых определяем это ребро
         return True
      }
    return False
}

Основной код алгоритма выглядит следующим образом:

var G = GRAPH                          // для краткости
//G[1].@in.G[2] = True                 // можем задать ограничения

Mind.set_graph(G)                      // устанавливаем антирефлексивность

var Open = [ G.copy() ]                // помещаем в очередь начальный граф
var N_MODELS = 0                       // число сгенерённых моделей       

while !Open.empty() {                  // пока очередь не пустая
   GRAPH = Open.pop()                  // извлекаем из неё граф
   if !set_edges(Open){                // если не нашли Undef рёбер
      N_MODELS++                       // это финальная модель
      //out GRAPH                      // выводим её
   }
}

out "Count of graphs: ", N_MODELS      // выводим число моделей
out System.time(),"ms"                 // выводим время работы

Описанный выше алгоритм встроен в объект Mind. Ему необходимо передать текущий граф (первый аргумент) и тип ребра (@in) которые следует устанавливать в графе в значения True и False при неопределённом значении (Undef):

GRAPH.clear_edges()                    // убиваем  все рёбра
System.time(0)                         // сбрасываем таймер

out "Count of graphs: ", 
     Mind.count_models(GRAPH, @in)     // Mind подсчитывает число моделей

out System.time(),"ms"                 // выводим время работы

Полный код этого примера находится в файле in_models_calc.ds.