diff --git a/README.md b/README.md index ac3f3fc..d3d15b4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ## Jupyter Notebooks - [shape.ipynb](shape.ipynb): This notebook implements Polygon and Polyhedron class. You can use it to plot the area that you can clip through a slope. 多角形と多面体のクラスを実装したJupyter Notebookです。これを使って斜面をすり抜ける範囲を描画することができます。 - [hitbox.ipynb](hitbox.ipynb): This notebook gives a demonstration to read collision data from Dolphin and draw ground/roof/wall hitboxs with the shape library made by sup39. Dolphinから三角形のデータを取得し、サポミクが実装した図形のライブラリを使って床・天井・壁の判定を描画する方法を紹介します。 +- [gap.ipynb](gap.ipynb): 水面抜けの探索ツール ## Dependencies Notebooks in this repo use [dolphin-memory-lib](https://github.com/RenolY2/dolphin-memory-lib/blob/main/memorylib.py) made by RenolY2. diff --git a/gap.ipynb b/gap.ipynb new file mode 100644 index 0000000..1ffacef --- /dev/null +++ b/gap.ipynb @@ -0,0 +1,919 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d3cf5102-a75e-4223-ac77-d7638661c6af", + "metadata": {}, + "source": [ + "# SMS水面抜け" + ] + }, + { + "cell_type": "markdown", + "id": "fb09a37b-e0a0-49ec-a139-8aab8634afd7", + "metadata": {}, + "source": [ + "## 準備\n", + "高速化のために[numpy](https://numpy.org/doc/stable/reference/index.html#reference)と[pytorch](https://pytorch.org/docs/stable/index.html)を使います" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee560598-d34e-41b1-8510-3abf0dd35d9f", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install numpy torch tqdm sms-gap==0.1.0" + ] + }, + { + "cell_type": "markdown", + "id": "6f3888db-40f8-4404-b61e-fa6f74984140", + "metadata": {}, + "source": [ + "次に、必要なpackageをimportします" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e6e4ef0-6601-4bc1-9a12-e0c673710d0d", + "metadata": {}, + "outputs": [], + "source": [ + "# cudaを使う場合は次の命令をuncommentしてください\n", + "#%set_env SMS_PYTORCH_DEVICE=cuda" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c11636cf-a5c8-4bf7-851c-c2461268da5d", + "metadata": {}, + "outputs": [], + "source": [ + "import time # 実行時間を計るため\n", + "from tqdm.notebook import tqdm # progress bar\n", + "import numpy as np\n", + "import torch\n", + "from itertools import combinations as combin\n", + "import operator as op\n", + "import logging\n", + "from functools import reduce\n", + "from sms.gap import find_x, find_z, find_neigh, find_all, find_in_result, verify, frange, f32, binsearch\n", + "logger = logging.getLogger('SMS-gap')\n", + "\n", + "def hook():\n", + " from memorylib import Dolphin\n", + " global d, dolphin\n", + " d = dolphin = Dolphin()\n", + " assert dolphin.find_dolphin(), 'Dolphin not found'\n", + " assert dolphin.init_shared_memory(), 'MEM1 not found'\n", + " assert dolphin.read_ram(0, 3).tobytes() == b'GMS', 'Current game is not Sunshine'\n", + "\n", + "import struct\n", + "read_struct = lambda addr, fmt: struct.unpack(fmt, d.read_ram(addr-0x80000000, struct.calcsize(fmt)))\n", + "read_ptr = lambda addr: d.read_uint32(addr)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a265298-e7cc-4b83-bd03-427c8529289a", + "metadata": {}, + "outputs": [], + "source": [ + "# 後からdeviceを変更したい時は次のようにpackageをreloadする必要があります\n", + "## 使うdevice\n", + "#%set_env SMS_PYTORCH_DEVICE=cpu\n", + "#import importlib\n", + "#import sms.gap\n", + "#importlib.reload(sms.gap)\n", + "#from sms.gap import find_x, find_z, find_neigh, find_all, find_in_result, verify, frange, f32" + ] + }, + { + "cell_type": "markdown", + "id": "17755dd0-79c4-4f89-a8d0-453bc3193fc4", + "metadata": {}, + "source": [ + "## 境界を指定" + ] + }, + { + "cell_type": "markdown", + "id": "668d9d1b-01fa-46c2-9b86-727bd98580c7", + "metadata": {}, + "source": [ + "### 方法1: 座標で指定" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2972ffee-a928-4d93-889f-e6d33da3092d", + "metadata": {}, + "outputs": [], + "source": [ + "bd = (\n", + " (-6000, -33900),\n", + " ( 6200, 32700),\n", + ")" + ] + }, + { + "attachments": { + "f124c718-30d3-4d16-b195-31462045b3df.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "id": "971f32c6-c0a6-45b2-a82e-acc335f0c55a", + "metadata": {}, + "source": [ + "### 方法2: 二つの三角形の座標(hex形式)で指定\n", + "[Dolphin-memory-engine](https://github.com/aldelaro5/Dolphin-memory-engine)\n", + "を使う場合、次のように設定すれば水面の三角形の座標をhex形式で取得できます。\n", + "\n", + "![image.png](attachment:f124c718-30d3-4d16-b195-31462045b3df.png)\n", + "\n", + "Level 2について、 \n", + "水面三角形を調べる場合は`E4`にし、 \n", + "普通の床三角形を調べる場合は`E0`にします。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3cc8afd9-1797-48ed-9466-9ae0090391ba", + "metadata": {}, + "outputs": [], + "source": [ + "# 三角形1のhex\n", + "raw1 = 'C5 BB 80 00 00 00 00 00 C7 04 6C 00 C5 BB 80 00 00 00 00 00 46 FF 78 00 45 C1 C0 00 00 00 00 00 46 FF 78 00'" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c2248666-4d53-49ad-9e6e-d96fdbd2e80a", + "metadata": {}, + "outputs": [], + "source": [ + "# 三角形2のhex\n", + "raw2 = 'C5 BB 80 00 00 00 00 00 C7 04 6C 00 45 C1 C0 00 00 00 00 00 46 FF 78 00 45 C1 C0 00 00 00 00 00 C7 04 6C 00'" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "96353351-0bec-44c8-a154-8b4136369d3f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "三角形1の座標:\n", + " [[ -6000. -33900.]\n", + " [ -6000. 32700.]\n", + " [ 6200. 32700.]]\n", + "三角形2の座標:\n", + " [[ -6000. -33900.]\n", + " [ 6200. 32700.]\n", + " [ 6200. -33900.]]\n", + "\n", + "境界: ((-6000.0, -33900.0), (6200.0, 32700.0))\n" + ] + } + ], + "source": [ + "tri1, tri2 = (\n", + " np.frombuffer(bytes.fromhex(raw), '>f').reshape(-1, 3)[:3, [0,2]]\n", + " for raw in (raw1, raw2)\n", + ")\n", + "print('三角形1の座標:\\n', tri1)\n", + "print('三角形2の座標:\\n', tri2)\n", + "\n", + "bds = reduce(op.__and__, (\n", + " set(map(\n", + " frozenset, combin(map(tuple, tri), 2)\n", + " ))\n", + " for tri in (tri1, tri2)\n", + ")) \n", + "assert len(bds)>0, '入力した三角形に共通の頂点座標がない'\n", + "if len(bds)>1: logger.warning('入力した三角形に共通の頂点座標が2個以上存在する')\n", + "bd = tuple(next(iter(bds)))\n", + "print('\\n境界:', bd)" + ] + }, + { + "cell_type": "markdown", + "id": "59c1482e-c69c-4757-8c63-c6c773ac26d4", + "metadata": {}, + "source": [ + "### 方法3: memorylibを用いてDolphinから直接に読み込む\n", + "サンシャインを起動して`hook()`を実行します" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "212e86c8-65b1-485f-aec9-2b58d23c3587", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2932239377344 0x2aab71623c0\n" + ] + } + ], + "source": [ + "hook()" + ] + }, + { + "cell_type": "markdown", + "id": "5a63a7d9-78cb-4172-a159-ab6bb3ab5abc", + "metadata": {}, + "source": [ + "調べるのは水面ならば`offset`を`0xE4`に、普通の床ならば`0xE0`にします" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "abf3085c-0537-49d2-9cd9-7aa4ffa97bf6", + "metadata": {}, + "outputs": [], + "source": [ + "offset = 0xE4" + ] + }, + { + "cell_type": "markdown", + "id": "eda9c35b-04ce-4100-8845-24bf46c1f0a5", + "metadata": {}, + "source": [ + "Dolphinで一つ目の水面/床三角形に移動し、次の命令を実行します" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a59abfbf-acd8-432c-811f-874762815508", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ -6000., -33900.],\n", + " [ -6000., 32700.],\n", + " [ 6200., 32700.]])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tri1 = np.array(read_struct(read_ptr(read_ptr(0x8040A378)+offset)+0x10, '>9f')).reshape(3, 3)[:, [0,2]]\n", + "tri1" + ] + }, + { + "cell_type": "markdown", + "id": "1c3fa7be-8fb0-40bd-b812-d91145963288", + "metadata": {}, + "source": [ + "次に、Dolphinで二つ目の水面/床三角形に移動し、次の命令を実行します" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d0ab10e3-1a52-4175-9453-b252b8971143", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ -6000., -33900.],\n", + " [ 6200., 32700.],\n", + " [ 6200., -33900.]])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tri2 = np.array(read_struct(read_ptr(read_ptr(0x8040A378)+offset)+0x10, '>9f')).reshape(3, 3)[:, [0,2]]\n", + "tri2" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "602def54-7516-421d-a069-9f3c1f4987fa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "境界: ((-6000.0, -33900.0), (6200.0, 32700.0))\n" + ] + } + ], + "source": [ + "bds = reduce(op.__and__, (\n", + " set(map(\n", + " frozenset, combin(map(tuple, tri), 2)\n", + " ))\n", + " for tri in (tri1, tri2)\n", + ")) \n", + "assert len(bds)>0, '入力した三角形に共通の頂点座標がない'\n", + "if len(bds)>1: logger.warning('入力した三角形に共通の頂点座標が2個以上存在する')\n", + "bd = tuple(next(iter(bds)))\n", + "print('境界:', bd)" + ] + }, + { + "cell_type": "markdown", + "id": "adf87d26-a957-4724-a85b-58b6322ca540", + "metadata": {}, + "source": [ + "## 探索\n", + "注意:逆算の誤差によりFalse Positive/False Negativeが発生する可能性があります" + ] + }, + { + "cell_type": "markdown", + "id": "ea3afb8c-7849-4897-a92a-c181f6e2c5b6", + "metadata": {}, + "source": [ + "### 近傍の探索" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0b0e6e62-97e5-422b-8bb5-c1cd6599a732", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "実行時間: 0.133(秒)\n" + ] + } + ], + "source": [ + "axis = 'x' # x軸に沿って探索する\n", + "xC = -1000 # 探索の中心(x座標)\n", + "R = 50 # 探索の半径\n", + "max_count = 1e8 # 最大1億件探索する\n", + "\n", + "t0 = time.time()\n", + "xzz, valid = find_neigh(axis, xC, 50, bd, max_count)\n", + "print('実行時間: %.3f(秒)'%(time.time()-t0))" + ] + }, + { + "cell_type": "markdown", + "id": "767e8380-15f3-4c93-bba6-34e714cd4182", + "metadata": {}, + "source": [ + "#### すり抜けできるx座標の個数" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "8cbf1362-3829-4535-bf1d-d9c033f3a622", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor(652310)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "valid.sum()" + ] + }, + { + "cell_type": "markdown", + "id": "5b52eaf8-9fcc-4e65-86c0-67aa1f97151c", + "metadata": {}, + "source": [ + "#### xC=-1000を中心に最も近い10+10件を表示" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "83547a60-8aaa-4a1e-a30d-ed79f799b515", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x\t\tz Min\t\tz Max\n", + "-1000.0003\t-6604.9204\t-6604.92\n", + "-1000.00037\t-6604.9204\t-6604.92\n", + "-1000.0004\t-6604.9204\t-6604.92\n", + "-1000.0005\t-6604.9204\t-6604.92\n", + "-1000.00055\t-6604.9204\t-6604.92\n", + "-1000.0006\t-6604.9204\t-6604.92\n", + "-1000.0007\t-6604.9204\t-6604.92\n", + "-1000.0017\t-6604.928\t-6604.9277\n", + "-1000.0018\t-6604.928\t-6604.9277\n", + "-1000.00183\t-6604.928\t-6604.9277\n", + "\n", + "x\t\tz Min\t\tz Max\n", + "-999.99927\t-6604.9126\t-6604.912\n", + "-999.9992\t-6604.9126\t-6604.912\n", + "-999.99915\t-6604.9126\t-6604.912\n", + "-999.9991\t-6604.9126\t-6604.912\n", + "-999.999\t-6604.9126\t-6604.912\n", + "-999.99896\t-6604.9126\t-6604.912\n", + "-999.9989\t-6604.9126\t-6604.912\n", + "-999.99884\t-6604.9126\t-6604.912\n", + "-999.9988\t-6604.9126\t-6604.912\n", + "-999.99774\t-6604.905\t-6604.9043\n" + ] + } + ], + "source": [ + "idxC = binsearch(xzz[:, 0], xC)\n", + "idxVC = valid[:idxC].sum()\n", + "xzzV = xzz[valid]\n", + "\n", + "header = ('x', 'z Min', 'z Max') if axis=='x' else ('z', 'x Min', 'x Max')\n", + "print(*header, sep='\\t\\t')\n", + "for r in xzzV[:idxVC][-10:].flip(0).cpu().numpy():\n", + " print(*r, sep='\\t')\n", + "\n", + "print()\n", + "print(*header, sep='\\t\\t')\n", + "for r in xzzV[idxVC:][:10].cpu().numpy():\n", + " print(*r, sep='\\t')" + ] + }, + { + "cell_type": "markdown", + "id": "2e3f3f94-4293-41ba-a948-f1948c5c91d5", + "metadata": {}, + "source": [ + "#### 結果をcsvファイルに保存" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "02b2dba6-e840-4397-aafc-41f4a3a82d7e", + "metadata": {}, + "outputs": [], + "source": [ + "with open('gap.csv', 'w') as fw:\n", + " print(*header, sep=',', file=fw)\n", + " for p in xzzV.cpu().numpy():\n", + " print(*p, sep=',', file=fw)" + ] + }, + { + "cell_type": "markdown", + "id": "94c1ae12-951d-4272-a60e-20e17674bfeb", + "metadata": {}, + "source": [ + "### 全探索\n", + "探索効率のために、探索の領域を二つに分けてそれぞれx軸とz軸に沿って探索することにします" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "88400bb3-4b7d-46ac-ab44-73fd0e45d3f6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "実行時間: 14.402(秒)\n", + "すり抜け可能/探索したx座標の個数: 17,028,477 / 49,272,585\n", + "すり抜け可能/探索したz座標の個数: 8,641,209 / 49,622,817\n" + ] + } + ], + "source": [ + "t0 = time.time()\n", + "\n", + "# 探索\n", + "xzz, zxx = find_all(bd)\n", + "# 結果\n", + "xzzValid = xzz[:,1] <= xzz[:,2]\n", + "zxxValid = zxx[:,1] <= zxx[:,2]\n", + "xzzV = xzz[xzzValid]\n", + "zxxV = zxx[zxxValid]\n", + "\n", + "print('実行時間: %.3f(秒)'%(time.time()-t0))\n", + "print(f'すり抜け可能/探索したx座標の個数: {xzzV.shape[0]:,} / {xzz.shape[0]:,}')\n", + "print(f'すり抜け可能/探索したz座標の個数: {zxxV.shape[0]:,} / {zxx.shape[0]:,}')" + ] + }, + { + "cell_type": "markdown", + "id": "c450c35e-d8f8-4bf1-a549-8223152b9dce", + "metadata": {}, + "source": [ + "例えば、コロロの水面の場合、x=197.4539\\~6199.9985まではx軸に沿って探索し、それ以外(z=-33900\\~-11320)はz軸に沿って探索します。" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "1a0cb7bb-0bc5-4659-b998-f567e42336aa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t x\t\tz Min\t z Max\n", + "tensor([[ 197.4539, -67.9980, -67.9971],\n", + " [ 197.4539, -67.9980, -67.9971],\n", + " [ 197.4539, -67.9980, -67.9971],\n", + " ...,\n", + " [ 6199.9966, 32699.9805, 32699.9805],\n", + " [ 6199.9971, 32699.9805, 32699.9824],\n", + " [ 6199.9985, 32699.9902, 32699.9902]])\n", + "\t z\t\tx Min\t x Max\n", + "tensor([[-3.3900e+04, -6.0000e+03, -6.0000e+03],\n", + " [-3.3900e+04, -6.0000e+03, -6.0000e+03],\n", + " [-3.3900e+04, -6.0000e+03, -6.0000e+03],\n", + " ...,\n", + " [-1.1320e+03, 2.5457e+00, 2.5461e+00],\n", + " [-1.1320e+03, 2.5457e+00, 2.5461e+00],\n", + " [-1.1320e+03, 2.5457e+00, 2.5461e+00]])\n" + ] + } + ], + "source": [ + "print('\\t x\\t\\tz Min\\t z Max')\n", + "print(xzzV)\n", + "print('\\t z\\t\\tx Min\\t x Max')\n", + "print(zxxV)" + ] + }, + { + "cell_type": "markdown", + "id": "8c8df25d-198b-4a01-a4ca-e78b05c62e36", + "metadata": {}, + "source": [ + "#### 結果を保存(ファイルサイズが大きいので注意!)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "64b5b5e1-bc2e-4963-a91f-b397f45065af", + "metadata": {}, + "outputs": [], + "source": [ + "torch.save((xzzV, zxxV), 'result.pt')" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "525c997b-04c8-405c-8890-3c3cd61ec286", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-rw-r--r-- 1 sup39 None 294M Jun 17 16:28 result.pt\n" + ] + } + ], + "source": [ + "!ls -lh result.pt\n", + "# コロロの水面抜け可能座標をファイルに保存すると294MBなります" + ] + }, + { + "cell_type": "markdown", + "id": "9b65254d-290c-4efa-9c70-e20614e99d11", + "metadata": {}, + "source": [ + "#### 結果をcsvファイルに保存\n", + "ファイルが非常に大きくなるのでおすすめしません!\n", + "\n", + "例えばコロロの水面の場合、合計約750MBになりました。\n", + "ファイルサイズが大きすぎてExcelなどでは開けない可能性が高いので、\n", + "保存するとしたら`torch.save()`を使うことをおすすめします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a351ba22-7127-4eee-8d47-b76441fcd7e8", + "metadata": {}, + "outputs": [], + "source": [ + "for sfx, rsfx, a in zip('xz', 'zx', (xzzV, zxxV)):\n", + " with open(f'result-{sfx}.csv', 'w') as fw:\n", + " print(sfx, rsfx+' Min', rsfx+' Max', sep=',', file=fw)\n", + " for p in tqdm(a.cpu().numpy()):\n", + " print(*p, sep=',', file=fw)" + ] + }, + { + "cell_type": "markdown", + "id": "bef3029d-7ee3-4942-8a78-ba9bbf90c915", + "metadata": {}, + "source": [ + "#### 全探索の結果で調べる" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "0f074fcb-e8f5-46e4-9c7b-ab84de096d80", + "metadata": {}, + "outputs": [], + "source": [ + "# 保存した結果を読み込む\n", + "xzzV, zxxV = torch.load('result.pt')" + ] + }, + { + "cell_type": "markdown", + "id": "5cad741f-da63-42b6-aa9f-0ad2d27ab746", + "metadata": {}, + "source": [ + "##### 特定のx座標" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "e85c9f26-21e8-4de3-bf7f-d4b0c5a7ffca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[-1000.0004, -6604.9204, -6604.9199]])" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "find_in_result('x', f32(-1000.00037), xzzV, zxxV)\n", + "# x, z Min, z Max" + ] + }, + { + "cell_type": "markdown", + "id": "23cd14d8-19f7-4b58-b4e6-b7d48c5afaec", + "metadata": {}, + "source": [ + "##### 特定のz座標" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "cf5fcdf5-a093-44f6-94c2-2f80ee52877e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[-6604.9204, -1000.0007, -1000.0003]])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "find_in_result('z', -6604.9204, xzzV, zxxV)\n", + "# z, x Min, x Max" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e0d500a7-a75c-4aaf-a67d-356d2e10196c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([], size=(0, 3))" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 何も返さなかったら該当なし\n", + "find_in_result('z', -6604, xzzV, zxxV)" + ] + }, + { + "cell_type": "markdown", + "id": "990345d9-f2eb-484b-b3a6-d8e64d4a725d", + "metadata": {}, + "source": [ + "##### 近傍での探索" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "85e8bc03-21cd-49e9-9cb7-cafb1971efa0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 950.0003, 4040.1663, 4040.1667],\n", + " [ 950.0004, 4040.1663, 4040.1667],\n", + " [ 950.0004, 4040.1663, 4040.1667],\n", + " ...,\n", + " [1049.9994, 4586.0605, 4586.0610],\n", + " [1049.9995, 4586.0605, 4586.0610],\n", + " [1049.9996, 4586.0605, 4586.0610]])" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "find_in_result('x', frange(950, 1050), xzzV, zxxV)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "40aac4c5-a26f-4d42-a408-6c2e39c41053", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 950.0059, 383.9348, 383.9353],\n", + " [ 950.0059, 383.9348, 383.9353],\n", + " [ 950.0060, 383.9348, 383.9353],\n", + " ...,\n", + " [1049.9987, 402.2517, 402.2522],\n", + " [1049.9988, 402.2517, 402.2522],\n", + " [1049.9989, 402.2517, 402.2522]])" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "r = find_in_result('z', frange(950, 1050), xzzV, zxxV)\n", + "r" + ] + }, + { + "cell_type": "markdown", + "id": "c2d819b6-5c84-4af4-b29d-256ff11c5633", + "metadata": {}, + "source": [ + "##### 結果をcsv形式でexport" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "3ee934fe-262f-44b0-ba5f-1a275a77e826", + "metadata": {}, + "outputs": [], + "source": [ + "axis = 'z'\n", + "\n", + "header = ('x', 'z Min', 'z Max') if axis=='x' else ('z', 'x Min', 'x Max')\n", + "with open('gap.csv', 'w') as fw:\n", + " print(*header, sep=',', file=fw)\n", + " for p in r.cpu().numpy():\n", + " print(*p, sep=',', file=fw)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6ac3de6-9487-42dd-8d49-92a7ba77e5b5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc3d0d67-9534-4e1f-b08a-ee452c59f93e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b746ed07-0ce1-4859-8e0c-1ab3b1bd2056", + "metadata": {}, + "source": [ + "## LICENSE" + ] + }, + { + "cell_type": "raw", + "id": "37eb0a0d-4c0a-4eb6-b55e-2fc8048b6682", + "metadata": {}, + "source": [ + "Copyright (c) 2022 sup39[サポミク]\n", + "\n", + "Permission is hereby granted, free of charge, to any person\n", + "obtaining a copy of this software and associated documentation\n", + "files (the \"Software\"), to deal in the Software without\n", + "restriction, including without limitation the rights to use,\n", + "copy, modify, merge, publish, distribute, sublicense, and/or sell\n", + "copies of the Software, and to permit persons to whom the\n", + "Software is furnished to do so, subject to the following\n", + "conditions:\n", + "\n", + "The above copyright notice and this permission notice shall be\n", + "included in all copies or substantial portions of the Software.\n", + "\n", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n", + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n", + "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n", + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n", + "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n", + "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n", + "OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7ef093f-0100-4c55-be94-26727769e189", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}