ラベル Julia の投稿を表示しています。 すべての投稿を表示
ラベル Julia の投稿を表示しています。 すべての投稿を表示

2020年12月17日木曜日

素数の計算(4)

 素数の計算(3)の続き

というわけで,JuliaのPrimes.jlでは,primes()を使って探す方が,prime()を計算するより早いということがわかってしまった。1億番目の素数を求める場合,prime()単体では,primes()を用いた場合より35倍遅い。なんだか・・・。なお,素数計数関数 $n = \pi(x)$の逆関数としては,前回のものをちょっと修整して,$x(n) = 10^{1.05 \log(n) + 1.0}$を用いた。

using Primes

function testp(n,k)
  if(k==1)
    m=Int64(floor(10^(1.05*n+1.0)))
    p=primes(m)
    println(p[10^n])
  elseif(k==2)
    println(prime(10^n))
  end
end

for i in 6:8
  @time testp(i,1)
end

for i in 6:8
  @time testp(i,2)
end


15485863
0.067474 seconds (43 allocations: 14.778 MiB)
179424673
0.915835 seconds (163 allocations: 151.275 MiB, 0.35% gc time)
2038074743
12.159200 seconds (163 allocations: 1.536 GiB, 0.10% gc time)
15485863
2.350876 seconds (2.53 M allocations: 38.664 MiB)
179424673
33.536126 seconds (29.35 M allocations: 447.880 MiB)
2038074743
426.317886 seconds (333.40 M allocations: 4.968 GiB, 0.12% gc time)

2020年12月14日月曜日

素数の計算(3)

 素数の計算(2)からの続き

JuliaのPrime.jlのprime()がおかしいのであれば,n番目までの素数が登場する範囲の整数の範囲がわかれば,その整数をすべてisprime()でチェックしたほうが早いのではないかという考えで,n番目の素数から整数範囲を求める方法を調べた。

素数定理によれば,素数の数$n=\pi(x)$は${\rm Li}(x)=\int_2^x \frac{1}{log t}dt$で与えられるということだ。対数積分は,Pythonのライブラリにはあるが,JuliaのSpecialFunctions.jlには入っていない。いずれにせよ近似値でよいのだから,素数定理の$\pi(x)$の表をながめて,えいやっと逆関数の近似式を求めた。

$x=\pi^{-1}(n)=10^{\dfrac{9.11*\log_{10}n+6.5}{8.5}}$

残念ながらこれで計算したprime()の代替関数はオリジナルの15倍時間がかかるという情けない結果に終わってしまった。猿の浅知恵の典型的パターンである。

using Primes

function logint(n)
  x=10.0^((9.11*log10(n)+6.5)/8.5)
  m = BigInt(floor(x))
  println(m)
  return m
end

function test(n)
  m = logint(n)
  i = 0
  for p in 1:m
    if(isprime(p))
      i = i+1
      if(i == n)
        println(p)
        break
      end
    end
  end
end

@time test(10^6)
@time println(prime(10^6))


15678124
15485863
32.740030 seconds (124.81 M allocations: 4.688 GiB, 12.73% gc time)
15485863
2.493369 seconds (2.53 M allocations: 38.664 MiB, 0.99% gc time)

2020年12月13日日曜日

素数の計算(2)

素数の計算(1)の続き

Juliaの素数計算が遅い原因を絞り込みたい。python3とJuliaを比べると,素数判定のisprimeや範囲指定素数のprimerangeやprimesではともに高速であるが,順序指定素数のprimeにおいて,juliaはpythonの200倍程度時間がかかっている。しかし,同じ範囲の素数は,素数判定や範囲指定素数では高速に計算できているので,juliaのprimeのコードがややこしいことになっているのが問題だと思われる。

python3では, 
- - - - - - - - - - 
from sympy import *
import time
start_time=time.time()
n=179424673
m=10**7
for p in range(n,n+50):
    if isprime(p)==1:
        print(p)
lap1_time=time.time()
print(list(primerange(n, n+50)))
lap2_time=time.time()
for i in range(m,m+4):
    print(prime(i))
end_time=time.time()
print(lap1_time-start_time)
print(lap2_time-lap1_time)
print(end_time-lap2_time)

- - - - - - - - - - 
179424673
179424691
179424697
179424719
[179424673, 179424691, 179424697, 179424719]
179424673
179424691
179424697
179424719
0.0050160884857177734
0.002671957015991211
0.5041019916534424

julia1.5.1では 
- - - - - - - - - - 
using Primes

function test1(n)
  for p in n:n+50
    if(isprime(p))
      println(p)
    end
  end
end

function test2(n)
  println(primes(n,n+50))
end

function test3(n)
  for p in n:n+3
    println(prime(p))
  end
end

@time test1(179424673)
@time test2(179424673)
@time test3(10^7)

- - - - - - - - - - 
179424673
179424691
179424697
179424719
0.000310 seconds (146 allocations: 3.500 KiB)
[179424673, 179424691, 179424697, 179424719]
0.000521 seconds (207 allocations: 8.109 KiB)
179424673
179424691
179424697
179424719
128.789228 seconds (117.41 M allocations: 1.750 GiB, 0.34% gc time)


2020年11月30日月曜日

素数の計算(1)

 ソフィー・ジェルマン素数の続き

件の記事の中には,「p=999671で 2p+1=1999343,が約数となる。 2^p−1=2^99671−1 は10進法で300932桁となったが,開発用等ではないスペックのかなり低いパソコンでも2~3分で計算してくれた」とあったので追試しようとして,はまってしまった。

Mathematicaでは,このあたりのソフィー・ジェルマン素数を次のように一瞬で計算した。
In[1]:= Timing[ Do[p = Prime[2^16 + i];  If[Mod[p, 4] == 3 && PrimeQ[2*p + 1],  Print[p, " : ", Mod[2^p - 1, 2*p + 1]]], {i, 12900, 13000}]]
999611 : 0
999623 : 0
999671 : 0
1000151 : 0
1000211 : 0
1000403 : 0
Out[1]= {0.007209, Null}

In[2]:= Timing[Print[p = Prime[10^8], " ", PrimeQ[p]]]
2038074743 True
Out[2]= {0.000185, Null}
そこで,10^8番目の素数をpython3で計算して判定すると,
from sympy import *
import time
start_time=time.time()
p=prime(10**8)
end_time=time.time()
print(p,isprime(p),end_time-start_time)

2038074743 True 2.994921922683716

同様に,10^8番目の素数をjulia 1.5で計算して判定すると, 

using Primes
n=10^8
function sgp(n)
  p=prime(n)
  println(isprime(p)," ",p)
end

@time sgp(n)
true 2038074743
420.535971 seconds (333.45 M allocations: 4.970 GiB, 0.24% gc time)

なお,p=999671は,prime(78476)であり,この計算時間はjuliaでも0.153秒だった。それにしてもこの遅さはいったい何ということでしょう・・・? 

2020年9月20日日曜日

Juliaの日時処理

Juliaによる日時処理の練習をしてみた。 
日曜日の誕生日は11回目だ。生まれてからまだ2万4427日しかたっていなかった。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
using Dates
daytoday = today()
birthday = Date(1953,9,20)
println(daytoday," ",isleapyear(daytoday))
println(yearmonthday(birthday)," ",isleapyear(birthday)) 
println(yearmonth(birthday)," ",monthday(birthday)) 
println(daysofweekinmonth(birthday)," ",dayofweekofmonth(birthday)) 
println(monthname(birthday)) 
println(dayofweek(birthday)," ",dayname(birthday)) 
println(daytoday-birthday) 

function bd()
  for i in 1953:2020
    k=Date(i,9,20)
    kk=dayname(k)
    age=i-1953
    star=""
    if(kk == "Sunday")
      star="*"
    end
    println(k," ",age," ",kk," ",star)
  end
end

bd()
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

2020-09-20 true
(1953, 9, 20) false
(1953, 9) (9, 20)
4 3
September
7 Sunday
24472 days
1953-09-20 0 Sunday *
1954-09-20 1 Monday 
1955-09-20 2 Tuesday 
1956-09-20 3 Thursday 
1957-09-20 4 Friday 
1958-09-20 5 Saturday 
1959-09-20 6 Sunday *
1960-09-20 7 Tuesday 
1961-09-20 8 Wednesday 
1962-09-20 9 Thursday 
1963-09-20 10 Friday 
1964-09-20 11 Sunday *
1965-09-20 12 Monday 
1966-09-20 13 Tuesday 
1967-09-20 14 Wednesday 
1968-09-20 15 Friday 
1969-09-20 16 Saturday 
1970-09-20 17 Sunday *
1971-09-20 18 Monday 
1972-09-20 19 Wednesday 
1973-09-20 20 Thursday 
1974-09-20 21 Friday 
1975-09-20 22 Saturday 
1976-09-20 23 Monday 
1977-09-20 24 Tuesday 
1978-09-20 25 Wednesday 
1979-09-20 26 Thursday 
1980-09-20 27 Saturday 
1981-09-20 28 Sunday *
1982-09-20 29 Monday 
1983-09-20 30 Tuesday 
1984-09-20 31 Thursday 
1985-09-20 32 Friday 
1986-09-20 33 Saturday 
1987-09-20 34 Sunday *
1988-09-20 35 Tuesday 
1989-09-20 36 Wednesday 
1990-09-20 37 Thursday 
1991-09-20 38 Friday 
1992-09-20 39 Sunday *
1993-09-20 40 Monday 
1994-09-20 41 Tuesday 
1995-09-20 42 Wednesday 
1996-09-20 43 Friday 
1997-09-20 44 Saturday 
1998-09-20 45 Sunday *
1999-09-20 46 Monday 
2000-09-20 47 Wednesday 
2001-09-20 48 Thursday 
2002-09-20 49 Friday 
2003-09-20 50 Saturday 
2004-09-20 51 Monday 
2005-09-20 52 Tuesday 
2006-09-20 53 Wednesday 
2007-09-20 54 Thursday 
2008-09-20 55 Saturday 
2009-09-20 56 Sunday *
2010-09-20 57 Monday 
2011-09-20 58 Tuesday 
2012-09-20 59 Thursday 
2013-09-20 60 Friday 
2014-09-20 61 Saturday 
2015-09-20 62 Sunday *
2016-09-20 63 Tuesday 
2017-09-20 64 Wednesday 
2018-09-20 65 Thursday 
2019-09-20 66 Friday 
2020-09-20 67 Sunday *

2020年5月8日金曜日

小田垣さんのSIQR

朝日新聞に,九州大学名誉教授の小田垣さんの計算として「PCR検査を倍にすれば、接触「5割減」でも収束可能?」という記事がでた。twitterでも注目を集めていた。

小田垣さんのホームページにその論考があった[1]。SIQRモデルということで,感染者を2段階に分けていた。我々のSIIDR2モデルと本質的に同じではないか。感受性保持者S(t)から直接我々の重症患者I2(t),すなわち小田垣さんの隔離感染者Q(t)に遷移する項があって,ここは違うのだが,議論が始まる前の段階でこの項を落としているので結局同じです。違うのはこちらには死亡数D(t)への遷移を含んでいることくらいである。

そこで両方のモデルで使用しているパラメタを比較してみた。左が小田垣さんのSIQR,右が我々のSIIDR2である。
\begin{equation}
\begin{aligned}
N\beta \quad (0.07) &= \beta \quad (0.4-0.6)  \\
p \quad (0.096) &= \dfrac{1}{\alpha_2} \quad (0.04) \\
\gamma \quad (0.04) &= \dfrac{1}{\alpha_1} \quad (0.16) \\
\gamma \quad (0.04) &= \dfrac{1}{\gamma_1}\quad  (0.064) \\
noparameter (0) &= \dfrac{1}{\gamma_2}\quad   (0.00267) \\
\end{aligned}
\end{equation}
感染率の$\beta$が一桁違うことがわかる。その他はファクターが異なるくらいである。I(t)からR(t)への遷移時間とQ(t)からR(t)への遷移時間が同じとしていることもやや疑問に感じる。また,小田垣氏は論考の中で,基本再生産数(実効再生産数)は$N\beta/p$でなく$N\beta/(p+\gamma)$とすべきだと主張しているが,これはどうなのだろうか。

とりあえず,感染初期のS(t)=Nのときにあてはめて,接触5割減の議論に持ち込んでいる部分は興味深いが,実際にこのパラメータを使って日本の感染データの全体像を説明できるのだろうか。我々のコードを少し修正して彼らのモデルとパラメタを使った計算を再現してみた(初期値の細かな調整は我々のモデルを前提としているがとりあえずはそこはそのままにしておく)。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
using DifferentialEquations
using ParameterizedFunctions
using Plots; gr()

sky = @ode_def SIQR_model begin
  du0 = 1 # u0:time
  du1 = -β*u1*u2/n # u1:Noimmunity(Susceptible)
  du2 = β*u1*u2/n -u2/α2 -u2/α1 # u2:(Infected)
  du3 = u2/α2 -u3/γ1 # u3:(Quarantined)
  du4 = u3/γ2 # u4:Dead(not in use)
  du5 = u2/α1 +u3/γ1 # u5:Recovered
  du6 = u2/α2 # u6:Accumulated Quarantined
  du7 = u3/γ1 # u7:Accululated Recovered
end n α1 α2 β γ1 γ2 λ τ

function epidm(β,ν,λ,τ,T)
n=10000.0 #total number of population
α1=1/0.04   #5.0/0.8 #latent to recovery (days/%)
α2=1/0.096  #5.0/0.2 #latent to onset (days/%)
β=0.07  #0.45  #infection rate (/day・person)
γ1=1/0.04   #15.0/0.96 #onset to recovery (days/%)
γ2=15.0/0.04   #onset to death (days/%) (not in use)
u0 = [0.0,n-11ν,4ν,2ν,0.0,5ν,ν,0.0] #initial values
p = (n,α1,α2,β,γ1,γ2,λ,τ) #parameters
tspan = (0.0,T) #time span in days
prob = ODEProblem(sky,u0,tspan,p)
sol = solve(prob)
return sol
end

#japan-data(start=3/1)
xj=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,
14,15,16,17,18,19,20,21,22,23,24,25,26,27,
28,29,30,31,32,33,34,35,36,37,38,39,40,41,
42,43,44,45,46,47,48,49,50,51,52,53,54,55,
56,57,58,59,60,61,62]
yj=[0.0190,0.0202,0.0213,0.0225,0.0252,0.0277,0.0324,
0.0361,0.0387,0.0408,0.0451,0.0492,0.0536,0.0568,
0.0619,0.0646,0.0658,0.0658,0.0693,0.0754,0.0790,
0.0830,0.0864,0.0895,0.0947,0.103,0.110,0.119,
0.134,0.148,0.155,0.173,0.189,0.208,0.232,
0.260,0.290,0.310,0.338,0.378,0.424,0.477,
0.536,0.576,0.607,0.643,0.681,0.728,0.777,
0.822,0.853,0.882,0.912,0.946,0.983,1.018,
1.046,1.062,1.078,1.099,1.118,1.133,1.154]
zj=[0.040,0.048,0.048,0.048,0.048,0.048,0.048,
0.048,0.056,0.071,0.095,0.119,0.151,0.167,
0.175,0.190,0.222,0.222,0.230,0.262,0.278,
0.286,0.325,0.333,0.341,0.357,0.365,0.389,
0.413,0.429,0.444,0.452,0.452,0.516,0.548,
0.556,0.579,0.635,0.643,0.675,0.698,0.746,
0.778,0.810,0.865,0.944,1.079,1.175,1.222,
1.278,1.357,1.476,2.198,2.278,2.516,2.651,
2.762,2.786,2.984,3.087,3.294,3.429,3.603]/100
plot(xj,yj,st=:scatter,label="Confirmed-japan")
#plot!(xj,zj,st=:scatter,label="Deaths-japan")

β=0.07
ν=0.01
T=60

@time so=epidm(β,ν,λ,τ,T)
#plot!(so,vars=(0,2))
#plot!(so,vars=(0,3))
plot!(so,vars=(0,4))
#plot!(so,vars=(0,5))
#plot!(so,vars=(0,6))
plot!(so,vars=(0,7))
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

結果は以下の通りである。確かに初期の段階ではデータを説明しているが,その後の振る舞いは説明できない。このモデル(のパラメタ)はあまりよろしくないのかもしれない。

図 日本の新規感染数累計の推移(3/1-5/1)u6をデータと比較する


[1]新型コロナウイルスの蔓延に関する一考察(小田垣孝,2020.5.5)
[2]隔離と市中の感染者を分ける SIR モデル(佐野雅己,2020.4.29)
[3]3.11以後の科学リテラシー No. 89(牧野淳一郎,2020 科学5月号)
[4]感染症の数理シミュレーション(8)(2020.3.15)

2020年3月28日土曜日

湖北省の集団免疫率

基本再生産数と集団免疫率からの続き

昨日の基本再生産数と集団免疫率のところで,湖北省(武漢以外)における集団免疫率をSIIDR2モデルで推定したところ,0.2%という結果が得られた。ところで湖北省の感染中心である武漢はどうだろうか。そこで,武漢を含む湖北省について同様の計算を行った。

パラメタを決定するための新規感染数累計と死亡数累計のデータとしては,WHOのSituation Reportを使う。ただし,データが存在しているのは,2月1日のNo.12から3月15日のNo.55までであり,死亡数データはNo.25からNo.55までに限定されている。また2月13日のNo.24から2月14日No.25以降の新規感染数累計のデータには,確定感染者(Confirmed)の定義変更に伴うギャップが存在する。そこで,次のようにデータを加工した。

①定義変更ギャップについては変更前後のデータ倍率を古いデータにも当てはめて新しい定義にそろえる。
②欠落している初期部分については指数関数的な振る舞いを仮定して,未欠落部分につががるように補完する。こちらの方は,データ数もそれほど多くないので,後のモデルパラメタ推定にはそれほど大きな影響を与えない。

2月1日を基準日0として50日分の以下のjuliaコード用のデータが得られた。xhは時間(日)
yhは新規感染数累計,zhは死亡数累計である。なお湖北省の人口を5900万人として,1万人当りの数に換算している。

xh=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,
22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,
43,44,45,46,47,48,49]
yh=[0.370,0.478,0.612,0.777,0.979,1.223,1.517,1.944,2.420,2.957,
3.684,4.387,4.982,5.677,6.225,6.872,7.428,7.885,8.318,8.811,
9.221,9.534,9.861,10.168,10.455,10.514,10.621,10.755,10.862,
10.896,10.981,11.049,11.118,11.172,11.244,11.340,11.373,11.393,
11.412,11.435,11.456,11.469,11.476,11.482,11.485,11.487,11.488,
11.489,11.490,11.491]
zh=[0.028,0.031,0.035,0.039,0.043,0.049,0.054,0.060,0.067,0.075,
0.084,0.094,0.105,0.117,0.130,0.145,0.162,0.181,0.202,0.223,
0.247,0.271,0.287,0.303,0.326,0.344,0.363,0.381,0.398,0.423,
0.434,0.443,0.448,0.455,0.462,0.468,0.475,0.480,0.487,0.492,
0.497,0.502,0.506,0.510,0.513,0.516,0.518,0.519,0.521,0.523]

SIIDR2モデルのパラメタを次のようにとると,上記データがほぼ再現できる。
$\alpha_1=5/0.80, \alpha_2=5/0.20, \gamma_1=15/0.96, \gamma_2=15/0.04, \beta=0.88, \nu=0.30, \lambda=7, \tau=5$。もちろんパラメタには不定性があるが,データの再現性を制約条件とした場合に,感染数や回復数(免疫獲得数)などの結果が大きく変わることはない。以下の図では,u2:軽症感染数(無症状含む),u3:重症感染数(隔離済),u4:死亡数累計=Deaths,u5:新規感染数累計=Confirmed,u6:回復数(免疫獲得数)である。

図1 湖北省(2/1〜3/15)の1万人当り感染状況

つまり,人口5900万人の湖北省では1万人当り約60人の水準(0.6%)で免疫獲得数が飽和している。これを前回の結果(武漢以外の湖北省で1万人当り約20人)と組み合わせると,武漢市の免疫獲得者は5900*60-4800*20=258,000人となり,武漢市の集団免疫率は2.6%になる。これでもまだ少ないのでいつでも再燃する可能性がある。

このパラメタにおける実効再生産数 $R_{\rm eff}= \alpha \beta(t)  = \alpha \dfrac{\beta}{15}\{8+7 \tanh(-\frac{t-\tau}{\lambda}) \} $は次のようになる。$t$=20日までの平均で$R_{\rm eff}=1.415$,$t$=30日までの平均は$R_{\rm eff}=1.044$となる。


図2 湖北省データを再現する実効再生産数 R_eff(t=0〜49)

2020年3月27日金曜日

基本再生産数と集団免疫率

感染症数理の基本的なモデルであるSIRモデルについて自分は十分理解できていなかった。奈良女子大の高須さん[1]や神戸大の牧野さん[2]や京大の門さん[3]の資料が大変わかりやすくて勉強になった。

きっかけはツイッター@nagayaさんの次のコメント
R0=2なら国民の50%、3なら66%、逆に1.5なら33%の免疫獲得で理論上は収束に向かいます(この理論値を超えて免疫獲得した分が「オーバーシュート」)。日本は自粛とクラスター追跡、検査を絞って1.2程度にして医療崩壊を避けようとしたんでしょう。無理です。長期化しますし、指数関数的に潜在します。
基本再生産数$R_0$と集団免疫率に関係あったっけ?ということで高須さんの資料で学び直し。

SIRモデルは,感受性保持者(Susceptible,$S(t)$)・感染者(Infected,$I(t)$)・免疫保持者(Recovered,$R(t)$ )からなる力学系モデルであり次の微分方程式を満たす($\beta, \gamma$の定義に注意 )。ここで$t$は時間であり,最も単純なバージョンでは集団の人数を$N$として$S(t)+I(t)+R(t)=N$は保存量である。なお,$\beta$は感染率(1人単位時間当りの感染数),$\gamma$は感染期間である。
\begin{equation}
\begin{aligned}
\dfrac{dS}{dt} &= -\beta \dfrac{S}{N} I\\
\dfrac{dI}{dt} &= \beta \dfrac{S}{N} I  - \dfrac{I}{\gamma} \\
\dfrac{dR}{dt} &=  - \dfrac{I}{\gamma}\\
\end{aligned}
\end{equation}
これからわかること。集団中の感受性保持者(免疫非保持者)の割合を$p(t)=\dfrac{S}{N} $とすると,$p(t) > \dfrac{1}{\beta \gamma}$で感染拡大,$p(t) < \dfrac{1}{\beta \gamma}$で感染縮小となる。そこで,基本再生産数を$R_0 = \beta \gamma$と定義すると,$p(0)=1$の初期状態において,$R_0>1$で感染拡大,$R_0<1$で感染縮小となる。なお,実効再生産数が$R_{\rm eff}=R_0 p(t)$  と定義される。

また集団免疫率を$\pi(t) = 1-p(t)$と定義すると,感染拡大と縮小の分岐点$\tau$では,$1-\pi(\tau) = \dfrac{1}{R_0}$である。これが@nagayaさんの示した関係式だった。

次に十分時間がたって$I(\infty)=0$となるときの集団免疫率$\pi(\infty)$と$R_0$の関係を求めてみよう。もとの微分方程式系の第1式と第3式から次の微分方程式が得られる。
\begin{equation}
\begin{aligned}
\dfrac{dS}{dR} &= -R_0 \dfrac{S}{N}\\
\dfrac{dS}{S} &= -R_0 \dfrac{dR}{N}\\
\log S + C &= -R_0 \dfrac{R}{N} = -R_0 (1-\dfrac{I}{N} - p(t) ) = -R_0 (\pi(t) - i(t) )\\
\end{aligned}
\end{equation}
ただし,$i(t)=\dfrac{I}{N}$であり,$i(\infty)=0$。そこで,$S(0)=N$として$\pi(\infty)=\pi$とすると,
\begin{equation}
\begin{aligned}
\dfrac{S(t)}{S(0)} &= e^{-R_0\{ \pi(t) - i (t) \}} \\
p(t) = 1-\pi(t) &=  e^{-R_0\{ \pi(t) - i (t) \}} \\
1- \pi &= e^{ -R_0 \pi } \\
R_0 &= - \dfrac{1}{\pi}\log (1- \pi )
\end{aligned}
\end{equation}
図1 t=∞における集団免疫率π(横軸)と基本再生産数R0(縦軸)

分岐点における$\pi(t)$と$R_0$の関係式とは異なった関係を与えていることに注意しよう。

付録:juliaにおける上記プロットのコード
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
using Plots
gr()
x = 0.05:0.05:0.95
f(x) = .- 1 ./ x .* log.(1 .- x)
y = f(x)
plot(x, y, label="")
savefig("sir.png")
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

追伸:
以上は簡単なSIRモデルの場合だが,湖北省(武漢以外)をおおむね説明したSIIDR2モデルにたいして,十分な時間がたったときの湖北省(武漢以外)の集団免疫率はどのように推定できるだろうか。パラメタセット,$\alpha_1=5/0.80, \alpha_2=5/0.20, \gamma_1=15/0.96, \gamma_2=15/0.04, \beta=0.915, \nu=0.025, \lambda=7, \tau=7$について計算すると,Recovered $u_5$は1万人当り19程度に収束し,集団免疫率はたかだか0.2%にどどまる
図2 湖北省(武漢以外)のSIIDR2モデルよる集団免疫率の評価

[1]伝染病のモデル−大域情報学(高須夫悟)
[2]コロナウィルス(SARS-CoV-2)の(単純な) 数学モデル(牧野淳一郎)
[3]この感染は拡大か収束か:再生産数 R の物理的意味と決定(門信一郎)

2020年3月15日日曜日

感染症の数理シミュレーション(8)

感染症の数理シミュレーション(7)からの続き

中間まとめ
いろいろと書き散らしてきたので,ここまでに得られた結果を整理してみる。

感染症の数理シミュレーション(3)において,感染対策時間因子を修正した図1のSIIDR2モデルで,湖北省(武漢以外)の新規感染者数累計と死亡数累計説明できるパラメタセットを得ることができた
図1 SIIDR2モデルの概念図,$n=u_1+u_2+u_3+u_4+u_5$, $p+q=1$

\begin{equation}
\begin{aligned}
S: \dfrac{du_1}{dt} &= -\dfrac{\beta(t)}{n} u_1 u_2\\
I_1: \dfrac{du_2}{dt} &= \dfrac{\beta(t)}{n} u_1 u_2 -\dfrac{u_2}{\alpha_1} -\dfrac{u_2}{\alpha_2}\\
I_2: \dfrac{du_3}{dt} &= \dfrac{u_2}{\alpha_2} - \dfrac{u_3}{\gamma_1} -\dfrac{u_3}{\gamma_2}\\
D: \dfrac{du_4}{dt} &= \dfrac{u_3}{\gamma_2}\\
R: \dfrac{du_5}{dt} &= \dfrac{u_2}{\alpha_1} + \dfrac{u_3}{\gamma_1}\\
I_a: \dfrac{du_6}{dt} &= \dfrac{u_2}{\alpha_2}\\
\beta(t)&= \dfrac{\beta}{15}\{8+7 \tanh(-\frac{t-\tau}{\lambda})
\end{aligned}
\end{equation}
湖北省(武漢以外)のデータを再現するパラメタセットは以下のとおりであり,これを用いたシミュレーション結果は図2のように与えられる。
$\alpha_1=5/0.80, \alpha_2=5/0.20=25$, $\beta=0.915$, $\gamma_1=15/0.96, \gamma_2=15/0.04=375$,  $\lambda=\tau=7, \nu=0.025$,$u_2$が軽症感染者数(発症無),$u_3$が重症感染者数(発症有),$\dfrac{u_2}{\alpha_2}$が新規重症感染者数$f(t)$,$\dfrac{u_3}{\gamma_2}$が新規死亡数$h(t)$,$u_4$が死亡数累計$i(t)=\int h(t) dt$,$u_6$が新規重症感染者累計$g(t)=\int f(t) dt$,である。WHOで報告されているデータ[1]では,感染者累計(Confirmed)が$g(t)$,死亡数累計(Deaths)が$i(t)$に対応している。


図2 湖北省(武漢以外)のSIIDR2モデルによるシミュレーション
(○はConfirmedとDeathsの観測値,WHOとtencentニュースサイトのデータ)

半分パラメタフィッティングなので,$\nu,\tau,\lambda$を調整して勝手に象の鼻を描いている感は否めないのが微妙なところである

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
using DifferentialEquations
using ParameterizedFunctions
using Plots; gr()

sky = @ode_def SIIDR2_model begin
  du0 = 1 # u0:time
  du1 = -β/15*(8+7*tanh(-(u0-τ)/λ))*u1*u2/n # u1:Noimmunity(Susceptible)
  du2 = β/15*(8+7*tanh(-(u0-τ)/λ))*u1*u2/n -u2/α1 -u2/α2 # u2:Mild(Infected-a)
  du3 = u2/α2 -u3/γ1 -u3/γ2 # u3:Serious(Infected-b)
  du4 = u3/γ2 # u4:Dead
  du5 = u2/α1 +u3/γ1 # u5:Recovered
  du6 = u2/α2 # u6:Accumulated Infected-b
end n α1 α2 β γ1 γ2 λ τ

function epidm(β,ν,λ,τ,T)
n=10000.0 #total number of population
α1=5.0/0.80 #latent to recovery (days/%)
α2=5.0/0.20 #latent to onset (days/%)
#β=0.45 #infection rate (/day・person)
γ1=15.0/0.96 #onset to recovery (days/%)
γ2=15.0/0.04 #onset to death (days/%)
u0 = [0.0,n-11ν,4ν,2ν,0.0,5ν,ν] #initial values
p = (n,α1,α2,β,γ1,γ2,λ,τ) #parameters
tspan = (0.0,T) #time span in days
prob = ODEProblem(sky,u0,tspan,p)
sol = solve(prob)
return sol
end

β=0.915 #infection rate
ν=0.025 #inital value of accumulated infected-b
λ=7 #pandemic supression range (days)
τ=7 #pandemic supression start (day)
T=40 #period of simulation

xc=[0,3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48]
yc=[0.003,0.03,0.23,0.67,1.27,2.00,2.52,2.88,
3.44,3.58,3.71,3.69,3.71,3.71,3.71,3.71,3.71]
zc=[0.0,0.0,0.0,0.01,0.02,0.03,0.04,0.05,
0.07,0.09,0.10,0.11,0.11,0.12,0.12,0.13,0.13]

# kohoku-bukan model
# β=0.915,ν=0.025,λ=7,τ=7,α2=5.0/0.20,γ2=15.0/0.04
@time so=epidm(β,ν,λ,τ,T)
#plot(so,vars=(0,2))
plot(so,vars=(0,3))
plot!(so,vars=(0,4))
plot!(so,vars=(0,5))
plot!(so,vars=(0,6))
plot!(so,vars=(0,7))
plot!(xc,yc,st=:scatter)
plot!(xc,zc,st=:scatter)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

[1]Coronavirus disease (COVID-2019) situation reports
[2]新型環状病毒肺炎疫情動態(tencentニュースサイト)
[3]新型コロナウイルス感染症対策専門家会議(第5回)(3月2日)
[4]特設サイト:新型コロナウイルス(NHK)
[5]新型コロナウイルス感染速報(Su Wei)
[6]Coronavirus Disease (COVID-19) – Statistics and Research(Our World in DATA)
[7]Databrew's COVID-19 data explorer(Databrew)
[8]COVID-19情報共有(佐藤彰洋)
[9]時間遅れを考慮した確率 SIR モデルの安定性解析(石川昌明)
[10]分布的時間遅れをもつ確率感染症モデルの安定性解析(石川昌明)

P. S. [6]のように,プロがシミュレーションをすると遅延SIRモデルみたいなことになるのか。感染率にステップ関数を導入しているので鋭い構造が出現しているようだ。遅延確率SIRモデルになるとちょっとついていけません(3/17/2020)。


感染症の数理シミュレーション(9)に続く

2020年2月28日金曜日

感染症の数理シミュレーション(3)

感染症の数理シミュレーション(2)からの続き

一昨日のニュース。2020年2月26日,基本方針が全く具体的でないと厳しく指摘されてあせる政府の要請により,PerfumeやEXILEのコンサートが中止になった。また,3月11日まで国立劇場の公演は延期や中止,国立博物館・美術館が閉館となり,社会の空気が相転移した。一方,大阪でただ1名の感染報告者のガイドさんが,回復・退院後再び症状が現れてしまった。さらに,昨日は安倍官邸が全国の小中学校の休校を要請するに至った。SNSでは根拠が不確かな情報がひろがっているということなので,このblogもあまり信用しないほうがいい(ソースコードは提供しており反証は可能だ)。

さて,パラメタが4つあれば象の絵が描けるといわれているので,5つもパラメタを含んだモデルで遊ぶのは慎んだほうがよいのだ。しかし,精緻な理論化に取り組む前に,現象の本質につながるトイモデルであれこれ試すのは,物理学あるいはコンピュータ・シンキング(プログラミング的思考じゃないダス)のキモである。前回のモデルにおけるパラメタセットの問題点を指摘し,それを除く修正モデルを提案して今後の感染状況の推移の全体像の可能性を探りたい。

前回のSIIDRモデルには6つのパラメタが現れる。このうち集団の人口$n$は固定されており,これは問題ない。感染率(1人1日あたりの感染人数)$\beta$や軽症感染者からの平均離脱日$\alpha_1, \alpha_2$,重症感染者からの平均離脱日$\gamma_1, \gamma_2$の5つの範囲をどのように合理的に推定するかがポイントだ。

東京大学の黒田玲子産業医が個人的な見解としてまとめたメモによれば,新型コロナウイルス感染症の潜伏期は1〜12.5日(多くは5〜6日)で,感染力は1人→2〜3人,予後はほぼ治癒だが,ICU入院が5%弱で致死率が0.7%とある。したがって,軽症感染者の平均離脱日を7日とするのはまあまあだ。また,重症感染者への移行率は5%とした。黒田メモの分母は感染者だが,我々のモデルの感染者には発症していないものも含む(5%より小さくなる)。その一方でICUまで至らない重症感染者も考えられる(5%より大きくなる)ので,とりあえず5%を採用する。そこで,$\alpha_1=7/0.95, \alpha_2=7/0.05$とする。また,重症感染者についても同じ7日を仮定し,重症感染者からの死亡率を5%とした。つまり,$\gamma_1=7/0.95, \gamma_2=7/0.05$である。その結果,このモデルの感染者致死率は0.25%となるが,ここでの軽症感染者には発症していないものを含めているので,上記の致死率0.7%とは矛盾しない。こうして4つのパラメタは合理的な範囲で収められる。

最も重要な不確定因子は感染率$\beta$である。これは,ウイルスとホストの生物学的な特性でほぼ決まるが,同時に,ホストである人間集団の社会構造(人口密度,経済活動,生活習慣,文化的要因)にも強く影響されるので,想定する集団によって大きく変わりうる。一方で,感染力(基本再生産数 Ro)は2.2と報告されているため,軽症感染者の平均離脱日数7日で割れば,$\beta=0.3$あたりがこの値の想定範囲となる。

とりあえず,前回のモデルをダイヤモンドプリンセスに当てはめて計算してみた。初期値として5%が感染したところから初めた(2/28/2020日経朝刊の対談より)。$\beta=0.3$のときに3700人あたり4人の死亡数(30日目)が再現できる。このときの累積重症感染者は3700人に換算して590人であり,報告されている700人とほぼ対応している。

次に,このパラメタセットで中国の湖北省の状況を説明できるかどうかを確認したい。モデルの軽症感染者には感染力を持つ非発症者を含んでいるため,報告されている感染者数とは直接比較できない。一方,軽症感染者数の時間構造と重症感染者数の時間構造は1週間ほどのタイムラグがあるがほぼ同形である。また,死亡者数についてもあいまいさが少ない。そこで,我々は,(1)モデルの重症感染者数(1万人当り)の感染者数飽和時期と(2) 現時点の死亡数(1万人当り)の2要素を湖北省のデータと比較する。2019年12月8日に最初の報告があってから,ほぼ3ヶ月たった現時点で湖北省の感染者数は飽和しそうである。また,湖北省の死亡数は約2700人だ(湖北省総人口は5900万人で人口密度は日本と同程度。また,死亡数の8割が集中する武漢は人口が1100万人で人口密度は愛知県なみ)。湖北省の感染者数飽和時の1万人当りの死亡数は0.5人のオーダーと推定される(特異的な武漢を除く湖北省では0.1人)。

図1 β=0.3(左の山/丘)とβ=0.2(右の山/丘)の重症感染者数(u4)と死亡数(u4)

図1のように,上記のパラメタで3ヶ月後(90日目)に感染者数飽和を与える$\beta$は0.30前後だが,このときの死亡数は20人となり1桁以上違う。一方,$\beta=0.20$として,3ヶ月後の死亡数を0.5人あたりに近づけると感染者数飽和時期(右の山の累積値の飽和)が7ヶ月後になり,このモデルでは湖北省データをうまく説明できない。その原因は,中国の強力な対策によって感染率$\beta$を制御できたことにあるのではないかと考え,$\beta$が時間$t$の関数として減少する図2のSIIDR2モデルを導入する。パラメタ数をなるべく減らしたいので,ここでは,$\beta(t)=\beta \exp(-t / \lambda)$という1パラメタの指数関数を導入する。$\lambda$は感染対策時間因子であり1〜数ヶ月のオーダーだ。短期間の変化は1次関数で近似される。

図2 感染率がβ(t)として時間に依存するSIIDR2モデル

図3のように,$\beta=0.30, \lambda=65$で,湖北省データと矛盾しない結果が得られる。これは感染初期に3ヶ月で感染率を1/4まで減らしたことに対応し,実際中国は武漢封鎖などの強力な措置によってこれを達成したのかもしれない。このときの飽和感染者数は70人(1万人当り)であり,武漢に換算すると8万人となる(非発症者を含む)。なお,この感染対策時間因子が$\lambda=100$にとどまれば,重症感染者数の飽和数は数倍に跳ね上がる。

図3 小さな山/丘(λ=65)が湖北省データに対応,大きな山/丘はλ=100の場合

この湖北省で得られたパラメタをこのまま日本に当てはめると,30日目の現在の重症感染者数は0.65人/1万人,0.05人/1万人である。つまり現時点における全国の重症感染者数はu3=6500人,死亡数はu4=500人に達することになる。しかし,たいへん残念なことに日本には正確なデータがない。通常の肺炎の死亡者は,年間10万人(日平均300人)なので,埋もれて観測されていない可能性も排除できないがそれはないと思いたい。例えば,$\beta=0.20, \lambda=100$とすると(武漢を除く湖北省に近いケース),30日目の現時点においてそれぞれu3=1000人とu4=100人となり(観測されていないものがあるという考え),よりマイルドな結果を与える。この場合,3〜4ヶ月後に飽和する重症感染者数は全国で7〜8万人,最終死亡数は500人のオーダーとなる(パラメタ次第で,結果を象の鼻を動かすように変えられるのがいたい・・・orz)。

P. S. 下記の参考文献[6][7][8]のように,中国からはよりまともな遅延ダイナミカル・シミュレーションの結果が報告されている。日本では誰か計算していないのだろうか・・・

[1]緊急寄稿新型コロナウィルス(COVID-19)感染症(川名明彦・三笠桂一・泉川公一)
[2]2019-nCoVについてのメモとリンク(中澤港)
[3]人口と感染症の数理(稲葉寿)
[4]数理モデルによる感染症流行制御の検証(大森亮介)
[5]新興感染症の国際的伝播を予測する数理モデル(西浦博・木下諒)
[6]CoVID-19 in Japan: What could happen in the future?(medRxivから)
[7]The reproductive number R0 of COVID-19 based on estimate of a statistical time delay dynamical system(medRxivから)
[8]A Time Delay Dynamical Model for Outbreak of 2019-nCoV and the Parameter Identification(arxivから)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
using DifferentialEquations
using ParameterizedFunctions
using Plots; gr()

sky = @ode_def SIIDR2_model begin
  du0 = 1 # u0:time
  du1 = -β*exp(-u0/λ)*u1*u2/n # u1:Noimmunity(Susceptible)
  du2 = β*exp(-u0/λ)*u1*u2/n -u2/α1 -u2/α2 # u2:Mild(Infected-a)
  du3 = u2/α2 -u3/γ1 -u3/γ2 # u3:Serious(Infected-b)
  du4 = u3/γ2 # u4:Dead
  du5 = u2/α1 +u3/γ1 # u5:Recovered
  du6 = u3 # u6:Accumulated Infection
end n α1 α2 β γ1 γ2 λ

n=10000.0 #total number of population
α1=7.0/0.95 #latent to recovery (days/%)
α2=7.0/0.05 #latent to onset (days/%)
β=0.3 #infection rate
γ1=7.0/0.95 #onset to recovery (days/%)
γ2=7.0/0.05 #onset to death (days/%)
λ=65 #pandemic supression time (days)

function epidm(n,α1,α2,β,γ1,γ2,λ)
u0 = [0.0,9999.0,1.0,0.0,0.0,0.0,0.0] #initial values
p = (n,α1,α2,β,γ1,γ2,λ) #parameters
tspan = (0.0,30.0) #time span in days
prob = ODEProblem(sky,u0,tspan,p)
sol = solve(prob)
return sol
end

β=0.20
λ=100
@time so=epidm(n,α1,α2,β,γ1,γ2,λ)
plot(so,vars=(0,4))
plot!(so,vars=(0,5))
#plot!(so,vars=(0,7))
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

感染症の数理シミュレーション(4)に続く

2020年2月25日火曜日

感染症の数理シミュレーション(2)

感染症の数理シミュレーション(1)からの続き

動機
政府は,NHKテレビを通じて「ここ1,2週間が瀬戸際」で「感染のピークシフトを」と訴えている。ところが,具体策としては,多少具合が悪くても自宅で寝ておけというもので,それ以上の(中国のような)大胆な政策出動には及び腰だ。北海道や和歌山県などでは感染者が報告されてきたが,大阪や京都で感染者がほとんどいないのはどうみても不自然だ。SNSのうわさでは,保健所や病院でたらい回しにされながら検査を拒否されているという。ごく一部の例で感染拡大をアピールしながら,厚生労働省の検査基準と各地方自治体の保健所の連係プレーで,医療資源保護や東京五輪のために検出の押さえ込みを図っていると見えてしまう。

ところで,日本の医療資源の限界とはどの程度のもので,感染によってこれがどの程度枯渇することが想定されているのか,あるいは,ピークシフトの時間スケールはどうなのかという半定量的な情報はほとんど出されていない。重症化した感染者が病院で病床について治療されることを考えてみる。厚生労働省の医療施設動態調査(平成30年2月)によれば,一般診療所を除く日本全国の病院の療養病床を除く病床数は約123万床なので国民100人に1床の割合だ。なお月末病床利用率というのも厚生労働省から報告されており,全国平均で約80%である。したがって,コロナウィルスの対応可能病床数は100人に0.2床ということになる。実際にはすべての病床が対応可能ではないから,100人に0.1床(1万人に10人)を上回る重症者が発生すれば危険というオーダーか。

そこで,今回の新型コロナウイルス感染症の特徴を満たすSEIRをもとにした数理モデルを作成して,重症者や死亡者のシミュレーションを行ってみる。なお,PFNの丸山宏さんが,林祐輔さんが公開したSEIRモデルを参考に,ダイアモンド・プリンセスにおけるCOVID-19発症日別報告数を観測データとして,最適化ツールOptunaを用いてパラメターフィッティングを試みている。丸山さんは,SIRモデルを仮定し(感染しない潜伏期間がないもの),N=3711人の集団に対し,初期の感染者(発症していない人を含む)I0,感染率β(Nで割っていない),感染者がある日に発症する率α,発症期間γをパラメータ推定した。発症率が4%で,初期感染者が209名,一人当たり感染数(Nβ)は41だ(βの定義に注意)。

モデル
SEIRモデルを少し修正する。免疫を持たないものが感染する部分は同じである。この軽症感染者は一定期間をへて,回復者又は重症感染者にいたる。重症感染者は,一定期間をへて回復者または死亡者にいたる。重症感染者は病院または自宅で隔離されるためそれ以上の感染源とはならない。死亡者も同様だ。軽症感染者は無症状かもしくは軽い風邪程度の症状なので普通に活動し,免疫非保持者を感染させる可能性がある。この重症感染者の数を病床数と比較することで,医療資源の破綻の程度が判断できる。
図 SEIRモデルを修正したSIIDRモデルの概念図

$\dfrac{du_1}{dt} = -\beta \dfrac{ u_1*u_2}{n}$
$\dfrac{du_2}{dt} = \beta \dfrac{ u_1*u_2}{n} -\dfrac{u_2}{\alpha_1} -\dfrac{u_2}{\alpha_2}$
$\dfrac{du_3}{dt} = \dfrac{u_2}{\alpha_2} -\dfrac{u_3}{\gamma_1} -\dfrac{u_3}{\gamma_2}$
$\dfrac{du_4}{dt} = \dfrac{u_3}{\gamma_2}$
$\dfrac{du_5}{dt} = \dfrac{u_2}{\alpha_1} +\dfrac{u_3}{\gamma_1}$


計算
$n$=10,000人の集団を考える。免疫非保持者$u_1$が9,999人,軽症感染者$u_2$が1人の初期状態から約1年間の推移をシミュレートする。軽症感染者の感染期間を14日として,その96%が回復し,4%が重症化すると仮定する。すなわち,$\alpha_1=14/0.96$ ,$\alpha_2=14/0.04$。さらに,重症感染者の感染期間を7日として,その90%が回復し,10%が死亡するとする。感染者の死亡率は0.4%になる。すなわち,$\gamma_1=7/0.90$ ,$\gamma_2=7/0.10$ 。感染率$\beta$をパラメタとして,0.1から0.3まで変化させた。ここで得られる結果$u_1, \cdots u_5$ はすべて集団1万人あたりの人数になる。そこで,重症感染者数 $u_3$と1万人当たりの受け入れ可能病床数(10床)を比較して,医療資源の枯渇の様子を調べる。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
using DifferentialEquations
using ParameterizedFunctions
using Plots; gr()

sky = @ode_def SIIDR_model begin
  du1 = -β*u1*u2/n # u1:Noimmunity(Susceptible)
  du2 = β*u1*u2/n -u2/α1 -u2/α2 # u2:Mild(Infected-a)
  du3 = u2/α2 -u3/γ1 -u3/γ2 # u3:Serious(Infected-b)
  du4 = u3/γ2 # u4:Dead
  du5 = u2/α1 +u3/γ1 # u5:Recovered
end n α1 α2 β γ1 γ2

n=10000.0 #total number of population
α1=14.0/0.97 #latent to recovery (days/%)
α2=14.0/0.03 #latent to onset (days/%)
β=1.0 #infection rate
γ1=7.0/0.90 #onset to recovery (days/%)
γ2=7.0/0.10 #onset to death (days/%)

function epidm(n,α1,α2,β,γ1,γ2)
u0 = [9999.0,1.0,0.0,0.0,0.0] #initial values
p = (n,α1,α2,β,γ1,γ2) #parameters
tspan = (0.0,360.0) #time span in days
prob = ODEProblem(sky,u0,tspan,p)
sol = solve(prob)
return sol
end

β=0.3
@time so=epidm(n,α1,α2,β,γ1,γ2)
println(β," ",so[4,45]," ",so[3,19])
plot(so,vars=(0,3))
β=0.2
@time so=epidm(n,α1,α2,β,γ1,γ2)
println(β," ",so[4,40]," ",so[3,19])
plot!(so,vars=(0,3))
β=0.15
@time so=epidm(n,α1,α2,β,γ1,γ2)
println(β," ",so[4,34]," ",so[3,20])
plot!(so,vars=(0,3))
β=0.12
@time so=epidm(n,α1,α2,β,γ1,γ2)
println(β," ",so[4,30]," ",so[3,22])
plot!(so,vars=(0,3))
β=0.1
@time so=epidm(n,α1,α2,β,γ1,γ2)
println(β," ",so[4,28]," ",so[3,24])
plot!(so,vars=(0,3))
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

結果
上記モデルを用いたピークシフトのふるまいは,次のグラフのような結果となる。
図:1万人当たりの重症感染者数の感染率β依存性,左からβ=0.30, 0.20, 0.15, 0.12, 0.10

重症感染者数$u_3$のグラフから得られた結果と死亡者数$u_4$の値をまとめると次のようになる。

表:感染率βと1万人当たりの重症感染者のピーク及び1年後の1万人当たり死亡者数
 β  ピーク日 ピーク病床  死亡者
 0.30      50    54    30
 0.20      80    38    28
 0.15    125    23    25
 0.12    185    13    20
 0.10    280    7    13

重症感染者のふるまいの感染率$\beta$への依存性は非常に大きい。$\beta$が0.12を越えると,必要なピーク病床数は簡単に受け入れ可能病床数の10を超えてしまい,医療パニックが発生する。これが,感染開始1ヶ月から半年の間に起こる(当然オリンピックの時期を含む)。非常に激しければ短期(3ヶ月)で収束するが混乱は限度を超える。感染率を押さえこんだとしても影響が長期化することは避けられない。

付記
肺炎による死亡は,悪性新生物や心疾患についで日本人の死因の第3位になっており,年々死亡率(10万人当たり死者数)が上昇している。1万人当りに換算すると,年に10人が肺炎で死亡していることになる。上記のβ=0.10の場合の13人はこれと同じオーダーであることから,もし中間的な過程が隠蔽されたとしても,1年後には明らかに新型コロナウィルス肺炎による肺炎死亡者の急増が観測されるはずである。

感染症の数理シミュレーション(3)に続く


2020年2月24日月曜日

感染症の数理シミュレーション(1)

新型環状病毒肺炎からの続き

あの神戸大学の岩田健太郎さんが新型コロナウィルス感染症の数理シミュレーションの論文を書いていた[1]。調べてみると基本的なSEIRモデルを使った簡単な微分方程式系だ。感染症に対して免疫を持たない者(Susceptible),感染症が潜伏期間中の者(Exposed),発症者(Infectious),感染症から回復し免疫を獲得した者(Recovered)から構成され,その頭文字をとってSEIRモデルと呼ばれる。このうち集団全体の出生率と死亡率を0に置いたモデルで計算していた。

このモデルの詳細については,Compartmental models in epidemiologyに詳しいが,ここでは上の日本語版WikippediaのSEIRの記事で十分だ。早速,Juliaで追計算してみた。下の例では,n=1000人の集団に1人の感染者がいるところからはじまる。感染率βは1日に1人で,潜伏期間αは12日,発症期間γは8.5日である。このモデルでは潜伏期間には感染しない。コロナウイルスは潜伏期間にも感染するようなので,この部分は簡単にモデルを修正できる。以下のコードではδとして導入した項だ(ピークがかなり前倒しになる)。

αとγは制御できないので,感染率βを如何にコントロールするかが対策のキモとなる。潜伏期間に感染するδ=1.0のモデルでは,β=0.2で集団の9割以上が感染してしまうが,β=0.1で8割,β=0.07では1年かけて5割強,β=0.05では1年後にも1割以下となり,非常にセンシティブだ。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
using DifferentialEquations
using ParameterizedFunctions
using Plots; gr()

sky = @ode_def SEIR_model begin
  du1 = -β*u1*(δ*u2+u3)/n # u1:Susceptible
  du2 = β*u1*(δ*u2+u3)/n -u2/α # u2:Exposed
  du3 = u2/α -u3/γ # u3:Infectious
  du4 = u3/γ # u4:Recovered
end n α β γ δ

function dif()

# hayashi-model
#n=1000.0 #total number of population
#α=5.5 #latent period (days)
#β=1.0 #disease transmission rate
#γ=7.0 #infectious period (days)
#δ=0.0 #latent transmission rate

# iwata-model
n=1000.0 #total number of population
α=12.0 #latent period (days)
β=1.0 #disease transmission rate
γ=8.5 #infectious period (days)
δ=0.0 #latent transmission rate

u0 = [999.0,1.0,0,0] #initial values
p = (n,α,β,γ,δ) #parameters
tspan = (0.0,100.0) #time span in days
prob = ODEProblem(sky,u0,tspan,p)
sol = solve(prob)
plot(sol,vars=(0,1))
plot!(sol,vars=(0,2))
plot!(sol,vars=(0,3))
plot!(sol,vars=(0,4))
end

@time dif()
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
岩田さんが例にあげているアウトブレイクするパラメタを代入してみると,よく似たグラフが得られたが,発症者のピーク位置や感染者と発症者の山の重なりが微妙に違う。彼の論文ではパラメタの領域しか提示されていないため,厳密な比較ができない。そこで,林祐輔さんのシミュレーション[2]と比較した。こちらのほうとは,ピタリと一致したのでたぶん上のコードは正しいのだろう。

図 JuliaによるSEIRモデルの確認


[1]A Simulation on Potential Secondary Spread of Novel Coronavirus in an Exported Country Using a Stochastic Epidemic SEIR Model(K. Iwata, C. Miyakoshi)
[2]COVID-19/stochasticSEIRmodel.ipynbY. Hayashi
[3]面向新冠疫情的数据可视化分析与模拟预测(陈宝权 北京大学前沿计算研究中心)
[4]corona_virus_model fitting_by optina(H. Maruyama)

 感染症の数理シミュレーション(2)に続く

2020年2月4日火曜日

立春の問題

今日は立春。暖く晴れた春の一日。

こんな日は数学パズル。これを解くのがComputational Thinkingなのだろうか?
次の四角の中には1〜9の数が1回づずはいる。その配列を求めよ。という問題をtwitterでみかけた。
図 ピーターからの問題?

これを解くためにJuliaを用いると次のようになった。9重の整数のループがあってとてもださい。ループの範囲を集合にして1つずつ減らしていくということも考えられたが,もっときたなくなりそうなのでこれはやめ。それでも取り合えず答えは得られた。和と積で絞るのはちょっとトリッキーなので,正統的ではない。答えは1通りしかないというヒントに基づいている。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
function loop()
  for i1 in 1:9
    for i2 in i1+1:9
      for i3 in i2+1:9
        for i4 in 1:9
          for i5 in 1:9
            for i6 in 1:9
              for i7 in 1:9
                for i8 in 1:9
                  for i9 in 1:9
                    a=i1+i2+i3+i4+i5+i6+i7+i8+i9
                    b=i1*i2*i3*i4*i5*i6*i7*i8*i9
                    c=i1/(10*i4+i7)+i2/(10*i5+i8)+i3/(10*i6+i9)
                    if a==45 && b==362880 && c==1
                      return (i1,i2,i3,i4,i5,i6,i7,i8,i9)
                    end
                  end
                end
              end
            end
          end
        end
      end
    end
  end
end
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
@time loop()
  0.191865 seconds (201.31 k allocations: 10.933 MiB, 2.17% gc time)
(5, 7, 9, 3, 6, 1, 4, 8, 2)


2020年1月13日月曜日

ab+bc+cd=n

Mathtodonに@antimonさんがこんな問題を出していた。

【問題1】S(n) を「 ab+bc+cd=n (a,b,c,d≥1) の整数解の個数」とする(例: S(5)=5 )。 S(k)=S(k+1) となる最小の整数 k(≥3)を求めよ。

【問題2】kを 問題1 の解とする時、S(S(S(S(S(k))))) を求めよ。

で,@栄造さんにならって,juliaで解いてみた。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function s(n)
  m=0
  for b in 1:ceil(Int,(n-1)/2)
    for c in 1:ceil(Int,min((n-1)/2,n/b))
      for a in 1:ceil(Int,(n-c)/b-c)
        for d in 1 :ceil(Int,(n-a*b)/c-b)
          if n == a*b + b*c + c*d
#           println(a," ",b," ",c," ",d)
            m = m+1
          end
        end
      end
    end
  end
  return m
end

for k in 3:20
  println(k,":",s(k))
end

@time println(s(14))
@time println(s(s(14)))
@time println(s(s(s(14))))
@time println(s(s(s(s(14)))))
@time println(s(s(s(s(s(14))))))
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3:1
4:2
5:5
6:6
7:11
8:13
9:17
10:22
11:27
12:29
13:37
14:44
15:44
16:55
17:59
18:68
19:71
20:81
44
  0.000135 seconds (38 allocations: 1.047 KiB)
319
  0.000084 seconds (36 allocations: 1.156 KiB)
4256
  0.001191 seconds (37 allocations: 880 bytes)
178474
  0.270975 seconds (187 allocations: 11.359 KiB)
13153386
788.437834 seconds (186 allocations: 10.188 KiB)

juliaをたまに使うと,割り算の整数化やかけ算記号が省略できないことなどでつまずく。工夫のないプログラムなので時間はやたらにかかってしまう。

2019年12月27日金曜日

数値計算のリスク

精度保証付き数値計算についてのQiitaのAdvent Calendarの記事が話題になっていた。数値計算はなかなか奥が深いので困る。

[Rumpの例題]
$f(a,b) = 333.75 b^6 + a^2 (11 a^2 b^2 − b^6−121 b^4−2) + 5.5 b^8 +\dfrac{a}{2b}$
に $(a,b)=(77617.0, 33096.0)$ を代入した値は?

Mathematicaの場合

In[1]:= f[a_, b_] :=  333.75 b^6 + a^2 (11 a^2 b^2 - b^6 - 121 b^4 - 2) + 5.5 b^8 + a/(2 b)
In[2]:= f[77617.0, 33096.0]
Out[2]= -1.18059*10^21

In[3]:= g[a_, b_] :=  1335/4 b^6 + a^2 (11 a^2 b^2 - b^6 - 121 b^4 - 2) + 11/2 b^8 + a/(2 b)
In[4]:= N[g[77617, 33096], 20]
Out[4]= -0.82739605994682136814

Juliaの場合

[1] function g(a::BigInt,b::BigInt)
    x = 1335/4*b^6 + a^2*(11*a^2*b^2 - b^6 - 121*b^4 - 2) + 11/2*b^8 + a/(2*b)
end
[2] g(BigInt(77617), BigInt(33096))
[2] -0.8273960599468213681411650954798162919990331157843848199178148416727096930142628

[3] function g128(a::Int128,b::Int128)
   x = 1335/4*b^6 + a^2*(11*a^2*b^2 - b^6 - 121*b^4 - 2) + 11/2*b^8 + a/(2*b)
end
[4] g128(Int128(77617), Int128(33096))
[4] 1.1805916207174113e21

[5] function f(a::BigFloat,b::BigFloat)
    x = 1335/4*b^6 + a^2*(11*a^2*b^2 - b^6 - 121*b^4 - 2) + 11/2*b^8 + a/(2*b)
end
[6] f(BigFloat(77617), BigFloat(33096))
[6] -0.8273960599468213681411650954798162919990331157843848199178148416727096930142628

[7] function f64(a::Float64,b::Float64)
    x = 1335/4*b^6 + a^2*(11*a^2*b^2 - b^6 - 121*b^4 - 2) + 11/2*b^8 + a/(2*b)
end
[8] f(Float64(77617), Float64(33096))
[8] -1.1805916207174113e21

2019年11月2日土曜日

Makie.jl

Juliaのパッケージ Grassmann.jl がリニューアルされたというニュースが伝わってきたので,早速調べてみたら, Makie.jl を用いたベクトル場の流線表示の図形が載っていた。Makieは日本語の蒔絵に由来してネーミングされた,GPUを用いる高機能なjulia用グラフィックスのパッケージのようだ。

さっそく,Makie.jlをPkg.add("Makie") してみたがなかなかうまくいかない。そもそも例題が実行できないのだ。あの物性理論の永井祐紀さんが,「Juliaで綺麗なプロットを作る:Makie.jlのインストールと使い方」という記事を2018年12月に書いていたので,早速試してみた。

まず,AbstractPlotting.jlとMakie.jl と GLMakie.jl をインストールせよとある。そうなのか。
サンプルはすべて,FileIO.jl を使って,save("filename.png", scene)としている。そうなのか。ちなみに,ファイルに保存せずに直接画面に出力しようとすると,No renderer could be found for output. It has the following MIME types: というエラーが出てしまう。

とりあえず,指示に従うと,サンプルファイルはほぼ再現できたが,残念ながら,minimum( ) があるものはすべて,収束しないエラーで挫折してしまった。あと,streamplotの中のvectorfieldやlinesの中のpointなどがないといわれる。何が足りないのか?

成功した例は次のようなもの。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
using AbstractPlotting

 x = range(0, stop = 2pi, length = 80)
 f1(x) = sin.(x)
 f2(x) = exp.(-x) .* cos.(2pi*x)
 y1 = f1(x)
 y2 = f2(x)

 scene = lines(x, y1, color = :blue)
 scatter!(scene, x, y1, color = :red, markersize = 0.1)

 lines!(scene, x, y2, color = :black)
 scatter!(scene, x, y2, color = :green, marker = :utriangle, markersize = 0.1)
 save("graph.png",scene)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

図 Makieを用いた関数のプロットの例



2019年10月22日火曜日

1次元井戸型ポテンシャル(2)

1次元井戸型ポテンシャル(1)からの続き)

テレビは朝から即位の礼のニュースで埋めつくされているのでなかなか気持ちが悪い。ラグビーワールドカップが終わった(実はまだ終わっていないの)と思ったらこれだ。オリンピックかIRカジノまでこの調子なのだろうか。

Mathematicaによる1次元井戸型ポテンシャルの解法をjuliaに移植してみた。Mathematicaプログラミングは土地勘があるので,簡単なガイドがあれば大丈夫だ。juliaプログラミングはそこまで熟達していないので,地図とガイドとネットでの評判を駆使して歩き回ることになる。ポイントは2つ。非線形方程式を解くFindRootや代数方程式を解くNSolveをどうするか。グラフをどうするか。それさえクリアすればよいのだが,なかなか難しかった。

非線形方程式を解くパッケージ NLsolve,1次元の数値積分を実行するパッケージQuadGKを導入した。図形描画のためのPlotとGRは既に導入済みである。こういうときに助けになるのが阪大のサイバーメディアセンターの降旗大介さんのページ(Applied Mathematics 9)。NLSolveは生で使うと非常にわかりにくい仕様になっているので,降旗さんがシンタックスシュガーを作ってくれている。おかげで比較的簡単に使うことができるが,MathematicaのFindRootの方がわかりやすいと思うのは気のせいか。規格化条件から波動関数の振幅を求める連立方程式も,MathematicaのNSolveに対応するものが見当たらなかったので,NLsolveを使うことにした。

問題はグラフだ。MathematicaのPlotルーチンにはなじんでいるので,およその様子はわかるが,juliaの方はさっぱりで難渋した。データを離散的なリストの形にするところまでは問題なかったが,そうすると横軸がデータ数でプロットされる。これをもとの変数に変換するためには,Plotの引数にxのリストを与える必要があることに気付くまで半日要した。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
using NLsolve
using QuadGK
gr()

function nls(func, params...; ini = [0.0])
#
#スカラー変数 x スカラー関数 f(x,params)=0
# nls( f, params, ini = xの初期値 )
#ベクトル変数 x ベクトル関数 f(x,params)=0
# nls( f, params, ini = xの初期ベクトル )
#
    if typeof(ini) <: Number
        r = nlsolve((vout,vin)->vout[1]=func(vin[1],params...), [ini])
        v = r.zero[1]
    else
        r = nlsolve((vout,vin)->vout .= func(vin,params...), ini)
        v = r.zero
    end
#    return v, r.f_converged
    return v
end

function heaviside(t)
  0.5 * (sign(t) + 1)
end

function r2(v0,a)
  r=10^6*v0*a^2/(2000)^2
  return r
end

function h1(x, p)
  a,b = x # x[1] とか x[2] と書くのは面倒なので,a,b で代用
  c,d = p # p[1] とか p[2] と書くのは面倒なので,c,d で代用
  return [b+a/tan(a)+c, a^2+b^2-d]
end

function h2(y, q)
  a,b = y # y[1] とか y[2] と書くのは面倒なので,a,b で代用
  c,d = q # q[1] とか q[2] と書くのは面倒なので,c,d で代用      
  (f,hf) =quadgk(x -> sin(c*x)^2, 0, 1)
  (g,hg) =quadgk(x -> exp(-2*d*x),1,10)
  return [a*sin(c)-b*exp(-d), a^2*f+b^2*g-1]
end

function wf(x,s,t)
  (pa,qa)=s
  (a,b)=t
  return [heaviside(1-t)*a*sin(pa*t)+heaviside(t-1)*b*exp(-qa*t) for t in x]
end

r = [0, r2(50, 2)]
ini_v = [2.0, 1.0]
s = nls(h1, r, ini = ini_v)
ini_u = [1.0, 1.0]
t = nls(h2, s, ini = ini_u)
x = 0:0.01:3
plot(x,wf(x,s,t))
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
図 1次元井戸型ポテンシャルの波動関数


2019年9月23日月曜日

ケンプナー級数(2)

(ケンプナー級数(1)からの続き)

Wolfram MathworldのKempner Sereisの項には,ケンプナー級数の具体的な数値が与えられている。というわけで,さっそくJuliaを使って計算してみよう。そうそう,いつの間にか,Juliaは1.2にバージョンアップしている。再帰を使ったコードは次のとおり。

上述のMathworldでは,9を除去した場合のケンプナー級数の収束値が22.92になっているので,10^10個までの範囲でも7割程度の値しか得られていない。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function core(is,m,x,sum)
#  local e,y::BigFloat
  e=1
  is=is-1
  for k in 1:9
    y=10*x+m[k]
    if(y!=0 && is==0)
      sum=sum+e/y
#      println(y)
    end
    if(is>0)
      sum=core(is,m,y,sum)
    end
  end
  is=is+1
  return sum
end

function kempner(is,id)
#is : depth of recursive call core
#id : figure # to remove from sum
#  local e,a,sum::BigFloat
  e=1
  m=[0,1,2,3,4,5,6,7,8,9]
  m=deleteat!(m,id+1)
  sum=0   
  for i in 1:9
    a=m[i]
    sum=core(is,m,a,sum)
  end
  println(sum)
end

for is in 1:10
    println(is)
    @time kempner(is,9)
end
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1
4.77184876508206
  0.000109 seconds (60 allocations: 1.344 KiB)
2
6.589720190283038
  0.000105 seconds (61 allocations: 1.813 KiB)
3
8.223084402866208
  0.000134 seconds (60 allocations: 1.641 KiB)
4
9.692867792106203
  0.000408 seconds (60 allocations: 1.641 KiB)
5
11.015650849872554
  0.002865 seconds (59 allocations: 1.313 KiB)
6
12.206153622565859
  0.024687 seconds (60 allocations: 1.641 KiB)
7
13.277605939858102
  0.216985 seconds (212 allocations: 12.234 KiB)
8
14.2419130093833
  1.947383 seconds (210 allocations: 10.734 KiB)
9
15.109789370852864
 17.840434 seconds (211 allocations: 11.078 KiB)
10
15.890878115347133
160.770402 seconds (210 allocations: 10.750 KiB)


比較のため,単純な調和級数の計算コードと所要時間をあげておく。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function test(n)
    e=1
    m=10^n
      sum=0
    for i in 1:m
      sum=sum+e/i
    end
    println(sum)
end

for i in 1:10
    println(i)
    @time test(i)
end
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1
2.9289682539682538
  0.000078 seconds (60 allocations: 1.531 KiB)
2
5.187377517639621
  0.000071 seconds (59 allocations: 1.344 KiB)
3
7.485470860550343
  0.000075 seconds (58 allocations: 1.156 KiB)
4
9.787606036044348
  0.000158 seconds (58 allocations: 1.156 KiB)
5
12.090146129863335
  0.000668 seconds (58 allocations: 1.156 KiB)
6
14.392726722864989
  0.005231 seconds (58 allocations: 1.156 KiB)
7
16.695311365857272
  0.050618 seconds (58 allocations: 1.156 KiB)
8
18.997896413852555
  0.520200 seconds (212 allocations: 13.266 KiB)
9
21.30048150234855
  5.296834 seconds (210 allocations: 10.906 KiB)
10
23.6030665949975
 53.307042 seconds (209 allocations: 10.578 KiB)