三角関数の和積の公式の使い道

# はじめに
三角関数の和積の公式は、どのような場合で使用するのか見極めるのが難しい。というのは、一般的に、和の場合は単純な形だったのが積の形にすることで複雑な式になってしまうからである。（例えば、三角関数の積分計算は積よりも和の形が計算しやすいので、積和の公式が多用される。）そこで、今回はとっつきにくい和積の公式はどのようなタイミングで使用すればいいのかを知るために、2つの例を紹介する。
1つ目は物理学の範囲になるが、振動数が異なる2つの正弦波を重ね合わせると、振動数の差だけ低い周波数のうなりが生じることをまずは紹介する。
2つ目は、ある三角形の3つの角に対して三角関数の情報式が与えられていた場合、そのような三角形を満たす三角形の形状を求めるとい問題である。

最後に、参考としてプログラムによる図示も行うことで、理解を深める。
# 公式の導入
```math
f(A,B)=\sin A+\sin B
```

という、2つの位相が異なる三角関数の和について考える。

対称性から、以下の2式が成立する。

```math
A=\frac{A+B}{2}+\frac{A-B}{2}
```

```math
B=\frac{A+B}{2}-\frac{A-B}{2}
```

したがって、

```math
f(A,B)=\sin A+\sin B=\sin (\frac{A+B}{2}+\frac{A-B}{2})+\sin (\frac{A+B}{2}-\frac{A-B}{2})=2\sin \frac{A+B}{2} \cos \frac{A-B}{2}
```

したがって、

```math
\sin A+\sin B=2\sin \frac{A+B}{2} \cos \frac{A-B}{2}
```
が成立する。これを三角関数を和から積に直す公式、通称和積の公式という。



# うなり
上式を用いて以下の2つの波の重ね合わせを考える。

## 問題

```math
g(t)=\sin (2\pi f_1 t)+\sin (2\pi f_2 t)=2\sin \frac{2\pi (f_1+f_2)t}{2} \cos \frac{2\pi(f_1-f_2)t}{2}
```
ここで、
```math
g_1(t)=\cos \frac{2\pi(f_1-f_2)t}{2}
```
とし、$g_1(t)$に着目する。
$g_1(t)$が0になった場合、次に0になる$t$は$t=t+\frac{1}{|f_1-f_2|}$である。

したがって、うなりの周波数は$|f_1-f_2|$と表すことができる。

## プログラム

```python wave_sum.py
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import math
f_1=50
f_2=60
x=np.linspace(0, 1, 10000)
y=np.sin(2*math.pi*f_1*x)+np.sin(2*math.pi*f_2*x)
plt.plot(x, y, color='blue')
plt.xlabel('時間')
plt.ylabel('振幅')
plt.savefig("うなり.png")
plt.show()
```

これを実行すると、以下のグラフが出力される。

![うなり.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/e679b396-8e54-4311-9eb2-61250fac6325.png)

$|f_1-f_2|=|50-60|=10[Hz]$なので、0.1秒間隔で同じような波形が繰り返されている。

# 三角形の形状決定
さて、ここから本題の数学の話をしよう。
## 問題
ある$\Delta ABC$が与えられたとする。ただし、$A(1,0)$とし、$B$,$C$は反時計回りで単位円周上に存在するとする。

このとき、$\cos 2A+\cos2B+\cos2C$が最小となるのは正三角形であることを示す。

![output2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/5468803b-a5d1-4963-8190-4d4df01a1927.gif)

## 解説

三角形の角度における制約条件は内角の和が180度であるという条件なので、今回はこれを上手く利用する。

積和の公式より、
```math
\cos 2A+\cos2B+\cos2C=(\cos 2A+\cos2B)+\cos2(\pi-A-B)=2\cos(A+B)\{cos(A-B)+\cos(A+B)\}-1=-2\cos C \cos(A-B)+2\cos^2 C-1=2\{\cos C-\frac{\cos(A-B)}{2}\}^2-\frac{\cos^2(A-B)}{2}-1
```
ゆえに、二次関数の平方完成と最小値の関係性より、$A=B=C=\frac{\pi}{3}$つまり正三角形のとき最小となる。

（なぜなら、$\cos C=\frac{\cos(A-B)}{2}$かつ$\cos^2(A-B)=1$を同時に満たすのは、$A=B=C=\frac{\pi}{3}$しかないからである。）

## プログラム
上記の議論を参考に、最急降下法で目的関数$f(A,B)=\cos 2A+\cos2B+\cos2C$が最小になるような角度$A,B,C$の値について探索した。

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

# # アニメを作る初期設定
fig = plt.figure()
ims = []#グラフを保存する配列
plt.gca().set_aspect('equal', adjustable='box')


A_ary = []  # Aの角度を保存する配列
B_ary = []  # Bの角度を保存する配列
C_ary = []  # Cの角度を保存する配列

def triangle(A,B):
  ## アニメ
  im=[]
  theta=np.linspace(0, 2*math.pi, 100)
  im1=plt.plot(np.cos(theta), np.sin(theta), color='blue', label='単位円')
  # 三角形の成立条件
  C=math.pi-(A+B)
  #円周角の定理により、点A,B,Cの座標を定義する。
  arg_B=2*C
  arg_C=-2*B
  ## アニメ
  im2=plt.plot([1,np.cos(arg_B),np.cos(arg_C),1],[0,np.sin(arg_B),np.sin(arg_C),0],'-',color='red')
  im3=plt.plot(1,0,'o',label='A',color='black')
  im4=plt.plot(np.cos(arg_B),np.sin(arg_B),'o',label='B',color='green')
  im5=plt.plot(np.cos(arg_C),np.sin(arg_C),'o',label='C',color='purple')
  im=im1+im2+im3+im4+im5
  #目的関数
  f=(np.cos(2*A)+np.cos(2*B)+np.cos(2*C))
  ##アニメ静止画を保存
  ims.append(im)#グラフを配列に追加
  return A,B,C, f
#微小変化率
h=1e-5
#計算回数
num=10
#学習率
alpha=0.2

#初期値
A=10*math.pi/180
B=80*math.pi/180

#目的関数と計算回数の配列
i_ary = []
f_ary = [] 

for i in range(num):
  A,B,C,f = triangle(A, B)
  # 勾配降下法の更新
  # AとBの微分を計算
  A_A,B_A,C_A,f_A = triangle(A+h, B)
  A_B,B_B,C_B,f_B = triangle(A, B+h)
  dA = (f_A - f) / h
  dB = (f_B - f) / h
  A = A - alpha * dA
  B = B - alpha * dB
  C =math.pi - (A + B)  # Cの更新
  i_ary.append(i)
  A_ary.append(A * 180 / math.pi)  # Aの角度を保存
  B_ary.append(B * 180 / math.pi)  # Bの角度を保存
  C_ary.append(C * 180 / math.pi)  # Cの角度を保存
  f_ary.append(f)

# アニメーションの設定
ani = animation.ArtistAnimation(fig, ims, interval=100)#100ms ごとに表示
ani.save("output2.gif", writer='pillow', fps=10)  # GIFとして保存
plt.show()  # グラフを表示


# ##目的関数の最適化
# plt.plot(i_ary, f_ary)
# plt.xlabel("反復回数")
# plt.ylabel("目的関数")
# plt.savefig("sankaku_opt.png")
# plt.show()  # グラフを表示

## 角度の変化をプロット
# plt.plot(i_ary, A_ary, label='A', color='blue')
# plt.plot(i_ary, B_ary, label='B', color='green')
# plt.plot(i_ary, C_ary, label='C', color='purple')
# plt.xlabel("反復回数")
# plt.ylabel("角度 (度)")
# plt.legend()
# plt.savefig("sankaku_en.png")
# plt.show()
```

これを実行すると以下のような動画が出力される。

![output2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/5468803b-a5d1-4963-8190-4d4df01a1927.gif)

このように、確かに正三角形に漸近していっているのが分かる。


一方で、以下に学習率を0.01とし、積算回数を100回とした場合の目的関数の推移を示す。


![sankaku_opt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/dabda173-32c3-40e9-8546-037977dc0187.png)


このように、積算回数を増やすにつれて、最小値探索が上手くできていることが分かる。

最後に、角$A,B,C$の推移を以下に示す。

![sankaku_en.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/16d4dbcc-20c3-4328-8270-3b42bcb093be.png)

このように、積算回数を増やすにつれて、全ての角が均一の値に収束しているのが分かる。


# まとめ

今回は、高校数学の三角関数のラスボスである、和積の公式の使用例を学んだ。この公式は知っているだけでは意味がないので、使用例などをしっかりと理解しておくことが大切である。また、プログラミングによる視覚化もこのような数学的な議論を理解するのに役立つだろう。