Применение метода CLP для обработки действительных чисел - CLP(R)
Рассмотрим следующий запрос: ?- 1 + х = 5.
В языке Prolog такое согласование оканчивается неудачей, поэтому ответом системы Prolog является "по". Но если пользователь имел в виду, что X — число, а знаком "+ " обозначена операция арифметического сложения, то ответ X = 4 был бы более приемлемым. Использование вместо знака "=" встроенного предиката is не позволяет полностью добиться такой интерпретации, а система CLP(R) позволяет. В соответствии с применяемыми синтаксическими соглашениями (версии SICStus Prolog) этот запрос CLP(R) может быть записан следующим образом:
?- { 1 + X = 5 !. % Числовое ограничение X = 4
Это ограничение обрабатывается специализированной процедурой решения задач в ограничениях, которая способна выполнять операции с действительными числами и обычно может находить решения систем уравнений, заданных в виде равенств или неравенств определенных типов. В соответствии с применяемыми синтаксическими соглашениями множество ограничений вставляется в предложение Prolog в виде цели, заключенной в фигурные скобки. Отдельные ограничения разделяются запятыми и точками с запятой. Как и в языке Prolog, запятая означает конъюнкцию, а точка с запятой - дизъюнкцию. Поэтому конъюнкция ограничений Cl. C2 и СЗ может быть записана так: <; Cl, C2, сз)
Каждое ограничение задается в следующей форме:
Exprl Operator Expr2
И Exprl, и Бхрг2 представляют собой обычные арифметические выражения. Они могут также, в зависимости от конкретной системы CLP(R), включать вызовы некоторых стандартных функций, таких как sin;X). В качестве Operator может быть задан один из следующих операторов, в зависимости от типа ограничения.
• =. Проверка на равенство.
• =\=. Проверка на неравенство.
• <,_<, >, >=. Арифметическое сравнение.
306 Часть II. Применение языка Prolog в области искусственного интеллекта
Теперь рассмотрим некоторые простые примеры использования этих ограничений и, в частности, определим, насколько более гибкими они являются по сравнению с обычными встроенными средствами языка Prolog.
В языке Prolog для преобразования значений температуры из градусов Цельсия в градусы Фаренгейта может применяться встроенный предикат is, например, следующим образом:
convert; Centigrade, Fahrenheit) :-Centigrade is (Fahrenheit - 32)*5/9.
С помощью этой программы можно легко преобразовать значение температуры 35 градусов Цельсия в градусы Фаренгейта, но обратное преобразование невозможно, поскольку предполагается, что при использовании встроенного предиката is все, что находится справа от него, должно быть конкретизировано. Для того чтобы обеспечить работу этой процедуры в обоих направлениях, необходимо проверить, являются ли ее параметры конкретизированы значением числа, а затем использовать формулу преобразования, подготовленную соответствующим образом для каждого случая. Но все эти операции могут быть реализованы гораздо более изящно в системе CLP(R), где одна и та же формула, интерпретируемая как числовое ограничение, действует в обоих направлениях, как показано ниже.
convert E Centigrade, Fahrenheit) :-
{ Centigrade = [Fahrenheit - 32) *S/9 ). ?- convert ( 35, F). F = 95
?- convert ( C, 95) . С = 35
Такая программа CLP{R) работает, даже если не конкретизирован ни один из двух параметров:
?- convert! С, F). t F = 32.0 + 1.8*С }
Поскольку вычисление в этом случае невозможно, ответом является формула, которая означает следующее: решение — это множество всех значений F и С, которые удовлетворяют этой формуле. Обратите внимание на то, что эта формула, выработанная системой CLP, представляет собой упрощение ограничения в рассматриваемой программе convert.
Типичная процедура решения задач в ограничениях способна решать системы линейных уравнений, заданных с помощью операторов проверки на равенство и неравенство, а также операторов арифметического сравнения. Ниже приведены подобные примеры.
?- ( 3*Х - 2*Y = 6, 2*Y = X}.
X - 3.0
У - 1.5
?- { 1 =< Х-2, Z =< 6-Х, 2 + 1 = 2).
Е = 1.0
{X >= 3.0}
(X=< 5.0}
Процедура поиска решения системы CLP(R) включает также средства линейной оптимизации, которые позволяют находить предельное значение заданного линейного выражения в области, которая удовлетворяет указанным линейным ограничениям. Для этого применяются следующие встроенные предикаты CLP(R):
minimize! Expr) maximize( Expr)
В этих двух предикатах Expr — это линейное выражение в терминах переменных, которые появляются в линейных ограничениях. Указанные предикаты находят значения переменных, удовлетворяющих этим ограничениям, и соответственно минимизируют или максимизируют значение выражения, как показано ниже.
Глава 14.Логическое программирование в ограничениях
■?- { | X =< 5), maximize(X). |
X - | 5.0 |
?- { | X =< 5, 2 =< X}, minimize ( 2*X + 3) |
X = | 2.0 |
?- { | X >-2, Y >=2, Y -< X+l, 2*Y -< 3-Х, |
X - | 4.0 |
Y = | 2.0 |
Z = | 14.0 |
?- { | X =< 5}, minimize! X). |
no |
S
2*X + 3*Y}r maximize(2)
В последнем примере значение X не имеет нижней границы, поэтому цель минимизации не достигается.
Следующие предикаты CLP(R) находят супремум (наименьшую верхнюю грань) или инфимум (наибольшую нижнюю грань) любого выражения:
supf Expr, MaxVal)
inf( Expr, MinVal)
где Expr — это линейное выражение в терминах переменных с линейными ограничениями, a MaxValи MinVal — максимальное и минимальное значения, которые принимает это выражение в тойобласти, в которойудовлетворяются ограничения. В отличие от предикатов maximize/1 иminimize/1, переменные в выражении Expr не конкретизируются крайними значениями, как показано ниже,
?- { 2 =< Хг X =< 5), inf(X, Min), sup; X, Мак].
Max =5.0
Min -2.0
(X >- 2.0J
{X =< 5.0У
?- (X >=2, Y >=2, Y -< X+l, 2*Y=< 8-X, 2 = 2*X + 3*Y},
sup ( Z,Max), inflZ.Min) , maximize ( Z) .
X = 4.0
Y = 2 . 0
Z = 14.0
Мак- 14.0
Min = 10.0
Приведенный в начале этой главы простой пример составления расписания с заданиями а, Ь, с и d может быть представлен в системе CLP(R) в следующем непосредственном виде:
?- (Та + 2=<= ТЬ, % | Задание |
Та + 2 =< Тс, % | Задание |
ТЬ + 3 =< Td, % | Задание |
Тс + 5 =< Tf, % | Задание |
Td + 4 = < Tf }, % | Задание |
minimize( Tf), | |
Та = 0.0 | |
Tb = 2.0 | |
Td = 5.0 | |
Tf » 9.0 | |
{Тс -< 4.0} | |
{Тс >= 2.0> |
а предшествует заданию Ь а предшествует заданию с Ь предшествует заданию d с завершается ко времени завершения Tf
d завершается ко времени завершения Tf
Следующий пример еще более наглядно показывает, насколько гибкими являются ограничения по сравнению со стандартными арифметическими средствами Prolog. Рассмотрим применение предиката fib { N, F) для вычисления Ы-го числа Фибоначчи Г следующим образом: F (0) -1, F(l)«l, Г(2)»2, F<3)-3, Г(4)=5и т.д. В общем для N > 1, F(N> = F(N-l) + F (N-2). Ниже приведено определение процедуры fib/2 на языке Prolog.
fib( и, F) :-N=0, F=l
И-1, F=l
308 Часть II. Применение языка Prolog в области искусственного интеллекта
В>1,
N1 is H-l, fib(Hl.Pl), N2 is H-2, fib{N2,F2), F is Fl + F2.
Предполагается, что эта программа должна использоваться таким образом: ?- fib(6, г} . F=13
Но рассмотрим следующий вопрос, в котором предпринята попытка решить обратную ^задачу: ?- fib CM, 13) .
Он приводит к ошибке, поскольку цель N > 1 выполняется с неконкретизирован-ным значением к. Но эта же программа, записанная с помощью определений CLP(R), может использоваться более гибко, как показано ниже,
fib ( N, Т) :-i N = О, F - 13
'(И - 1, F-1}
t К > 1, F = F1+F2, HI -S-1, N2 = Н-2Ь fib(HI, Fl), fib(N2, F2>.
Эта программа может быть вызвана на выполнение в противоположном направлении. Например, она позволяет следующим образом определить по числу Фибоначчи F его индекс Ы: ?- fib( N, 13) . N = 6
Но данная программа все еще сталкивается с затруднениями после получения вопроса, для которого нельзя найти удовлетворительного решения: ?- fib{ Ы, 4}.
Эта программа постоянно предпринимает попытки найти такие два числа Фибоначчи F1 и F2, что Fl + F2 = 4. Она продолжает вырабатывать все более крупные значения F1 и F2 в расчете на то, что в конечном итоге их сумма будет равна 4. Программе неизвестно, что после того как сумма чисел Фибоначчи превысит 4, она будет лишь возрастать и поэтому никогда не станет равной 4. Наконец, этот бессмысленный поиск оканчивается переполнением стека. Решение, которое показывает, как исправить эту ситуацию, введя некоторые очевидные дополнительные ограничения в процедуру fib, является очень поучительным. Легко понять, что для всех N имеет место F(N) £ N. Поэтому переменные N1, Fl, N2 и F2 в данной программе должны всегда удовлетворять следующим ограничениям: F1 >= N1, F2 >= N2. Эти дополнительные ограничения можно добавить к ограничениям в третьем дизъюнкте в теле предложения fib. В результате эта программа принимает вид
fib( S, F) :-
{ Я > 1, F = F1+F2, Hi = Н-1, N2 = 8-2,
F1 >= HI, F2 >- Н2}, % Дополнительные ограничения fib(HI, Fl) , fib( 82» F2) .
Указанные дополнительные ограничения позволяют программе определить, что приведенный выше вопрос должен окончиться неудачей:
?- fib С К, 4) .
по
При рекурсивных вызовах процедуры fib фактически развертывается выражение
для F в условии F = 4:
4 - F - Fl + F2 =
Fl' + F2' + F2 =
Р1< ' + F2" + F2" + F2
Глава 14. Логическое программирование в ограничениях
После каждого развертывания этого выражения к предыдущим ограничениям добавляются новые ограничения. Ко времени получения данного выражения суммы с четырьмя термами процедура решения задач в ограничениях обнаруживает, что накопленные ограничения содержат противоречие, которое никогда не удастся разрешить.
Рассмотрим кратко системы CLP(Q), которые являются ближайшими аналогами систем CLP(R). Различие между ними состоит в том, что в системах CLP(R) действительные числа аппроксимируются числами с плавающей точкой, а областью определения Q являются рациональные числа, т.е. дроби, состоящие из двух целых чисел. Они могут использоваться в качестве другого способа аппроксимации действительных чисел. Область определения Q может иметь преимущество над областью определения R (представленной с помощью чисел с плавающей точкой) в том, что решения некоторых арифметических ограничений могут быть определены в виде дробей точно, а числа с плавающей точкой позволяют получить лишь приближенные значения. Соответствующий пример приведен ниже.
?- ( X=2*Y, Y=l-X }.
Процедура решения CLP(Q) дает следующий ответ: X = 2/3, Y = 1/3. Процедура решения CLP{R) сообщает приблизительно такой ответ: X = 0.666666666, Y = 0.333333333.
Упражнение
14,3.Покажите, как при выполнении запроса "?-fib( N, 4; .", приведенного выше в качестве примера, процедура решения задач в ограничениях позволяет обнаружить, что накопленные ограничения не могут быть удовлетворены.
Планирование с помощью метода CLP
Задачи составления расписаний, которые рассматриваются в этом разделе, состоят из перечисленных ниже элементов.
• Множество задач7;, ...,Т:..
• Продолжительности Di........ Dn задач.
• Ограничения предшествования, заданные как отношения, следующим образом:
prec( Ti, ?,!
Такое ограничение указывает, что выполнение задания Т. должно закончиться до того времени, как может начаться выполнение задания Tj.
• Множество процессоров::., которые могут применяться для выполнения заданий.
• Ограничения на ресурсы: какие задания могут выполняться теми или иными процессорами (какие процессоры являются подходящими для выполнения конкретного задания).
Задача состоит в том, чтобы составить расписание, время завершения которого
является минимальным. В расписании назначается процессор для каждого задания и
задается время начала выполнения каждого задания. Безусловно, расписание должно
удовлетворять всем ограничениям предшествования и распределения ресурсов: каж
дое задание должно быть выполнено подходящим процессором, причем ни один про
цессор не может выполнять два задания одновременно. В соответствующей формули
ровке CLPзадачи составления расписания применяются следующие переменные:
значение времени начала, S1, ..., Sn, и имена процессоров, назначенных для каждого
задания, Р1........ Рп.
Простым частным случаем этой задачи составления расписания является отсутствие ограничений на ресурсы. В таком случае предполагается, что ресурсы не ограничены, поэтому в любое время всегда имеется свободный процессор для выполнения
310 Часть II. Применение языка Prolog в области искусственного интеллекта
любого задания. Таким образом, достаточно добиться удовлетворения ограничений, соответствующих отношениям предшествования между заданиями. Как уже было показано во вступительном разделе данной главы, подобная задача может быть сформулирована очень просто. Предположим, что имеется следующее ограничение предшествования, касающееся заданий а и Ь, — prec ( a, b). Допустим, что продолжительность задания а равна Da, а значения времени начала выполнения заданий а и b равны Sa и Sb. Чтобы было удовлетворено ограничение предшествования, значения Sa и sb должны соответствовать следующему ограничению: { За + Da -< Sb }
Кроме того, требуется обеспечить, чтобы ни одно задание Т. не могло начаться до времени 0, а все задания должны быть выполнены ко времени завершения расписания FinTime следующим образом: { Si >- 0, Si + Di =< FinTime }
Для определения конкретной задачи составления расписания введем следующие предикаты: tasksC [Taskl/Durationl, Task2/Duration2, ...])
Приведенный ниже предикат задает список всех имен заданий и значений их продолжительности. prec( Taskl, Task2)
Этот предикат определяет отношение предшествования между заданиями Taskl и Тазк2. resource! Task, [ Procl, Proc2, ...])
Данный предикат указывает, что задание Task может быть выполнено любым из
процессоров Procl, Proc2................. Обратите внимание на то, что это - спецификация
задачи составления расписания, аналогичная используемой в главе 12 (раздел 12.3), где для составления наилучшего расписания применялся эвристический поиск по заданному критерию. Но фактически применяемая здесь формулировка является немного более общей. В главе 12 ограничения на ресурсы заключались лишь в том, что лимитировалось количество процессоров, но все эти процессоры считались пригодными для выполнения любого задания.
Вначале рассмотрим простой случай без ограничений на ресурсы. Один из способов решения задачи планирования такого рода см. в разделе 14.1. Программа, приведенная в листинге 14.1, является более общей реализацией той же идеи. В этой программе подразумевается использование некоторого определения конкретной задачи планирования с использованием представления, описанного выше. Основным предикатом является schedule! Schedule, FinTime)
где Schedule — наилучшее расписание для задачи, которая определена с помощью предикатов tasks и prec, a FinTime — время завершения расписания. Расписание имеет следующее представление:
Schedule = [ Taskl/Startl/Durationl, Task2 /Start2/Duration2, ...]
Листинг 14.1. Планирование с учетом ограничений предшествования и без учета ограничений на ресурсы
I Планирование с помощью метода ■.::.■..' С неограниченными ресурсами
scheduler Schedule, FinTime) :-tasks С TasksDurs), precedence_constr{ TasksDurs, Schedule, FinTime), % Сформировать отношения
% предшествования minimize! FinTime).
precedence_constr( [1, [], FinTime).
Глава 14. Логическое программирование в ограничениях
precedence_constr( [T/D ] TDs], [T/Start/D | Rest], FinTime) :-
{ Start >- 0, % Выполнение должно начаться не раньше времени О
Start + D =< FinTime}, % Временем завершения должно быть FinTime precedence_constr( TDs, Rest, FinTime), prec_constr( T/Start/D, Rest).
pzec_constx [ _, []) .
prec_constr( T/S/D, [T1/S1/D1I Rest]) :-
( prec( T, Tl;, !, t S+D -< SI}
s precl Tl, T) , ! , { Sl + Dl =< S)
true ),
prec_constr( T/S/D, Rest).
% Список заданий, подлежащих планированию
tasks; [ tl/5, t2/7, t3/10,t4/2, t5/9L>.
% Ограничения предшествования
prec[ tl, t2). prec( tl,t-4) . prec ( t2, t3). prec i t4, t5).
Процедура schedule, по сути, выполняет описанные ниже действия.
1. Формирует ограничения неравенства между значениями времени начала в расписании, соответствующие отношениям предшествования между заданиями.
2. Минимизирует время завершения с учетом сформированных ограничений неравенства,
Поскольку все ограничения являются линейными неравенствами, данная задача сводится к задаче линейной оптимизации, для решения которой в системе CLP(R) предусмотрены встроенные средства.
Предикат pracedeiice_constr« TasksDuraticns, Schedule, FinTime)
формирует ограничения между значениями времени начала заданий в расписании Schedule и значением времени завершения расписания FinTime. Предикат prec_constr[ Task/Start/Duration, RestOfSchedule)
формирует ограничения между значением времени начала Start задания Task и значениями времени начала заданий в списке RestOfSchedule таким образом, чтобы эти ограничения на значения времени начала соответствовали ограничениям предшествования между заданиями.
В программе, приведенной в листинге 14.1, содержится также определение простой задачи составления расписания с пятью заданиями. Эта программа-планировщик вызывается на выполнение с помощью следующего вопроса:
?- schedule; Schedule, FinTime) .
FinTime = 22,
Schedule = [tl/0/5, t2/5/7, t3/12/10,t4/S4/2,t5/S5/9],
(SS =< 13)
{S4 >= 5}
(S4-S5 =< -2)
Для заданий t4 и t5 предусмотрена определенная степень свободы в отношении времени их начала. При всех значениях времени начала 3 4 и S5 для заданий t4 и t5 в пределах указанных интервалов достигается оптимальное время завершения расписания. Три другие задания (tl, t2 и t3) находятся на критическом пути, и время их начала не может сдвигаться.
Часть II. Применение языка Prolog в области искусственного интеллекта
Теперь рассмотрим задачи планирования более сложного типа — с ограничениями на ресурсы. Например, в задаче планирования, приведенной в главе 12 (см. раздел 12.3, рис. 12.6), имеются всего три процессора. Хотя любой из процессоров может выполнять какое угодно задание, это ограничение означает, что одновременно могут обрабатываться не больше трех заданий.
На этот раз приходится иметь дело с ограничениями следующих двух типов.
1. Отношения предшествования между заданиями.
2. Ограничения на ресурсы.
Ограничения предшествования могут обрабатываться таким же образом, как и в программе, приведенной в листинге 14.1. Теперь рассмотрим ограничения на ресурсы. Для удовлетворения ограничений на ресурсы необходимо решить задачу назначения некоторого процессора для каждого задания. Такую задачу можно решить, введя для каждого задания Ti еще одну переменную, Pi, возможными значениями которой являются имена процессоров. В соответствии с этим необходимо немного расширить представление расписания:
Schedule = [ Taskl/Frocl/Startl/Durl, Task2/Proc2/Start2/Dur2, ...]
где Procl — процессор, назначенный заданию Taskl, Startl — время начала задания Taskl и Durl — его продолжительность. Теперь с использованием такого представления расписания можно разработать программу, приведенную в листинге 14.2, как расширение программы из листинга 14.1. Основным предикатом снова является schedule ( BestSchedule, BestTime)
Этот предикат позволяет составить расписание с минимальным временем завершения BestTime. Ограничения неравенства, налагаемые на значения времени начала с учетом отношений предшествования между заданиями, снова формируются с помощью следующего предиката:
precedence^onstr ( TasksDurations, Schedule, FinTime!
Листинг 14.2. Программа составления расписания CLP(R)для задач с ограничениями предшествования и ограничениями на ресурсы
& Планирование с помощью метода CLP с ограниченными ресурсами
schedule ( BestSchedule, EestTime) :-tasks{ TasksDurs), precedence_constr{ TasksDurs, Schedule, FinTime), % Задать неравенства,
\ обозначающие отношения предшествования
initialise bound, % Инициализировать предельное
% значение времени завершения
assign_processors{ Schedule, FinTime), '--. Назначить процессоры для заданий
minimize( FinTime),
update_bound( Schedule, FinTime),
fail \ Выполнить перебор с возвратами, чтобы найти другие расписания
bestsofar( BestSchedule, EestTime) . % Наилучшее к этому времени расписание
% precedence_constr { TasksDurs, Schedule, FinTime):
% На основании исходных данных, которые представляй^ собой задания и значения
их продолжительности, сформировать структуру Schedule, состоящую -:
% переменных, которые представляют время начала выполнения заданий.
% Значения этих переменных и времени завершения FinTime определяются
Щ с учетом ограничений предшествования, сформулированных в виде неравенств
precedence_constr{ [], [], FinTime).
precedenee_constr ( [T/D | TDs), [T/Proc/Start/D [ Rest], FinTime) :-
{ Start >- 0, % Выполнение должно начаться не раньше времени 0
Start + D =< FinTime } , % Временем завершения должно быть FinTime
precedence_Constr( TDs, Rest, FinTime;,
Глава 14. Логическое программирование в ограничениях
prec_Constr( T/Proc/Start/D, Rest) .
prec_constr( _, [}) .
prec_constr[ T/P/S/D, [T1/P1/S1/D1 I Rest]) :-( ~prec( T, 11) , 1 , { S+D =< SI}
precf Tl, T) , i, { Sl+Dl =< S}
true ) , prec_constr ( T/P/S/D, Rest) .
I assign_prccessors( Schedule, FinTime}:
% назначить процессоры заданиям в расписании Schedule
assign^processors I [] , FinTime).
assign_processors { [T/P/S/D | Rest], FinTime} :-
assign_Drocessors( Rest, FinTime),
resource; T, Processors), % Задание Т может быть выполнено любым
4 процессором из списка Processors
member( p, Processors), % Выбрать один из подходящих процессоров
resource_constr ( T/P/S/D, Rest}, % Учесть ограничения на ресурсы
bestsofarf _, BestTimeSoFarj,
( FinTime < BestTimeSoFar} . % Новое расписание лучше любого
% найденного к этому времени
% resource__coristr ( ScheduledTask, TaskList) :
сформировать ограничения так, чтобы гарантировалось отсутствие I противоречивых назначений ресурсов в списках ScheduledTasJc и TaskList
resource_constr( _, []) .
resource COnetr< Task, [Taskl I Rest]) • ;-
no_conflict( Task, Taskl), resource_constr ( Task, Rest).
no_conflict[ T/P/S/D, T1/P1/S1/D1)
P \« PI, ! % Разные процессоры
prec{ T, Tl) , ! % Ограничения уже учтены
prec{ Tl, T) , ! % Ограничения уже учтены
( -S+D =< 51 % Тот же процессор,- перекрытие по времени отсутствует
Sl+Dl =< S} . initialise_bound : -retract (feestsefar (_,_}>, fail
assert! bestsofarf dummy_schedule, 9999) ) . % Предполагается, что время Ч завершения ни при каких условиях не превышает 9999
% update_bound[ Schedule, FinTime):
* обновить определение наилучшего расписания и значение времени
update_bound( Schedule, FinTime) :-retract ( bestsofar[_,_)), ! , assert( bestsofar( Schedule, FinTime]).
% Список заданий, которые должны быть включены в расписание
tasks! [tl/4,t2/2,t3/2, t4/20, t5/20, t6/ll, t7/ll]) .
Часть II. Применение языка Prolog в области искусственного интеллекта
- Ограничения предшествования
ргес[ tl, t4), prec( tl, t5). precC t2, t4). prec( t2, t5). prect t2, t6). prec( t3, t5). prect t3, t«>. prec< t3, tl).
i resource) Task, Processors):
% любой процессор из списка Processors, пригодный для выполнения задания Task
resource) _, [1,2,3]). % Три процессора, причем любой из них пригоден
% для выполнения любого задания
Данная программа почти аналогична приведенной в листинге 14.1. Единственное небольшое различие между ними связано с разными представлениями расписания.
Теперь рассмотрим ограничения на ресурсы. Задачу составления расписания с учетом этих ограничений нельзя решить столь же эффективно, как и с учетом ограничений предшествования. Чтобы удовлетворить ограничения на ресурсы, необходимо найти оптимальное распределение процессоров но заданиям. Для этого требуется выполнить поиск среди возможных вариантов назначения, а общего способа выполнения такого поиска за время, измеряемое некоторым полиномом, не существует. В программе, приведенной в листинге 14.2, такой поиск выполняется с помощью .метода ветвей и границ, который можно кратко описать следующим образом. С помощью недетерминированного способа один за другим формируются варианты расписания (этот способ состоит в том, что формируется расписание и вызывается цель fail). Наилучшее расписание из всех составленных до сих пор вносится в базу данных как факт. После составления каждого нового расписания в базе данных обновляется расписание, наилучшее к этому времени (которое обозначается как bestsofar). При составлении нового расписания наилучшее к этому моменту время завершения используется в качестве верхней границы для времени завершения нового расписания. Как только обнаруживается, что новое частично сформированное расписание не позволяет достичь сокращения времени завершения по сравнению с наилучшим к данному моменту, происходит отказ от дальнейшей работы над этим расписанием.
Эта идея реализована в программе, приведенной в листинге 14.2, следующим образом. Процедура assign_processors недетерминированно назначает заданиям один за другим подходящие процессоры. Назначение процессора заданию приводит к появлению дополнительных ограничений на значения времени начала, поскольку необходимо обеспечить, чтобы не перекрывалось время между заданиями, назначенными одному и тому же процессору. Поэтому после каждого назначения процессора заданию частично составленное расписание улучшается. После каждого такого улучшения частично составленного расписания оно проверяется для определения того, есть ли какая-либо возможность улучшить расписание, которое к данному времени является наилучшим. Для того чтобы текущее частично составленное расписание предоставляло хотя бы малейшую такую возможность, его значение FinTime должно быть меньше по сравнению с наилучшим временем, достигнутым до сих пор. В программе это условие учитывается с помощью следующего ограничения: ( FinTime < BestTimeSoFar }
Если это ограничение несовместимо с другими текущими ограничениями, то данное частично составленное расписание не дает ни малейшей возможности для улучшения. А поскольку для окончательного составления этого расписания необходимо удовлетворить еще больше ограничений на ресурсы, то фактически время его завершения может в конечном итоге стать еще хуже. Поэтому, если ограничение FinTime < BestTimeSoFar удовлетворить невозможно, то работа над текущим частично составленным расписанием прекращается; в противном случае процессору назначается другое задание и т.д. После составления каждого полного расписания можно гарантировать, что время его завершения меньше по сравнению с наилучшим временем завершения, найденным до сих пор. Поэтому обновляется расписание, наилучшее к
Глава 14. Логическое программирование в ограничениях
данному моменту. Наконец, если характеристики, наилучшие к данному моменту, ' нельзя улучшить, поиск останавливается. Безусловно, этот алгоритм вырабатывает только одно из многих возможных наилучших расписаний.
Следует отметить, что этот процесс является комбинаторно сложным из-за экспоненциального количества возможных вариантов назначения процессоров заданиям. Но благодаря тому, что характеристики текущего частично составленного расписания контролируются с помощью значения BestTimeSoFar, удается отказаться от целых групп некачественных расписаний еще до их полного составления. Количество времени вычисления, которое экономится благодаря этому, зависит от того, насколько удачно выбрана эта верхняя граница. Если верхняя граница не оставляет возможностей для маневра, то некачественные расписания распознаются и отбрасываются на ранних этапах их создания, что позволяет экономить много времени. Поэтому, чем быстрее удается найти какой-то качественное расписание, тем скорее начинает применяться жестко заданная верхняя граница и тем большая часть пространства поиска исключается из рассмотрения.
В листинге 14.2 содержится также спецификация задачи составления расписания (см. рис. 12.6), которая подготовлена в соответствии с принятыми соглашениями по оформлению условий задачи. Вопрос, с помощью которого может быть составлено расписание, соответствующее условиям этой задачи, приведен ниже. ?- schedule Schedule, FiiiTime] . FinTime = 24
Schedule = t tl/3/0/4,12/2/0/2,13/1/0/2, t4/3/4/20, t5/2/4/20, 16/1/2/11,17/1/13/11]
Задача 11 выполняется процессором З, начиная со времени О, задача t2 выполняется процессором 2, начиная со времени 0 и т.д. В этой задаче планирования представляет интерес еще один нюанс. Все три доступных процессора являются эквивалентными, поэтому перестановка списков назначений процессоров заданиям не приводит к каким-либо изменениям. Таким образом, нет смысла выполнять поиск по всем подобным перестановкам, так как они должны давать одинаковые результаты. Чтобы избежать таких бесполезных перестановок, можно, например, закрепить процессор 1 за заданием t7 и ограничить выбор процессора для задания t6 только процессорами 1 и 2. Такое решение можно легко реализовать, изменив предикат resource. Тем не менее, хотя в целом это — неплохая идея, как оказалось, ее нет смысла реализовывать в данном конкретном примере. Несмотря на то что при этом количество возможных вариантов назначения могло бы сократиться в б раз, экономия времени является незначительной, что на первый взгляд кажется необъяснимым, Но причина этого состоит в том, что после обнаружения оптимального расписания устанавливается жесткая верхняя граница выбора времени завершения, поэтому в дальнейшем отказ от других возможных назначений процессора происходит очень быстро.
Упражнения
14.4. Проведите эксперименты с программой, приведенной в листинге 14.1. Попробуйте применить разные спецификации ресурсов, предназначенные для исключения бесполезных перестановок, и проведите измерения продолжительности времени выполнения программы. В чем состоят достигнутые улучшения?
14.5. В программе, приведенной в листинге 14.2, верхняя граница значений времени завершения (best sofa г) инициализируется большим числовым значением, которое намного превосходит все возможные значения времени завершения. Это позволяет гарантировать, что оптимальное расписание будет находиться в пределах установленной границы и будет обнаружено программой. Такой подход, хотя и безопасный, является неэффективным, поскольку подобная верхняя граница, оставляющая большую свободу для маневра, не позволяет достаточно качественно ограничивать поиск. Рассмотрите другие подходы к инициализации и корректировке верхней границы, например, при которых работа
316 Часть II. Применение языка Prolog в области искусственного интеллекта
начинается с очень низкой границы и увеличивается по мере необходимости (если в пределах этой границы не существует ни одного расписания). Сравните значения продолжительности работы программы при использовании разных подходов. Проведите измерения времени выполнения для того случая, когда граница сразу же устанавливается равной действительно минимальному времени завершения.