2020年4月10日金曜日

AならばBについて

二つの条件 p 、q に対して、「 p を満たすものは全て q も満たす 」 というとき、「 p は q である為の十分条件である 」 あるいは 「 q は p である為の必要条件である 」 という。また、「 p は q である為の十分条件であり、q は p である為の十分条件である 」 というとき、「 p は q である為の必要十分条件である 」 あるいは 「 p と q とは同値である 」 という。 となっています。
この辺、中々面倒なので、こちらが参考になります。 https://mathtrain.jp/conditions

AならばB(A→B)を論理で表現すると
~A |B
となります。これは、AがTrueとすると~AはFalse、よってBがTrueとなることが必然となることから理解できます。

反対方向、B→Aは、
~B|A
となります。必要にして十分、同値の関係は、上記が、同時に成立することですから
~A|B & A|~B
となります。よって、同値制約は、以下の通りとなります。


def 等号制約(a,b,s):
    sc3.AddHard( (a|~b) &(~a|b),s)

2020年4月9日木曜日

土日夜勤した場合10日Window内で公休を1日取得2日ある場合はその月内でもう1日取得

実は、前に実装した土日夜勤した場合前後の3日間で代休を取る が破綻してしまいました。1年を通してシミュレーションしてみると2021年5月の連休で物理的に取得不可能なことが判り、クライアントに1ヶ月3分割することを提案しました。で、出てきた仕様が次です。
■土日夜勤の場合、公休が2日取得となりますが そのうち1日は必ず「1か月を3分割し、そのWindow以内に公休を消化する」にし、もう1日は同月内であれば、いつでも良いとする。もちろん、金土夜勤の場合、日月夜勤の場合は今まで通り「1か月を3分割し、そのWindow以内に公休を消化する」こととする。
■仕様の背景は、理解していません。とりあえず、実装し易い形に変形しています。 以下をPythonで実装しています。 土日夜勤した場合10日Window内で公休を1日取得2日ある場合はその月内でもう1日取得()
ここでの、ポイントは、公休(代休)が取得される論理です。例えば、A Window期間中に土日のどちらか夜勤があれば、A Window中、公休は必ず1個取得されます。これが、Ac==Aa|Ab 部になります。さらに、B Window中に土日連続夜勤があったとき(Bab) A Window で公休が発生するというロジックです。夜勤数、公休数共に、2個以下に制限してあるので、この条件が発生するのはAc==Babでしか起こりえません。 また、公休が発生しないのは、(Aa|Ab|Bab) のどれも該当しない、つまり夜勤が発生しないときは、公休を生成させないということに符合します。 ということで、簡単なロジックで、必要にして十分、つまり同値であることが判ります。同値のルーチンは、前々回に説明しました。また、Acや、Acは、ΣXi>=1 という関係です。これは、前回説明しました。それぞれのWindow内でORを取ればよいことが判ります。 同様にして、他のWindowも処理できます。 かなり複雑な仕様ですが、Pythonで100行足らずで記述できました。かなり難しい部類のプログラムだと思いますが、SeqCompや、SeqExpr等の制約関数を使っていないので、性能的にもMaxになっているはずです。

a:土夜 b:日夜
c:公休(土日以外)
A:Window1 B:Window2 C:Window3
Ac== (Aa| Ab |Bab)
Bc== (Ba| Bb |Cab)
Cc== (Ca| Cb |Aab)
Σ(a|b)<=2 今月
Σc<=2 今月

def 等号制約(a,b,s):
    sc3.AddHard( (a|~b) &(~a|b),s)
 
def 代休window処理_v2(person,window):
    holiday_domain=[]
    sat_sun=[]
    ss=[]
    for day in window:
        if day in 土 or day in 日:
            sat_sun.append(day)
        else:
            holiday_domain.append(day)
        if day in 土 and day+1 in window:
            ss.append(day)
    
    vreturn_list=[]
    vsat_sun=[]
    vholidays=[]
    vssdays=[]
    for day in ss:
         #sc3.print('土'+str(day)+'\n')
         v1=sc3.GetShiftVar(person,day,'入り')#
         v2=sc3.GetShiftVar(person,day+1,'明け')
         vssdays.append(v1&v2)#土日連続夜勤

    for day in sat_sun:
         #sc3.print('土'+str(day)+'\n')
         v1=sc3.GetShiftVar(person,day,'入り')#
         v2=sc3.GetShiftVar(person,day,'明け')
         vsat_sun.append(v1|v2)#土日に夜勤
    for day in holiday_domain:
         #sc3.print('H'+str(day))
         v1=sc3.GetShiftVar(person,day,'公休')#
         vholidays.append(v1)#公休
    s=staffdef[person]+daydef[day]+'代休処理'
    #sc3.AddSoft(sc3.SeqComp(vsat_sun,vholidays),s,7)
    vreturn_list.append(sc3.Or(vsat_sun))
    vreturn_list.append(sc3.Or(vholidays))
    vreturn_list.append(sc3.Or(vssdays))
    return vreturn_list
       


def 土日夜勤した場合10日Window内で公休を1日取得2日ある場合はその月内でもう1日取得():
    今月日数=len(今月)
    WindowDays=今月日数/3 #3分割
    win1=[]
    win2=[]
    win3=[]
    for day in 今月:
        if len(win1)<   WindowDays:
            win1.append(day)
        elif len(win2)< WindowDays:
            win2.append(day)
        else:
            win3.append(day)
    sc3.print('window1=',str(len(win1))+'\n')
    sc3.print('window2=',str(len(win2))+'\n')
    sc3.print('window3=',str(len(win3))+'\n')
    
    
    #今月で見たときに土日出勤数==公休数とする
    sat_sun=[]
    holiday_domain=[]
    for day in 今月:
        if day in 土 or day in 日:
            sat_sun.append(day)
        else:
            holiday_domain.append(day)

    for person in 夜勤可能:
        vsat_sun=[]
        vholidays=[]
        vlist1=代休window処理_v2(person,win1)#Window内では1日に限定
        vlist2=代休window処理_v2(person,win2)#Window内では1日に限定
        vlist3=代休window処理_v2(person,win3)#Window内では1日に限定
    
        for day in sat_sun:
            #sc3.print('土'+str(day)+'\n')
            v1=sc3.GetShiftVar(person,day,'入り')#
            v2=sc3.GetShiftVar(person,day,'明け')
            vsat_sun.append(v1|v2)
        for day in holiday_domain:
            #sc3.print('H'+str(day))
            v1=sc3.GetShiftVar(person,day,'公休')#
            vholidays.append(v1)
        s=staffdef[person]+daydef[day]+'今月処理'
       
        sc3.AddHard(sc3.SeqLE(0,2,vsat_sun),s+'土日')#土日夜勤は2回以内
        sc3.AddHard(sc3.SeqLE(0,2,vholidays),s)#公休2回以内
        等号制約(vlist1[0]|vlist2[2],vlist1[1],'Ac==(Aa|Ab|Bab)') 
        等号制約(vlist2[0]|vlist3[2],vlist2[1],'Bc==(Ba|Bb|Cab)') 
        等号制約(vlist3[0]|vlist1[2],vlist3[1],'Cc==(Ca|Cb|Aab)') 

2020年4月8日水曜日

ORは、ΣXi>=1

SAT上の論理和は、ΣXi>=1 です。つまり、OR演算で、ΣXi>=1が計算できることになります。
これを利用して、加算器を使わずに計算ができることがあります。

反対にΣXi<=1は、どうなるでしょうか? これは、全ての2個の加算がXi+Xk==2となる全ての組み合わせを禁止すればよいです。これには、nC2個分の節を要します。 

それでは、ΣXi==1は、どうなるでしょうか? ΣXi>=1 && ΣXi<=1 と制約すればよいことになります。

上記は、1でしたが、2,3...を想像してみてください。途端に組み合わせ爆発してしまうのが容易に想像できると思います。


2020年4月7日火曜日

同値 ⇔ とは

C言語的には==、 論理学的には ⇔ です。 
 論理回路的には、XOR(排他的論理和)の否定になります。

命題 P命題 QP ⇔ Q
真真真
真偽偽
偽真偽
偽偽
真
 
命題 P命題 QP ⊻ Q
真真偽
真偽真
偽真真
偽偽偽

では、同値をどのように実装するかというと、排他的論理和の否定からすると
~(~P& Q| P&~Q)
=P&~Q & ~P&Q
となります。または、真理値表から、P&Q| ~P&~Q でもよいですね。
これをPythonで記述すればよいです。
下のようになります。sは、制約名になります。排他的論理和も同様にして記述できます。
<基数制約の難しさ>
ちなみにSAT界における基数制約(OR界における基本制約である線形加算不等式制約)も、AND OR NOTを用いて、理論的には記述可能です。やってみれば直ぐわかるのですが、加算器というのは、論理回路で普通に合成すると排他的論理和XORのSeaofGatesで、とんでもない量の加算器が必要になります。この原因は、組み合わせ解空間がとんでもなく広いということにあります。ですから基数制約を記述するには、用意されている専用の制約関数を用いることになります。しかし、専用の制約関数はあるのですが、それを用いるからといって、そもそもの組み合わせ解空間が狭くなる訳ではありません。依然としてそのままあります。ですから、基数制約を記述しなくて済むならば、そちらがベターです。
<UnitPropagation>
SAT界では、UnitPropagationが重要ですが、EXOR系(同値も)の記述では、これが上手くいきません。ですので、次のような記述の順序が推奨されます。

EXORを含まない論理記述 > 基数制約> EXOR系

下の記述を用いて、かなり難しい制約例を紹介するのですが、あと一回背景知識が必要となります。

def 等号制約(a,b,s):
    sc3.AddHard( (a|~b) &(~a|b),s)

2020年4月3日金曜日

土日の日準を禁止

休みの連続勤務を禁止する制約ですが、GUIで書けます。日準パターンを禁止にすればよいのですが、最初の日(日勤)を土曜日とすればよいです。よって、下のようになります。



2020年4月2日木曜日

休みの数だけ公休取得

月毎に公休数が変わる職場が多いと思いますが、各月毎に公休数を設定するのが面倒という方のためのスニペットが以下になります。今月の休診日数を取得して、同じ数だけ、公休数を取得するという制約になります。


import sc3
def 今月休診日数():
    休診日数=0
    for day in 全日:
        if day < 制約開始日:
            continue
        if day in 休日:
            休診日数+=1
    return 休診日数

def 休診日の数だけ公休を取る():
    休診日数=今月休診日数()
    for person in 全スタッフ:
        vlist=[]
        for day in 今月:
            v1=sc3.GetShiftVar(person,day,'公休')
            vlist.append(v1)
        s='公休数を休診日数だけ取る '+staffdef[person]
        sc3.AddHard(sc3.SeqLE(休診日数,休診日数,vlist),s)

休診日の数だけ公休を取る()

2020年4月1日水曜日

最新論文で言及

[45] T.Sugawaraとして、 IntelのNadelさんの最新論文で言及されています。これで、3-4度言及されています。
https://ieeexplore.ieee.org/abstract/document/8894273

なるほど、そういう考え方もありますが、私は別なアプローチで改善することにしました。
SAT系については、大体やり尽くした感があったのですが、今少し改善の余地があることに気付きました。次のリリースに向けて実装してみようと思います。