3次関数の接線の本数についての数値計算

# はじめに
3次関数の接線の本数は3次方程式の解の個数と対応する。しかし、一般的に3次方程式の解を求めるのは困難である。そこで、微分を使い、3次方程式の解の個数だけ求めるという手法がある。今回は、『代々木ゼミナールの数学講師、荻野暢也師』の『この点は出ねぇよぉ』で話題となった問題について一般的な解法と、計算機による数値計算での推定方法を比較することを目的とする。

厳密解空間
![解空間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/e0152bdb-33a4-4970-a51d-0c8ca4b31691.png)


推定解空間

![接線の個数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/6241cd4a-4d18-4f6a-9b3b-2a1824a9897c.png)






# 問題

ある点$(a,b)$から、ある3次関数($y=x^3-3x$)まで接線を引くことを考える。このとき、接線が3本引けるときのある点$(a,b)$の存在範囲を求めよという有名問題がある。

![接線の個数_接線4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/db078f23-3f37-408a-a952-6a1e01dab7be.png)

上記のグラフのように、3次関数の外側ならば、接線が多く引けるように見える。各領域で引ける接線の個数が1,2,3個と異なっているので、実際にグラフを描いて実験すると面白い。今回は、そのイメージがつかめたもとで、解説をする。

# 解法
とりあえず、接線ときたら、$x$座標が$t$の接点をおきたくなる。
ゆえに、そのような接線の方程式は以下のようになる。

```math
y=(3t^2-3)(x-t)+t^3-3t
```
ゆえに、
```math
y=(3t^2-3)x-2t^3
```

ここで、題意よりこの接線は$(a,b)$を通るので、


```math
b=(3t^2-3)a-2t^3
```
が成立する。つまり、

```math
2t^3-3at^2+(3a-b)=0
```
となる。この$t$に関する3次方程式の解が3つとなる範囲（つまり、3本の接線が引ける範囲）を求めたい。

ここで、考えるべきことは、3次方程式を解くのは基本手計算では不可能に近く、因数分解できる運がいいときだけ可能であるということである。

だが、今回は解の値というよりも、解の個数を知りたいので、$y=f(t)=2t^3-3at^2+(3a-b)$と$y=0$がどのような位置関係にあるのかを知るだけでいい。

そこで、$f(t)$の極大値と極小値を求める。（3つの解があるはずなので、必ず存在するはず。）

```math
f'(t)=6t^2-6at=6t(t-a)
```

したがって、極大点もしくは極小点は以下のようになる。

$(0,3a+b),(a,-a^3+3a+b)$

ゆえに、y座標についてどちらかが正でどちらかが負であればいいので、

```math
(3a+b)(-a^3+3a+b)<0
```

が求める$(a,b)$の領域となる。

参考までに、上記の領域を描写するプログラムを作成した。

```python solution_space.py
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import math

n=1000
L=3
a_ary=np.linspace(-L,L,n)
b_ary=np.linspace(-L,L,n)
A,B=np.meshgrid(a_ary,b_ary)
C=np.zeros((n,n))
for i in range(n):
  for k in range(n):
    a=A[i][k]
    b=B[i][k]
    c=(3*a+b)*(-a**3+3*a+b)
    if c<0:
      C[i][k]=np.log(-c)
    else:
      C[i][k]=np.nan
plt.contourf(A, B, C, levels=1000, cmap='jet')
plt.xlabel('a')
plt.ylabel('b')
plt.colorbar(label='C(a,b)')
plt.savefig("解空間.png")
plt.show()
```

ただし、解像度を上げるため、対数を取った。


![解空間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/e0152bdb-33a4-4970-a51d-0c8ca4b31691.png)

# 数値計算
本章では、計算機を用いて解空間を推定したときに、同じような結果がでるのかを調査した。

## アルゴリズム
以下に示す$t$に関する3次方程式が3つの解を持つ条件を調べる。
```math
f(t)=2t^3-3at^2+(3a-b)=0
```

解が$-L\le t\le L$の範囲にあると仮定し、
$t_{n+1}=t_{n}+\Delta t(t_1=-L)$とする$t_n$をおくと、
nを増加させていくとき、
```math
f(t_n)\cdot f(t_{n+1})\le 0
```

となった瞬間があるとき、3次方程式の解の１つ$t_{ex}$は以下の関係式を満たす。
```math
t_n\le t_{ex} \le t_{n+1}
```

以上のアルゴリズムで、解の個数を推定するプログラムを作成したので、以下に示す。

## プログラム

```python max_min.py
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import math

L=5
m=300
t_ary=np.linspace(-L, L, m)
def equation_ab(a,b):
  sum=0
  f_ary=[]
  for i in range(m):
      t=t_ary[i]
      f=2*t**3-3*a*t**2+3*a+b
      if i>0:
        if f*f_ary[-1]<0:
            sum += 1
      f_ary.append(f)
  return sum
num=1000
a_ary=np.linspace(-L, L, num)
b_ary=np.linspace(-L, L, num)
A,B=np.meshgrid(a_ary, b_ary)
C=np.zeros((num, num))
for p in range(num):
   for k in range(num):
      a=A[p][k]
      b=B[p][k]
      C[p][k]=equation_ab(a, b)
plt.xlim(-3, 3)
plt.ylim(-3, 3)
plt.contourf(A, B, C, levels=np.arange(0, 4, 1), cmap='jet')
plt.colorbar(label='方程式の解の数')  
plt.savefig("接線の個数.png")
plt.show()
```

このプログラムの心臓部は、解の推定個数をカウントするequation_ab(a,b)関数である。

上記のプログラムを実行すると、以下のようになる。


![接線の個数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/6241cd4a-4d18-4f6a-9b3b-2a1824a9897c.png)

なるほど、確かに解空間と推定した解空間は一致しているようである。

# まとめ
今回は、3次関数の接線の本数についての有名問題を扱った。一般的な3次方程式の解を厳密に求めるのは手計算では不可能に近いが微分法を用いて、極大値と極小値さえ求めることができれば、解の個数は手計算でも求めることができる。前半では、この事実をもとにして解空間を求めた。後半では、計算機の力を借りて、解の推定個数をカウントするプログラムを作成して推定解空間を描写した。結果、両者は、ほぼ一致した。

# 参考文献


https://tale-sci.online/?p=1356