# コア


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

### Firebaseの鍵（JSON）をtomlに変換

これをstreamlit/secrets.toml に入れる．

``` python
import toml

output_file = "secrets.toml"

with open("serviceAccountKey.json") as json_file:
    json_text = json_file.read()

config = {"textkey": json_text}
toml_config = toml.dumps(config)

with open(output_file, "w") as target:
    target.write(toml_config)
```

### Geocodingのためのデータ

以下から入手可能な全国の町丁目レベル（190,165件）の住所データをもとに緯度・経度の辞書を準備する．

https://github.com/geolonia/japanese-addresses

また，郵便番号データから緯度・経度と住所が得られるような辞書も準備する．

``` python
#保存しておく
import pickle
with open('geocode.dump', 'wb') as f:
    pickle.dump(pref_dic, f)
```

``` python
#使うときには以下のように読み込んで使う; 7.3MB
import pickle
with open('../data/geocode.dump', 'rb') as f:
    pref_dic = pickle.load(f)
pref_dic["千葉県"]["八千代市"]['勝田台一丁目']
```

    (35.713482, 140.125796)

### 郵便番号との紐付け

一部離島は郵便番号はあるが緯度・経度がない．

``` python
#使うときには以下のように読み込んで使う 10.8 MB
import pickle
with open('../data/zipcode.dump', 'rb') as f:
    zip_dic = pickle.load(f)
zip_dic[2760049]
```

    [('千葉県', '八千代市', '緑が丘一丁目', 35.730675, 140.076559),
     ('千葉県', '八千代市', '緑が丘二丁目', 35.725406, 140.071748),
     ('千葉県', '八千代市', '緑が丘三丁目', 35.727209, 140.07583),
     ('千葉県', '八千代市', '緑が丘四丁目', 35.727973, 140.077712),
     ('千葉県', '八千代市', '緑が丘五丁目', 35.730743, 140.081562),
     ('千葉県', '八千代市', '緑が丘西一丁目', 35.730146, 140.069284),
     ('千葉県', '八千代市', '緑が丘西二丁目', 35.731764, 140.071975),
     ('千葉県', '八千代市', '緑が丘西三丁目', 35.736655, 140.071581),
     ('千葉県', '八千代市', '緑が丘西四丁目', 35.736712, 140.068912),
     ('千葉県', '八千代市', '緑が丘西五丁目', 35.740159, 140.068172),
     ('千葉県', '八千代市', '緑が丘西六丁目', 35.743217, 140.069375),
     ('千葉県', '八千代市', '緑が丘西七丁目', 35.74131, 140.074058),
     ('千葉県', '八千代市', '緑が丘西八丁目', 35.744167, 140.072585)]

### GeocodingのためのExcelテンプレート生成

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L40"
target="_blank" style="float:right; font-size:smaller">source</a>

### make_excel_geocoding

``` python

def make_excel_geocoding(
    
):

```

*Call self as a function.*

``` python
wb = make_excel_geocoding()
wb.save("geocoding-templete.xlsx")
```

``` python
#緯度経度変換の使用例
wb = load_workbook("geocoding-templete.xlsx")
ws = wb.active
for i, row in enumerate(ws.iter_rows(min_row=2)):
    #inf = zip_dic[int(row[0].value)]
    #print(inf) #dropdown list で選択＋書き込み
    D = pref_dic
    for j, cell in enumerate(row[1:]):
        #print(j, cell.value)
        if cell.value is not None:
            try:
                D = D[cell.value] #dic
            except:
                print(D)
                #候補を示す
            if isinstance(D, tuple):
                ws.cell(i+2,6).value = D[0]
                ws.cell(i+2,7).value = D[1]
        else:
            break
```

    0 千葉県
    1 八千代市
    2 緑が丘一丁目
    3 None

``` python
wb.save("geocoding-ex1.xlsx")
```

### 住所文字列を都道府県，市区町村，それ以降に分割する関数 address_split

正規表現を用いる．

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L68"
target="_blank" style="float:right; font-size:smaller">source</a>

### address_split

``` python

def address_split(
    address:str
):

```

*Call self as a function.*

``` python
address ="千葉県八千代市緑が丘一丁目"
address_split(address)
```

    ['千葉県', '八千代市', '緑が丘一丁目']

### 住所列の文字列を分解する関数 fill_separate_address

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L75"
target="_blank" style="float:right; font-size:smaller">source</a>

### fill_separate_address

``` python

def fill_separate_address(
    wb
):

```

*Call self as a function.*

``` python
#df = pd.read_excel("geocoding-ex1.xlsx", sheet_name="住所")
wb = load_workbook("geocoding-ex1.xlsx")
wb = fill_separate_address(wb)
wb.save("geocoding-ex3.xlsx")
```

### 日付時刻もしくは時刻型の差を計算して，秒を返す関数 time_delta

引数：

- finish: 終了時刻を表す日付時刻型（もしくは時刻型）
- start: 開始時刻を表す日付時刻型（もしくは時刻型）

返り値：

- 時刻の差（秒）

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L89"
target="_blank" style="float:right; font-size:smaller">source</a>

### time_delta

``` python

def time_delta(
    finish, start
):

```

*日付時刻もしくは時刻型の差を計算して，秒を返す関数*

#### time_deltaの使用例

``` python
print( time_delta( dt.datetime(2020,1,2,10,0), dt.datetime(2020,1,1,0,0)) )
print( time_delta( dt.time(10,0), dt.time(11,0)) )
print( time_delta( dt.time(12,0), dt.time(6,0)) )
```

    122400
    -3600
    21600

### 日付時刻（もしくは時刻）型に指定した時間（秒）を加えた時刻を返す関数 add_seconds

引数 - start: 開始時刻を表す日付時刻型（もしくは時刻型） - seconds:
時間（秒）

返値： - 指定した時間だけすすめた（日付）時刻

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L101"
target="_blank" style="float:right; font-size:smaller">source</a>

### add_seconds

``` python

def add_seconds(
    start, seconds
):

```

*Call self as a function.*

#### add_secondsの使用例

``` python
print( add_seconds( dt.datetime(2020,1,1,10,0), 1000) )
print( add_seconds( dt.time(10,0), 1000) )
```

    2020-01-01 10:16
    10:16

### ソルバーのエラークラス

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L110"
target="_blank" style="float:right; font-size:smaller">source</a>

### SolverError

``` python

def SolverError(
    args:VAR_POSITIONAL, kwargs:VAR_KEYWORD
):

```

*ソルバーのエラーのための例外クラス*

## 地点（顧客）間の距離と移動時間の計算関数 compute_durations

OSRMを必要とする．

引数： - cust_df:
顧客データフレーム（名称と緯度・経度情報をもつデータフレーム） -
plnt_df: 工場データフレーム（オプション；既定値 None) - toll:
有料道路が使用可のときTrue（既定値 True) - ferry:
フェリーが使用可のときTrue（既定値 True) (TODO:
OSRMの前処理時に，car.luaファイルを以下を修正する必要がある．）

    excludable = Sequence {
            Set {'toll'},
            Set {'motorway'},
            Set {'ferry'}, 
            Set {'ferry', 'toll'})
        },

- host: ホスト名；既定値は “localhost”

返値： - durations:
地点間の移動時間（計算に失敗した場合には大きな数字が入っている．） -
distances:
地点間の道路距離（計算に失敗した場合には大きな数字が入っている．） -
node_df: 点のデータフレーム（顧客もしくは顧客と工場を結合したもの）

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L117"
target="_blank" style="float:right; font-size:smaller">source</a>

### compute_durations

``` python

def compute_durations(
    cust_df, plnt_df:NoneType=None, toll:bool=True, host:str='localhost'
):

```

*Call self as a function.*

### compute_durations関数の使用例

``` python
# 
# cust_df = pd.read_csv(folder+"Cust.csv")
# durations,  distances, node_df = compute_durations(cust_df, toll=True, host="localhost")
# print(durations[0])
```

``` python
cust_df = pd.read_csv(folder+"Cust.csv")
durations,  distances, node_df = compute_durations(cust_df, toll=False, host=host)
print(sum(durations[0]))
```

    3730084.0

## 地点間の距離と移動時間のデータフレームを生成する関数 make_time_df

引数： - node_df:
点のデータフレーム（顧客もしくは顧客と工場を結合したもの） - durations:
地点間の移動時間（計算に失敗した場合には大きな数字が入っている．） -
distances:
地点間の道路距離（計算に失敗した場合には大きな数字が入っている．）

返値： - time_df:
発地，着地，移動時間（秒），道路距離（m）を入れたデータフレーム

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L152"
target="_blank" style="float:right; font-size:smaller">source</a>

### make_time_df

``` python

def make_time_df(
    node_df, durations, distances
):

```

*Call self as a function.*

### make_time_df関数の使用例

``` python
time_df =  make_time_df(node_df, durations, distances)
time_df.head()
#time_df.to_csv(folder + "melos/time.csv")
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">from_node</th>
<th data-quarto-table-cell-role="th">from_name</th>
<th data-quarto-table-cell-role="th">to_node</th>
<th data-quarto-table-cell-role="th">to_name</th>
<th data-quarto-table-cell-role="th">time</th>
<th data-quarto-table-cell-role="th">distance</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>0</td>
<td>札幌市</td>
<td>0</td>
<td>札幌市</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>0</td>
<td>札幌市</td>
<td>1</td>
<td>青森市</td>
<td>23888</td>
<td>427015</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>0</td>
<td>札幌市</td>
<td>2</td>
<td>盛岡市</td>
<td>28257</td>
<td>546453</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>0</td>
<td>札幌市</td>
<td>3</td>
<td>仙台市</td>
<td>36161</td>
<td>721614</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>0</td>
<td>札幌市</td>
<td>4</td>
<td>秋田市</td>
<td>31428</td>
<td>610043</td>
</tr>
</tbody>
</table>

</div>

## time_dfからdurationsとdistancesを生成する関数 make_durations

上で作成した time_df から，移動時間と移動距離を生成する．

引数： - time_df: 移動時間と道路距離を保持するデータフレーム

返値： - durations: 移動時間行列 - distances: 道路距離行列

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L174"
target="_blank" style="float:right; font-size:smaller">source</a>

### make_durations

``` python

def make_durations(
    time_df
):

```

*Call self as a function.*

### make_durations関数の使用例

``` python
time_df = pd.read_csv(folder + "melos/time.csv")
durations, distances = make_durations(time_df)
distances
```

    array([[      0.,  427015.,  546453., ..., 2247148., 2288685., 3080358.],
           [ 426987.,       0.,  183876., ..., 1815653., 1857189., 2648863.],
           [ 546501.,  183838.,       0., ..., 1736609., 1778145., 2569819.],
           ...,
           [2247483., 1814532., 1736893., ...,       0.,  132654.,  924327.],
           [2293427., 1860476., 1782837., ...,  134850.,       0.,  795782.],
           [3083057., 2650105., 2572466., ...,  924479.,  795964.,       0.]])

## seabornのカラーパレットと色数を指定すると、Plotly用のカラーパレットを返す関数 get_colorpalette

seabornのカラーパレットと色数を指定すると、Plotly用のカラーパレットを返す．

使用できるseabornのパレット名は，以下の通り．

    deep, muted, bright, pastel, dark, colorblind

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L184"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_colorpalette

``` python

def get_colorpalette(
    colorpalette:str='pastel', n_colors:int=1
):

```

*Call self as a function.*

### get_colorpalette関数の使用例

``` python
n_legends = 12
x = np.arange(0, 1, .01)
y = np.random.rand(n_legends, 100) + \
  np.arange(n_legends).reshape(-1, 1)

colors = get_colorpalette('pastel', n_legends)
data = [
    go.Scatter(
        x=x, y=y[i], name=f'Legend {i}',
        marker={'color':colors[i]})
    for i in range(n_legends)]
fig = go.Figure(data=data)
#plotly.offline.plot(fig);
```

![](00_core_files/figure-commonmark/cell-32-output-1.png)

## グラフクラス SCMGraph

> サプライ・チェインのためのグラフクラス

SCMGraphクラスは，networkXの有向グラフクラスDiGraphから派生したものであり，以下のメソッドが追加されている．

- random_directed_tree(n=1, seed=None):
  ランダムな有向木を生成する．引数は点数n（既定値1）と擬似乱数の種seed（既定値None）.
- layered_network(num_in_layer=None, p=0.5, seed=None):
  ランダムな層型の有向グラフを生成する．
  引数num_in_layerは，各層内の点数を入れたリストで既定値は\[1,1\]，ｐは枝の発生確率で既定値は0.5，seedは擬似乱数の種である．
- layout():
  有向グラフを描画する際の座標を返す関数．返値は点の名称をキーとし，点のx,y座標を値とした辞書．
- down_order():
  閉路を含まない有向グラフに対して，供給地点側から需要地点側の順番で点を返すジェネレータ関数．
- up_order():
  閉路を含まない有向グラフに対して，需要地点側から供給地点側の順番で点を返すジェネレータ関数．
- dp_order(): 有向木に対して，葉から順番に点を生成するジェネレータ関数．
  安全在庫配置問題に対する動的最適化で用いる．
- bfs(start):
  点startを開始点として，広がり優先探索で点を生成するジェネレータ関数．
- find_ancestors():
  各点から到達可能な点の集合（自分自身を含む）を保持するリスト．点の順序はup_orderとする．

<!-- -->

    /Users/mikiokubo/Documents/dev/manual/.venv/lib/python3.13/site-packages/fastcore/docscrape.py:259: UserWarning: Unknown section See Also
      else: warn(msg)

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/core.py#L191"
target="_blank" style="float:right; font-size:smaller">source</a>

### SCMGraph

``` python

def SCMGraph(
    incoming_graph_data:NoneType=None, # Data to initialize graph.  If None (default) an empty
graph is created.  The data can be an edge list, or any
NetworkX graph object.  If the corresponding optional Python
packages are installed the data can also be a 2D NumPy array, a
SciPy sparse array, or a PyGraphviz graph.
    attr:VAR_KEYWORD, # Attributes to add to graph as key=value pairs.
):

```

*SCMGraph is a class of directed graph with edge weight that can be any
object.* I just use the functions such as in_degree, out_degree,
successors, predecessors, in_edges_iter, out_edges_iter. So it is
convertible to any graph class that can access the adjacent nodes more
quickly.

## SCMGraphの使用例

階層型ネットワークを生成し，点の座標（レイアウト）を計算して描画する．

``` python
G = SCMGraph()
G.layered_network(num_in_layer=[3, 2, 3], p=0.4, seed=123)
pos = G.layout()
nx.draw(G, pos=pos, with_labels=True, node_color="Yellow")
```

![](00_core_files/figure-commonmark/cell-35-output-1.png)

``` python
print("up order")
for i in G.up_order():
    print(i, end=" ")
```

    up order
    7 6 3 5 4 2 1 0 

``` python
print("down order")
for i in G.down_order():
    print(i, end=" ")
```

    down order
    2 1 0 4 7 5 3 6 

``` python
print("dp order")
for i in G.dp_order():
    print(i, end=" ")

G.remove_edge(0, 3)

for i in G.dp_order():
    print(i, end=" ")
```

    dp order
    Graph is not a tree.
    0 1 2 3 5 6 7 4 

``` python
print("bfs")
for i in G.bfs(1):
    print(i, end=" ")
```

    bfs
    1 4 5 6 7 

``` python
print("ancestor")
G.find_ancestors()
```

    ancestor

    {0: {0, 4, 5, 6, 7},
     1: {1, 4, 5, 6, 7},
     2: {2, 4, 5, 6, 7},
     3: {3, 6},
     4: {4, 5, 6, 7},
     5: {5},
     6: {6},
     7: {7}}

``` python
pos
```

    {0: (0, 0),
     1: (0, 1),
     2: (0, 2),
     3: (1, 0),
     4: (1, 1),
     6: (2, 0),
     5: (2, 1),
     7: (2, 2)}

``` python
#Pyvisを利用して描画（メソッドにする）
net = pyvis.network.Network(height='500px', width='800px', directed = True, notebook = True, 
                            bgcolor='#ffffff', font_color=False, layout=True, heading='Sample')
level =[]
pos = G.layout()
for i in pos:
   x,y = pos[i]
   level.append(x)
for i in G.nodes():
    net.add_node(i, label=f"node{i}", level=level[i], shape="box", color="yellow")
    #net.add_node(i, label=f"node{i}", x=pos[i][1], y=pos[i][0], shape="box", color="yellow")
for (i,j) in G.edges():
    net.add_edge(i,j, value=i*j, title=f"Edge{i},{j}",color="green", dashes=False, selectionWidth=10, arrowStrikethrough=False)
net.show("example.html")
```

        <iframe
            width="800px"
            height="500px"
            src="example.html"
            frameborder="0"
            allowfullscreen
            &#10;        ></iframe>
        &#10;

## random_directed_tree関数

ランダムな有向木を生成する。

引数： 点数 n

返値： 点数 n のランダムな有向木

``` python
G = SCMGraph()
G.random_directed_tree(10, seed=1)
pos = G.layout()
nx.draw(G, pos=pos, with_labels=True, node_color="Yellow")
```

![](00_core_files/figure-commonmark/cell-43-output-1.png)
