93275


高校数学の知識がほとんどなく、方法が思いつきません。

2次元平面を例とします。今現在の点Aに適当な乱数により生成したステップSを足して次点Bを作っています（実際に実装するのは、ずっとこれを繰り返すランダムウォークです）。

[code]

    B(x+u, y+v) = A(x, y) + S(u, v)
    
[/code]

これに対して、ある適当な角度θによる制限を行いたいです。  
（ちなみに角度θは、n次元ベクトル2つのなす角θが内積のcosθより得られるということなので、試しにAと2つのランダムな点E、Fとの間にAE、AFベクトルを作ってみて、これを使うつもりです。しかし制限は変わらず角度のみによるので、例えば2つのベクトルの先端を繋いで三角形の領域の中に制限したりする、などはしません）

（追記）やりたいことを図示しました。意図が伝われば幸いです。  
画像の点B1〜B5は、生成をやり直したS1〜S5により決まっています。  
B3〜B5のように、AE、AFベクトルのなす角θの範囲内にのみ点Bが生まれるようにしたいです。  
[![imagine](https://i.stack.imgur.com/C9mDT.png)](https://i.stack.imgur.com/C9mDT.png)

例えばn次元座標の一様乱数を作るにあたって、[a, b)の範囲内の乱数を以下のように生成できますが、ここに角度も指定できないでしょうか。

[code]

    np.random.rand(1, n) * (b - a) + a
    
[/code]

乱数が生成されたらその角度の範囲内にあるかどうかを検知して、whileで角度の範囲内に生成されるまでやり直すというのは一つの手段だと思うのですが、それでも実装方法が考えつかないですし、n次元においては途方もない計算量になってしまうと思います。

それから他の例として、2次元における下記コードのような極座標の考え方を用いる方法は見つけたのですが、これだとある点からの距離と角度の二度乱数を生成して、それらにより1次元分の座標値を段階的に決定してしまっています。あくまで事前に用意する任意の乱数生成一度分に対して制限を加えたいです。  
ただ視覚的に見れば、ほとんどやりたいことが実現できています。アニメーション画像はこのコードによるものです。

もちろんこれらの改善に限らず、実現する方法があればなんでもありがたいです。ぜひよろしくお願いいたします。

[![random plot
gif](https://i.stack.imgur.com/CXF3u.gif)](https://i.stack.imgur.com/CXF3u.gif)

[code]

    import math
    import random
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    
    # 点A(1, 0)からの角度、距離の上下限
    angle_range = (0, 45)
    distance_range = (1, 10)
    
    
    def random_point(frames):
        _ = frames
    
        # ランダムに角度、距離を生成
        angle = random.uniform(angle_range[0], angle_range[1])
        distance = random.uniform(distance_range[0], distance_range[1])
    
        # 角度をラジアンに変換
        radian = math.radians(angle)
    
        # 極座標によりx, y座標を求める
        x = distance * math.cos(radian)
        y = distance * math.sin(radian)
    
        plt.scatter(x, y, c='r')
    
    
    fig = plt.figure()
    plt.grid(True)
    plt.xlim(-10, 10)
    plt.ylim(-10, 10)
    
    # frames：生成点数
    anim = FuncAnimation(fig, random_point, frames=50, interval=200)
    plt.show()
    
[/code]

（2023年1月7日追記）  
n次実数空間の中で定義されたある関数の最小値となる解の座標x,y,...nを求める「最適化問題」に取り組んでいます。  
下記リンクの後半で多数図示されているのは各関数の2次元f(x, y)における景観です（ちなみに質問における一様乱数の区間は、この各関数f(x,
y,...n)における変数x, y…nそれぞれの定義域（探索域）のことです）。  
<https://qiita.com/tomitomi3/items/d4318bf7afbc1c835dda>

このような問題を、解析的に解こうとせずに、最適点を「探索」する手法を用いて解こうとしています。

さて、[粒子群最適化法（PSO）](https://ja.wikipedia.org/wiki/%E7%B2%92%E5%AD%90%E7%BE%A4%E6%9C%80%E9%81%A9%E5%8C%96)のような多点探索手法の個体群に、空間を探索させているとします。そしてそれ以外に1体、それとは全く関係なく、空間中をただランダムウォーク（散歩）している存在がいます（質問の点AですのでAとします）。

このとき、Aが次の位置Bを決めるにあたってまれに、他の手法の個体群が探索中のエリアに誘導されるようにしたいです（まれに、としたのは、散歩をしているAが気分（確率によって切り替わる）によってその方向に向かうこともあるようにしたいから）。  
→「本当に実現したいこと」です。

「個体群が探索中のエリア」は仮想的に決められるもので、簡易的には互いのノルムが最も離れている2つの個体（質問の点E,Fです）によって作ることができると考えています（本格的に作るとするなら、個体群の中で最も外側にいる個体全てを利用することになると思います）。

これらの個体E,Fに対してAがAE,AFベクトルを作って、なす角θを求めれば、角度で制限された範囲内でのみAはランダムウォークをするので（擬似的な視野角のようなものになる）、気長に待てばそのエリアにたどり着く（制限の結果として誘導してやれる）だろう、と考えました。  
→「本当に実現したいこと」を実現できそうなアイデアであり、本質問における「実現したいこと」です。


CC BY-SA 4.0
2023-01-04T04:49:36.627
93176
2023-01-16T07:06:41.170
2023-01-06T22:34:50.453
56372
56372
question
python 数学
n次元座標における乱数の生成領域を特定角度の範囲内に制限したい


（くだらない話ですが…）

おそらくやりたいことは，

【ある方向を示すベクトル `v` があるときに，この `v` と成す角がある閾値`θ`以内となる範囲（下図のオレンジ色の範囲）内に点 `S` を生成する】

ことだと見える．

[![画像の説明をここに入力](https://i.stack.imgur.com/o2Qvk.png)](https://i.stack.imgur.com/o2Qvk.png)

> 乱数が生成されたらその角度の範囲内にあるかどうかを検知して、whileで角度の範囲内に生成されるまでやり直す

という話を単純に考えると，  
「乱数で `S` を生成 → その場所が図のオレンジ色の領域でない場合にはやり直し」という話となるが，  
例えば下図の緑色の範囲に `S` が生成された場合には，角度を `θ` だけ減じてやれば（= `A` を回転中心として `S` を `θ`
だけ回転してやれば）オレンジ色の範囲に入るのだから，乱数からやり直す必要は無いであろう．

[![画像の説明をここに入力](https://i.stack.imgur.com/HxhZZ.png)](https://i.stack.imgur.com/HxhZZ.png)

さらにより角度が大きい範囲に `S` が生成された場合でも話は同様であり，

`補正後の角度 = (乱数で生成した S と v との成す角) % θ`

のような感じで角度の値を補正してやればオレンジ色の範囲内の点の座標を手に入れることができる．

もちろん， `θ` が180を割り切れない場合には，180度付近の領域に対しては「乱数からやり直し」を行う必要がある（一様性のために）けれども，  
このようなちょっとした工夫で「やり直し」となる確率を相応に減らすことはできるのではあるまいか．


CC BY-SA 4.0
2023-01-06T04:32:41.067
93209
2023-01-06T04:32:41.067
52236
93176
answer


> 乱数が生成されたらその角度の範囲内にあるかどうかを検知して、whileで角度の範囲内に生成されるまでやり直す

について2次元の記述例とその結果図を示します。「乱数から生成したベクトル方向の単位ベクトル」と「与えられた2ベクトル（AE,
AF）に挟まれた角の2等分線の単位ベクトル」との内積を使って方向の可否判定をしました。なお，大きさも方向も境界は含まない条件にしています。

[code]

    import numpy as np
    from numpy.linalg import norm
    import matplotlib.pyplot as plt
    
    DIMENSION = 2
    POINT_B_NUM = 2000
    rng = np.random.default_rng()
    r_min, r_max = 1.0, 10.0
    
    a = np.array([-1.0, -1.0])   # point A
    e = np.array([-2.6, 1.4])    # point E
    f = np.array([-1.9, -2.4])   # point F
    ae1 = (e - a) / norm(e - a)  # unit vector of AE
    af1 = (f - a) / norm(f - a)  # unit vector of AF
    # unit vector of angle bisector of EAF
    am1 = (ae1 + af1) / norm(ae1 + af1)
    # cos(half angle of EAF)
    cos_ha = ae1 @ am1
    
    counter = 0
    rand_walk = []
    while True:
        ab = rng.uniform(-r_max, r_max, DIMENSION)
    
        r = norm(ab)
        if not r_min < r < r_max:
            continue
    
        ab1 = ab / r
        if not ab1 @ am1 > cos_ha:
            continue
    
        rand_walk.append(ab)
        counter += 1
        if counter >= POINT_B_NUM:
            break
    
    # plot
    fig = plt.figure(figsize=[6, 6])
    ax = fig.add_subplot()
    ax.set_aspect('equal')
    ax.set_xlim(-15, 5)
    ax.set_ylim(-10, 10)
    ax.set_xticks(np.arange(-15, 6, 5))
    ax.set_yticks(np.arange(-10, 11, 5))
    ax.minorticks_on()
    ax.grid(which='major', color='black')
    ax.grid(which='minor', color='gray', linestyle='--')
    
    for rw in rand_walk:
        ax.plot(*(a + rw),
                marker='.', color='red', markersize=1)
    
    ax.arrow(*a, *(e - a),
             length_includes_head=True, head_width=0.2)
    ax.arrow(*a, *(f - a),
             length_includes_head=True, head_width=0.2)
    ax.text(0, 0, ' O')
    ax.text(*a, ' A')
    ax.text(*e, ' E')
    ax.text(*f, '  F')
    
    plt.show()
    
[/code]

[![2次元の描画結果](https://i.stack.imgur.com/2AySx.png)](https://i.stack.imgur.com/2AySx.png)  
3次元の場合の記述例（2次元からの変更部と `# plot`
以降は全て）とその結果図も示します。添付図からは分かり難いですが，点B（赤点）は点Aを頂点する円錐の中に分布しています。

[code]

    DIMENSION = 3
    
    a = np.array([-1.0, -1.0, 0.0])   # point A
    e = np.array([-2.6, 1.4, 0.0])    # point E
    f = np.array([-1.9, -2.4, 0.0])   # point F
    
    # plot
    fig = plt.figure(figsize=[6, 6])
    ax = fig.add_subplot(projection='3d')
    ax.set_aspect('equal')
    ax.set_xlim(-15, 5)
    ax.set_ylim(-10, 10)
    ax.set_zlim(-10, 10)
    ax.set_xticks(np.arange(-15, 6, 5))
    ax.set_yticks(np.arange(-10, 11, 5))
    ax.set_zticks(np.arange(-10, 11, 5))
    ax.minorticks_on()
    
    for rw in rand_walk:
        ax.plot(*(a + rw),
                marker='.', color='red', markersize=1)
    
    ax.quiver(*a, *(e - a))
    ax.quiver(*a, *(f - a))
    ax.plot(0, 0, 0, marker='o', markersize=3)
    ax.text(0, 0, 0, ' O')
    ax.text(*a, ' A')
    ax.text(*e, ' E')
    ax.text(*f, ' F')
    
    plt.show()
    
[/code]

[![3次元の描画結果](https://i.stack.imgur.com/pKeny.png)](https://i.stack.imgur.com/pKeny.png)


CC BY-SA 4.0
2023-01-06T10:55:34.243
93218
2023-01-06T10:55:34.243
54588
93176
answer


最適化問題なのでなるべく計算が軽量な方法を考えます。  
まず誘導の目的地です。  
一番楽なのは通常のPSOですでに記録している、全粒子のベスト位置を使いまわします。  
他には全粒子の現在の平均位置とか全粒子のパーソナルペストの平均とか  
多少計算が重くなりますが、全粒子の位置を各粒子の評価値で重み付けした重心などです。

次に誘導の強さです。もとの方法でθに相当するものです。  
もとの方法ではノルムが最も離れた2点を使用して誘導方向とともに決定するとしていますが、  
これは大体粒子数の2乗回の距離計算が必要になります。  
粒子位置の各成分の分散を用いるのが良いかと思います。

最後に誘導の方法です。  
何度も繰り返せばそのうち誘導される挙動を達成したいのであれば、  
多次元空間で超円錐を考えるより、  
ランダムウォークによる移動が、誘導目的地方向に偏るようにオフセットさせるほうが直感的でしょう。

単純には粒子位置の各成分の分散に比例する範囲の一様乱数から移動量を作り、  
Aから誘導目的地へのベクトルの定数倍と足してみればそんな感じの移動が実現できます  
(この方法だとパラメータ間に相関があるような粒子の分布をいまいち反映できませんが、それを考慮しようとすると計算が大変そうなのでやめました。)

[![画像の説明をここに入力](https://i.stack.imgur.com/FXoJ0.png)](https://i.stack.imgur.com/FXoJ0.png)  
[![画像の説明をここに入力](https://i.stack.imgur.com/TL2F0.png)](https://i.stack.imgur.com/TL2F0.png)  
2次元、黒点が誘導地点  
破線が粒子集団の分散を想定


CC BY-SA 4.0
2023-01-10T01:10:02.193
93275
2023-01-16T07:06:41.170
2023-01-16T07:06:41.170
13127
13127
93176
answer