diff --git a/docs/Tutorial_DAMP.ipynb b/docs/Tutorial_DAMP.ipynb new file mode 100644 index 000000000..c57011438 --- /dev/null +++ b/docs/Tutorial_DAMP.ipynb @@ -0,0 +1,792 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0cdc3c0a", + "metadata": {}, + "source": [ + "# DAMP: Discord-Aware Matrix Profile" + ] + }, + { + "cell_type": "markdown", + "id": "1bcc1071", + "metadata": {}, + "source": [ + "Authors in [DAMP](https://www.cs.ucr.edu/~eamonn/DAMP_long_version.pdf) presented a method for discord detection that is scalable and it can be used in offline and online mode.\n", + "\n", + "To better understand the mechanism behind this method, we should first understand the difference between the full matrix profile and the left matrix profile of a time series `T`. For a subsequence with length `m`, and the start index `i`, i.e. `S_i = T[i:i+m]`, there are two groups of neighbors, known as left and right neighbors. The left neighbors are the subsequences on the left side of `S_i`, i.e. the subsequences in `T[:i]`. And, the right neighbors are the subsequences on the right side of `S_i`, i.e. the subsequences in `T[i+1:]`. The `i`-th element of the full matrix profile is the minimum distance between `S_i` and all of its neighbors, considering both left and right ones. However, in the left matrix profile, the `i`-th element is the minimum distance between the subsequence `S_i` and its left neighbors. In both cases, it is recommended to avoid considering subsequences that are close to `S_i`, known as trivial neighbors.\n", + "\n", + "One can use either the full matrix profile or the left matrix profile to find the top discord, a subsequence whose distance to its nearest neighbor is larger than the distance of any other subsequences to their nearest neighbors. However, using full matrix profile for detecting discords might result in missing the case where there are two rare subsequences that happen to also be similar to each other (a case that is known as \"twin freak\"). On the other hand, the left matrix profile resolves this problem by capturing the discord at its first occurance. Hence, even if there are two or more of such discords, we can still capture the first occurance by using the left matrix profile." + ] + }, + { + "cell_type": "markdown", + "id": "27bf47eb", + "metadata": {}, + "source": [ + "The original `DAMP` algorithm needs a parameter called `split_idx`. For a given `split_idx`, the train part is `T[:split_idx]` and the potential anomalies should be coming from `T[split_idx:]`. The value of `split_idx` is problem dependent. If `split_idx` is too small, then `T[:split_idx]` may not contain all different kinds of regular patterns. Hence, we may incorrectly select a subsequence as a discord. If `split_idx` is too large, we may miss a discord if that discord and its nearest neighbor are both in `T[:split_idx]`. The following two extreme scenarios can help with understanding the importance of choosing proper `split_idx`.\n", + "\n", + "(1) `split_idx = 0`: In this case, the first subsequence can be a discord itself as it is a \"new\" pattern.
\n", + "(2) `split_idx = len(T) - m` In such case, the last pattern is the only pattern that will be analyzed for the discord. It will be compared against all previous subsequences!\n", + "\n", + "As we can see, the problem-dependent parameter `split_idx` should be set to a proper value by the user." + ] + }, + { + "cell_type": "markdown", + "id": "ce3102c1", + "metadata": {}, + "source": [ + "# Getting Started" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c9564cff", + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "import time\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import stumpy\n", + "\n", + "from numba import njit\n", + "from stumpy import core\n", + "from scipy.io import loadmat" + ] + }, + { + "cell_type": "markdown", + "id": "ecad47df", + "metadata": {}, + "source": [ + "## Naive approach" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "409dad09", + "metadata": {}, + "outputs": [], + "source": [ + "def naive_DAMP(T, m, split_idx):\n", + " \"\"\"\n", + " Compute the top-1 discord in `T`, where the subsequence discord resides in T[split_index:]\n", + " \n", + " Parameters\n", + " ----------\n", + " T : numpy.ndarray\n", + " A time series for which the top discord will be computed.\n", + " \n", + " m : int\n", + " Window size\n", + " \n", + " split_idx : int\n", + " The split index between train and test. See note below for further details.\n", + " \n", + " Returns\n", + " -------\n", + " PL : numpy.ndarry\n", + " The (exact) left matrix profile. All infinite distances are ingored when computing\n", + " the discord.\n", + " \n", + " discord_dist : float\n", + " The discord's distance, which is the distance between the top discord and its\n", + " left nearest neighbor\n", + " \n", + " discord_idx : int\n", + " The start index of the top discord\n", + " \n", + " \"\"\"\n", + " mp = stumpy.stump(T, m)\n", + " IL = mp[:, 2].astype(np.int64)\n", + " PL = core._idx_to_mp(IL, T, m, check_neg=False)\n", + " \n", + " # ignore the values upto `split_idx`\n", + " PL[:split_idx] = np.inf\n", + " \n", + " discord_idx = np.where(np.isinf(PL), np.NINF, PL).argmax()\n", + " discord_dist = PL[discord_idx]\n", + " if discord_dist == np.inf:\n", + " discord_idx = -1\n", + " \n", + " return PL, discord_dist, discord_idx" + ] + }, + { + "cell_type": "markdown", + "id": "505d4586", + "metadata": {}, + "source": [ + "## DAMP" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c21c4587", + "metadata": {}, + "outputs": [], + "source": [ + "def next_pow2(val):\n", + " \"\"\"\n", + " Compute the smallest \"power of two\" number that is greater than/ equal to `val`\n", + " \n", + " Parameters\n", + " ----------\n", + " val : float\n", + " A real positive value\n", + " \n", + " Returns\n", + " -------\n", + " out : int\n", + " An integer value that is power of two, and satisfies `out >= v`\n", + " \"\"\"\n", + " return int(math.pow(2, math.ceil(math.log2(val))))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "dd1f7b3d", + "metadata": {}, + "outputs": [], + "source": [ + "def naive_get_damp_range(stop, m, start=0):\n", + " \"\"\"\n", + " naive version of the function `get_damp_range`\n", + " \"\"\"\n", + " out = []\n", + " \n", + " chunksize = next_pow2(m)\n", + " chunk_stop = stop\n", + " while start < chunk_stop:\n", + " chunk_start = max(chunk_stop - chunksize, start)\n", + " out.append([chunk_start, chunk_stop])\n", + " \n", + " if chunk_start == start:\n", + " break\n", + " \n", + " chunk_stop = chunk_start + m - 1\n", + " chunksize = 2 * chunksize\n", + " \n", + " return np.array(out)\n", + "\n", + "\n", + "def get_damp_range(stop, m, start=0):\n", + " \"\"\"\n", + " For the given range `(start, stop)`, segment it into chunks,\n", + " each with a length of power of two. Each chunk is preceded \n", + " by one that is twice in size. The last chunk (i.e. the left-most \n", + " chunk) starts at index `start`.\n", + " \n", + " Parameters\n", + " ----------\n", + " stop : int\n", + " The stop index\n", + " \n", + " m : int\n", + " Window size\n", + " \n", + " start : int, default 0\n", + " The start index\n", + " \n", + " Returns\n", + " -------\n", + " out : numpy.ndarray\n", + " A 2D numpy array, where the i-th row shows the \n", + " range (start, stop) of the i-th chunk. out[0, 1]\n", + " must be `stop` and out[-1, 0] must be `start`. \n", + " \"\"\"\n", + " out = []\n", + " \n", + " n_lower = math.ceil(math.log2(next_pow2(m)))\n", + " n_upper = math.ceil(math.log2(stop + 2**n_lower)) + 1\n", + " chunk_stop = stop\n", + " \n", + " for chunk_size in np.power(2, range(n_lower, n_upper)):\n", + " chunk_start = chunk_stop - chunk_size\n", + " out.append([max(chunk_start, start), chunk_stop])\n", + " chunk_stop = chunk_start + m - 1\n", + " \n", + " if chunk_start <= start:\n", + " break\n", + " \n", + " return np.array(out)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5e45d72c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 92, 100],\n", + " [ 80, 96],\n", + " [ 52, 84],\n", + " [ 0, 56]])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Example\n", + "chunks = get_damp_range(100, m=5)\n", + "chunks" + ] + }, + { + "cell_type": "markdown", + "id": "e4bfcc2f", + "metadata": {}, + "source": [ + "The lengths of chunks are: 8, 16, 32, and 56 (not 64 as the chunk must be truncated at `start=0`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "79663447", + "metadata": {}, + "outputs": [], + "source": [ + "@njit(fastmath={\"nnan\", \"nsz\", \"arcp\", \"contract\", \"afn\", \"reassoc\"})\n", + "def _backward_process(\n", + " T, \n", + " m, \n", + " query_idx, \n", + " excl_zone,\n", + " M_T, \n", + " Σ_T, \n", + " T_subseq_isconstant, \n", + " chunks_range,\n", + " bsf,\n", + "):\n", + " \"\"\"\n", + " Compute the (approximate) distance between the subsequence `T[query_idx:query_idx+m]`\n", + " and its nearest neighbor, and update the best-so-far discord distance. \n", + " \n", + " Parameters\n", + " ----------\n", + " T : numpy.ndarray\n", + " A time series\n", + " \n", + " m : int\n", + " Window size\n", + " \n", + " query_idx : int\n", + " The start index of the query with length `m`, i.e. `T[query_idx:query_idx+m]`\n", + " \n", + " excl_zone : int\n", + " Size of the exclusion zone\n", + " \n", + " M_T : np.ndarray\n", + " The sliding mean of `T`\n", + " \n", + " Σ_T : np.ndarray\n", + " The sliding standard deviation of `T`\n", + " \n", + " T_subseq_isconstant : numpy.ndarray\n", + " A numpy boolean array whose i-th element indicates whether the subsequence\n", + " `T[i : i+m]` is constant (True)\n", + " \n", + " chunks_range : numpy.ndarray\n", + " A 2D numpy array consisting of rows, each represents\n", + " the (start, stop) range of a chunk\n", + " \n", + " bsf : float\n", + " The best-so-far discord distance\n", + " \n", + " Returns\n", + " -------\n", + " nn_distance : float\n", + " The (approximate) left matrix profile value that corresponds to \n", + " the query, `T[query_idx : query_idx + m]`.\n", + " \n", + " bsf : float\n", + " The best-so-far discord distance \n", + " \"\"\"\n", + " nn_distance = np.inf\n", + " for (start, stop) in chunks_range:\n", + " QT = core._sliding_dot_product(\n", + " T[query_idx : query_idx + m], \n", + " T[start : stop],\n", + " )\n", + " D = core._mass(\n", + " T[query_idx : query_idx + m],\n", + " T[start : stop],\n", + " QT=QT,\n", + " μ_Q=M_T[query_idx],\n", + " σ_Q=Σ_T[query_idx],\n", + " M_T=M_T[start : stop - m + 1],\n", + " Σ_T=Σ_T[start : stop - m + 1],\n", + " Q_subseq_isconstant=T_subseq_isconstant[query_idx],\n", + " T_subseq_isconstant=T_subseq_isconstant[start : stop - m + 1],\n", + " )\n", + " \n", + " nn_distance = min(nn_distance, np.min(D))\n", + " if nn_distance < bsf:\n", + " break\n", + " \n", + " bsf = max(bsf, nn_distance)\n", + " \n", + " return nn_distance, bsf" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c76317b5", + "metadata": {}, + "outputs": [], + "source": [ + "@njit(fastmath={\"nnan\", \"nsz\", \"arcp\", \"contract\", \"afn\", \"reassoc\"})\n", + "def _foreward_process(\n", + " T, \n", + " m, \n", + " excl_zone,\n", + " M_T, \n", + " Σ_T, \n", + " T_subseq_isconstant, \n", + " query_idx,\n", + " lookahead,\n", + " PL,\n", + "):\n", + " \"\"\"\n", + " Update the (approximate) left matrix profile `PL` for a chunk of forthcoming subsequences\n", + " by computing their distance to their left neighbor T[query_idx : query_idx+m]\n", + " \n", + " Paramaters\n", + " ----------\n", + " T : numpy.ndarray\n", + " A time series\n", + " \n", + " m : int\n", + " Window size\n", + " \n", + " excl_zone : int\n", + " Size of the exclusion zone\n", + " \n", + " M_T : numpy.ndarray\n", + " The sliding mean\n", + " \n", + " Σ_T : numpy.ndarray\n", + " The sliding standard deviation\n", + " \n", + " T_subseq_isconstant : numpy.ndarray\n", + " A numpy boolean array whose i-th element indicates whether the subsequence\n", + " `T[i : i+m]` is constant (True)\n", + " \n", + " query_idx : int\n", + " The start index of the query with length `m`, i.e. `T[query_idx:query_idx+m]`\n", + " \n", + " lookahead : int\n", + " The size of chunk whose subsequences are compared with the query.\n", + " \n", + " PL : numpy.ndarray\n", + " A 1D numpy array that contains the (approximate) values of\n", + " the left matrix profile.\n", + " \n", + " Returns\n", + " -------\n", + " None\n", + " \"\"\" \n", + " start = query_idx + excl_zone + 1\n", + " stop = min(start + lookahead, len(T))\n", + " \n", + " if stop - start >= m:\n", + " QT = core._sliding_dot_product(T[query_idx : query_idx + m], T[start : stop])\n", + " D = core._mass(\n", + " T[query_idx : query_idx + m],\n", + " T[start : stop],\n", + " QT=QT,\n", + " μ_Q=M_T[query_idx],\n", + " σ_Q=Σ_T[query_idx],\n", + " M_T=M_T[start : stop - m + 1],\n", + " Σ_T=Σ_T[start : stop - m + 1],\n", + " Q_subseq_isconstant=T_subseq_isconstant[query_idx],\n", + " T_subseq_isconstant=T_subseq_isconstant[start : stop - m + 1],\n", + " )\n", + " \n", + " PL[start : stop - m + 1] = np.minimum(PL[start : stop - m + 1], D)\n", + "\n", + " return" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "769bddad", + "metadata": {}, + "outputs": [], + "source": [ + "def DAMP(T, m, split_idx): \n", + " \"\"\"\n", + " Compute the approximate left matrix profile for `T`. The global maximum\n", + " of this approximate profile is exact.\n", + " \n", + " Parameters\n", + " ----------\n", + " T : numpy.ndarray\n", + " A time series of interest\n", + " \n", + " m : int\n", + " Window size\n", + " \n", + " split_idx : int\n", + " The location of split point between train and test. The data `T[:split_idx]`\n", + " is considered as the train set and the remaining, i.e. `T[split_idx:]` is test.\n", + " \n", + " Returns\n", + " -------\n", + " PL : numpy.ndarry\n", + " The (approximate) left matrix profile. The finite global maximum of `PL` is exact,\n", + " and its corresponding index indicates the location of discord.\n", + " \n", + " discord_dist : float\n", + " The distance between the discord and its nearest neighbor.\n", + " \n", + " discord_idx : int\n", + " The start index of discord.\n", + " \"\"\"\n", + " T, M_T, Σ_T, T_subseq_isconstant = core.preprocess(T, m)\n", + " \n", + " l = len(T) - m + 1\n", + " PL = np.full(l, np.inf, dtype=np.float64) \n", + " \n", + " excl_zone = int(math.ceil(m / stumpy.core.config.STUMPY_EXCL_ZONE_DENOM))\n", + " chunks_range = get_damp_range(split_idx - excl_zone + m, m, start=0)\n", + " last_chunk_size = chunks_range[-1, 1] - chunks_range[-1, 0]\n", + " last_chunk_size_cutoff = next_pow2(last_chunk_size)\n", + " \n", + " bsf = np.NINF \n", + " lookahead = next_pow2(m)\n", + " for i in range(split_idx, l):\n", + " if PL[i] >= bsf: \n", + " PL[i], bsf = _backward_process(\n", + " T, \n", + " m, \n", + " i, \n", + " excl_zone, \n", + " M_T, \n", + " Σ_T, \n", + " T_subseq_isconstant, \n", + " chunks_range,\n", + " bsf,\n", + " )\n", + " _foreward_process(T, m, excl_zone, M_T, Σ_T, T_subseq_isconstant, i, lookahead, PL)\n", + " \n", + " chunks_range[:] = chunks_range + 1 \n", + " if last_chunk_size < last_chunk_size_cutoff:\n", + " chunks_range[-1, 0] = 0 \n", + " last_chunk_size += 1\n", + " else:\n", + " chunks_range = np.append(chunks_range, np.array([[0, m]]), axis=0)\n", + " last_chunk_size = m\n", + " last_chunk_size_cutoff = 2 * last_chunk_size_cutoff\n", + " \n", + " discord_idx = np.where(np.isinf(PL), np.NINF, PL).argmax()\n", + " discord_dist = PL[discord_idx]\n", + " if discord_dist == np.inf:\n", + " discord_idx = -1\n", + " \n", + " return PL, discord_dist, discord_idx" + ] + }, + { + "cell_type": "markdown", + "id": "f67cd2b9", + "metadata": {}, + "source": [ + "## Example 1" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "2cfbc771", + "metadata": {}, + "outputs": [], + "source": [ + "seed = 100\n", + "np.random.seed(seed)\n", + "\n", + "T = np.random.rand(100000)\n", + "m = 50\n", + "split_index = 200" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "a935f31f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "discord_dist: 8.500883427933504\n", + "discord_index: 209\n", + "running time [sec]: 4.886792898178101\n" + ] + } + ], + "source": [ + "naive_DAMP(T, m, split_index) # dummy run\n", + "t_start = time.time()\n", + "excl_zone_denom = core.config.STUMPY_EXCL_ZONE_DENOM\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = 1.0\n", + "PL, discord_dist, discord_index = naive_DAMP(T, m, split_index)\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = excl_zone_denom\n", + "t_end = time.time()\n", + "\n", + "print('discord_dist: ', discord_dist)\n", + "print('discord_index: ', discord_index)\n", + "print('running time [sec]: ', t_end - t_start)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "2b461592", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "discord_dist: 8.500883427933504\n", + "discord_index: 209\n", + "running time [sec]: 0.4022231101989746\n" + ] + } + ], + "source": [ + "DAMP(T, m, split_index) # dummpy run\n", + "\n", + "t_start = time.time()\n", + "excl_zone_denom = core.config.STUMPY_EXCL_ZONE_DENOM\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = 1\n", + "PL, discord_score, discord_idx = DAMP(T, m, split_index)\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = excl_zone_denom\n", + "t_end = time.time()\n", + "\n", + "print('discord_dist: ', discord_score)\n", + "print('discord_index: ', discord_idx)\n", + "print('running time [sec]: ', t_end - t_start)" + ] + }, + { + "cell_type": "markdown", + "id": "d5fccfb3", + "metadata": {}, + "source": [ + "## Example 2" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "d8e9fa63", + "metadata": {}, + "outputs": [], + "source": [ + "seed = 100\n", + "np.random.seed(seed)\n", + "\n", + "T = np.random.rand(100000)\n", + "m = 50\n", + "split_index = 5000" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "263bca9c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "discord_dist: 7.606279752022369\n", + "discord_index: 8109\n", + "running time [sec]: 5.08968710899353\n" + ] + } + ], + "source": [ + "naive_DAMP(T, m, split_index) # dummpy run\n", + "\n", + "t_start = time.time()\n", + "excl_zone_denom = core.config.STUMPY_EXCL_ZONE_DENOM\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = 1\n", + "PL, discord_score, discord_idx = naive_DAMP(T, m, split_index)\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = excl_zone_denom\n", + "t_end = time.time()\n", + "\n", + "print('discord_dist: ', discord_score)\n", + "print('discord_index: ', discord_idx)\n", + "print('running time [sec]: ', t_end - t_start)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "78d09bda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "discord_dist: 7.606279752022363\n", + "discord_index: 8109\n", + "running time [sec]: 2.9500508308410645\n" + ] + } + ], + "source": [ + "DAMP(T, m, split_index) # dummpy run\n", + "\n", + "t_start = time.time()\n", + "excl_zone_denom = core.config.STUMPY_EXCL_ZONE_DENOM\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = 1\n", + "PL, discord_score, discord_idx = DAMP(T, m, split_index)\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = excl_zone_denom\n", + "t_end = time.time()\n", + "\n", + "print('discord_dist: ', discord_score)\n", + "print('discord_index: ', discord_idx)\n", + "print('running time [sec]: ', t_end - t_start)" + ] + }, + { + "cell_type": "markdown", + "id": "90688aa9", + "metadata": {}, + "source": [ + "## Example 3" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6d64f488", + "metadata": {}, + "outputs": [], + "source": [ + "seed = 100\n", + "np.random.seed(seed)\n", + "\n", + "T = np.random.rand(100000)\n", + "m = 500\n", + "split_index = 5000" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "159e03dc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "discord_dist: 29.42331306408431\n", + "discord_index: 6110\n", + "running time [sec]: 5.911180019378662\n" + ] + } + ], + "source": [ + "naive_DAMP(T, m, split_index) # dummpy run\n", + "\n", + "t_start = time.time()\n", + "excl_zone_denom = core.config.STUMPY_EXCL_ZONE_DENOM\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = 1\n", + "PL, discord_score, discord_idx = naive_DAMP(T, m, split_index)\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = excl_zone_denom\n", + "t_end = time.time()\n", + "\n", + "print('discord_dist: ', discord_score)\n", + "print('discord_index: ', discord_idx)\n", + "print('running time [sec]: ', t_end - t_start)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "9ef28e10", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "discord_dist: 29.423313064084333\n", + "discord_index: 6110\n", + "running time [sec]: 34.58638381958008\n" + ] + } + ], + "source": [ + "DAMP(T, m, split_index) # dummpy run\n", + "\n", + "t_start = time.time()\n", + "excl_zone_denom = core.config.STUMPY_EXCL_ZONE_DENOM\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = 1\n", + "PL, discord_score, discord_idx = DAMP(T, m, split_index)\n", + "core.config.STUMPY_EXCL_ZONE_DENOM = excl_zone_denom\n", + "t_end = time.time()\n", + "\n", + "print('discord_dist: ', discord_score)\n", + "print('discord_index: ', discord_idx)\n", + "print('running time [sec]: ', t_end - t_start)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ae28748", + "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.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}