У меня есть следующие условия для двух переменных A и B:

[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
     % expression 1
;
     % expression 2
)
%cntd

Проблема в строке 2, решатель не знает о значении A и B, как решить, какая ветвь условия будет продолжена без указания значения переменных в строке 2?

Разумным действием является выбор этой ветви на основе значения переменных, когда решатель пересекает возможные значения переменных. Но, как я обнаружил, он проходит через одно из этих выражений, прежде чем узнает значение переменных. Какое решение предотвратить это?

1
OmG 13 Май 2019 в 14:48

2 ответа

Лучший ответ

Программирование с ограничениями хорошо подходит для Prolog, если вы придерживаетесь чистой логики. Но, как вы демонстрируете, нельзя свободно смешивать процедурные элементы, такие как cut (!) и if-then-else (->;) , с логикой ограничения.

Использование if-then-else или cuts безопасно только в том случае, если условие влечет за собой (то есть безусловно верно) или отключено (безоговорочно false) в «время установки ограничения». На практике это означает, что такие условия не должны содержать проблемные переменные (переменные домена и т. Д.), А только проблемные параметры (константы), которые известны априори. Выделенные языки моделирования различают эти две вещи, а Пролог - нет.

Как НЕ выражать альтернативы в моделях ограничений

Вышесказанное означает, что вы не можете использовать cut / if-then-else для выражения той альтернативы, которую вы хотели выразить. Может быть заманчиво просто избавиться от аспекта условного выбора условного выбора и переписать его как чистую дизъюнкцию. Например, вы можете переписать

( Usage #>= 1000 -> Rate#=7, Bonus#=100              % WRONG
;                   Rate#=9, Bonus#=0
)

Как чистый разрыв

( Usage #>= 1000, Rate#=7, Bonus#=100                % INEFFICIENT
; Usage #<  1000, Rate#=9, Bonus#=0
)

Хотя теперь это логически правильно, не делайте этого! Пролог исследует альтернативы (выраженные с помощью точки с запятой (;) или нескольких предложений) с помощью обратного отслеживания, т. Е. С нетерпением сначала выбирая одну альтернативу, а затем возвращаясь к другой. Это обычно разрушает любую надежду на эффективную программу ограничения! В программе ограничений весь поиск должен быть расположен в процедуре поиска / маркировки.

Ограничения Reified

Если и условие, и ветви альтернатив являются ограничениями, которые имеют усовершенствованную реализацию (т. Е. Реализацию, которая может отражать истинность ограничения в булевой переменной), вам повезло: вы можете переписать всю условную альтернативу с помощью из специальных связок для ограниченных ограничений (в ECLiPSe: and, or, neg, =>, #=). Для приведенного выше примера:

Usage #>= 1000  =>  Rate#=7 and Bonus#=100,            % OK
Usage #<  1000  =>  Rate#=9 and Bonus#=0

Или

Usage #>= 1000 and Rate#=7 and Bonus#=100 or           % OK
Usage #<  1000 and Rate#=9 and Bonus#=0

К сожалению, только основные арифметические ограничения имеют реализованные версии и могут быть объединены таким образом!

Использование других встроенных ограничений

В некотором смысле, работа с альтернативами является наиболее сложной частью решения проблем, и многие встроенные ограничения решают эту проблему. Поэтому стоит проверить, можно ли смоделировать проблему поверх существующих встроенных ограничений без каких-либо явных различий в модели. Кандидаты element / 3, table / 2. disjunctive / 2 и многие другие.

Задержка выбора

Последним средством решения проблемы является задержка изучения альтернатив до тех пор, пока не будет однозначно определена истинность условия. В ECLiPSe это проще всего с пунктами задержки. Используя пример ОП:

delay choice(A, B) if var(A);var(B).     % wait for A,B to be known
choice(A, B) :-
    ( (A>3 ; B>3) ->                     % can now use normal Prolog tests
        write("expression 1")
    ;
        write("expression 2")
    ).

Это работает, но будет действовать только после создания экземпляров A и B. Если, как и в этом случае, условие можно исправить, мы можем сделать несколько лучше:

choice(A, B) :-
    Bool #= (A#>3 or B#>3),
    delayed_choice(Bool).

delay delayed_choice(Bool) if var(Bool).
delayed_choice(1) :- write("expression 1").
delayed_choice(0) :- write("expression 2").

Это будет действовать, когда условие выполнено доменами:

?- choice(A, B), B #> 3.
expression 1

Превращение общих дизъюнкций в ограничение

ECLiPSe имеет изящную функцию под названием Обобщенное распространение в библиотека (пропия). Это может эффективно превратить дизъюнкции Пролога в ограничение, используя простую аннотацию. Начиная с правильной, но неэффективной формулировки выше, мы можем написать:

?- ( Usage #>= 1000, Rate#=7, Bonus#=100
   ; Usage #<  1000, Rate#=9, Bonus#=0
   ) infers most.

Usage = Usage{-1.0Inf .. 1.0Inf}
Rate = Rate{[7, 9]}
Bonus = Bonus{[0, 100]}
There is 1 delayed goal.
Yes (0.00s cpu)

Как показывают области Rate и Bonus, полезная информация была извлечена из дизъюнкции, еще до того, как может быть выбрана применимая альтернатива.

1
jschimpf 15 Май 2019 в 15:24

В чем проблема? Важное замечание заключается в использовании -> (стрелка) для if-else. Когда у нас есть выражение S -> T ; U, будет вычисляться S, и если оно содержит некоторые переменные, это может иметь некоторые побочные эффекты в коде. Чтобы быть более понятным, попробуйте запустить несколько примеров:

?-[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
    write("expression 1")
;
    write("expression 2")
).

Так как значение A и B не определено, условие всегда выполняется и значение expression 1 будет напечатано. Кроме того, результат:

A = A{1 .. 10}
B = B{1 .. 10}
There are 6 delayed goals.
Yes (0.00s cpu)

Как вы можете видеть, границы A и B не меняются, так как они подвешены к будущим выражениям, и, как у нас нет, граница не изменяется.

Теперь попробуйте другой пример:

?- [A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
    write("expression 1")
;
    write("expression 2")
),
A = 3. % this line is added

Поскольку значение A определено, A #> 3 не соответствует действительности, но как насчет B? Это равно 3 или больше 3? Как мы уже говорили, условная часть будет выполнена! Следовательно, последнее ограничение B - B #> 3. И кроме выполнения write("expression 1"), результат:

A = 3
B = B{4 .. 10}
Yes (0.00s cpu)

Последний пример:

?- [A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
    write("expression 1")
;
    write("expression 2")
),
A = 3,
B = 3. % this line is added

Также в этом примере напечатано expression 1 и результат:

No (0.00s cpu)

Это из-за того, что глава стрелки и всегда expression 1 будут выполнены.

Одно из решений использует оператор ;:

[A,B] #:: 1..10, 
(
    (A = 3, B = 3, write('expression 21'))
    ; 
    (A = 3, B #> 3, write('expression 11'))
    ; 
    (A #> 3, B #> 3, write('expression 12'))
    ; 
    (A #> 3, B = 3, write('expression 13'))
), 
A = 3,
B = 5.

Результат приведенного выше кода:

A = 3
B = 5
Yes (0.00s cpu, solution 1, maybe more)

И он печатает:

expression 21 expression 11

Это означает, что первая ветвь выходит из строя, но она не удалась и автоматически вернулась, и переходит к следующему случаю! и как следующий случай применяется, все идет успешно там!

-1
OmG 15 Май 2019 в 10:52