semantic-typescript 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +587 -574
- package/dist/asynchronous/collectable.js +10 -4
- package/dist/optional.js +0 -1
- package/dist/readonly-map.d.ts +1 -0
- package/dist/readonly-map.js +26 -0
- package/dist/synchronous/collectable.js +2 -1
- package/package.json +1 -1
- package/readme.cn.md +1271 -1271
- package/readme.de.md +1397 -1397
- package/readme.es.md +1381 -1381
- package/readme.fr.md +1390 -1390
- package/readme.jp.md +1300 -1300
- package/readme.kr.md +1297 -1297
- package/readme.md +1442 -1442
- package/readme.ru.md +1378 -1378
- package/readme.tw.md +1271 -1271
package/readme.jp.md
CHANGED
|
@@ -1,1300 +1,1300 @@
|
|
|
1
|
-
# **Semantic-TypeScript**
|
|
2
|
-
|
|
3
|
-
**Flow, Indexed.** あなたのデータを、正確に制御します。
|
|
4
|
-
|
|
5
|
-
[English](./readme.md) · [简体中文](./readme.cn.md) · [繁體中文](./readme.tw.md) · 日本語 · [한국어](./readme.kr.md) · [Deutsch](./readme.de.md) · [Français](./readme.fr.md) · [Español](./readme.es.md) · [Русский](./readme.ru.md)
|
|
6
|
-
|
|
7
|
-
**JavaScript ジェネレーター**、**Java Stream**、そして**インデックスベースのデータアドレッシング**を統合する、型安全なストリーム処理ライブラリです。各要素は `bigint` インデックスを保持し、すべてのパイプラインは遅延評価され、すべての終端操作は明示的な opt-in です。
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install semantic-typescript
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
- 実行時依存関係ゼロ、純粋な ESM、独自の型定義を同梱。
|
|
14
|
-
- 684 のリグレッションテスト:`npm test`。
|
|
15
|
-
- 変更履歴全体:[CHANGELOG.md](./CHANGELOG.md)。
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Table of Contents
|
|
20
|
-
|
|
21
|
-
1. [設計哲学](#1-design-philosophy)
|
|
22
|
-
2. [アーキテクチャ](#2-architecture)
|
|
23
|
-
3. [クイックスタート](#3-quick-start)
|
|
24
|
-
4. [ファクトリ関数](#4-factory-functions)
|
|
25
|
-
5. [変換オペレーター](#5-transformation-operators)
|
|
26
|
-
6. [コレクタブルと終端操作](#6-collectables-and-terminal-operations)
|
|
27
|
-
7. [統計とウィンドウ](#7-statistics-and-windows)
|
|
28
|
-
8. [インデックスモデル](#8-the-index-model)
|
|
29
|
-
9. [非同期モデル](#9-the-asynchronous-model)
|
|
30
|
-
10. [周期性解析:リング・場・スコア](#10-periodic-analysis-rings-fields-and-scores)
|
|
31
|
-
11. [ユーティリティヘルパー](#11-utility-helpers)
|
|
32
|
-
12. [パフォーマンスと落とし穴](#12-performance-and-pitfalls)
|
|
33
|
-
13. [位相環:正確な周期](#13-phase-rings)
|
|
34
|
-
14. [信号解析:窓・スペクトル・時間](#14-signal-analysis)
|
|
35
|
-
15. [ライセンス](#15-license)
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
<a id="1-design-philosophy"></a>
|
|
40
|
-
## 1. 設計哲学
|
|
41
|
-
|
|
42
|
-
三つのアイデア、ひとつのパイプライン:
|
|
43
|
-
|
|
44
|
-
**インデックスはデータです。** すべての要素は `bigint` によってアドレス指定されます。反転、回転、シャッフル、分割はバッファ操作ではなくインデックス演算です。コレクタブルが要求するまで、何も具体化されません。
|
|
45
|
-
|
|
46
|
-
**遅延がデフォルトです。** ファクトリはジェネレーターをラップし、オペレーターはさらに別のジェネレーターをラップします。終端操作が実行されるまで要素は要求されないため、下流の何かが停止させる限り、無限のソースはまったく合法です。
|
|
47
|
-
|
|
48
|
-
**終端は明示的です。** `map` が返すのは配列ではなく `Semantic` です。値が現れるのは `toUnordered()`、`toOrdered()`、`toNumericStatistics()`、`toWindow()` といったコレクタブルを名指ししたときだけで、それぞれがどれだけのコストを払い、どのような順序を生むのかを明示します。
|
|
49
|
-
|
|
50
|
-
同じ規律はライブラリの数学的な半分にも当てはまります。`Complex`、`Angle`、`Frequency` は構築時に不変条件が検査される不変オブジェクトであり、`Optional` は `null` を置き換え、位相リングは自分が保持する要素数、周期、そしてどれだけ均衡しているかを正確に語ります。
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
<a id="2-architecture"></a>
|
|
55
|
-
## 2. アーキテクチャ
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
factory ──▶ Semantic ──▶ Semantic ──▶ Collectable ──▶ result
|
|
59
|
-
(source) (map) (filter) (terminal)
|
|
60
|
-
▲
|
|
61
|
-
└── every operator returns a new Semantic wrapping a generator
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
| Layer | Module | Responsibility |
|
|
65
|
-
| :--- | :--- | :--- |
|
|
66
|
-
| Sources | `synchronous/collectors`, `asynchronous/collectors` | イテラブル、ジェネレーター、Promise、Blob、イベントターゲットをラップします |
|
|
67
|
-
| Pipelines | `synchronous/semantic`, `asynchronous/semantic` | 遅延オペレーターとインデックスの書き換え |
|
|
68
|
-
| Collectables | `synchronous/semantic`, `asynchronous/semantic` | 順序付き、順序なし、統計、ウィンドウ、およびカスタムの終端 |
|
|
69
|
-
| Collectors | `synchronous/collector`, `asynchronous/collector` | すべての終端がそこから組み立てられる畳み込みプリミティブ |
|
|
70
|
-
| Mathematics | `mathematics/*` | 角度、複素数、周波数、スペクトル、ウィンドウ、推定量、周転円、位相リング |
|
|
71
|
-
| Structures | `structure/*` | リスト、集合、マップ、ツリー、フォレスト、リング、場 |
|
|
72
|
-
| Errors | `structure/error` | 型付き `SemanticError` 階層とそのブランドガード |
|
|
73
|
-
|
|
74
|
-
同期系と非同期系はひとつの語彙を共有します。同じオペレーター名、同じインデックスセマンティクス、同じコレクタブルです。非同期版は単に Promise を返し、書き込みをキューに入れるため、書き込みの後に発行された読み取りは常にその書き込みを観測します。
|
|
75
|
-
|
|
76
|
-
### 2.1 今回のリリースで移ったもの、壊れたもの
|
|
77
|
-
|
|
78
|
-
三つのモジュールが位置または形を変え、いくつかの既定値は意味が変わりました。古い挙動はどれも明示的なオプションから到達できます。置き換えなしに削除されたものはありません。
|
|
79
|
-
|
|
80
|
-
| Change | Before | Now |
|
|
81
|
-
| :--- | :--- | :--- |
|
|
82
|
-
| フォレスト | `structure/tree/rootedForest`、`structure/tree/unionFindForest` | `structure/forest/rootedForest`、`structure/forest/unionFindForest`;`tree/index.ts` は再エクスポートしなくなりました |
|
|
83
|
-
| エラー | 場当たり的なネイティブエラー | `structure/error.ts` の十五クラス階層。各クラスにブランドガード、加えて `SemanticRangeError` 別名 |
|
|
84
|
-
| 推定量オプション | 固定の定義 | `mathematics/quantile.ts` が `QuantileMethod`、`QuantileOptions`、`SkewnessEstimator`、`KurtosisEstimator`、`VarianceOptions` をエクスポート |
|
|
85
|
-
| `quantile` | Hyndman–Fan タイプ 1 | タイプ 7 — R `quantile`、NumPy `linear`、Excel `PERCENTILE.INC` |
|
|
86
|
-
| `skewness` / `kurtosis` | `g1` / `g2` | バイアス補正された `G1` / `G2` |
|
|
87
|
-
| `variance` / `standardDeviation` | `ddof = 0` | `ddof = 1`、`Optional` でラップ |
|
|
88
|
-
| `decibel` | 単一の `20·log10` 関数 | 削除。`amplitudeDecibel`(20·log10)と `powerDecibel`(10·log10)に分割 |
|
|
89
|
-
| `Spectrum.flatness()` | 両半分の振幅 | 片側半分のパワー |
|
|
90
|
-
| `Spectrum.bandEnergy()` | 生の両側和 | 既定は片側で、共役な半分を折り畳みます |
|
|
91
|
-
| `shortTimeFourierTransform` / `spectrogram` | 対称 Hann、完全なフレームのみ | 周期 Hann、`boundary: "zeros"`、`padded: true` |
|
|
92
|
-
|
|
93
|
-
第 6、7、10 章が置き換えを説明しています。実測された影響は [CHANGELOG.md](./CHANGELOG.md) にあります。
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
<a id="3-quick-start"></a>
|
|
98
|
-
## 3. クイックスタート
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
import {
|
|
102
|
-
useOf, useFrom, useRange, useText, useStringify, usePromise
|
|
103
|
-
} from "semantic-typescript";
|
|
104
|
-
|
|
105
|
-
// Numeric aggregation
|
|
106
|
-
const sum: number = useOf(10, 20, 30, 40)
|
|
107
|
-
.map(n => n * 2)
|
|
108
|
-
.toNumericStatistics()
|
|
109
|
-
.summate(); // 200
|
|
110
|
-
|
|
111
|
-
// BigInt aggregation
|
|
112
|
-
const bigSum: bigint = useOf(10n, 20n, 30n, 40n)
|
|
113
|
-
.map(n => n * 2n)
|
|
114
|
-
.toBigintStatistics()
|
|
115
|
-
.summate(); // 200n
|
|
116
|
-
|
|
117
|
-
// Reordering is index arithmetic — collect with toOrdered() for it to take effect
|
|
118
|
-
const reversed: number[] = useFrom([1, 2, 3, 4, 5]).reverse().toOrdered().toArray(); // [5,4,3,2,1]
|
|
119
|
-
const rotated: number[] = useFrom([1, 2, 3, 4, 5]).translate(2).toOrdered().toArray(); // [4,5,1,2,3]
|
|
120
|
-
|
|
121
|
-
// Lazy evaluation over a large range
|
|
122
|
-
const multiples: bigint[] = useRange(0n, 1_000_000n)
|
|
123
|
-
.filter(n => n % 17n === 0n)
|
|
124
|
-
.limit(10n)
|
|
125
|
-
.toUnordered()
|
|
126
|
-
.toArray(); // [0n,17n,…,153n]
|
|
127
|
-
|
|
128
|
-
// Unicode-safe string processing (code points, not UTF-16 units)
|
|
129
|
-
useText("semantic 👍").toUnordered().forEach(character => console.log(character));
|
|
130
|
-
|
|
131
|
-
// Circular-safe serialisation
|
|
132
|
-
const cyclic: any = { a: 1 }; cyclic.self = cyclic;
|
|
133
|
-
useStringify(cyclic); // '{"a":1}'
|
|
134
|
-
|
|
135
|
-
// Terminals hand the elements to real structures, not only to arrays
|
|
136
|
-
useOf(3, 1, 4, 1, 5).toCollectable().toOrderedSet().toArray(); // [1,3,4,5]
|
|
137
|
-
useFrom([3, 1, 4, 1, 5]).toCollectable().toBinarySearchTree().size(); // 5n
|
|
138
|
-
|
|
139
|
-
// Async sources
|
|
140
|
-
const total = await usePromise(Promise.resolve(7)).toNumericStatistics().summate(); // 7
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
<a id="4-factory-functions"></a>
|
|
146
|
-
## 4. ファクトリ関数
|
|
147
|
-
|
|
148
|
-
構築は常に O(1) です — ファクトリはジェネレーターをラップするだけです。以下の表は `n` 個の要素を*消費*するコストと、ソース自身が必要とする追加の空間を示します。
|
|
149
|
-
|
|
150
|
-
| Factory | Signature (result) | Time (consume n) | Space (source) | Notes |
|
|
151
|
-
| :--- | :--- | :--- | :--- | :--- |
|
|
152
|
-
| `useOf` | `useOf<E>(...elements) → SynchronousSemantic<E>` | O(n) | O(n) | インデックスは 0..n−1;スプレッド引数はすでに具体化されています |
|
|
153
|
-
| `useFrom` | `useFrom<E>(Iterable<E>) → SynchronousSemantic<E>`<br>`useFrom<E>(AsyncIterable<E>) → AsynchronousSemantic<E>` | O(n) | O(1) | 任意のイテラブルをラップ;収集時に一度だけ反復します |
|
|
154
|
-
| `useRange` | `useRange<N>(start, end, step?) → SynchronousSemantic<N>` | O(n) | O(1) | `end` は排他的;`step` は非ゼロでなければならず、`number` と `bigint` の両方が使えます |
|
|
155
|
-
| `useFill` | `useFill<E>(element \| supplier, count) → SynchronousSemantic<E>` | O(n) | O(1) | サプライヤーは要素ごとに一度だけ呼ばれます |
|
|
156
|
-
| `useEmpty` | `useEmpty<E>() → SynchronousSemantic<E>` | O(1) | O(1) | `concat` の単位元 |
|
|
157
|
-
| `useText` | `useText(text, delimiter?)` | O(n) | テキスト長に対して O(n) | **コードポイント**単位で反復するため、絵文字や CJK も単一要素のままです |
|
|
158
|
-
| `useAttribute` | `useAttribute<T>(object)` | O(v + e) | O(d + v) | 循環保護付きでネストしたオブジェクトを再帰的に走査します |
|
|
159
|
-
| `useGenerate` | `useGenerate<E>(supplier, interrupt)` | O(n) × サプライヤーのコスト | O(1) | 停止する `interrupt` か、下流の `limit` が**必須**です |
|
|
160
|
-
| `useIterate` | `useIterate<E>(Iterable<E>)` / `(AsyncIterable<E>)` | 収集ごとに O(n) | O(1) | 収集のたびにイテレーターを再生します |
|
|
161
|
-
| `useInterval` | `useInterval(period, delay?) → AsynchronousSemantic<number>` | n × period(実時間) | O(1) | 経過時間を発行します |
|
|
162
|
-
| `usePromise` | `usePromise<T>(promise) → AsynchronousSemantic<T>` | O(1) | O(1) | インデックス `0n` の単一要素 |
|
|
163
|
-
| `useBlob` | `useBlob(blob, chunk?) → AsynchronousSemantic<Uint8Array>` | O(blob のサイズ) | コピーごとに O(chunk) | デフォルトのチャンクは 64 KiB |
|
|
164
|
-
| `useSubscription` | `useSubscription(target, subscriber, event) → AsynchronousSemantic<Event>` | イベントごとに 1 要素 | O(1) | `limit()` / `takeWhile()` が購読解除の境界を定義します |
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
useOf(1, 2, 3).toUnordered().toArray(); // [1,2,3]
|
|
168
|
-
useFrom(new Set([1, 2, 2])).toUnordered().toArray(); // [1,2]
|
|
169
|
-
useRange(0, 5).toUnordered().toArray(); // [0,1,2,3,4]
|
|
170
|
-
useRange(10n, 0n, -5n).toUnordered().toArray(); // [10n,5n]
|
|
171
|
-
useFill("x", 3n).toUnordered().toArray(); // ["x","x","x"]
|
|
172
|
-
useText("A😊B").toUnordered().toArray(); // ["A","😊","B"]
|
|
173
|
-
useGenerate(() => 7, (_e, index) => index >= 3n).toUnordered().toArray(); // [7,7,7]
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
<a id="5-transformation-operators"></a>
|
|
179
|
-
## 5. 変換オペレーター
|
|
180
|
-
|
|
181
|
-
すべてのオペレーターは新しい `Semantic` を返します;コレクタブルが実行されるまで、どれも要素に触れません。Time は**要素ごと**の追加コストであり、Space はパイプラインの実行中にオペレーターが保持し続けるものです。
|
|
182
|
-
|
|
183
|
-
| Operator | Purpose | Index effect | Time | Space |
|
|
184
|
-
| :--- | :--- | :--- | :--- | :--- |
|
|
185
|
-
| `map(mapper)` | 1:1 変換 | 転送 | O(1) | O(1) |
|
|
186
|
-
| `filter(predicate)` | 条件に合う要素を残す | 転送 | O(1) | O(1) |
|
|
187
|
-
| `flat(mapper)` / `flatMap(mapper)` | 1:N の平坦化 | 0 から再インデックス | 発行ごとに O(1) | O(1) |
|
|
188
|
-
| `distinct(keyExtractor?)` | 重複キーを捨て、最初のものを残す | 転送 | 平均 O(1) | O(n) |
|
|
189
|
-
| `peek(consumer)` | 副作用、要素はそのまま通過 | 転送 | O(1) | O(1) |
|
|
190
|
-
| `limit(count)` | 最初の *n* 個を取る | 0 から再インデックス | O(1) | O(1) |
|
|
191
|
-
| `skip(count)` | 最初の *n* 個を捨てる | 転送 | O(1) | O(1) |
|
|
192
|
-
| `sub(start, end)` | 位置指定スライス `[start, end)` | 転送 | O(1) | O(1) |
|
|
193
|
-
| `takeWhile(predicate)` | 述語が成り立つ間だけ取る | 転送 | O(1) | O(1) |
|
|
194
|
-
| `dropWhile(predicate)` | 述語が成り立つ間だけ捨てる | 0 から再インデックス | O(1) | O(1) |
|
|
195
|
-
| `concat(other)` | `Semantic` またはイテラブルを後ろに連結 | インデックスを継続 | O(1) | O(1) |
|
|
196
|
-
| `redirect(redirector)` | 新しいインデックスを好きなように計算 | **任意** | O(1) | O(1) |
|
|
197
|
-
| `reverse()` | `redirect((_e, i) => -i - 1n)` | 符号反転してシフト | O(1) | O(1) |
|
|
198
|
-
| `translate(offset \| mapper)` | すべてのインデックスをシフト | `i + offset` | O(1) | O(1) |
|
|
199
|
-
| `shuffle(mapper?)` | インデックスをランダム化 | ランダム | O(1) | O(1) |
|
|
200
|
-
| `pipe(conversion)` | ジェネレーターを独自関数に渡す | 任意 | 任意 | 任意 |
|
|
201
|
-
| `semantic()` | コレクタブルを `Semantic` に戻す | 変更なし | O(1) | O(1) |
|
|
202
|
-
|
|
203
|
-
**`reverse`、`translate`、`shuffle` はインデックスを書き換えるだけです。** `toOrdered()` が要素数を法としてインデックスを正規化するまで、何もバッファリングされず、何も回転しません:
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
useFrom([1, 2, 3, 4, 5]).reverse().toOrdered().toArray(); // [5,4,3,2,1]
|
|
207
|
-
useFrom([1, 2, 3, 4, 5]).translate(2).toOrdered().toArray(); // [4,5,1,2,3] (rotation)
|
|
208
|
-
useFrom([1, 2, 3, 4, 5]).translate(7).toOrdered().toArray(); // [4,5,1,2,3] (7 ≡ 2 mod 5)
|
|
209
|
-
useFrom([1, 2, 3]).shuffle((_e, i) => -i - 1n).toOrdered().toArray(); // [3,2,1]
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
<a id="6-collectables-and-terminal-operations"></a>
|
|
215
|
-
## 6. コレクタブルと終端操作
|
|
216
|
-
|
|
217
|
-
### 6.1 コレクタブル
|
|
218
|
-
|
|
219
|
-
コレクタブルは**順序**と**インデックスの簿記**の両方を担います。`n` は要素数です。
|
|
220
|
-
|
|
221
|
-
| Collectable | Structure | Time | Order | Use when |
|
|
222
|
-
| :--- | :--- | :--- | :--- | :--- |
|
|
223
|
-
| `toUnordered()` | インデックスをキーとする `Map<bigint, E>` | O(n) | 挿入順 | 最大のスループット;生のインデックスはそのまま保持されます |
|
|
224
|
-
| `toOrdered()` | 要素インデックスでソートされた配列 | O(n log n) | 正規化されたインデックス順 | 反転や回転を反映させたいとき |
|
|
225
|
-
| `toOrdered(comparator)` | 独自の比較器でソート | O(n log n) | 独自の比較器順 | ソート済みのビュー |
|
|
226
|
-
| `toNumericStatistics()` | 値でソートされたバッファ | O(n log n) | 値順 | `number` に対する統計 |
|
|
227
|
-
| `toBigintStatistics()` | 値でソートされたバッファ | O(n log n) | 値順 | `bigint` に対する統計 |
|
|
228
|
-
| `toWindow()` | 値でソートされたバッファ | O(n log n) | 値順 | `slide` / `tumble` |
|
|
229
|
-
| `toCollectable(mapper)` | mapper が返したもの | — | — | カスタムのコレクタブル |
|
|
230
|
-
|
|
231
|
-
### 6.2 終端
|
|
232
|
-
|
|
233
|
-
すべての終端は `Semantic` ではなく**コレクタブル**の上にあります。「終端は明示的である」とは実践的にはそういう意味です。コレクタブルを名指しするまでパイプラインは何も生み出さず、mapper を渡さない `toCollectable()` がもっとも安価な名指しの方法です。
|
|
234
|
-
|
|
235
|
-
| Terminal | Returns | Time | Notes |
|
|
236
|
-
| :--- | :--- | :--- | :--- |
|
|
237
|
-
| `toArray()` / `toSet()` | `Array<E>` / `Set<E>` | O(n) | バッファを排出します;`toSet` は同一性で重複排除します |
|
|
238
|
-
| `toMap(key, value?)` | `Map<K, V>` | 平均 O(n) | 最後の書き込みが優先されます |
|
|
239
|
-
| `count()` / `isEmpty()` | `bigint` / `boolean` | O(1) | バッファがすでに知っています |
|
|
240
|
-
| `forEach(action)` | `void` | O(n) | `action(element, index)` |
|
|
241
|
-
| `join(…)` | `string` | O(n) | `join()`、`join(delimiter)`、`join(prefix, delimiter, suffix)` |
|
|
242
|
-
| `reduce(…)` | `Optional<E>` / `E` / `R` | O(n) | 空のストリーム → 空の `Optional` |
|
|
243
|
-
| `group(classifier)` / `groupBy(key, value)` | `Map<K, E[]>` | 平均 O(n) | — |
|
|
244
|
-
| `partition(count)` | `Array<Array<E>>` | O(n) | インデックスによるラウンドロビン |
|
|
245
|
-
| `partitionBy(classifier)` | `Array<Array<E>>` | O(n) | バケット番号は分類器から決まります |
|
|
246
|
-
| `anyMatch` / `allMatch` / `nonMatch` | `boolean` | 最悪 O(n)、短絡します | — |
|
|
247
|
-
| `findAny` / `findFirst` / `findLast` | `Optional<E>` | O(1)–O(n) | 具体化された順序での位置指定 |
|
|
248
|
-
| `findAt(k)` / `findNegativeAt(k)` | `Optional<E>` | O(k) / O(n−k) | 先頭/末尾から数えます |
|
|
249
|
-
| `findMaximum(cmp?)` / `findMinimum(cmp?)` | `Optional<E>` | O(n) | デフォルトは構造的比較 |
|
|
250
|
-
| `frequency()` | `Map<E, bigint>` | 平均 O(n) | 統計コレクタブルのみ |
|
|
251
|
-
| `write(stream, accumulator?)` | `WritableStream<S>` | O(n) | IO 的な終端 |
|
|
252
|
-
| `collect(collector)` | `R` | O(n) | 上記すべての終端を支える汎用の畳み込み |
|
|
253
|
-
|
|
254
|
-
### 6.3 構造コレクター
|
|
255
|
-
|
|
256
|
-
`toArray`、`toSet`、`toMap` はパイプラインを JavaScript のコンテナで終わらせます。さらに十二の終端は、ライブラリ自身の構造のいずれかでそれを終わらせます。結果は配列がたまたま持つ不変量ではなく、その構造が保証する不変量を携えます。これらはすべてのコレクタブルで利用でき、`toCollectable()` が返す生のコレクタブルも含まれます。
|
|
257
|
-
|
|
258
|
-
| Terminal | Returns | Order | Cost |
|
|
259
|
-
| :--- | :--- | :--- | :--- |
|
|
260
|
-
| `toArray()` | `Array<E>` | 走査順 | O(n) |
|
|
261
|
-
| `toSet()` | `Set<E>` | 挿入順、SameValueZero | 平均 O(n) |
|
|
262
|
-
| `toMap(key, value?)` | `Map<K, V>` | 挿入順;後書きが優先 | 平均 O(n) |
|
|
263
|
-
| `toOrderedSet(comparator?)` | `SynchronousOrderedSet<E>` | 比較器の昇順 | 最悪 O(n²) |
|
|
264
|
-
| `toUnorderedSet()` | `SynchronousUnorderedSet<E>` | 未指定(ハッシュバケット) | 期待 O(n) |
|
|
265
|
-
| `toOrderedMap(key, value?)` | `SynchronousOrderedMap<K, V>` | 挿入順;後書きが優先 | 期待 O(n) |
|
|
266
|
-
| `toUnorderedMap(key, value?)` | `SynchronousUnorderedMap<K, V>` | 未指定 | 期待 O(n) |
|
|
267
|
-
| `toList()` | `SynchronousList<E>` | 走査順、重複を保持 | O(n) |
|
|
268
|
-
| `toRing(span?)` | `SynchronousRing<E>` | 走査順がリング上の位置を決めます | O(n) |
|
|
269
|
-
| `toLRUSet(capacity)` | `SynchronousUnorderedLRUSet<E>` | 最近使用順;最後の `capacity` 個だけが残ります | 最悪 O(n²) |
|
|
270
|
-
| `toLRUMap(capacity, key, value?)` | `SynchronousUnorderedLRUMap<K, V>` | 最近使用順 | 期待 O(n) |
|
|
271
|
-
| `toSkipSet(comparator?)` | `SynchronousSkipSet<E>` | 比較器の昇順、ナビゲート可能 | 最悪 O(n²) |
|
|
272
|
-
| `toSkipMap(key, value?)` | `SynchronousSkipMap<K, V>` | キー比較器順、ナビゲート可能 | 期待 O(n) |
|
|
273
|
-
| `toBinarySearchTree(comparator?)` | `SynchronousBinarySearchTree<E>` | 比較器の昇順、高さ平衡 | O(n log n) |
|
|
274
|
-
| `toRootedForest()` | `SynchronousRootedForest<E>` | 走査順がそのまま構造順になります | O(n) |
|
|
275
|
-
|
|
276
|
-
結果の型はコレクタブル自身の要素型に従うため、どの終端も型引数を取りません。`SynchronousCollectable<E>` は `Array<E>`、`SynchronousList<E>`、`SynchronousOrderedSet<E>` をそのまま返します。残るのは業務上の引数だけです — `toOrderedSet` / `toSkipSet` / `toBinarySearchTree` の比較器、LRU ペアの容量、`toRing` のスパン、そしてマップ族のキーと値の抽出器です。
|
|
277
|
-
|
|
278
|
-
リングは集合なので、重複した要素は最初の位置だけを保ちます。ほかの構造は、自身の契約が別段に定めない限り重複を保持します(`toOrderedSet`、`toUnorderedSet`、`toSkipSet` は集合であり、`toBinarySearchTree` と `toList` はそうではありません)。
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
const numbers = useOf(3, 1, 4, 1, 5).toCollectable();
|
|
282
|
-
|
|
283
|
-
numbers.toOrderedSet().toArray(); // [1,3,4,5]
|
|
284
|
-
numbers.toUnorderedSet().size(); // 4n — the duplicate 1 collapses
|
|
285
|
-
numbers.toList().toArray(); // [3,1,4,1,5]
|
|
286
|
-
numbers.toSkipSet().first().get(); // 1 — navigable by comparator
|
|
287
|
-
numbers.toBinarySearchTree().size(); // 5n — duplicates retained
|
|
288
|
-
numbers.toRootedForest().getRoots().length; // 1
|
|
289
|
-
useOf(1, 2, 3).toCollectable().toRing().phaseAt(3n).get().toDegreeValue(); // 0
|
|
290
|
-
useOf(3, 1, 3).toCollectable().toOrderedMap(n => n, n => `v${n}`).get(3).get(); // "v3"
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
非同期のコレクタブルも同じ終端を持ち、それぞれを promise に解決します。したがって呼び出しの形は `await` が一つ増えるだけです。
|
|
294
|
-
|
|
295
|
-
---
|
|
296
|
-
|
|
297
|
-
<a id="7-statistics-and-windows"></a>
|
|
298
|
-
## 7. 統計とウィンドウ
|
|
299
|
-
|
|
300
|
-
### 7.1 統計のサーフェス
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
useOf(3, 1, 4, 1, 5).toNumericStatistics().summate(); // 14
|
|
304
|
-
useOf(3, 1, 4, 1, 5).toNumericStatistics().average(); // 2.8
|
|
305
|
-
useOf(3, 1, 4, 1, 5).toNumericStatistics().median(); // 3
|
|
306
|
-
useOf(3, 1, 4, 1, 5).toNumericStatistics().mode(); // 1
|
|
307
|
-
useOf(3, 1, 4, 1, 5).toNumericStatistics().variance().get(); // 3.2 (n − 1)
|
|
308
|
-
useOf(3, 1, 4, 1, 5).toNumericStatistics().standardDeviation().get(); // 1.7889 (n − 1)
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
両方の統計ファミリーは同じサーフェスを公開します — `summate`、`average`、`median`、`mode`、`variance`、`standardDeviation`、`frequency`、`findMaximum`、`findMinimum` — そして `toBigintStatistics()` では `bigint` 演算になります。散らばりの尺度と分位点ファミリーは `Optional` を返します。標本が小さすぎて定義できないことがあるからです。`.get()`、`.orElse(…)`、`.isPresent()` で取り出してください。
|
|
312
|
-
|
|
313
|
-
### 7.2 推定量と既定値
|
|
314
|
-
|
|
315
|
-
既定値は R、NumPy、pandas、Excel が使う定義そのものです。したがってここで算出した値は、補正係数なしでそれらのツールと比較できます。
|
|
316
|
-
|
|
317
|
-
| 統計量 | 既定 | 以前 | 古い値を取り出すには |
|
|
318
|
-
| :--- | :--- | :--- | :--- |
|
|
319
|
-
| `quantile(probability)` | Hyndman–Fan タイプ 7 | タイプ 1 | `{ method: "type-1" }` |
|
|
320
|
-
| `interquartileRange()` | Hyndman–Fan タイプ 7 | タイプ 1 | `{ method: "type-1" }` |
|
|
321
|
-
| `skewness()` | バイアス補正された `G1` | `g1` | `{ estimator: "g1" }` |
|
|
322
|
-
| `kurtosis()` | バイアス補正された超過 `G2` | `g2` | `{ estimator: "g2" }` |
|
|
323
|
-
| `variance()` | `ddof = 1` | `ddof = 0` | `{ ddof: 0 }` |
|
|
324
|
-
| `standardDeviation()` | `ddof = 1` | `ddof = 0` | `{ ddof: 0 }` |
|
|
325
|
-
|
|
326
|
-
九つの Hyndman–Fan タイプはすべて `"type-1"` … `"type-9"` として利用でき、`{ excess: false }` は峰度に三を足し戻して非超過の四次標準化モーメントを報告します。オプション型 — `QuantileMethod`、`QuantileOptions`、`SkewnessEstimator`、`KurtosisEstimator`、`VarianceOptions` — は `mathematics/quantile.ts` からエクスポートされます。
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
const sample = useOf(1, 2, 3, 4).toNumericStatistics();
|
|
330
|
-
|
|
331
|
-
sample.variance().get(); // 1.6667 (5/3, ddof = 1)
|
|
332
|
-
sample.variance({ ddof: 0 }).get(); // 1.25
|
|
333
|
-
sample.standardDeviation().get(); // 1.2910
|
|
334
|
-
sample.skewness().get(); // 0
|
|
335
|
-
sample.kurtosis().get(); // -1.2 (G2)
|
|
336
|
-
sample.kurtosis({ estimator: "g2" }).get(); // -1.36
|
|
337
|
-
sample.kurtosis({ excess: false }).get(); // 1.8
|
|
338
|
-
|
|
339
|
-
const quartiles = useOf(1, 2, 3, 4, 5, 6, 7, 8).toNumericStatistics();
|
|
340
|
-
quartiles.quantile(0.75).get(); // 6.25 (type 7)
|
|
341
|
-
quartiles.quantile(0.75, { method: "type-1" }).get(); // 6 (nearest rank)
|
|
342
|
-
quartiles.interquartileRange().get(); // 3.5
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
`G1` と `G2` はそれぞれ観測が三つ、四つ未満だと未定義になり、`ddof = 1` の標本分散と標準偏差は二つ未満で未定義になります。その場合ライブラリは誤解を招くゼロではなく、空の `Optional` を返します。
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
useOf(1, 2, 3).toNumericStatistics().kurtosis().isEmpty(); // true (G2 needs four observations)
|
|
349
|
-
useOf(1, 2).toNumericStatistics().skewness().isEmpty(); // true (G1 needs three)
|
|
350
|
-
useOf(1).toNumericStatistics().variance().isEmpty(); // true (ddof = 1 needs two)
|
|
351
|
-
useOf(1).toNumericStatistics().variance({ ddof: 0 }).get(); // 0
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
bigint ビューも同じオプションを受け取り、すべての結果を整数の `bigint` に丸め戻します。
|
|
355
|
-
|
|
356
|
-
### 7.3 ウィンドウ
|
|
357
|
-
|
|
358
|
-
ウィンドウはバッファ内の要素を「パイプラインのパイプライン」として再公開します。そのため、ウィンドウを配列に具体化せずに消費できます。どちらの形式も最後の部分的なウィンドウを含みます。
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
useFrom([1, 2, 3, 4]).toWindow().slide(2n, 1n).toUnordered().toArray()
|
|
362
|
-
.map(window => window.toUnordered().toArray()); // [[1,2],[2,3],[3,4],[4]]
|
|
363
|
-
|
|
364
|
-
useFrom([1, 2, 3, 4]).toWindow().tumble(2n).toUnordered().toArray()
|
|
365
|
-
.map(window => window.toUnordered().toArray()); // [[1,2],[3,4]]
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
<a id="8-the-index-model"></a>
|
|
371
|
-
## 8. インデックスモデル
|
|
372
|
-
|
|
373
|
-
インデックスはすべての要素に付随する `bigint` です。オペレーターはそれを**転送**するか、ゼロから**再インデックス**するか、あなたに**計算**させます。
|
|
374
|
-
|
|
375
|
-
順序付き収集は各インデックスを要素数で剰余正規化してから、その結果でソートします。5 要素に対する `translate(7)` が `translate(2)` と同じになるのはこのためです:`7 ≡ 2 (mod 5)`。
|
|
376
|
-
|
|
377
|
-
順序なし収集はパイプラインが生成したインデックスをそのまま保ち、要素を到着順に返します。負のインデックスは合法であり、正規化されれば非負のインデックスより前に並ぶだけです。
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
|
-
<a id="9-the-asynchronous-model"></a>
|
|
382
|
-
## 9. 非同期モデル
|
|
383
|
-
|
|
384
|
-
`AsynchronousSemantic` は同期版のサーフェスを写し取り、Promise を返します。書き込みはキューに入るため、書き込みの後に発行された読み取りは常にそれを観測します。
|
|
385
|
-
|
|
386
|
-
```typescript
|
|
387
|
-
const ring = new AsynchronousRing([1, 2]);
|
|
388
|
-
const added = ring.add(3); // queued
|
|
389
|
-
const sampled = ring.elementAt(2n); // observes the write above
|
|
390
|
-
await added;
|
|
391
|
-
(await sampled).get().element; // 3
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
本質的に非同期なファクトリ — `usePromise`、`useBlob`、`useInterval`、`useSubscription` — は直接 `AsynchronousSemantic` を返し、`useFrom` / `useIterate` は `AsyncIterable` を渡されると自分自身をアップグレードします。
|
|
395
|
-
|
|
396
|
-
---
|
|
397
|
-
|
|
398
|
-
<a id="10-periodic-analysis-rings-fields-and-scores"></a>
|
|
399
|
-
## 10. 周期性解析:リング・場・スコア
|
|
400
|
-
|
|
401
|
-
ライブラリの数学的な半分は、周期(cycle)をルックアップテーブルではなく第一級のオブジェクトとして扱います。三つの構成物が協調して働きます。
|
|
402
|
-
|
|
403
|
-
### 10.1 リングはひとつの整数
|
|
404
|
-
|
|
405
|
-
`Ring` は **n 重リング**(n-fold ring)です:要素数、位置的周期、異なる位相の数は、すべて同じ整数 `n` です。位置の位相はその正確な剰余から計算されるため、リングはビット単位で周期的です:
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
import { SynchronousRing } from "semantic-typescript";
|
|
409
|
-
|
|
410
|
-
const ring = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
411
|
-
ring.phaseAt(0n).get().toDegreeValue(); // 0
|
|
412
|
-
ring.phaseAt(1n).get().toDegreeValue(); // 60
|
|
413
|
-
ring.phaseAt(6n).get().getValue() === ring.phaseAt(0n).get().getValue(); // true, exactly
|
|
414
|
-
ring.purity(); // 1
|
|
415
|
-
ring.isBalanced(); // true
|
|
416
|
-
ring.sequences().map(z => z.magnitude()); // [0, 6, 0, 0, 0, 0]
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
`frequency` は位置間の位相ステップではなく、**環境(ambient)**の回転速度です:環境はすべてのフェーザー(phasor)を同じ角度だけ動かすので、内在的なパターンを壊すことは決してありません。`span` はリングが何回転分を占めるかを固定します。
|
|
420
|
-
|
|
421
|
-
`purity()` は基本モードが運ぶエネルギーの割合であり、`max(|c₁|², |c_{n−1}|²) / (n · Σ|z|²)` です。リングが回転を除いて 1 の `n` 乗根であるとき、かつそのときに限り `1` になり、必要なビンは二つだけなのでコストは `O(n)` です。リングは円をどちら向きにも周りうるため、両方向が検査されます。
|
|
422
|
-
|
|
423
|
-
### 10.2 場は有界のスコア
|
|
424
|
-
|
|
425
|
-
`PhaseField` は相対位相を `[-1, 1]` のスコアに写します:
|
|
426
|
-
|
|
427
|
-
```typescript
|
|
428
|
-
import { useField, useRadian } from "semantic-typescript";
|
|
429
|
-
|
|
430
|
-
const cosine = useField([1]); // cos(2πΔ): +1 aligned, −1 opposed
|
|
431
|
-
const mixed = useField([0.4, 0.3, 0.3]); // first three harmonics
|
|
432
|
-
|
|
433
|
-
cosine.sample(useRadian(0)); // 1
|
|
434
|
-
cosine.sample(useRadian(Math.PI)); // -1
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
スコアは打ち切られた余弦級数 `a₀ + Σ wₘ·cos(2πmΔ)` であり、コンストラクターが `|a₀| + Σ|wₘ| ≤ 1` を強制します。三角不等式により、スコアが `[-1, 1]` を外れることは決してありません — この境界は**構築の帰結**であり、後から適用されるクランプではありません。
|
|
438
|
-
|
|
439
|
-
場がテーブルではなく級数である理由:`n` 点のテーブルは自分のサンプルを正確に再現しますが、その三角補間はサンプル間でオーバーシュートします。スコア `[+1, +0.5, −1, −0.5, 0]` を持つ 5 点のテーブルは `[−1.0428, 1.0041]` に補間され、これだけで約束を破っています。有界の級数ならそうはなりません。
|
|
440
|
-
|
|
441
|
-
### 10.3 解析はデータをすべての要素に対して測る
|
|
442
|
-
|
|
443
|
-
リングの各要素は参照位相です。`analyse` はすべての観測をリング上に位置づけ直し、各参照に対して測定します:
|
|
444
|
-
|
|
445
|
-
```typescript
|
|
446
|
-
const ring = new SynchronousRing(["a", "b", "c", "d", "e"]);
|
|
447
|
-
const report = ring.analyse(["a", "c", "c", "a", "zz", "e"]);
|
|
448
|
-
|
|
449
|
-
report.matched; // 5
|
|
450
|
-
report.missed; // 1 ('zz' is not on the ring)
|
|
451
|
-
report.references.map(r => r.count); // [2, 0, 2, 0, 1]
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
各参照の行は `count`、平均相対位相 `mean`、平均合成ベクトル長(mean resultant length)`resultant` を報告します。覚えておく価値のある同一性がひとつあります:**`resultant` はすべての参照で同じ**です。基準を変えても、すべての相対位相が定数分だけ回転するからです。参照ごとに異なるのは件数と平均です。
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
const ring = new SynchronousRing(["a", "b", "c"]);
|
|
458
|
-
ring.scoresAt(0, cosine); // one score per position, in [-1, 1]
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### 10.4 関連する道具
|
|
462
|
-
|
|
463
|
-
| Call | Answers | Cost |
|
|
464
|
-
| :--- | :--- | :--- |
|
|
465
|
-
| `purity()` | リングはまだ 1 の `n` 乗根ですか? | O(n) |
|
|
466
|
-
| `resultant(t?)` | フェーザーは今どれだけ集中していますか? | O(n) |
|
|
467
|
-
| `analyse(data)` | このデータは各要素に対してどう位置づけられますか? | O(n · m) |
|
|
468
|
-
| `toSpectrum()` | リングをスペクトルとして表現、周波数軸には波数 | O(n²) |
|
|
469
|
-
| `useAngularSpectrum(ring, instants, plan)` | 時間にわたる `(ω, n)` 平面 | O(n² · frames) |
|
|
470
|
-
|
|
471
|
-
さらに二つのプリミティブがモデルを完成させます:`combineRings(a, b)` は二つのリングを最小公倍数の上で結合し(積ではなく引き戻し(pullback)であり、同じ時点を共有できないペアは単に存在しません)、`rate()` は各要素に固有の周波数を与えて、リングが環境に対してドリフトできるようにします。`jetAt`、`windingAt`、`errorIntegralAt` はそのドリフトの微分状態を公開します。
|
|
472
|
-
|
|
473
|
-
### 10.5 スペクトルウィンドウ
|
|
474
|
-
|
|
475
|
-
リングの構成は解析サーフェスの半分です。もう半分はサンプリングされた信号を扱います。もっとも再利用されるプリミティブは窓関数で、ここにあるどの形状にも二つの形式があります。対称形式はフレームを `count − 1` で割り、周期(DFT-even)形式は `count` で割ります。後者は完全な一周期より一つ短いので、DFT が前提とする周期的拡張のもとでテーパーが連続になります。
|
|
476
|
-
|
|
477
|
-
```typescript
|
|
478
|
-
useHannWindow(1, 4); // 0.75 symmetric
|
|
479
|
-
useHannPeriodicWindow(1, 4); // 0.5 periodic
|
|
480
|
-
coherentGain(useHannPeriodicWindow, 16); // 0.5
|
|
481
|
-
equivalentNoiseBandwidth(useHannPeriodicWindow, 16); // 1.5
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
| 形状 | 対称 | 周期 | 備考 |
|
|
485
|
-
| :--- | :--- | :--- | :--- |
|
|
486
|
-
| 矩形 | `useRectangularWindow` | — | テーパーなし;周期の区別なし |
|
|
487
|
-
| Hann | `useHannWindow` | `useHannPeriodicWindow` | 短時間変換の既定 |
|
|
488
|
-
| Hamming | `useHammingWindow` | `useHammingPeriodicWindow` | — |
|
|
489
|
-
| Blackman | `useBlackmanWindow` | `useBlackmanPeriodicWindow` | — |
|
|
490
|
-
| Nuttall | `useNuttallWindow` | `useNuttallPeriodicWindow` | 4項 Blackman–Harris |
|
|
491
|
-
| フラットトップ | `useFlatTopWindow` | `useFlatTopPeriodicWindow` | 分解能より振幅精度 |
|
|
492
|
-
| Kaiser | `useKaiserWindow(beta)` | `useKaiserPeriodicWindow(beta)` | `beta` がメインローブ幅を決めます |
|
|
493
|
-
|
|
494
|
-
解析と重畳加算には周期形式を使ってください。半フレームのホップでは、周期 Hann 窓はどの長さでも和が `1.000000` になりますが、対称形式は `N = 16` で `0.841 dB`、`N = 1024` でも `0.013 dB` 波打ちます。フィルタ設計では対称形式を保ってください。そこでのフレームはテーパーの一周期ではありません。
|
|
495
|
-
|
|
496
|
-
フラットトップ窓はフレームの両端で**負の重み**を返します。フラットトップ窓とはそういうものだからです。ゼロで切り詰めず、そのまま返します。切り詰めるとメインローブが乱れ、この窓の存在理由である低いスキャロップ損失が壊れるからです。
|
|
497
|
-
|
|
498
|
-
```typescript
|
|
499
|
-
useFlatTopWindow(0, 1024); // -0.000421 — negative by definition
|
|
500
|
-
Array.from({ length: 1024 }, (_unused, index) => useFlatTopWindow(index, 1024))
|
|
501
|
-
.filter(weight => weight < 0).length; // 578
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
### 10.6 レベル、平坦度、帯域エネルギー
|
|
505
|
-
|
|
506
|
-
`decibel` は単一の関数としては消えました。振幅とパワーは同じ係数を共有しないからです。`amplitudeDecibel` は電圧のような比に `20·log10` を適用し、`powerDecibel` はパワー、エネルギー、平均二乗、パワースペクトル密度に `10·log10` を適用します。
|
|
507
|
-
|
|
508
|
-
```typescript
|
|
509
|
-
amplitudeDecibel(0.25); // -12.0412
|
|
510
|
-
powerDecibel(0.25); // -6.0206
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
`Bin.decibel` と `Spectrum.decibelOf` は振幅レベルであり、`Spectrum.powerDecibelOf` が対応するパワーレベルです。パワーを振幅形式に与えると `20·log10(power)` を報告し、デシベル値が二倍になります。
|
|
514
|
-
|
|
515
|
-
二つのスペクトル測定は読み方が変わりました。
|
|
516
|
-
|
|
517
|
-
```typescript
|
|
518
|
-
const twoBins = new Spectrum(new Sample(2, 1), [new Complex(2, 0), new Complex(1, 0)]);
|
|
519
|
-
twoBins.flatness(); // 0.8 — geometric over arithmetic mean of power
|
|
520
|
-
|
|
521
|
-
const shape = new Spectrum(new Sample(4, 1), [
|
|
522
|
-
new Complex(1, 0), new Complex(2, 0), new Complex(0, 0), new Complex(0, 0),
|
|
523
|
-
]);
|
|
524
|
-
shape.bandEnergy(new Frequency(0), new Frequency(2)); // 9
|
|
525
|
-
shape.bandEnergy(new Frequency(0), new Frequency(2), "two-sided"); // 5
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
`flatness()` はスペクトルの片側半分について `power()` の幾何平均と算術平均を比較します。両半分の振幅平均ではなく、`oneSided()` の表示専用の振幅二倍も意図的に適用しません。`bandEnergy()` は既定で片側の読み方を使い、共役な半分を折り畳みます。DC とナイキストは一度だけ、内部の各ビンは二度数えられます。軸全体を覆う帯域はこれで Parseval、`Σ|X|² = N·Σx²` を満たします。複素スペクトルには `"two-sided"` を渡してください。そのビンはすでに独立しています。
|
|
529
|
-
|
|
530
|
-
### 10.7 短時間解析
|
|
531
|
-
|
|
532
|
-
`Sampler.shortTimeFourierTransform` と `Sampler.spectrogram` は SciPy `stft` の慣例に従います。周期 Hann 窓、両端のゼロ境界、そして末尾のゼロ詰めです。
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
const sampler = new Sampler(new Sample(64, 1));
|
|
536
|
-
const spectrogram = sampler.spectrogram(time => Math.cos(2 * Math.PI * 4 * time), 16, 8);
|
|
537
|
-
|
|
538
|
-
spectrogram.size(); // 9 frames
|
|
539
|
-
spectrogram.getBoundary(); // "zeros"
|
|
540
|
-
spectrogram.getTime(6); // 0.75 — frame centres, not frame starts
|
|
541
|
-
sampler.spectrogram(time => Math.cos(2 * Math.PI * 4 * time), 16, 8, useHannWindow,
|
|
542
|
-
{ boundary: "none", padded: false }).size(); // 7 — complete frames only
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
両端の拡張は最初のフレームを最初のサンプルに中心合わせし、末尾の詰め物は最後のサンプルを到達可能にします。64 サンプル、フレーム 16、ホップ 8 では 7 ではなく 9 フレームになり、1000 サンプル、フレーム 256、ホップ 128 では 6 ではなく 9 フレームになります。フレーム時刻は開始ではなく中心です — 拡張ありのビューでは `(k·hop + (frameSize mod 2)/2)/rate`、拡張なしのビューでは `(k·hop + frameSize/2)/rate` — そして `toFrame()` は逆変換の前に拡張を切り詰めます。
|
|
546
|
-
|
|
547
|
-
---
|
|
548
|
-
|
|
549
|
-
<a id="11-utility-helpers"></a>
|
|
550
|
-
## 11. ユーティリティヘルパー
|
|
551
|
-
|
|
552
|
-
### 11.1 エラー階層
|
|
553
|
-
|
|
554
|
-
`structure/error.ts` は、パイプラインの**周囲**で送出されるエラー — 自分のコード、カスタムコレクター、アダプターから — のための十五クラス階層をエクスポートします。
|
|
555
|
-
|
|
556
|
-
```text
|
|
557
|
-
SemanticError
|
|
558
|
-
├── IllegalOperationError
|
|
559
|
-
├── ValidationError
|
|
560
|
-
│ ├── IndexError
|
|
561
|
-
│ ├── RangeError (also exported as SemanticRangeError)
|
|
562
|
-
│ └── ConfigurationError
|
|
563
|
-
├── CapacityError
|
|
564
|
-
├── StateError
|
|
565
|
-
│ └── IllegalStateError
|
|
566
|
-
├── ConcurrencyError
|
|
567
|
-
├── CollectionError
|
|
568
|
-
├── SerializationError
|
|
569
|
-
├── NumericError
|
|
570
|
-
│ └── ArithmeticError
|
|
571
|
-
└── PhaseError
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
各クラスは `name` を固定し、ネイティブの `cause` を転送し、任意の `context` 診断バッグを携え、`toJSON()` で直列化します。そして同名のブランドガードを持ちます。
|
|
575
|
-
|
|
576
|
-
```typescript
|
|
577
|
-
const error = new CapacityError("LRU capacity exhausted", { context: { capacity: 0 } });
|
|
578
|
-
|
|
579
|
-
error.name; // "CapacityError"
|
|
580
|
-
isCapacityError(error); // true
|
|
581
|
-
isValidationError(error); // false — a capacity failure is not a validation failure
|
|
582
|
-
isSemanticError(error); // true
|
|
583
|
-
error.toJSON(); // { name: "CapacityError", message: "…", context: { capacity: 0 } }
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
`SemanticRangeError` はエクスポートされる `RangeError` と同じクラスなので、`instanceof` はどちらの名前でも成立します。`RangeError` をインポートするとモジュール全体でグローバルコンストラクタを隠してしまうため、この別名が存在します。どちらのクラスもライブラリ自身の引数検査からは送出されません。そこではネイティブの `TypeError` と `RangeError` が投げられます。この階層はその上の層のための語彙です。
|
|
587
|
-
|
|
588
|
-
### 11.2 ツール表
|
|
589
|
-
|
|
590
|
-
| Helper | Purpose |
|
|
591
|
-
| :--- | :--- |
|
|
592
|
-
| `useCompare(a, b)` | 混在するプリミティブ間の全順序 |
|
|
593
|
-
| `useToNumber` / `useToBigInt` | 無損失かつ範囲検査付きの数値変換 |
|
|
594
|
-
| `useRandom` | シード付き/シードなしの乱数ヘルパー |
|
|
595
|
-
| `useTraverse` | ネストしたオブジェクトと配列の構造的走査 |
|
|
596
|
-
| `useStringify` | 循環参照に安全な JSON シリアライズ |
|
|
597
|
-
| `useHash` / `maskOf` / `register` / `unregister` | 構造的ハッシュとハッシュ戦略レジストリ |
|
|
598
|
-
| `invalidate` / `validate` | 一貫したエラー形状を送出するガード節 |
|
|
599
|
-
| `Optional<T>` | ライブラリにおける `null` の置き換え:`Optional.of(x)` / `Optional.empty()` |
|
|
600
|
-
|
|
601
|
-
### 11.3 `useTraverse`
|
|
602
|
-
|
|
603
|
-
`useTraverse(target, callback)` は任意のオブジェクトをその**非 null の葉**へ平坦化します。ネストしたオブジェクトを下り、イテラブル値は位置ごとに走査し、`null` と `undefined` は飛ばし、一度訪れたオブジェクトには二度と入りません — したがって循環のあるオブジェクトグラフはスタックを溢れさせずに終了します。
|
|
604
|
-
|
|
605
|
-
```typescript
|
|
606
|
-
const source = { id: 7, tags: ["a", "b"], owner: { name: "Ada", note: null } };
|
|
607
|
-
const leaves: string[] = [];
|
|
608
|
-
|
|
609
|
-
useTraverse(source, (key, value, path) => {
|
|
610
|
-
leaves.push(`${path.join(".")} = ${String(value)}`);
|
|
611
|
-
return true;
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
leaves; // ["id = 7", "tags.0 = a", "tags.1 = b", "owner.name = Ada"]
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
コールバックは葉ごとに一度だけ呼ばれ、その葉自身のキー(イテラブル由来ならその添字)、値、そしてそこへ至るパスを受け取ります。`false` を返すと現在の枝だけでなく**走査全体**を放棄します。
|
|
618
|
-
|
|
619
|
-
```typescript
|
|
620
|
-
let visited = 0;
|
|
621
|
-
useTraverse({ a: 1, b: 2, c: 3 }, () => {
|
|
622
|
-
visited += 1;
|
|
623
|
-
return false;
|
|
624
|
-
});
|
|
625
|
-
visited; // 1 — the first leaf already stops the walk
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
循環が安全なのも同じ理由です。
|
|
629
|
-
|
|
630
|
-
```typescript
|
|
631
|
-
const cyclic: any = { name: "root", child: { name: "leaf" } };
|
|
632
|
-
cyclic.child.parent = cyclic;
|
|
633
|
-
|
|
634
|
-
const names: string[] = [];
|
|
635
|
-
useTraverse(cyclic, (_key, value) => {
|
|
636
|
-
if (typeof value === "string") names.push(value);
|
|
637
|
-
return true;
|
|
638
|
-
});
|
|
639
|
-
names; // ["root", "leaf"] — the back edge to `cyclic` is not revisited
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
**コールバックごとに独立したパスのスナップショットが渡されます。** 走査は内部に作業用配列を一つだけ持ち、葉を引き渡す瞬間にそれを `Object.freeze` で凍結した新しい配列へ複製します。スナップショットはその後変化しないので、そのまま保持でき、二つのコールバックが同じ配列を共有することも、走査をやり直して既に渡したスナップショットが乱れることもありません。型は `ReadonlyArray` なので書き込みはコンパイルエラーであり、凍結された配列への実行時の書き込みは `TypeError` も投げます。この安全性の代償は、報告する葉ごとに `O(depth)` の割り当てが一度増えることで、何も報告しない枝では追加の割り当ては発生しません。
|
|
643
|
-
|
|
644
|
-
```typescript
|
|
645
|
-
const paths: Array<ReadonlyArray<PropertyKey>> = [];
|
|
646
|
-
useTraverse({ a: { b: 1 }, c: 2 }, (_key, _value, path) => {
|
|
647
|
-
paths.push(path); // safe to keep as-is
|
|
648
|
-
return true;
|
|
649
|
-
});
|
|
650
|
-
paths[0] === paths[1]; // false — one array per call
|
|
651
|
-
paths.map(path => path.join(".")); // ["a.b", "c"]
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
`useAttribute` はこの走査をファクトリにしたものです。同じ葉を `{ key, value }` の組として報告するので、規則もコストも同一です。
|
|
655
|
-
|
|
656
|
-
```typescript
|
|
657
|
-
useAttribute({ a: 1, b: { c: 2 } }).toUnordered().toArray();
|
|
658
|
-
// [{ key: "a", value: 1 }, { key: "c", value: 2 }]
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
---
|
|
662
|
-
|
|
663
|
-
<a id="12-performance-and-pitfalls"></a>
|
|
664
|
-
## 12. パフォーマンスと落とし穴
|
|
665
|
-
|
|
666
|
-
**遅延にはステージごとのコストがあります。** オペレーターはそれぞれジェネレーターのホップを一つ増やします。メモリ内配列に対するタイトな数値ループでは、素の `for` ループがどのパイプラインにも勝ります。パイプラインが有利になるのは、ソースが遅延的もしくは無限であるか、インデックスの規律が元を取れるほどパイプラインが長い場合です。
|
|
667
|
-
|
|
668
|
-
**`toOrdered()` はソートし、`toUnordered()` はソートしません。** 反転が「効いていない」ように見えるなら、そのコレクターは順序なしでした — インデックスの書き換えは、インデックスが正規化されて初めて順序になります。
|
|
669
|
-
|
|
670
|
-
**`interrupt` のない `useGenerate` は決して終わりません。** 停止述語か、下流の `limit` / `takeWhile` を必ず与えてください。
|
|
671
|
-
|
|
672
|
-
**`Ring` は挿入順を保つ集合です。** 要素は一意であり、最初に追加された順序を保ちます。重複する `add` は `false` を返し、重複を導入する `set` は例外を投げます。これにより、位相は(たまたま占めている位置ではなく)*要素*の関数になり得ます。
|
|
673
|
-
|
|
674
|
-
**`Spectrum` の片側ヘルパーは実信号を前提とします。** `oneSided()`、`getNyquist()`、`centroid()` は共役対称性を仮定します。位相リングの空間スペクトルは複素数であり、一般に対称ではありません — 円を逆向きに周るリングは、そのエネルギーをすべてナイキストの外側であるビン `n − 1` に置きます。ビンを直接読んでください。
|
|
675
|
-
|
|
676
|
-
**構造コレクターは終端であり、ビューではありません。** どれもパイプラインを排出してから完全な構造を構築するので、O(n) 時間と O(n) 空間を要し、無限のソースでは動きません。パイプラインの末尾で使い、パイプラインを回すループの中では決して使わないでください。
|
|
677
|
-
|
|
678
|
-
**`variance()` とその仲間は `Optional` を返します。** 小さすぎる標本で `variance().get()` を呼ぶと例外になります。標本サイズを自分で制御できないときは `.orElse(0)` を使うか `.isPresent()` を先に確認してください。`standardDeviation`、`quantile`、`interquartileRange`、`skewness`、`kurtosis` も同じです。
|
|
679
|
-
|
|
680
|
-
**解析に使うのは周期窓です。** `useHannWindow` などの対称形式はフィルタ設計用です。短時間変換には `useHannPeriodicWindow` とその同族が必要です。そうでなければ重畳加算の和がフレームの継ぎ目で波打ちます。
|
|
681
|
-
|
|
682
|
-
---
|
|
683
|
-
|
|
684
|
-
<a id="13-phase-rings"></a>
|
|
685
|
-
## 13. 位相環:正確な周期
|
|
686
|
-
|
|
687
|
-
第 10 章は環を「周期」のモデルとして導入し、データ集合をあらゆる位相に対して測るのに使った。
|
|
688
|
-
本章はその完全な面を与える。位置がどのように角度になり、環境回転が何を変え何を変えないのか、
|
|
689
|
-
そして環を時間に対して、データに対して、別の環に対して読む道具である。
|
|
690
|
-
|
|
691
|
-
`SynchronousRing` は位置リストであり、その下には先頭と末尾が接続された循環双方向リンクがある。
|
|
692
|
-
同時に挿入順の*集合*でもある。同じ要素は二度現れず、拒否された書き込みはリンクもサンプルも
|
|
693
|
-
変えない。これを単なるリストではなく環にしているのは、位置の位相が現在の長さに対する余りだけ
|
|
694
|
-
で決まることである。だから環はビット単位で周期的であり、平衡も漂流しない。
|
|
695
|
-
|
|
696
|
-
### 13.1 一つの整数を正確に計算する
|
|
697
|
-
|
|
698
|
-
環は `n` 個の要素を持ち、要素数・位置周期・異なる位相の数は同じ整数である。位置 `i` の位相は
|
|
699
|
-
|
|
700
|
-
`phase + span · 2π · (((i mod n) + n) mod n) / n`
|
|
701
|
-
|
|
702
|
-
であり、余りは `number` になる前に `bigint` で還元される。`phaseAt(i)` と `phaseAt(i + n)` が
|
|
703
|
-
単に近いのではなく*ビット単位で同一*なのはそのためである。二度目の呼び出しは同じ整数に還元
|
|
704
|
-
され、同じ積を評価し、同じ倍精度値を返す。`number` の正確な整数範囲をはるかに超える索引でも
|
|
705
|
-
正しい位置に着き、負の索引は環を逆にたどる。
|
|
706
|
-
|
|
707
|
-
| 呼び出し | 返り値 | 意味 |
|
|
708
|
-
| :--- | :--- | :--- |
|
|
709
|
-
| `new SynchronousRing(source, options?)` | 環 | `source` は任意の反復可能オブジェクト、重複は捨てる |
|
|
710
|
-
| `getPhase()` / `getFrequency()` / `getSpan()` | `Angle`、`Frequency`、`number` | 三つの構築パラメータ |
|
|
711
|
-
| `size()` | `bigint` | 周期でもある `n` |
|
|
712
|
-
| `phaseAt(index)` | `Optional<Angle>` | 固有位相のみ。時間・速率・周波数は無視 |
|
|
713
|
-
| `elementAt(index)` | `Optional<RingElement>` | 要素、固有位相、環境周波数、フェーザ |
|
|
714
|
-
| `frequencyAt(index)` | `Optional<Frequency>` | 環境回転。空環では空 |
|
|
715
|
-
| `toComplex()` / `toComplex(index)` | `Complex[]` / `Optional<Complex>` | 固有フェーザ |
|
|
716
|
-
|
|
717
|
-
`RingOptions` には要素ごとの関数が二つある。`magnitude(element, index)` はフェーザの長さを
|
|
718
|
-
与え、既定は 1。`rate(element, index)` は要素自身の固有周波数を与え、既定は 0、すなわち固定
|
|
719
|
-
マーカーである。
|
|
720
|
-
|
|
721
|
-
```typescript
|
|
722
|
-
import { SynchronousRing } from "semantic-typescript";
|
|
723
|
-
|
|
724
|
-
const ring = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
725
|
-
|
|
726
|
-
ring.size(); // 6n
|
|
727
|
-
ring.phaseAt(0n).get().toDegreeValue(); // 0
|
|
728
|
-
ring.phaseAt(1n).get().toDegreeValue(); // 60
|
|
729
|
-
ring.phaseAt(-1n).get().toDegreeValue(); // 300
|
|
730
|
-
ring.phaseAt(6n).get().getValue() === ring.phaseAt(0n).get().getValue(); // true
|
|
731
|
-
ring.phaseAt(10n ** 20n).get().toDegreeValue(); // 240
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
六要素の環で `phaseAt(-1n)` は `300`、`10n ** 20n` は六で割って `4` 余るので `240` と読める。
|
|
735
|
-
どちらも浮動小数点の積を蓄積しない。索引の表現が変わっても同一性が正確であり続ける理由である。
|
|
736
|
-
|
|
737
|
-
### 13.2 環境回転と或る瞬間の状態
|
|
738
|
-
|
|
739
|
-
`frequency` は環が埋め込まれた環境の**環境回転レート**であり、位置間の位相ステップではない。
|
|
740
|
-
環境はすべての要素の位相を同じ角度だけ進めるので、固有の模様を変えられず、平衡を壊せず、
|
|
741
|
-
どの `phaseAt` の結果も変えられない。既定は `0 Hz` である。
|
|
742
|
-
|
|
743
|
-
`relativeAt(index, time)` はその共通角度を引いて、`PhaseField` が採点する座標を残す。
|
|
744
|
-
`viewAt` と `viewsAt` は或る瞬間の完全な状態を要素自身の `rate` 込みで返し、微分アクセサは
|
|
745
|
-
ドリフトを露出する。
|
|
746
|
-
|
|
747
|
-
| 呼び出し | 式 | 読むもの |
|
|
748
|
-
| :--- | :--- | :--- |
|
|
749
|
-
| `relativeAt(index, time)` | `固有位相 + 自身速率 · t − 環境回転 · t` | 環境から見た位相 |
|
|
750
|
-
| `viewAt(index, time)` / `viewsAt(time)` | 同上に要素・索引・時間・速率・フェーザを加える | 完全な状態 |
|
|
751
|
-
| `jetAt(index, time)` | `t` の位相、自身速率、`slip = 自身速率 − 環境回転` | 微分状態 |
|
|
752
|
-
| `windingAt(index, time)` | `trunc(slip · t)` | 滑った整数回転、零へ切り捨て |
|
|
753
|
-
| `errorIntegralAt(index, time, origin?)` | `∫ 相対位相 dt`、単位は回転 · 秒 | どれだけ先行/遅延したか |
|
|
754
|
-
| `resultant(time?)` | `abs(Σ w · e^{iθ}) / Σ w` | 位相の集中度、環境回転の影響を受けない |
|
|
755
|
-
| `analyse(data, time?)` | 参考ごとの計数と平均 | データを各要素に対して測る |
|
|
756
|
-
| `scoreAt(index, time, field)` / `scoresAt(time, field)` | 相対位相で場を標本化 | 位置ごとの有界スコア |
|
|
757
|
-
|
|
758
|
-
```typescript
|
|
759
|
-
import { SynchronousRing, useHertz } from "semantic-typescript";
|
|
760
|
-
|
|
761
|
-
const moving = new SynchronousRing(["a", "b", "c", "d", "e", "f"], {
|
|
762
|
-
frequency: useHertz(1),
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
moving.getFrequency().getHertz(); // 1
|
|
766
|
-
moving.phaseAt(1n).get().toDegreeValue(); // 60 — intrinsic
|
|
767
|
-
moving.relativeAt(1n, 0.25).get().toDegreeValue(); // 330 — 60 − 90
|
|
768
|
-
moving.viewAt(1n, 0.25).get().relative.toDegreeValue(); // 330
|
|
769
|
-
moving.jetAt(1n, 0.25).get().slip; // -1
|
|
770
|
-
moving.purity(); // 1 — rotation cannot break it
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
`1 Hz` では環境は `0.25 s` で四分の一回転進むので、位置一の相対位相は `60 − 90 = 330` 度に
|
|
774
|
-
なり、固有位相は `60` のままである。要素自身は動かないので jet の `slip` は `-1` ヘルツで
|
|
775
|
-
あり、`purity()` は依然 `1` である。すべてのフェーザを同じ角度だけ回すのは変形ではない。
|
|
776
|
-
|
|
777
|
-
### 13.3 純度と平衡
|
|
778
|
-
|
|
779
|
-
`purity()` は環のエネルギーのうち基本モードが運ぶ割合である。
|
|
780
|
-
|
|
781
|
-
`max(abs(c₁)², abs(c_{n−1})²) / (n · Σ abs(z)²)`
|
|
782
|
-
|
|
783
|
-
環はどちらの向きにも円をたどり得るので、両方の基本ビンを見る。六要素で `span: 5` の環は一歩
|
|
784
|
-
`300` 度、すなわち `−60` 度で進む。逆向きに走り、全エネルギーを `c₅` に置くので、`c₁` だけを
|
|
785
|
-
見る式はそれを不純と誤判定する。必要なのはこの二つのビンと全エネルギーだけなので、費用は
|
|
786
|
-
`O(n)` のままである。
|
|
787
|
-
|
|
788
|
-
境界の扱いも契約の一部である。二要素未満の環には基本モードがなく、内容にかかわらず
|
|
789
|
-
`purity() === 0` と `isBalanced() === false` を報告する。フェーザの長さがすべて零の環は
|
|
790
|
-
エネルギーを持たず、同じく零を報告する。`isBalanced(epsilon?)` は `1 − purity() <= epsilon`
|
|
791
|
-
で、既定の許容差は `1E-9` である。
|
|
792
|
-
|
|
793
|
-
```typescript
|
|
794
|
-
import { SynchronousRing } from "semantic-typescript";
|
|
795
|
-
|
|
796
|
-
const forward = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
797
|
-
const backward = new SynchronousRing(["a", "b", "c", "d", "e", "f"], { span: 5 });
|
|
798
|
-
const weighted = new SynchronousRing(["a", "b", "c", "d"], {
|
|
799
|
-
magnitude: element => element === "a" ? 1 : 0,
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
forward.purity(); // 1
|
|
803
|
-
backward.phaseAt(1n).get().toDegreeValue(); // 300 — it runs backwards
|
|
804
|
-
backward.purity(); // 1 — the energy sits in c₅
|
|
805
|
-
new SynchronousRing(["only"]).purity(); // 0
|
|
806
|
-
new SynchronousRing(["only"]).isBalanced(); // false
|
|
807
|
-
weighted.purity(); // 0.25
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
### 13.4 合力長と対称成分
|
|
811
|
-
|
|
812
|
-
`resultant(time?)` は平均合力長であり、フェーザ和の大きさを大きさの和で割ったものである。
|
|
813
|
-
環がまだ `n` 乗根のままかを問う `purity` と違い、`resultant` は今この瞬間にフェーザがどれ
|
|
814
|
-
だけ集中しているかを問う。平衡環は `0` を報告する。すべての要素が同じ角度を共有する環なら
|
|
815
|
-
`1` だが、集合の意味論が重複要素を禁じるので、実際には `magnitude` が一つ以外を零にしたとき
|
|
816
|
-
に `1` が現れる。
|
|
817
|
-
|
|
818
|
-
`zeroSequence()` は平均フェーザ、すなわち `c₀ / n` である。`sequences()` は直接変換によって
|
|
819
|
-
`n` 個の対称成分を低次から返す。`toSpectrum()` はそれらを `Spectrum` に包み、ビン `j` は
|
|
820
|
-
環一回転あたり `j` 周期を報告するので、周波数軸は波数軸になる。
|
|
821
|
-
|
|
822
|
-
```typescript
|
|
823
|
-
import { SynchronousRing } from "semantic-typescript";
|
|
824
|
-
|
|
825
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
826
|
-
const ring = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
827
|
-
|
|
828
|
-
round(ring.resultant().get()); // 0
|
|
829
|
-
round(ring.zeroSequence().get().magnitude()); // 0
|
|
830
|
-
ring.sequences().map(component => round(component.magnitude())); // [0, 6, 0, 0, 0, 0]
|
|
831
|
-
ring.toSpectrum().get().size(); // 6
|
|
832
|
-
ring.toSpectrum().get().getResolution().getHertz(); // 1
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
成分は複素数であり、一般に**共役対称ではない**。平衡環は全エネルギーをビン一に置き、逆向き
|
|
836
|
-
の環はビン `n − 1` に置く。`Spectrum` の片側補助関数は実信号を仮定するので、折り返さずに
|
|
837
|
-
ビンを直接読むこと。
|
|
838
|
-
|
|
839
|
-
### 13.5 データを各要素に対して分析する
|
|
840
|
-
|
|
841
|
-
`analyse(data, time?)` は各観測を同一性で環に照合し、各要素を参考位相として測る。環が持た
|
|
842
|
-
ない観測は推測せず `missed` として数える。位相の唯一の源が環だからである。各参考行は
|
|
843
|
-
`count`、平均相対位相 `mean`、平均合力長 `resultant` を報告する。
|
|
844
|
-
|
|
845
|
-
覚えておく価値のある恒等式が一つある。**`resultant` はどの参考でも同じである**。基準を変える
|
|
846
|
-
とすべての相対位相が定数だけ回り、回転は合力長を変えられない。参考ごとに違うのは計数と平均
|
|
847
|
-
である。
|
|
848
|
-
|
|
849
|
-
```typescript
|
|
850
|
-
import { SynchronousRing } from "semantic-typescript";
|
|
851
|
-
|
|
852
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
853
|
-
const ring = new SynchronousRing(["a", "b", "c", "d", "e"]);
|
|
854
|
-
const report = ring.analyse(["a", "c", "c", "a", "zz", "e"]);
|
|
855
|
-
|
|
856
|
-
report.matched; // 5
|
|
857
|
-
report.missed; // 1
|
|
858
|
-
report.references.map(row => row.count); // [2, 0, 2, 0, 1]
|
|
859
|
-
report.references.map(row => round(row.resultant)); // [0.1453, 0.1453, 0.1453, 0.1453, 0.1453]
|
|
860
|
-
```
|
|
861
|
-
|
|
862
|
-
採点は同じ硬貨の裏側である。`PhaseField` は有界な余弦級数(10.2 節)であり、`scoreAt` /
|
|
863
|
-
`scoresAt` は相対位相でそれを標本化する。だから同じ環を、整列に対して、ある調波模様に対して、
|
|
864
|
-
あるいは任意の場に対して、環そのものを変えずに採点できる。
|
|
865
|
-
|
|
866
|
-
```typescript
|
|
867
|
-
import { SynchronousRing, useField } from "semantic-typescript";
|
|
868
|
-
|
|
869
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
870
|
-
const ring = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
871
|
-
const cosine = useField([1]);
|
|
872
|
-
|
|
873
|
-
round(ring.scoreAt(0n, 0, cosine).get()); // 1
|
|
874
|
-
round(ring.scoreAt(1n, 0, cosine).get()); // 0.5
|
|
875
|
-
ring.scoresAt(0, cosine).map(score => round(score)); // [1, 0.5, -0.5, -1, -0.5, 0.5]
|
|
876
|
-
```
|
|
877
|
-
|
|
878
|
-
### 13.6 環の合成と角スペクトル
|
|
879
|
-
|
|
880
|
-
`combineRings(left, right)` は二つの環を積ではなく最小公倍数で接続する。結合環の瞬間 `i` は
|
|
881
|
-
左環の `i mod m` と右環の `i mod n` を読み、`lcm(m, n)` 瞬間で閉じる。決して同時に起こり
|
|
882
|
-
得ない組——位置が `gcd(m, n)` を法として一致しないもの——は単に存在しない。だから結果は積では
|
|
883
|
-
なく引き戻しであり、`aligns(left, right)` は与えられた組が表現可能かを報告する。
|
|
884
|
-
|
|
885
|
-
`useAngularSpectrum(ring, instants, plan)` は `(ω, n)` 平面を作る。各瞬間がフェーザのフレーム
|
|
886
|
-
を寄与し、モード `j` のフレーム `k` はそのフレームの `j` 番目の対称成分であり、
|
|
887
|
-
`cells[frame][order]` が係数である。一列を取れば単一空間モードの時系列になり、
|
|
888
|
-
`spectrum(order)` はその時間スペクトルである。
|
|
889
|
-
|
|
890
|
-
```typescript
|
|
891
|
-
import { Sample, SynchronousRing, combineRings, useAngularSpectrum } from "semantic-typescript";
|
|
892
|
-
|
|
893
|
-
const left = new SynchronousRing(["a", "b", "c", "d"]);
|
|
894
|
-
const right = new SynchronousRing(["x", "y", "z", "w", "v", "u"]);
|
|
895
|
-
const joined = combineRings(left, right);
|
|
896
|
-
|
|
897
|
-
joined.period; // 12n
|
|
898
|
-
joined.divisor; // 2n
|
|
899
|
-
joined.ring.size(); // 12n
|
|
900
|
-
joined.aligns(0n, 0n); // true
|
|
901
|
-
joined.aligns(1n, 0n); // false
|
|
902
|
-
joined.ring.get(0n).join("-"); // a-x
|
|
903
|
-
|
|
904
|
-
const angular = useAngularSpectrum(left, [0, 0.25, 0.5, 0.75], new Sample(4, 1));
|
|
905
|
-
angular.order; // 4
|
|
906
|
-
angular.frameCount; // 4
|
|
907
|
-
angular.cells[0][1].magnitude(); // 4
|
|
908
|
-
angular.spectrum(1).get().size(); // 4
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
### 13.7 落とし穴
|
|
912
|
-
|
|
913
|
-
**正確さは正確さである。** `phaseAt(i)` と `phaseAt(i + n)` は同じ倍精度値であり、許容差の
|
|
914
|
-
中の二つの値ではない。要素ごとに `2π / n` を蓄積して作った環はこれを満たさない。この環は
|
|
915
|
-
まず `bigint` で索引を還元する。
|
|
916
|
-
|
|
917
|
-
**`frequency` は位相ステップではない。** 環境回転レートであり、既定は零で、すべてのフェーザ
|
|
918
|
-
を同じ角度だけ回しても固有の模様は壊れない。位置同士を相対的に進めたいなら `rate` を使い、
|
|
919
|
-
`frequency` は使わない。
|
|
920
|
-
|
|
921
|
-
**`purity()` は両方の基本ビンを見なければならない。** 逆向きの環も `n` 乗根のままである。
|
|
922
|
-
`c₁` だけでは不純と判定してしまう。
|
|
923
|
-
|
|
924
|
-
**`n < 2` は定義された境界である。** `purity()` は `0`、`isBalanced()` は `false` を返す。
|
|
925
|
-
空環の標本化メソッドは空の `Optional` を返す。
|
|
926
|
-
|
|
927
|
-
**`n` と因子を共有する span は軌道を潰す。** 六要素で `span: 2` は三つの異なる位相しか訪れ
|
|
928
|
-
ず、環はもはや平衡でない。
|
|
929
|
-
|
|
930
|
-
**`resultant` は回転不変であって時間不変ではない。** 環境回転は変えないが、要素ごとの
|
|
931
|
-
`rate` は環が位相を失うにつれて変える。
|
|
932
|
-
|
|
933
|
-
**環は集合であり、`analyse` は同一性で照合する。** 重複要素は構築時に捨てられ、環の要素と
|
|
934
|
-
構造的に等しいが同じ参照でないオブジェクトは missed と数えられる。
|
|
935
|
-
|
|
936
|
-
**`toSpectrum()` は複素スペクトルである。** ビンは共役対称でなく、`Spectrum` の片側補助関数
|
|
937
|
-
は適用できない。ビンを直接読むこと。
|
|
938
|
-
|
|
939
|
-
**`windingAt` は零へ切り捨て、`errorIntegralAt` は位相を積分する。** 半回転の滑りは `0n` と
|
|
940
|
-
報告される。誤差積分の単位は 回転 · 秒であり、速率ではない。
|
|
941
|
-
|
|
942
|
-
```typescript
|
|
943
|
-
import { SynchronousRing, useHertz } from "semantic-typescript";
|
|
944
|
-
|
|
945
|
-
const collapsed = new SynchronousRing(["a", "b", "c", "d", "e", "f"], { span: 2 });
|
|
946
|
-
collapsed.phaseAt(1n).get().toDegreeValue(); // 120
|
|
947
|
-
collapsed.phaseAt(3n).get().toDegreeValue(); // 0
|
|
948
|
-
Number(collapsed.purity().toFixed(4)); // 0
|
|
949
|
-
|
|
950
|
-
const falling = new SynchronousRing(["a", "b", "c", "d"], {
|
|
951
|
-
frequency: useHertz(2),
|
|
952
|
-
rate: () => useHertz(0.5),
|
|
953
|
-
});
|
|
954
|
-
falling.jetAt(0n, 2).get().slip; // -1.5
|
|
955
|
-
falling.windingAt(0n, 1).get(); // -1n
|
|
956
|
-
falling.windingAt(0n, 0.5).get(); // 0n
|
|
957
|
-
|
|
958
|
-
const objects = new SynchronousRing([{ key: 1 }, { key: 2 }]);
|
|
959
|
-
objects.analyse([{ key: 1 }]).missed; // 1 — identity, not structure
|
|
960
|
-
```
|
|
961
|
-
|
|
962
|
-
---
|
|
963
|
-
|
|
964
|
-
<a id="14-signal-analysis"></a>
|
|
965
|
-
## 14. 信号解析:窓・スペクトル・時間
|
|
966
|
-
|
|
967
|
-
第 10 章では環を位相の周期として扱った。本章はその時間側の対応物である。実数値の記録は、
|
|
968
|
-
サンプルそのものに加えて、すべてのスペクトル公式が必要とする二つの文脈を携える——ビン番号を
|
|
969
|
-
ヘルツに変えるサンプリング計画と、コヒーレントゲインと雑音帯域幅の両方を決める窓である。
|
|
970
|
-
|
|
971
|
-
値オブジェクトは `SynchronousSignalCollectable`、その待ち行列版の双子が
|
|
972
|
-
`AsynchronousSignalCollectable` である。どちらも同じ `SignalCollectable` の面を公開する。
|
|
973
|
-
二十三のメソッドがあり、それぞれが `synchronous/collectors` の
|
|
974
|
-
`useSynchronous<Method>` ファクトリ一つに厳密に委譲する。この分割は意図的である。クラスは
|
|
975
|
-
文書化されたファサード、ファクトリが累積を所有し、コンストラクタはこの長さで
|
|
976
|
-
`sum(w) / count` と `count * sum(w^2) / sum(w)^2` を一度だけ計算して保持する。
|
|
977
|
-
「コヒーレントゲインで割る」ことを覚えておかなければならない呼び出し側はいつか忘れるし、
|
|
978
|
-
そこで生じる 6.02 デシベルはもっともらしい測定値に見えてしまう。
|
|
979
|
-
|
|
980
|
-
### 14.1 信号は自前の校正を持つ
|
|
981
|
-
|
|
982
|
-
信号は、反復可能オブジェクトとサンプリングレートから、既存の `Frame` から、あるいは明示的な
|
|
983
|
-
`Sample` を伴うコンストラクタから作る。既定の窓は周期 Hann 窓である。窓を指定しない解析は
|
|
984
|
-
すべてこれを使い、どの解析も上書きを受け付ける。メタデータのアクセサは構築時に固定された
|
|
985
|
-
状態を定数時間で読むだけなので、信号を渡し歩いても文脈がずれない。
|
|
986
|
-
|
|
987
|
-
| 呼び出し | 返り値 | 意味 |
|
|
988
|
-
| :--- | :--- | :--- |
|
|
989
|
-
| `SynchronousSignalCollectable.useRate(values, rate, window?)` | 信号 | 長さ `count / rate` |
|
|
990
|
-
| `SynchronousSignalCollectable.useFrame(frame, window?)` | 信号 | その frame の計画を保つ |
|
|
991
|
-
| `new SynchronousSignalCollectable(plan, values, window?)` | 信号 | 計画のフレーム数はサンプル数と一致必須 |
|
|
992
|
-
| `getSample()` / `getValues()` / `getWindow()` | 計画・サンプル・窓 | 凍結された文脈 |
|
|
993
|
-
| `getRate()` | `number` | 毎秒のサンプル数 |
|
|
994
|
-
| `getNyquist()` / `getResolution()` | `Frequency` | `rate / 2` と `rate / count` |
|
|
995
|
-
| `getCoherentGain()` / `getNoiseBandwidth()` | `number` | この長さで再計算される |
|
|
996
|
-
| `size()` / `at(index)` / `toFrame()` / `equals(other)` / `toString()` | — | 値の意味論 |
|
|
997
|
-
|
|
998
|
-
```typescript
|
|
999
|
-
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1000
|
-
|
|
1001
|
-
const signal = SynchronousSignalCollectable.useRate(
|
|
1002
|
-
Array.from({ length: 64 }, (_unused, index) => Math.cos(2 * Math.PI * 8 * index / 64)),
|
|
1003
|
-
64,
|
|
1004
|
-
);
|
|
1005
|
-
|
|
1006
|
-
signal.size(); // 64n
|
|
1007
|
-
signal.getRate(); // 64
|
|
1008
|
-
signal.getNyquist().getHertz(); // 32
|
|
1009
|
-
signal.getResolution().getHertz(); // 1
|
|
1010
|
-
signal.getCoherentGain(); // 0.5
|
|
1011
|
-
signal.getNoiseBandwidth(); // 1.5
|
|
1012
|
-
```
|
|
1013
|
-
|
|
1014
|
-
`getResolution()` はビン間隔であって分解能ではない。変換が `count` 個のビンを持つので
|
|
1015
|
-
`rate / count` であり、ゼロ詰めは間隔を変えるが、窓が何を分離できるかは変えない(14.8 節)。
|
|
1016
|
-
|
|
1017
|
-
### 14.2 二十三のメソッド
|
|
1018
|
-
|
|
1019
|
-
メソッドは六つの族に分かれる。七つはスペクトルの読み、二つは時間周波数変換、四つは解析量、
|
|
1020
|
-
三つは系列統計量、五つは形状記述子、そして二つは別の信号を返す。
|
|
1021
|
-
|
|
1022
|
-
| メソッド | 返り値 | 規約 |
|
|
1023
|
-
| :--- | :--- | :--- |
|
|
1024
|
-
| `spectrum(size?, window?)` | `Spectrum` | 生のまま非正規化、複素格子を全保持 |
|
|
1025
|
-
| `oneSided(size?, window?)` | `Bin[]` | 共役半を折り返し、DC と Nyquist は二倍しない |
|
|
1026
|
-
| `amplitude(window?)` | `number[]` | `2·abs(X) / (count · CG)`、DC と Nyquist は一度だけ |
|
|
1027
|
-
| `power(window?)` | `number[]` | 内部ビンは `A² / 2`、DC と Nyquist は `A²` |
|
|
1028
|
-
| `decibel(window?)` | `number[]` | `20·log10(振幅)` |
|
|
1029
|
-
| `powerDecibel(window?)` | `number[]` | `10·log10(電力)` |
|
|
1030
|
-
| `powerSpectralDensity(frameSize?, hopSize?, window?)` | `SpectralDensity` | Welch 平均、`abs(X)² / (rate · Σw²)` |
|
|
1031
|
-
| `shortTimeFourierTransform(frameSize, hopSize, window?)` | `Spectrum[]` | 両端を延長し、フレーム中心で時刻を報告 |
|
|
1032
|
-
| `spectrogram(frameSize, hopSize, window?)` | `Spectrogram` | 同じ列に時間軸を付ける |
|
|
1033
|
-
| `analyticSignal()` | `Complex[]` | Hilbert 対 `x + i·H(x)` |
|
|
1034
|
-
| `instantaneousPhase()` | `Angle[]` | `[0, 2π)` に折り畳む |
|
|
1035
|
-
| `instantaneousFrequency(rate?, gate?)` | `number[]` | 位相の差分を展開し、包絡線で門限を掛ける |
|
|
1036
|
-
| `envelope()` | `number[]` | `abs(z[n])`、両端は循環の影響を受ける |
|
|
1037
|
-
| `zeroCrossingRate(perSecond?)` | `number` | 隣接サンプル対あたりの零交差 |
|
|
1038
|
-
| `spectralFlux(frameSize?, hopSize?, window?)` | `number[]` | フレーム間の単位ノルム振幅距離 |
|
|
1039
|
-
| `autocorrelation(maximumLag?, normalization?)` | `number[]` | 既定は係数規約、`ρ[0] = 1` |
|
|
1040
|
-
| `spectralCentroid(window?)` | `Optional<Frequency>` | 電力重み付き平均周波数 |
|
|
1041
|
-
| `spectralBandwidth(window?)` | `Optional<Frequency>` | 電力重み付き標準偏差 |
|
|
1042
|
-
| `spectralRolloff(ratio?, window?)` | `Optional<Frequency>` | 累積電力が初めて `ratio` に達するビン |
|
|
1043
|
-
| `spectralFlatness(window?)` | `number` | 電力の幾何平均 / 算術平均 |
|
|
1044
|
-
| `signalToNoiseRatio(frameSize?, hopSize?, window?, floorQuantile?)` | `Optional<number>` | 中央値の雑音床、`10·log10` |
|
|
1045
|
-
| `detrended(mode?)` | 信号 | `"none"`、`"constant"`(既定)、`"linear"` |
|
|
1046
|
-
| `windowed(window?)` | 信号 | 窓を適用し、それを採用する |
|
|
1047
|
-
|
|
1048
|
-
省略可能な引数は飾りではない。`frameSize` の既定は `min(256, size)`、ホップは半フレームの
|
|
1049
|
-
切り捨て、`instantaneousFrequency` の門限はピーク包絡線の `1E-3`、`spectralRolloff` は
|
|
1050
|
-
`0.85` である。どの既定値も、参考実装が「名前だけ与えて値は渡さない」ときに選ぶ値である。
|
|
1051
|
-
|
|
1052
|
-
### 14.3 振幅・電力・密度
|
|
1053
|
-
|
|
1054
|
-
三つの異なる問いがあるので、三つの正規化が要る。
|
|
1055
|
-
|
|
1056
|
-
`amplitude()` は「このビンの正弦波はどれだけ大きいか」に答える。内部ビンは
|
|
1057
|
-
`2·abs(X[k]) / (count · CG)`、DC と Nyquist は `abs(X[k]) / (count · CG)` である。二倍は
|
|
1058
|
-
共役半の折り返し、コヒーレントゲインは窓の除去である。どちらを省いても単位振幅の正弦波は
|
|
1059
|
-
`0.5`、すなわち −6.02 デシベルと読めてしまう。
|
|
1060
|
-
|
|
1061
|
-
`power()` は「このビンの正弦波はどれだけの平均二乗を持つか」に答える。内部ビンは `A² / 2`、
|
|
1062
|
-
DC と Nyquist は `A²` である。後者は振動しないからだ。これはピーク規約であり、パーシバル
|
|
1063
|
-
の定理を意図的に満たさない。窓が各スペクトル線を主ローブに広げるので、全ビンの和は平均二乗
|
|
1064
|
-
より等価雑音帯域幅の分だけ大きい——Hann では `1.5` である。
|
|
1065
|
-
|
|
1066
|
-
`powerSpectralDensity()` は「毎ヘルツどれだけの電力か」に答える。
|
|
1067
|
-
`abs(X)² / (rate · Σw²)` を重複するセグメントで平均する。重みの二乗和を分母にしたときだけ
|
|
1068
|
-
白色雑音の床が不偏になる。`count` で割ると単位雑音は `0.375`、`count · CG²` で割ると `1.5`
|
|
1069
|
-
と読めてしまう。
|
|
1070
|
-
|
|
1071
|
-
```typescript
|
|
1072
|
-
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1073
|
-
|
|
1074
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
1075
|
-
const tone = SynchronousSignalCollectable.useRate(
|
|
1076
|
-
Array.from({ length: 256 }, (_unused, index) => Math.cos(2 * Math.PI * 32 * index / 256)),
|
|
1077
|
-
256,
|
|
1078
|
-
);
|
|
1079
|
-
|
|
1080
|
-
round(tone.amplitude()[32]); // 1
|
|
1081
|
-
round(tone.power()[32]); // 0.5
|
|
1082
|
-
round(tone.power().reduce((sum, power) => sum + power, 0)); // 0.75
|
|
1083
|
-
round(tone.powerSpectralDensity(64n, 32n).totalPower()); // 0.5
|
|
1084
|
-
tone.powerSpectralDensity(64n, 32n).getSegments(); // 7n
|
|
1085
|
-
```
|
|
1086
|
-
|
|
1087
|
-
覚えておく価値があるのはこの二つの総量である。ピーク規約の和は `0.75`、密度の積分は `0.5`、
|
|
1088
|
-
その比はちょうど雑音帯域幅になる。三つのうちフレーム長を変えても意味が保たれるのは密度だけ
|
|
1089
|
-
である。
|
|
1090
|
-
|
|
1091
|
-
### 14.4 レベル:二つのデシベル規約
|
|
1092
|
-
|
|
1093
|
-
振幅と電力は同じ因子を共有しないので、同じ対数も共有しない。`amplitudeDecibel` は
|
|
1094
|
-
`20·log10` を適用し、電圧・変位・音圧に正しい。`powerDecibel` は `10·log10` を適用し、
|
|
1095
|
-
電力・エネルギー・平均二乗・密度に正しい。信号のメソッド名も同じ分割に従う。
|
|
1096
|
-
|
|
1097
|
-
```typescript
|
|
1098
|
-
import { amplitudeDecibel, powerDecibel, SynchronousSignalCollectable } from "semantic-typescript";
|
|
1099
|
-
|
|
1100
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
1101
|
-
const tone = SynchronousSignalCollectable.useRate(
|
|
1102
|
-
Array.from({ length: 256 }, (_unused, index) => Math.cos(2 * Math.PI * 32 * index / 256)),
|
|
1103
|
-
256,
|
|
1104
|
-
);
|
|
1105
|
-
|
|
1106
|
-
amplitudeDecibel(0.25); // -12.0412
|
|
1107
|
-
powerDecibel(0.25); // -6.0206
|
|
1108
|
-
round(tone.decibel()[32]); // 0
|
|
1109
|
-
round(tone.powerDecibel()[32]); // -3.0103
|
|
1110
|
-
```
|
|
1111
|
-
|
|
1112
|
-
同じ音の二つの読みはどちらも正しい。振幅は 1(ゼロデシベル)、平均二乗は二分の一
|
|
1113
|
-
(−3.01 デシベル)である。電力を振幅規約に通すとデシベル値が二倍になり、正しい −6.02 が
|
|
1114
|
-
−12.04 になる。
|
|
1115
|
-
|
|
1116
|
-
### 14.5 片側の折り返し
|
|
1117
|
-
|
|
1118
|
-
実信号は `X[count − k] = conj(X[k])` を満たすので、負周波数の半は独立な情報を持たない。
|
|
1119
|
-
折り返しは内部ビンをすべて二倍し、自己共役な二つのビン——DC と、偶数長では Nyquist——を
|
|
1120
|
-
変換が作ったまま残す。この二つは自分自身の鏡像であり、二倍にするとエネルギーを捏造し、
|
|
1121
|
-
ちょうど 6.02 デシベル高く報告する。
|
|
1122
|
-
|
|
1123
|
-
```typescript
|
|
1124
|
-
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1125
|
-
|
|
1126
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
1127
|
-
const edge = SynchronousSignalCollectable.useRate(
|
|
1128
|
-
Array.from({ length: 16 }, (_unused, index) => 2 + Math.cos(Math.PI * index)),
|
|
1129
|
-
16,
|
|
1130
|
-
);
|
|
1131
|
-
const bins = edge.oneSided();
|
|
1132
|
-
|
|
1133
|
-
bins.length; // 9
|
|
1134
|
-
round(bins[0].magnitude()); // 16
|
|
1135
|
-
round(bins[8].magnitude()); // 8
|
|
1136
|
-
round(edge.amplitude()[0]); // 2
|
|
1137
|
-
round(edge.amplitude()[8]); // 1
|
|
1138
|
-
```
|
|
1139
|
-
|
|
1140
|
-
生の振幅が `16` と `8` なのは窓の重みの和が 8 だからである。`count · CG = 8` で割ると
|
|
1141
|
-
校正済みの振幅 `2` と `1` になる。端のビンを二倍すると `4` と `2` と読め、その誤りは
|
|
1142
|
-
決して例外を投げない。
|
|
1143
|
-
|
|
1144
|
-
### 14.6 時間周波数の眺め
|
|
1145
|
-
|
|
1146
|
-
`shortTimeFourierTransform` と `spectrogram` は参考の短時間変換と同じように記録を分割する。
|
|
1147
|
-
両端に半フレームの零を置き、末尾を零で埋めて整数個のホップに揃える。最初のフレームは最初の
|
|
1148
|
-
サンプルを中心にし、最後のフレームは最後のサンプルに届き、フレーム `m` の中心は
|
|
1149
|
-
`m · hop / rate` 秒である。完全なフレームだけを残すと最大 `frameSize − 1` サンプルを捨てる
|
|
1150
|
-
——1000 サンプル、フレーム 256、ホップ 128 では 104 サンプル、記録の約十分の一である。
|
|
1151
|
-
|
|
1152
|
-
```typescript
|
|
1153
|
-
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1154
|
-
|
|
1155
|
-
const tone = SynchronousSignalCollectable.useRate(
|
|
1156
|
-
Array.from({ length: 64 }, (_unused, index) => Math.cos(2 * Math.PI * 4 * index / 64)),
|
|
1157
|
-
64,
|
|
1158
|
-
);
|
|
1159
|
-
const spectrogram = tone.spectrogram(16n, 8n);
|
|
1160
|
-
|
|
1161
|
-
spectrogram.size(); // 9
|
|
1162
|
-
spectrogram.getFrameSize(); // 16
|
|
1163
|
-
spectrogram.getBoundary(); // "zeros"
|
|
1164
|
-
spectrogram.getTime(0); // 0
|
|
1165
|
-
spectrogram.getTime(6); // 0.75
|
|
1166
|
-
tone.shortTimeFourierTransform(16n, 8n).length; // 9
|
|
1167
|
-
tone.spectralFlux(16n, 8n).length; // 8
|
|
1168
|
-
```
|
|
1169
|
-
|
|
1170
|
-
各列は `frameSize` 個のビンを持つ `Spectrum` で、同じフレームに `spectrum()` を呼んだ結果と
|
|
1171
|
-
完全に一致する。`getTime()` が報告するのはフレームの開始ではなく中心である。
|
|
1172
|
-
|
|
1173
|
-
### 14.7 窓・コヒーレントゲイン・COLA
|
|
1174
|
-
|
|
1175
|
-
どの形にも二つの形式がある。対称形式は分母が `count − 1`、周期(DFT 偶数)形式は分母が
|
|
1176
|
-
`count` で、一周期より一つ短いため、DFT が仮定する周期延長の下で連続になる。解析と重畳加算
|
|
1177
|
-
には周期形式が要り、フィルタ設計は対称形式を保つ。
|
|
1178
|
-
|
|
1179
|
-
差は測定でき、しかも決して例外にならない。50% 重複では周期 Hann 窓の和はどの位置でも 1 に
|
|
1180
|
-
なる。対称形式は波打ち、16 サンプルで `0.84` デシベル、64 で `0.21`、1024 でも `0.013`
|
|
1181
|
-
残る。クラスは保持する窓のコヒーレントゲインと等価雑音帯域幅を公開するので、補正は決して
|
|
1182
|
-
ハードコードされない。対称 Hann は 8 サンプルで `0.4375` と読み、`0.5` ではない。
|
|
1183
|
-
|
|
1184
|
-
```typescript
|
|
1185
|
-
import {
|
|
1186
|
-
coherentGain, equivalentNoiseBandwidth, useFlatTopWindow,
|
|
1187
|
-
useHannPeriodicWindow, useHannWindow,
|
|
1188
|
-
} from "semantic-typescript";
|
|
1189
|
-
|
|
1190
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
1191
|
-
|
|
1192
|
-
round(useHannWindow(1, 4)); // 0.75
|
|
1193
|
-
round(useHannPeriodicWindow(1, 4)); // 0.5
|
|
1194
|
-
coherentGain(useHannPeriodicWindow, 16); // 0.5
|
|
1195
|
-
equivalentNoiseBandwidth(useHannPeriodicWindow, 16); // 1.5
|
|
1196
|
-
useFlatTopWindow(0, 1024); // -0.000421
|
|
1197
|
-
Array.from({ length: 1024 }, (_unused, i) => useFlatTopWindow(i, 1024)).filter(w => w < 0).length; // 578
|
|
1198
|
-
```
|
|
1199
|
-
|
|
1200
|
-
フラットトップの行は表の誤りではない。フラットトップ窓は定義上部分的に負であり、ここでは
|
|
1201
|
-
1024 個の重みのうち 578 個が負である。それらを零に切り詰めると主ローブが乱れ、この窓の
|
|
1202
|
-
存在理由である低いスキャロッピング損失が壊れる。ライブラリは系列をそのまま返す。
|
|
1203
|
-
|
|
1204
|
-
### 14.8 落とし穴
|
|
1205
|
-
|
|
1206
|
-
**ゼロ詰めは補間であり、決して分解しない。** 変換長をサンプル数より大きくしても、同じ
|
|
1207
|
-
DTFT をより密に標本化するだけである。ピークの*位置*は細かくなり、ビン間隔は `rate / size`
|
|
1208
|
-
になるが、二つの成分を分離できるかは窓の長さと形が決めたままである。ちょうど二つのビンの
|
|
1209
|
-
中間にある音は、詰めない格子では `82.1093` と `80.8598`、詰めた格子では自分の周波数で
|
|
1210
|
-
`128` と読める。
|
|
1211
|
-
|
|
1212
|
-
```typescript
|
|
1213
|
-
import { SynchronousSignalCollectable, useRectangularWindow } from "semantic-typescript";
|
|
1214
|
-
|
|
1215
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
1216
|
-
const tone = SynchronousSignalCollectable.useRate(
|
|
1217
|
-
Array.from({ length: 256 }, (_unused, index) => Math.cos(2 * Math.PI * 100.5 * index / 256)),
|
|
1218
|
-
256,
|
|
1219
|
-
);
|
|
1220
|
-
const flat = tone.spectrum(256n, useRectangularWindow);
|
|
1221
|
-
const padded = tone.spectrum(1024n, useRectangularWindow);
|
|
1222
|
-
|
|
1223
|
-
flat.getResolution().getHertz(); // 1
|
|
1224
|
-
padded.getResolution().getHertz(); // 0.25
|
|
1225
|
-
round(flat.magnitudeAt(100).get()); // 82.1093
|
|
1226
|
-
round(flat.magnitudeAt(101).get()); // 80.8598
|
|
1227
|
-
round(padded.magnitudeAt(402).get()); // 128
|
|
1228
|
-
padded.frequencyAt(402).get().getHertz(); // 100.5
|
|
1229
|
-
```
|
|
1230
|
-
|
|
1231
|
-
`0.4` ビン離れた等振幅の二音は、どんなゼロ詰めでも融合したままである。256 点の記録と Hann
|
|
1232
|
-
窓では、8192 まで詰めても局所最大は一つしか現れない。
|
|
1233
|
-
|
|
1234
|
-
**Hilbert 変換は循環である。** `analyticSignal`、`envelope`、`instantaneousPhase` は最初と
|
|
1235
|
-
最後のサンプルを隣人として扱うので、周期が長さを割り切らない記録は両端が汚染される。記録の
|
|
1236
|
-
中ほどは信頼できるが、両端は信頼できない。
|
|
1237
|
-
|
|
1238
|
-
```typescript
|
|
1239
|
-
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1240
|
-
|
|
1241
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
1242
|
-
const edge = SynchronousSignalCollectable.useRate(
|
|
1243
|
-
Array.from({ length: 512 }, (_unused, index) => Math.cos(2 * Math.PI * 8.3 * index / 512)),
|
|
1244
|
-
512,
|
|
1245
|
-
);
|
|
1246
|
-
const envelope = edge.envelope();
|
|
1247
|
-
|
|
1248
|
-
round(envelope.slice(8, -8).reduce((sum, value) => sum + value, 0) / 496); // 1.0025
|
|
1249
|
-
round(Math.max(...envelope.slice(0, 8))); // 1.2592
|
|
1250
|
-
round(Math.min(...envelope.slice(-8))); // 0.1314
|
|
1251
|
-
```
|
|
1252
|
-
|
|
1253
|
-
**消える包絡線は位相を定義しない。** 包絡線が潰れるところでは、丸め誤差の二つの角度の差が
|
|
1254
|
-
半回転になり得て、サンプリングレートの半分として読める。門限こそがこの数を意味のあるものに
|
|
1255
|
-
する。既定値はピーク包絡線の `1E-3` である。
|
|
1256
|
-
|
|
1257
|
-
```typescript
|
|
1258
|
-
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1259
|
-
|
|
1260
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
1261
|
-
const beat = SynchronousSignalCollectable.useRate(
|
|
1262
|
-
Array.from({ length: 4096 }, (_unused, index) =>
|
|
1263
|
-
Math.cos(2 * Math.PI * 100 * index / 2048) + Math.cos(2 * Math.PI * 102 * index / 2048)),
|
|
1264
|
-
2048,
|
|
1265
|
-
);
|
|
1266
|
-
|
|
1267
|
-
round(Math.max(...beat.instantaneousFrequency(2048, 0).map(Math.abs))); // 920.218
|
|
1268
|
-
round(Math.max(...beat.instantaneousFrequency().map(Math.abs))); // 101
|
|
1269
|
-
```
|
|
1270
|
-
|
|
1271
|
-
**正規化されていない相関は類似度ではない。** 生の和 `r[k]` は記録長とともに増えるので、
|
|
1272
|
-
二つの長さは比較できない。既定の `"coefficient"` は `r[0]` で割り、すべての値を
|
|
1273
|
-
`[-1, 1]` に保つ。`"unbiased"` は `count − k` で割り、分散が遅れとともに増える代償を払う。
|
|
1274
|
-
|
|
1275
|
-
```typescript
|
|
1276
|
-
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1277
|
-
|
|
1278
|
-
const round = (value: number): number => Number(value.toFixed(4));
|
|
1279
|
-
const periodic = SynchronousSignalCollectable.useRate(
|
|
1280
|
-
Array.from({ length: 1024 }, (_unused, index) => Math.cos(2 * Math.PI * 8 * index / 1024)),
|
|
1281
|
-
1024,
|
|
1282
|
-
);
|
|
1283
|
-
const rho = periodic.autocorrelation(200n);
|
|
1284
|
-
|
|
1285
|
-
round(rho[0]); // 1
|
|
1286
|
-
round(rho[128]); // 0.875
|
|
1287
|
-
round(rho[64]); // -0.9375
|
|
1288
|
-
round(periodic.autocorrelation(200n, "none")[0]); // 512
|
|
1289
|
-
```
|
|
1290
|
-
|
|
1291
|
-
もう一つ警告に値する読みがある。`spectralFlux` は比較の前に各フレームを正規化するので、
|
|
1292
|
-
レベル変化だけでは通量は零になる。その先頭と末尾の値は零境界の縁の効果であり、立ち上がり
|
|
1293
|
-
ではない。
|
|
1294
|
-
|
|
1295
|
-
---
|
|
1296
|
-
|
|
1297
|
-
<a id="15-license"></a>
|
|
1298
|
-
## 15. ライセンス
|
|
1299
|
-
|
|
1300
|
-
MIT © Eloy Kim
|
|
1
|
+
# **Semantic-TypeScript**
|
|
2
|
+
|
|
3
|
+
**Flow, Indexed.** あなたのデータを、正確に制御します。
|
|
4
|
+
|
|
5
|
+
[English](./readme.md) · [简体中文](./readme.cn.md) · [繁體中文](./readme.tw.md) · 日本語 · [한국어](./readme.kr.md) · [Deutsch](./readme.de.md) · [Français](./readme.fr.md) · [Español](./readme.es.md) · [Русский](./readme.ru.md)
|
|
6
|
+
|
|
7
|
+
**JavaScript ジェネレーター**、**Java Stream**、そして**インデックスベースのデータアドレッシング**を統合する、型安全なストリーム処理ライブラリです。各要素は `bigint` インデックスを保持し、すべてのパイプラインは遅延評価され、すべての終端操作は明示的な opt-in です。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install semantic-typescript
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- 実行時依存関係ゼロ、純粋な ESM、独自の型定義を同梱。
|
|
14
|
+
- 684 のリグレッションテスト:`npm test`。
|
|
15
|
+
- 変更履歴全体:[CHANGELOG.md](./CHANGELOG.md)。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Table of Contents
|
|
20
|
+
|
|
21
|
+
1. [設計哲学](#1-design-philosophy)
|
|
22
|
+
2. [アーキテクチャ](#2-architecture)
|
|
23
|
+
3. [クイックスタート](#3-quick-start)
|
|
24
|
+
4. [ファクトリ関数](#4-factory-functions)
|
|
25
|
+
5. [変換オペレーター](#5-transformation-operators)
|
|
26
|
+
6. [コレクタブルと終端操作](#6-collectables-and-terminal-operations)
|
|
27
|
+
7. [統計とウィンドウ](#7-statistics-and-windows)
|
|
28
|
+
8. [インデックスモデル](#8-the-index-model)
|
|
29
|
+
9. [非同期モデル](#9-the-asynchronous-model)
|
|
30
|
+
10. [周期性解析:リング・場・スコア](#10-periodic-analysis-rings-fields-and-scores)
|
|
31
|
+
11. [ユーティリティヘルパー](#11-utility-helpers)
|
|
32
|
+
12. [パフォーマンスと落とし穴](#12-performance-and-pitfalls)
|
|
33
|
+
13. [位相環:正確な周期](#13-phase-rings)
|
|
34
|
+
14. [信号解析:窓・スペクトル・時間](#14-signal-analysis)
|
|
35
|
+
15. [ライセンス](#15-license)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<a id="1-design-philosophy"></a>
|
|
40
|
+
## 1. 設計哲学
|
|
41
|
+
|
|
42
|
+
三つのアイデア、ひとつのパイプライン:
|
|
43
|
+
|
|
44
|
+
**インデックスはデータです。** すべての要素は `bigint` によってアドレス指定されます。反転、回転、シャッフル、分割はバッファ操作ではなくインデックス演算です。コレクタブルが要求するまで、何も具体化されません。
|
|
45
|
+
|
|
46
|
+
**遅延がデフォルトです。** ファクトリはジェネレーターをラップし、オペレーターはさらに別のジェネレーターをラップします。終端操作が実行されるまで要素は要求されないため、下流の何かが停止させる限り、無限のソースはまったく合法です。
|
|
47
|
+
|
|
48
|
+
**終端は明示的です。** `map` が返すのは配列ではなく `Semantic` です。値が現れるのは `toUnordered()`、`toOrdered()`、`toNumericStatistics()`、`toWindow()` といったコレクタブルを名指ししたときだけで、それぞれがどれだけのコストを払い、どのような順序を生むのかを明示します。
|
|
49
|
+
|
|
50
|
+
同じ規律はライブラリの数学的な半分にも当てはまります。`Complex`、`Angle`、`Frequency` は構築時に不変条件が検査される不変オブジェクトであり、`Optional` は `null` を置き換え、位相リングは自分が保持する要素数、周期、そしてどれだけ均衡しているかを正確に語ります。
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<a id="2-architecture"></a>
|
|
55
|
+
## 2. アーキテクチャ
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
factory ──▶ Semantic ──▶ Semantic ──▶ Collectable ──▶ result
|
|
59
|
+
(source) (map) (filter) (terminal)
|
|
60
|
+
▲
|
|
61
|
+
└── every operator returns a new Semantic wrapping a generator
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Layer | Module | Responsibility |
|
|
65
|
+
| :--- | :--- | :--- |
|
|
66
|
+
| Sources | `synchronous/collectors`, `asynchronous/collectors` | イテラブル、ジェネレーター、Promise、Blob、イベントターゲットをラップします |
|
|
67
|
+
| Pipelines | `synchronous/semantic`, `asynchronous/semantic` | 遅延オペレーターとインデックスの書き換え |
|
|
68
|
+
| Collectables | `synchronous/semantic`, `asynchronous/semantic` | 順序付き、順序なし、統計、ウィンドウ、およびカスタムの終端 |
|
|
69
|
+
| Collectors | `synchronous/collector`, `asynchronous/collector` | すべての終端がそこから組み立てられる畳み込みプリミティブ |
|
|
70
|
+
| Mathematics | `mathematics/*` | 角度、複素数、周波数、スペクトル、ウィンドウ、推定量、周転円、位相リング |
|
|
71
|
+
| Structures | `structure/*` | リスト、集合、マップ、ツリー、フォレスト、リング、場 |
|
|
72
|
+
| Errors | `structure/error` | 型付き `SemanticError` 階層とそのブランドガード |
|
|
73
|
+
|
|
74
|
+
同期系と非同期系はひとつの語彙を共有します。同じオペレーター名、同じインデックスセマンティクス、同じコレクタブルです。非同期版は単に Promise を返し、書き込みをキューに入れるため、書き込みの後に発行された読み取りは常にその書き込みを観測します。
|
|
75
|
+
|
|
76
|
+
### 2.1 今回のリリースで移ったもの、壊れたもの
|
|
77
|
+
|
|
78
|
+
三つのモジュールが位置または形を変え、いくつかの既定値は意味が変わりました。古い挙動はどれも明示的なオプションから到達できます。置き換えなしに削除されたものはありません。
|
|
79
|
+
|
|
80
|
+
| Change | Before | Now |
|
|
81
|
+
| :--- | :--- | :--- |
|
|
82
|
+
| フォレスト | `structure/tree/rootedForest`、`structure/tree/unionFindForest` | `structure/forest/rootedForest`、`structure/forest/unionFindForest`;`tree/index.ts` は再エクスポートしなくなりました |
|
|
83
|
+
| エラー | 場当たり的なネイティブエラー | `structure/error.ts` の十五クラス階層。各クラスにブランドガード、加えて `SemanticRangeError` 別名 |
|
|
84
|
+
| 推定量オプション | 固定の定義 | `mathematics/quantile.ts` が `QuantileMethod`、`QuantileOptions`、`SkewnessEstimator`、`KurtosisEstimator`、`VarianceOptions` をエクスポート |
|
|
85
|
+
| `quantile` | Hyndman–Fan タイプ 1 | タイプ 7 — R `quantile`、NumPy `linear`、Excel `PERCENTILE.INC` |
|
|
86
|
+
| `skewness` / `kurtosis` | `g1` / `g2` | バイアス補正された `G1` / `G2` |
|
|
87
|
+
| `variance` / `standardDeviation` | `ddof = 0` | `ddof = 1`、`Optional` でラップ |
|
|
88
|
+
| `decibel` | 単一の `20·log10` 関数 | 削除。`amplitudeDecibel`(20·log10)と `powerDecibel`(10·log10)に分割 |
|
|
89
|
+
| `Spectrum.flatness()` | 両半分の振幅 | 片側半分のパワー |
|
|
90
|
+
| `Spectrum.bandEnergy()` | 生の両側和 | 既定は片側で、共役な半分を折り畳みます |
|
|
91
|
+
| `shortTimeFourierTransform` / `spectrogram` | 対称 Hann、完全なフレームのみ | 周期 Hann、`boundary: "zeros"`、`padded: true` |
|
|
92
|
+
|
|
93
|
+
第 6、7、10 章が置き換えを説明しています。実測された影響は [CHANGELOG.md](./CHANGELOG.md) にあります。
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
<a id="3-quick-start"></a>
|
|
98
|
+
## 3. クイックスタート
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import {
|
|
102
|
+
useOf, useFrom, useRange, useText, useStringify, usePromise
|
|
103
|
+
} from "semantic-typescript";
|
|
104
|
+
|
|
105
|
+
// Numeric aggregation
|
|
106
|
+
const sum: number = useOf(10, 20, 30, 40)
|
|
107
|
+
.map(n => n * 2)
|
|
108
|
+
.toNumericStatistics()
|
|
109
|
+
.summate(); // 200
|
|
110
|
+
|
|
111
|
+
// BigInt aggregation
|
|
112
|
+
const bigSum: bigint = useOf(10n, 20n, 30n, 40n)
|
|
113
|
+
.map(n => n * 2n)
|
|
114
|
+
.toBigintStatistics()
|
|
115
|
+
.summate(); // 200n
|
|
116
|
+
|
|
117
|
+
// Reordering is index arithmetic — collect with toOrdered() for it to take effect
|
|
118
|
+
const reversed: number[] = useFrom([1, 2, 3, 4, 5]).reverse().toOrdered().toArray(); // [5,4,3,2,1]
|
|
119
|
+
const rotated: number[] = useFrom([1, 2, 3, 4, 5]).translate(2).toOrdered().toArray(); // [4,5,1,2,3]
|
|
120
|
+
|
|
121
|
+
// Lazy evaluation over a large range
|
|
122
|
+
const multiples: bigint[] = useRange(0n, 1_000_000n)
|
|
123
|
+
.filter(n => n % 17n === 0n)
|
|
124
|
+
.limit(10n)
|
|
125
|
+
.toUnordered()
|
|
126
|
+
.toArray(); // [0n,17n,…,153n]
|
|
127
|
+
|
|
128
|
+
// Unicode-safe string processing (code points, not UTF-16 units)
|
|
129
|
+
useText("semantic 👍").toUnordered().forEach(character => console.log(character));
|
|
130
|
+
|
|
131
|
+
// Circular-safe serialisation
|
|
132
|
+
const cyclic: any = { a: 1 }; cyclic.self = cyclic;
|
|
133
|
+
useStringify(cyclic); // '{"a":1}'
|
|
134
|
+
|
|
135
|
+
// Terminals hand the elements to real structures, not only to arrays
|
|
136
|
+
useOf(3, 1, 4, 1, 5).toCollectable().toOrderedSet().toArray(); // [1,3,4,5]
|
|
137
|
+
useFrom([3, 1, 4, 1, 5]).toCollectable().toBinarySearchTree().size(); // 5n
|
|
138
|
+
|
|
139
|
+
// Async sources
|
|
140
|
+
const total = await usePromise(Promise.resolve(7)).toNumericStatistics().summate(); // 7
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
<a id="4-factory-functions"></a>
|
|
146
|
+
## 4. ファクトリ関数
|
|
147
|
+
|
|
148
|
+
構築は常に O(1) です — ファクトリはジェネレーターをラップするだけです。以下の表は `n` 個の要素を*消費*するコストと、ソース自身が必要とする追加の空間を示します。
|
|
149
|
+
|
|
150
|
+
| Factory | Signature (result) | Time (consume n) | Space (source) | Notes |
|
|
151
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
152
|
+
| `useOf` | `useOf<E>(...elements) → SynchronousSemantic<E>` | O(n) | O(n) | インデックスは 0..n−1;スプレッド引数はすでに具体化されています |
|
|
153
|
+
| `useFrom` | `useFrom<E>(Iterable<E>) → SynchronousSemantic<E>`<br>`useFrom<E>(AsyncIterable<E>) → AsynchronousSemantic<E>` | O(n) | O(1) | 任意のイテラブルをラップ;収集時に一度だけ反復します |
|
|
154
|
+
| `useRange` | `useRange<N>(start, end, step?) → SynchronousSemantic<N>` | O(n) | O(1) | `end` は排他的;`step` は非ゼロでなければならず、`number` と `bigint` の両方が使えます |
|
|
155
|
+
| `useFill` | `useFill<E>(element \| supplier, count) → SynchronousSemantic<E>` | O(n) | O(1) | サプライヤーは要素ごとに一度だけ呼ばれます |
|
|
156
|
+
| `useEmpty` | `useEmpty<E>() → SynchronousSemantic<E>` | O(1) | O(1) | `concat` の単位元 |
|
|
157
|
+
| `useText` | `useText(text, delimiter?)` | O(n) | テキスト長に対して O(n) | **コードポイント**単位で反復するため、絵文字や CJK も単一要素のままです |
|
|
158
|
+
| `useAttribute` | `useAttribute<T>(object)` | O(v + e) | O(d + v) | 循環保護付きでネストしたオブジェクトを再帰的に走査します |
|
|
159
|
+
| `useGenerate` | `useGenerate<E>(supplier, interrupt)` | O(n) × サプライヤーのコスト | O(1) | 停止する `interrupt` か、下流の `limit` が**必須**です |
|
|
160
|
+
| `useIterate` | `useIterate<E>(Iterable<E>)` / `(AsyncIterable<E>)` | 収集ごとに O(n) | O(1) | 収集のたびにイテレーターを再生します |
|
|
161
|
+
| `useInterval` | `useInterval(period, delay?) → AsynchronousSemantic<number>` | n × period(実時間) | O(1) | 経過時間を発行します |
|
|
162
|
+
| `usePromise` | `usePromise<T>(promise) → AsynchronousSemantic<T>` | O(1) | O(1) | インデックス `0n` の単一要素 |
|
|
163
|
+
| `useBlob` | `useBlob(blob, chunk?) → AsynchronousSemantic<Uint8Array>` | O(blob のサイズ) | コピーごとに O(chunk) | デフォルトのチャンクは 64 KiB |
|
|
164
|
+
| `useSubscription` | `useSubscription(target, subscriber, event) → AsynchronousSemantic<Event>` | イベントごとに 1 要素 | O(1) | `limit()` / `takeWhile()` が購読解除の境界を定義します |
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
useOf(1, 2, 3).toUnordered().toArray(); // [1,2,3]
|
|
168
|
+
useFrom(new Set([1, 2, 2])).toUnordered().toArray(); // [1,2]
|
|
169
|
+
useRange(0, 5).toUnordered().toArray(); // [0,1,2,3,4]
|
|
170
|
+
useRange(10n, 0n, -5n).toUnordered().toArray(); // [10n,5n]
|
|
171
|
+
useFill("x", 3n).toUnordered().toArray(); // ["x","x","x"]
|
|
172
|
+
useText("A😊B").toUnordered().toArray(); // ["A","😊","B"]
|
|
173
|
+
useGenerate(() => 7, (_e, index) => index >= 3n).toUnordered().toArray(); // [7,7,7]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
<a id="5-transformation-operators"></a>
|
|
179
|
+
## 5. 変換オペレーター
|
|
180
|
+
|
|
181
|
+
すべてのオペレーターは新しい `Semantic` を返します;コレクタブルが実行されるまで、どれも要素に触れません。Time は**要素ごと**の追加コストであり、Space はパイプラインの実行中にオペレーターが保持し続けるものです。
|
|
182
|
+
|
|
183
|
+
| Operator | Purpose | Index effect | Time | Space |
|
|
184
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
185
|
+
| `map(mapper)` | 1:1 変換 | 転送 | O(1) | O(1) |
|
|
186
|
+
| `filter(predicate)` | 条件に合う要素を残す | 転送 | O(1) | O(1) |
|
|
187
|
+
| `flat(mapper)` / `flatMap(mapper)` | 1:N の平坦化 | 0 から再インデックス | 発行ごとに O(1) | O(1) |
|
|
188
|
+
| `distinct(keyExtractor?)` | 重複キーを捨て、最初のものを残す | 転送 | 平均 O(1) | O(n) |
|
|
189
|
+
| `peek(consumer)` | 副作用、要素はそのまま通過 | 転送 | O(1) | O(1) |
|
|
190
|
+
| `limit(count)` | 最初の *n* 個を取る | 0 から再インデックス | O(1) | O(1) |
|
|
191
|
+
| `skip(count)` | 最初の *n* 個を捨てる | 転送 | O(1) | O(1) |
|
|
192
|
+
| `sub(start, end)` | 位置指定スライス `[start, end)` | 転送 | O(1) | O(1) |
|
|
193
|
+
| `takeWhile(predicate)` | 述語が成り立つ間だけ取る | 転送 | O(1) | O(1) |
|
|
194
|
+
| `dropWhile(predicate)` | 述語が成り立つ間だけ捨てる | 0 から再インデックス | O(1) | O(1) |
|
|
195
|
+
| `concat(other)` | `Semantic` またはイテラブルを後ろに連結 | インデックスを継続 | O(1) | O(1) |
|
|
196
|
+
| `redirect(redirector)` | 新しいインデックスを好きなように計算 | **任意** | O(1) | O(1) |
|
|
197
|
+
| `reverse()` | `redirect((_e, i) => -i - 1n)` | 符号反転してシフト | O(1) | O(1) |
|
|
198
|
+
| `translate(offset \| mapper)` | すべてのインデックスをシフト | `i + offset` | O(1) | O(1) |
|
|
199
|
+
| `shuffle(mapper?)` | インデックスをランダム化 | ランダム | O(1) | O(1) |
|
|
200
|
+
| `pipe(conversion)` | ジェネレーターを独自関数に渡す | 任意 | 任意 | 任意 |
|
|
201
|
+
| `semantic()` | コレクタブルを `Semantic` に戻す | 変更なし | O(1) | O(1) |
|
|
202
|
+
|
|
203
|
+
**`reverse`、`translate`、`shuffle` はインデックスを書き換えるだけです。** `toOrdered()` が要素数を法としてインデックスを正規化するまで、何もバッファリングされず、何も回転しません:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
useFrom([1, 2, 3, 4, 5]).reverse().toOrdered().toArray(); // [5,4,3,2,1]
|
|
207
|
+
useFrom([1, 2, 3, 4, 5]).translate(2).toOrdered().toArray(); // [4,5,1,2,3] (rotation)
|
|
208
|
+
useFrom([1, 2, 3, 4, 5]).translate(7).toOrdered().toArray(); // [4,5,1,2,3] (7 ≡ 2 mod 5)
|
|
209
|
+
useFrom([1, 2, 3]).shuffle((_e, i) => -i - 1n).toOrdered().toArray(); // [3,2,1]
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
<a id="6-collectables-and-terminal-operations"></a>
|
|
215
|
+
## 6. コレクタブルと終端操作
|
|
216
|
+
|
|
217
|
+
### 6.1 コレクタブル
|
|
218
|
+
|
|
219
|
+
コレクタブルは**順序**と**インデックスの簿記**の両方を担います。`n` は要素数です。
|
|
220
|
+
|
|
221
|
+
| Collectable | Structure | Time | Order | Use when |
|
|
222
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
223
|
+
| `toUnordered()` | インデックスをキーとする `Map<bigint, E>` | O(n) | 挿入順 | 最大のスループット;生のインデックスはそのまま保持されます |
|
|
224
|
+
| `toOrdered()` | 要素インデックスでソートされた配列 | O(n log n) | 正規化されたインデックス順 | 反転や回転を反映させたいとき |
|
|
225
|
+
| `toOrdered(comparator)` | 独自の比較器でソート | O(n log n) | 独自の比較器順 | ソート済みのビュー |
|
|
226
|
+
| `toNumericStatistics()` | 値でソートされたバッファ | O(n log n) | 値順 | `number` に対する統計 |
|
|
227
|
+
| `toBigintStatistics()` | 値でソートされたバッファ | O(n log n) | 値順 | `bigint` に対する統計 |
|
|
228
|
+
| `toWindow()` | 値でソートされたバッファ | O(n log n) | 値順 | `slide` / `tumble` |
|
|
229
|
+
| `toCollectable(mapper)` | mapper が返したもの | — | — | カスタムのコレクタブル |
|
|
230
|
+
|
|
231
|
+
### 6.2 終端
|
|
232
|
+
|
|
233
|
+
すべての終端は `Semantic` ではなく**コレクタブル**の上にあります。「終端は明示的である」とは実践的にはそういう意味です。コレクタブルを名指しするまでパイプラインは何も生み出さず、mapper を渡さない `toCollectable()` がもっとも安価な名指しの方法です。
|
|
234
|
+
|
|
235
|
+
| Terminal | Returns | Time | Notes |
|
|
236
|
+
| :--- | :--- | :--- | :--- |
|
|
237
|
+
| `toArray()` / `toSet()` | `Array<E>` / `Set<E>` | O(n) | バッファを排出します;`toSet` は同一性で重複排除します |
|
|
238
|
+
| `toMap(key, value?)` | `Map<K, V>` | 平均 O(n) | 最後の書き込みが優先されます |
|
|
239
|
+
| `count()` / `isEmpty()` | `bigint` / `boolean` | O(1) | バッファがすでに知っています |
|
|
240
|
+
| `forEach(action)` | `void` | O(n) | `action(element, index)` |
|
|
241
|
+
| `join(…)` | `string` | O(n) | `join()`、`join(delimiter)`、`join(prefix, delimiter, suffix)` |
|
|
242
|
+
| `reduce(…)` | `Optional<E>` / `E` / `R` | O(n) | 空のストリーム → 空の `Optional` |
|
|
243
|
+
| `group(classifier)` / `groupBy(key, value)` | `Map<K, E[]>` | 平均 O(n) | — |
|
|
244
|
+
| `partition(count)` | `Array<Array<E>>` | O(n) | インデックスによるラウンドロビン |
|
|
245
|
+
| `partitionBy(classifier)` | `Array<Array<E>>` | O(n) | バケット番号は分類器から決まります |
|
|
246
|
+
| `anyMatch` / `allMatch` / `nonMatch` | `boolean` | 最悪 O(n)、短絡します | — |
|
|
247
|
+
| `findAny` / `findFirst` / `findLast` | `Optional<E>` | O(1)–O(n) | 具体化された順序での位置指定 |
|
|
248
|
+
| `findAt(k)` / `findNegativeAt(k)` | `Optional<E>` | O(k) / O(n−k) | 先頭/末尾から数えます |
|
|
249
|
+
| `findMaximum(cmp?)` / `findMinimum(cmp?)` | `Optional<E>` | O(n) | デフォルトは構造的比較 |
|
|
250
|
+
| `frequency()` | `Map<E, bigint>` | 平均 O(n) | 統計コレクタブルのみ |
|
|
251
|
+
| `write(stream, accumulator?)` | `WritableStream<S>` | O(n) | IO 的な終端 |
|
|
252
|
+
| `collect(collector)` | `R` | O(n) | 上記すべての終端を支える汎用の畳み込み |
|
|
253
|
+
|
|
254
|
+
### 6.3 構造コレクター
|
|
255
|
+
|
|
256
|
+
`toArray`、`toSet`、`toMap` はパイプラインを JavaScript のコンテナで終わらせます。さらに十二の終端は、ライブラリ自身の構造のいずれかでそれを終わらせます。結果は配列がたまたま持つ不変量ではなく、その構造が保証する不変量を携えます。これらはすべてのコレクタブルで利用でき、`toCollectable()` が返す生のコレクタブルも含まれます。
|
|
257
|
+
|
|
258
|
+
| Terminal | Returns | Order | Cost |
|
|
259
|
+
| :--- | :--- | :--- | :--- |
|
|
260
|
+
| `toArray()` | `Array<E>` | 走査順 | O(n) |
|
|
261
|
+
| `toSet()` | `Set<E>` | 挿入順、SameValueZero | 平均 O(n) |
|
|
262
|
+
| `toMap(key, value?)` | `Map<K, V>` | 挿入順;後書きが優先 | 平均 O(n) |
|
|
263
|
+
| `toOrderedSet(comparator?)` | `SynchronousOrderedSet<E>` | 比較器の昇順 | 最悪 O(n²) |
|
|
264
|
+
| `toUnorderedSet()` | `SynchronousUnorderedSet<E>` | 未指定(ハッシュバケット) | 期待 O(n) |
|
|
265
|
+
| `toOrderedMap(key, value?)` | `SynchronousOrderedMap<K, V>` | 挿入順;後書きが優先 | 期待 O(n) |
|
|
266
|
+
| `toUnorderedMap(key, value?)` | `SynchronousUnorderedMap<K, V>` | 未指定 | 期待 O(n) |
|
|
267
|
+
| `toList()` | `SynchronousList<E>` | 走査順、重複を保持 | O(n) |
|
|
268
|
+
| `toRing(span?)` | `SynchronousRing<E>` | 走査順がリング上の位置を決めます | O(n) |
|
|
269
|
+
| `toLRUSet(capacity)` | `SynchronousUnorderedLRUSet<E>` | 最近使用順;最後の `capacity` 個だけが残ります | 最悪 O(n²) |
|
|
270
|
+
| `toLRUMap(capacity, key, value?)` | `SynchronousUnorderedLRUMap<K, V>` | 最近使用順 | 期待 O(n) |
|
|
271
|
+
| `toSkipSet(comparator?)` | `SynchronousSkipSet<E>` | 比較器の昇順、ナビゲート可能 | 最悪 O(n²) |
|
|
272
|
+
| `toSkipMap(key, value?)` | `SynchronousSkipMap<K, V>` | キー比較器順、ナビゲート可能 | 期待 O(n) |
|
|
273
|
+
| `toBinarySearchTree(comparator?)` | `SynchronousBinarySearchTree<E>` | 比較器の昇順、高さ平衡 | O(n log n) |
|
|
274
|
+
| `toRootedForest()` | `SynchronousRootedForest<E>` | 走査順がそのまま構造順になります | O(n) |
|
|
275
|
+
|
|
276
|
+
結果の型はコレクタブル自身の要素型に従うため、どの終端も型引数を取りません。`SynchronousCollectable<E>` は `Array<E>`、`SynchronousList<E>`、`SynchronousOrderedSet<E>` をそのまま返します。残るのは業務上の引数だけです — `toOrderedSet` / `toSkipSet` / `toBinarySearchTree` の比較器、LRU ペアの容量、`toRing` のスパン、そしてマップ族のキーと値の抽出器です。
|
|
277
|
+
|
|
278
|
+
リングは集合なので、重複した要素は最初の位置だけを保ちます。ほかの構造は、自身の契約が別段に定めない限り重複を保持します(`toOrderedSet`、`toUnorderedSet`、`toSkipSet` は集合であり、`toBinarySearchTree` と `toList` はそうではありません)。
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const numbers = useOf(3, 1, 4, 1, 5).toCollectable();
|
|
282
|
+
|
|
283
|
+
numbers.toOrderedSet().toArray(); // [1,3,4,5]
|
|
284
|
+
numbers.toUnorderedSet().size(); // 4n — the duplicate 1 collapses
|
|
285
|
+
numbers.toList().toArray(); // [3,1,4,1,5]
|
|
286
|
+
numbers.toSkipSet().first().get(); // 1 — navigable by comparator
|
|
287
|
+
numbers.toBinarySearchTree().size(); // 5n — duplicates retained
|
|
288
|
+
numbers.toRootedForest().getRoots().length; // 1
|
|
289
|
+
useOf(1, 2, 3).toCollectable().toRing().phaseAt(3n).get().toDegreeValue(); // 0
|
|
290
|
+
useOf(3, 1, 3).toCollectable().toOrderedMap(n => n, n => `v${n}`).get(3).get(); // "v3"
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
非同期のコレクタブルも同じ終端を持ち、それぞれを promise に解決します。したがって呼び出しの形は `await` が一つ増えるだけです。
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
<a id="7-statistics-and-windows"></a>
|
|
298
|
+
## 7. 統計とウィンドウ
|
|
299
|
+
|
|
300
|
+
### 7.1 統計のサーフェス
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
useOf(3, 1, 4, 1, 5).toNumericStatistics().summate(); // 14
|
|
304
|
+
useOf(3, 1, 4, 1, 5).toNumericStatistics().average(); // 2.8
|
|
305
|
+
useOf(3, 1, 4, 1, 5).toNumericStatistics().median(); // 3
|
|
306
|
+
useOf(3, 1, 4, 1, 5).toNumericStatistics().mode(); // 1
|
|
307
|
+
useOf(3, 1, 4, 1, 5).toNumericStatistics().variance().get(); // 3.2 (n − 1)
|
|
308
|
+
useOf(3, 1, 4, 1, 5).toNumericStatistics().standardDeviation().get(); // 1.7889 (n − 1)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
両方の統計ファミリーは同じサーフェスを公開します — `summate`、`average`、`median`、`mode`、`variance`、`standardDeviation`、`frequency`、`findMaximum`、`findMinimum` — そして `toBigintStatistics()` では `bigint` 演算になります。散らばりの尺度と分位点ファミリーは `Optional` を返します。標本が小さすぎて定義できないことがあるからです。`.get()`、`.orElse(…)`、`.isPresent()` で取り出してください。
|
|
312
|
+
|
|
313
|
+
### 7.2 推定量と既定値
|
|
314
|
+
|
|
315
|
+
既定値は R、NumPy、pandas、Excel が使う定義そのものです。したがってここで算出した値は、補正係数なしでそれらのツールと比較できます。
|
|
316
|
+
|
|
317
|
+
| 統計量 | 既定 | 以前 | 古い値を取り出すには |
|
|
318
|
+
| :--- | :--- | :--- | :--- |
|
|
319
|
+
| `quantile(probability)` | Hyndman–Fan タイプ 7 | タイプ 1 | `{ method: "type-1" }` |
|
|
320
|
+
| `interquartileRange()` | Hyndman–Fan タイプ 7 | タイプ 1 | `{ method: "type-1" }` |
|
|
321
|
+
| `skewness()` | バイアス補正された `G1` | `g1` | `{ estimator: "g1" }` |
|
|
322
|
+
| `kurtosis()` | バイアス補正された超過 `G2` | `g2` | `{ estimator: "g2" }` |
|
|
323
|
+
| `variance()` | `ddof = 1` | `ddof = 0` | `{ ddof: 0 }` |
|
|
324
|
+
| `standardDeviation()` | `ddof = 1` | `ddof = 0` | `{ ddof: 0 }` |
|
|
325
|
+
|
|
326
|
+
九つの Hyndman–Fan タイプはすべて `"type-1"` … `"type-9"` として利用でき、`{ excess: false }` は峰度に三を足し戻して非超過の四次標準化モーメントを報告します。オプション型 — `QuantileMethod`、`QuantileOptions`、`SkewnessEstimator`、`KurtosisEstimator`、`VarianceOptions` — は `mathematics/quantile.ts` からエクスポートされます。
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const sample = useOf(1, 2, 3, 4).toNumericStatistics();
|
|
330
|
+
|
|
331
|
+
sample.variance().get(); // 1.6667 (5/3, ddof = 1)
|
|
332
|
+
sample.variance({ ddof: 0 }).get(); // 1.25
|
|
333
|
+
sample.standardDeviation().get(); // 1.2910
|
|
334
|
+
sample.skewness().get(); // 0
|
|
335
|
+
sample.kurtosis().get(); // -1.2 (G2)
|
|
336
|
+
sample.kurtosis({ estimator: "g2" }).get(); // -1.36
|
|
337
|
+
sample.kurtosis({ excess: false }).get(); // 1.8
|
|
338
|
+
|
|
339
|
+
const quartiles = useOf(1, 2, 3, 4, 5, 6, 7, 8).toNumericStatistics();
|
|
340
|
+
quartiles.quantile(0.75).get(); // 6.25 (type 7)
|
|
341
|
+
quartiles.quantile(0.75, { method: "type-1" }).get(); // 6 (nearest rank)
|
|
342
|
+
quartiles.interquartileRange().get(); // 3.5
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`G1` と `G2` はそれぞれ観測が三つ、四つ未満だと未定義になり、`ddof = 1` の標本分散と標準偏差は二つ未満で未定義になります。その場合ライブラリは誤解を招くゼロではなく、空の `Optional` を返します。
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
useOf(1, 2, 3).toNumericStatistics().kurtosis().isEmpty(); // true (G2 needs four observations)
|
|
349
|
+
useOf(1, 2).toNumericStatistics().skewness().isEmpty(); // true (G1 needs three)
|
|
350
|
+
useOf(1).toNumericStatistics().variance().isEmpty(); // true (ddof = 1 needs two)
|
|
351
|
+
useOf(1).toNumericStatistics().variance({ ddof: 0 }).get(); // 0
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
bigint ビューも同じオプションを受け取り、すべての結果を整数の `bigint` に丸め戻します。
|
|
355
|
+
|
|
356
|
+
### 7.3 ウィンドウ
|
|
357
|
+
|
|
358
|
+
ウィンドウはバッファ内の要素を「パイプラインのパイプライン」として再公開します。そのため、ウィンドウを配列に具体化せずに消費できます。どちらの形式も最後の部分的なウィンドウを含みます。
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
useFrom([1, 2, 3, 4]).toWindow().slide(2n, 1n).toUnordered().toArray()
|
|
362
|
+
.map(window => window.toUnordered().toArray()); // [[1,2],[2,3],[3,4],[4]]
|
|
363
|
+
|
|
364
|
+
useFrom([1, 2, 3, 4]).toWindow().tumble(2n).toUnordered().toArray()
|
|
365
|
+
.map(window => window.toUnordered().toArray()); // [[1,2],[3,4]]
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
<a id="8-the-index-model"></a>
|
|
371
|
+
## 8. インデックスモデル
|
|
372
|
+
|
|
373
|
+
インデックスはすべての要素に付随する `bigint` です。オペレーターはそれを**転送**するか、ゼロから**再インデックス**するか、あなたに**計算**させます。
|
|
374
|
+
|
|
375
|
+
順序付き収集は各インデックスを要素数で剰余正規化してから、その結果でソートします。5 要素に対する `translate(7)` が `translate(2)` と同じになるのはこのためです:`7 ≡ 2 (mod 5)`。
|
|
376
|
+
|
|
377
|
+
順序なし収集はパイプラインが生成したインデックスをそのまま保ち、要素を到着順に返します。負のインデックスは合法であり、正規化されれば非負のインデックスより前に並ぶだけです。
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
<a id="9-the-asynchronous-model"></a>
|
|
382
|
+
## 9. 非同期モデル
|
|
383
|
+
|
|
384
|
+
`AsynchronousSemantic` は同期版のサーフェスを写し取り、Promise を返します。書き込みはキューに入るため、書き込みの後に発行された読み取りは常にそれを観測します。
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
const ring = new AsynchronousRing([1, 2]);
|
|
388
|
+
const added = ring.add(3); // queued
|
|
389
|
+
const sampled = ring.elementAt(2n); // observes the write above
|
|
390
|
+
await added;
|
|
391
|
+
(await sampled).get().element; // 3
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
本質的に非同期なファクトリ — `usePromise`、`useBlob`、`useInterval`、`useSubscription` — は直接 `AsynchronousSemantic` を返し、`useFrom` / `useIterate` は `AsyncIterable` を渡されると自分自身をアップグレードします。
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
<a id="10-periodic-analysis-rings-fields-and-scores"></a>
|
|
399
|
+
## 10. 周期性解析:リング・場・スコア
|
|
400
|
+
|
|
401
|
+
ライブラリの数学的な半分は、周期(cycle)をルックアップテーブルではなく第一級のオブジェクトとして扱います。三つの構成物が協調して働きます。
|
|
402
|
+
|
|
403
|
+
### 10.1 リングはひとつの整数
|
|
404
|
+
|
|
405
|
+
`Ring` は **n 重リング**(n-fold ring)です:要素数、位置的周期、異なる位相の数は、すべて同じ整数 `n` です。位置の位相はその正確な剰余から計算されるため、リングはビット単位で周期的です:
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
import { SynchronousRing } from "semantic-typescript";
|
|
409
|
+
|
|
410
|
+
const ring = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
411
|
+
ring.phaseAt(0n).get().toDegreeValue(); // 0
|
|
412
|
+
ring.phaseAt(1n).get().toDegreeValue(); // 60
|
|
413
|
+
ring.phaseAt(6n).get().getValue() === ring.phaseAt(0n).get().getValue(); // true, exactly
|
|
414
|
+
ring.purity(); // 1
|
|
415
|
+
ring.isBalanced(); // true
|
|
416
|
+
ring.sequences().map(z => z.magnitude()); // [0, 6, 0, 0, 0, 0]
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
`frequency` は位置間の位相ステップではなく、**環境(ambient)**の回転速度です:環境はすべてのフェーザー(phasor)を同じ角度だけ動かすので、内在的なパターンを壊すことは決してありません。`span` はリングが何回転分を占めるかを固定します。
|
|
420
|
+
|
|
421
|
+
`purity()` は基本モードが運ぶエネルギーの割合であり、`max(|c₁|², |c_{n−1}|²) / (n · Σ|z|²)` です。リングが回転を除いて 1 の `n` 乗根であるとき、かつそのときに限り `1` になり、必要なビンは二つだけなのでコストは `O(n)` です。リングは円をどちら向きにも周りうるため、両方向が検査されます。
|
|
422
|
+
|
|
423
|
+
### 10.2 場は有界のスコア
|
|
424
|
+
|
|
425
|
+
`PhaseField` は相対位相を `[-1, 1]` のスコアに写します:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
import { useField, useRadian } from "semantic-typescript";
|
|
429
|
+
|
|
430
|
+
const cosine = useField([1]); // cos(2πΔ): +1 aligned, −1 opposed
|
|
431
|
+
const mixed = useField([0.4, 0.3, 0.3]); // first three harmonics
|
|
432
|
+
|
|
433
|
+
cosine.sample(useRadian(0)); // 1
|
|
434
|
+
cosine.sample(useRadian(Math.PI)); // -1
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
スコアは打ち切られた余弦級数 `a₀ + Σ wₘ·cos(2πmΔ)` であり、コンストラクターが `|a₀| + Σ|wₘ| ≤ 1` を強制します。三角不等式により、スコアが `[-1, 1]` を外れることは決してありません — この境界は**構築の帰結**であり、後から適用されるクランプではありません。
|
|
438
|
+
|
|
439
|
+
場がテーブルではなく級数である理由:`n` 点のテーブルは自分のサンプルを正確に再現しますが、その三角補間はサンプル間でオーバーシュートします。スコア `[+1, +0.5, −1, −0.5, 0]` を持つ 5 点のテーブルは `[−1.0428, 1.0041]` に補間され、これだけで約束を破っています。有界の級数ならそうはなりません。
|
|
440
|
+
|
|
441
|
+
### 10.3 解析はデータをすべての要素に対して測る
|
|
442
|
+
|
|
443
|
+
リングの各要素は参照位相です。`analyse` はすべての観測をリング上に位置づけ直し、各参照に対して測定します:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
const ring = new SynchronousRing(["a", "b", "c", "d", "e"]);
|
|
447
|
+
const report = ring.analyse(["a", "c", "c", "a", "zz", "e"]);
|
|
448
|
+
|
|
449
|
+
report.matched; // 5
|
|
450
|
+
report.missed; // 1 ('zz' is not on the ring)
|
|
451
|
+
report.references.map(r => r.count); // [2, 0, 2, 0, 1]
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
各参照の行は `count`、平均相対位相 `mean`、平均合成ベクトル長(mean resultant length)`resultant` を報告します。覚えておく価値のある同一性がひとつあります:**`resultant` はすべての参照で同じ**です。基準を変えても、すべての相対位相が定数分だけ回転するからです。参照ごとに異なるのは件数と平均です。
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
const ring = new SynchronousRing(["a", "b", "c"]);
|
|
458
|
+
ring.scoresAt(0, cosine); // one score per position, in [-1, 1]
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### 10.4 関連する道具
|
|
462
|
+
|
|
463
|
+
| Call | Answers | Cost |
|
|
464
|
+
| :--- | :--- | :--- |
|
|
465
|
+
| `purity()` | リングはまだ 1 の `n` 乗根ですか? | O(n) |
|
|
466
|
+
| `resultant(t?)` | フェーザーは今どれだけ集中していますか? | O(n) |
|
|
467
|
+
| `analyse(data)` | このデータは各要素に対してどう位置づけられますか? | O(n · m) |
|
|
468
|
+
| `toSpectrum()` | リングをスペクトルとして表現、周波数軸には波数 | O(n²) |
|
|
469
|
+
| `useAngularSpectrum(ring, instants, plan)` | 時間にわたる `(ω, n)` 平面 | O(n² · frames) |
|
|
470
|
+
|
|
471
|
+
さらに二つのプリミティブがモデルを完成させます:`combineRings(a, b)` は二つのリングを最小公倍数の上で結合し(積ではなく引き戻し(pullback)であり、同じ時点を共有できないペアは単に存在しません)、`rate()` は各要素に固有の周波数を与えて、リングが環境に対してドリフトできるようにします。`jetAt`、`windingAt`、`errorIntegralAt` はそのドリフトの微分状態を公開します。
|
|
472
|
+
|
|
473
|
+
### 10.5 スペクトルウィンドウ
|
|
474
|
+
|
|
475
|
+
リングの構成は解析サーフェスの半分です。もう半分はサンプリングされた信号を扱います。もっとも再利用されるプリミティブは窓関数で、ここにあるどの形状にも二つの形式があります。対称形式はフレームを `count − 1` で割り、周期(DFT-even)形式は `count` で割ります。後者は完全な一周期より一つ短いので、DFT が前提とする周期的拡張のもとでテーパーが連続になります。
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
useHannWindow(1, 4); // 0.75 symmetric
|
|
479
|
+
useHannPeriodicWindow(1, 4); // 0.5 periodic
|
|
480
|
+
coherentGain(useHannPeriodicWindow, 16); // 0.5
|
|
481
|
+
equivalentNoiseBandwidth(useHannPeriodicWindow, 16); // 1.5
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
| 形状 | 対称 | 周期 | 備考 |
|
|
485
|
+
| :--- | :--- | :--- | :--- |
|
|
486
|
+
| 矩形 | `useRectangularWindow` | — | テーパーなし;周期の区別なし |
|
|
487
|
+
| Hann | `useHannWindow` | `useHannPeriodicWindow` | 短時間変換の既定 |
|
|
488
|
+
| Hamming | `useHammingWindow` | `useHammingPeriodicWindow` | — |
|
|
489
|
+
| Blackman | `useBlackmanWindow` | `useBlackmanPeriodicWindow` | — |
|
|
490
|
+
| Nuttall | `useNuttallWindow` | `useNuttallPeriodicWindow` | 4項 Blackman–Harris |
|
|
491
|
+
| フラットトップ | `useFlatTopWindow` | `useFlatTopPeriodicWindow` | 分解能より振幅精度 |
|
|
492
|
+
| Kaiser | `useKaiserWindow(beta)` | `useKaiserPeriodicWindow(beta)` | `beta` がメインローブ幅を決めます |
|
|
493
|
+
|
|
494
|
+
解析と重畳加算には周期形式を使ってください。半フレームのホップでは、周期 Hann 窓はどの長さでも和が `1.000000` になりますが、対称形式は `N = 16` で `0.841 dB`、`N = 1024` でも `0.013 dB` 波打ちます。フィルタ設計では対称形式を保ってください。そこでのフレームはテーパーの一周期ではありません。
|
|
495
|
+
|
|
496
|
+
フラットトップ窓はフレームの両端で**負の重み**を返します。フラットトップ窓とはそういうものだからです。ゼロで切り詰めず、そのまま返します。切り詰めるとメインローブが乱れ、この窓の存在理由である低いスキャロップ損失が壊れるからです。
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
useFlatTopWindow(0, 1024); // -0.000421 — negative by definition
|
|
500
|
+
Array.from({ length: 1024 }, (_unused, index) => useFlatTopWindow(index, 1024))
|
|
501
|
+
.filter(weight => weight < 0).length; // 578
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### 10.6 レベル、平坦度、帯域エネルギー
|
|
505
|
+
|
|
506
|
+
`decibel` は単一の関数としては消えました。振幅とパワーは同じ係数を共有しないからです。`amplitudeDecibel` は電圧のような比に `20·log10` を適用し、`powerDecibel` はパワー、エネルギー、平均二乗、パワースペクトル密度に `10·log10` を適用します。
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
amplitudeDecibel(0.25); // -12.0412
|
|
510
|
+
powerDecibel(0.25); // -6.0206
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
`Bin.decibel` と `Spectrum.decibelOf` は振幅レベルであり、`Spectrum.powerDecibelOf` が対応するパワーレベルです。パワーを振幅形式に与えると `20·log10(power)` を報告し、デシベル値が二倍になります。
|
|
514
|
+
|
|
515
|
+
二つのスペクトル測定は読み方が変わりました。
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
const twoBins = new Spectrum(new Sample(2, 1), [new Complex(2, 0), new Complex(1, 0)]);
|
|
519
|
+
twoBins.flatness(); // 0.8 — geometric over arithmetic mean of power
|
|
520
|
+
|
|
521
|
+
const shape = new Spectrum(new Sample(4, 1), [
|
|
522
|
+
new Complex(1, 0), new Complex(2, 0), new Complex(0, 0), new Complex(0, 0),
|
|
523
|
+
]);
|
|
524
|
+
shape.bandEnergy(new Frequency(0), new Frequency(2)); // 9
|
|
525
|
+
shape.bandEnergy(new Frequency(0), new Frequency(2), "two-sided"); // 5
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
`flatness()` はスペクトルの片側半分について `power()` の幾何平均と算術平均を比較します。両半分の振幅平均ではなく、`oneSided()` の表示専用の振幅二倍も意図的に適用しません。`bandEnergy()` は既定で片側の読み方を使い、共役な半分を折り畳みます。DC とナイキストは一度だけ、内部の各ビンは二度数えられます。軸全体を覆う帯域はこれで Parseval、`Σ|X|² = N·Σx²` を満たします。複素スペクトルには `"two-sided"` を渡してください。そのビンはすでに独立しています。
|
|
529
|
+
|
|
530
|
+
### 10.7 短時間解析
|
|
531
|
+
|
|
532
|
+
`Sampler.shortTimeFourierTransform` と `Sampler.spectrogram` は SciPy `stft` の慣例に従います。周期 Hann 窓、両端のゼロ境界、そして末尾のゼロ詰めです。
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
const sampler = new Sampler(new Sample(64, 1));
|
|
536
|
+
const spectrogram = sampler.spectrogram(time => Math.cos(2 * Math.PI * 4 * time), 16, 8);
|
|
537
|
+
|
|
538
|
+
spectrogram.size(); // 9 frames
|
|
539
|
+
spectrogram.getBoundary(); // "zeros"
|
|
540
|
+
spectrogram.getTime(6); // 0.75 — frame centres, not frame starts
|
|
541
|
+
sampler.spectrogram(time => Math.cos(2 * Math.PI * 4 * time), 16, 8, useHannWindow,
|
|
542
|
+
{ boundary: "none", padded: false }).size(); // 7 — complete frames only
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
両端の拡張は最初のフレームを最初のサンプルに中心合わせし、末尾の詰め物は最後のサンプルを到達可能にします。64 サンプル、フレーム 16、ホップ 8 では 7 ではなく 9 フレームになり、1000 サンプル、フレーム 256、ホップ 128 では 6 ではなく 9 フレームになります。フレーム時刻は開始ではなく中心です — 拡張ありのビューでは `(k·hop + (frameSize mod 2)/2)/rate`、拡張なしのビューでは `(k·hop + frameSize/2)/rate` — そして `toFrame()` は逆変換の前に拡張を切り詰めます。
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
<a id="11-utility-helpers"></a>
|
|
550
|
+
## 11. ユーティリティヘルパー
|
|
551
|
+
|
|
552
|
+
### 11.1 エラー階層
|
|
553
|
+
|
|
554
|
+
`structure/error.ts` は、パイプラインの**周囲**で送出されるエラー — 自分のコード、カスタムコレクター、アダプターから — のための十五クラス階層をエクスポートします。
|
|
555
|
+
|
|
556
|
+
```text
|
|
557
|
+
SemanticError
|
|
558
|
+
├── IllegalOperationError
|
|
559
|
+
├── ValidationError
|
|
560
|
+
│ ├── IndexError
|
|
561
|
+
│ ├── RangeError (also exported as SemanticRangeError)
|
|
562
|
+
│ └── ConfigurationError
|
|
563
|
+
├── CapacityError
|
|
564
|
+
├── StateError
|
|
565
|
+
│ └── IllegalStateError
|
|
566
|
+
├── ConcurrencyError
|
|
567
|
+
├── CollectionError
|
|
568
|
+
├── SerializationError
|
|
569
|
+
├── NumericError
|
|
570
|
+
│ └── ArithmeticError
|
|
571
|
+
└── PhaseError
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
各クラスは `name` を固定し、ネイティブの `cause` を転送し、任意の `context` 診断バッグを携え、`toJSON()` で直列化します。そして同名のブランドガードを持ちます。
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
const error = new CapacityError("LRU capacity exhausted", { context: { capacity: 0 } });
|
|
578
|
+
|
|
579
|
+
error.name; // "CapacityError"
|
|
580
|
+
isCapacityError(error); // true
|
|
581
|
+
isValidationError(error); // false — a capacity failure is not a validation failure
|
|
582
|
+
isSemanticError(error); // true
|
|
583
|
+
error.toJSON(); // { name: "CapacityError", message: "…", context: { capacity: 0 } }
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
`SemanticRangeError` はエクスポートされる `RangeError` と同じクラスなので、`instanceof` はどちらの名前でも成立します。`RangeError` をインポートするとモジュール全体でグローバルコンストラクタを隠してしまうため、この別名が存在します。どちらのクラスもライブラリ自身の引数検査からは送出されません。そこではネイティブの `TypeError` と `RangeError` が投げられます。この階層はその上の層のための語彙です。
|
|
587
|
+
|
|
588
|
+
### 11.2 ツール表
|
|
589
|
+
|
|
590
|
+
| Helper | Purpose |
|
|
591
|
+
| :--- | :--- |
|
|
592
|
+
| `useCompare(a, b)` | 混在するプリミティブ間の全順序 |
|
|
593
|
+
| `useToNumber` / `useToBigInt` | 無損失かつ範囲検査付きの数値変換 |
|
|
594
|
+
| `useRandom` | シード付き/シードなしの乱数ヘルパー |
|
|
595
|
+
| `useTraverse` | ネストしたオブジェクトと配列の構造的走査 |
|
|
596
|
+
| `useStringify` | 循環参照に安全な JSON シリアライズ |
|
|
597
|
+
| `useHash` / `maskOf` / `register` / `unregister` | 構造的ハッシュとハッシュ戦略レジストリ |
|
|
598
|
+
| `invalidate` / `validate` | 一貫したエラー形状を送出するガード節 |
|
|
599
|
+
| `Optional<T>` | ライブラリにおける `null` の置き換え:`Optional.of(x)` / `Optional.empty()` |
|
|
600
|
+
|
|
601
|
+
### 11.3 `useTraverse`
|
|
602
|
+
|
|
603
|
+
`useTraverse(target, callback)` は任意のオブジェクトをその**非 null の葉**へ平坦化します。ネストしたオブジェクトを下り、イテラブル値は位置ごとに走査し、`null` と `undefined` は飛ばし、一度訪れたオブジェクトには二度と入りません — したがって循環のあるオブジェクトグラフはスタックを溢れさせずに終了します。
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
const source = { id: 7, tags: ["a", "b"], owner: { name: "Ada", note: null } };
|
|
607
|
+
const leaves: string[] = [];
|
|
608
|
+
|
|
609
|
+
useTraverse(source, (key, value, path) => {
|
|
610
|
+
leaves.push(`${path.join(".")} = ${String(value)}`);
|
|
611
|
+
return true;
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
leaves; // ["id = 7", "tags.0 = a", "tags.1 = b", "owner.name = Ada"]
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
コールバックは葉ごとに一度だけ呼ばれ、その葉自身のキー(イテラブル由来ならその添字)、値、そしてそこへ至るパスを受け取ります。`false` を返すと現在の枝だけでなく**走査全体**を放棄します。
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
let visited = 0;
|
|
621
|
+
useTraverse({ a: 1, b: 2, c: 3 }, () => {
|
|
622
|
+
visited += 1;
|
|
623
|
+
return false;
|
|
624
|
+
});
|
|
625
|
+
visited; // 1 — the first leaf already stops the walk
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
循環が安全なのも同じ理由です。
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
const cyclic: any = { name: "root", child: { name: "leaf" } };
|
|
632
|
+
cyclic.child.parent = cyclic;
|
|
633
|
+
|
|
634
|
+
const names: string[] = [];
|
|
635
|
+
useTraverse(cyclic, (_key, value) => {
|
|
636
|
+
if (typeof value === "string") names.push(value);
|
|
637
|
+
return true;
|
|
638
|
+
});
|
|
639
|
+
names; // ["root", "leaf"] — the back edge to `cyclic` is not revisited
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**コールバックごとに独立したパスのスナップショットが渡されます。** 走査は内部に作業用配列を一つだけ持ち、葉を引き渡す瞬間にそれを `Object.freeze` で凍結した新しい配列へ複製します。スナップショットはその後変化しないので、そのまま保持でき、二つのコールバックが同じ配列を共有することも、走査をやり直して既に渡したスナップショットが乱れることもありません。型は `ReadonlyArray` なので書き込みはコンパイルエラーであり、凍結された配列への実行時の書き込みは `TypeError` も投げます。この安全性の代償は、報告する葉ごとに `O(depth)` の割り当てが一度増えることで、何も報告しない枝では追加の割り当ては発生しません。
|
|
643
|
+
|
|
644
|
+
```typescript
|
|
645
|
+
const paths: Array<ReadonlyArray<PropertyKey>> = [];
|
|
646
|
+
useTraverse({ a: { b: 1 }, c: 2 }, (_key, _value, path) => {
|
|
647
|
+
paths.push(path); // safe to keep as-is
|
|
648
|
+
return true;
|
|
649
|
+
});
|
|
650
|
+
paths[0] === paths[1]; // false — one array per call
|
|
651
|
+
paths.map(path => path.join(".")); // ["a.b", "c"]
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
`useAttribute` はこの走査をファクトリにしたものです。同じ葉を `{ key, value }` の組として報告するので、規則もコストも同一です。
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
useAttribute({ a: 1, b: { c: 2 } }).toUnordered().toArray();
|
|
658
|
+
// [{ key: "a", value: 1 }, { key: "c", value: 2 }]
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
<a id="12-performance-and-pitfalls"></a>
|
|
664
|
+
## 12. パフォーマンスと落とし穴
|
|
665
|
+
|
|
666
|
+
**遅延にはステージごとのコストがあります。** オペレーターはそれぞれジェネレーターのホップを一つ増やします。メモリ内配列に対するタイトな数値ループでは、素の `for` ループがどのパイプラインにも勝ります。パイプラインが有利になるのは、ソースが遅延的もしくは無限であるか、インデックスの規律が元を取れるほどパイプラインが長い場合です。
|
|
667
|
+
|
|
668
|
+
**`toOrdered()` はソートし、`toUnordered()` はソートしません。** 反転が「効いていない」ように見えるなら、そのコレクターは順序なしでした — インデックスの書き換えは、インデックスが正規化されて初めて順序になります。
|
|
669
|
+
|
|
670
|
+
**`interrupt` のない `useGenerate` は決して終わりません。** 停止述語か、下流の `limit` / `takeWhile` を必ず与えてください。
|
|
671
|
+
|
|
672
|
+
**`Ring` は挿入順を保つ集合です。** 要素は一意であり、最初に追加された順序を保ちます。重複する `add` は `false` を返し、重複を導入する `set` は例外を投げます。これにより、位相は(たまたま占めている位置ではなく)*要素*の関数になり得ます。
|
|
673
|
+
|
|
674
|
+
**`Spectrum` の片側ヘルパーは実信号を前提とします。** `oneSided()`、`getNyquist()`、`centroid()` は共役対称性を仮定します。位相リングの空間スペクトルは複素数であり、一般に対称ではありません — 円を逆向きに周るリングは、そのエネルギーをすべてナイキストの外側であるビン `n − 1` に置きます。ビンを直接読んでください。
|
|
675
|
+
|
|
676
|
+
**構造コレクターは終端であり、ビューではありません。** どれもパイプラインを排出してから完全な構造を構築するので、O(n) 時間と O(n) 空間を要し、無限のソースでは動きません。パイプラインの末尾で使い、パイプラインを回すループの中では決して使わないでください。
|
|
677
|
+
|
|
678
|
+
**`variance()` とその仲間は `Optional` を返します。** 小さすぎる標本で `variance().get()` を呼ぶと例外になります。標本サイズを自分で制御できないときは `.orElse(0)` を使うか `.isPresent()` を先に確認してください。`standardDeviation`、`quantile`、`interquartileRange`、`skewness`、`kurtosis` も同じです。
|
|
679
|
+
|
|
680
|
+
**解析に使うのは周期窓です。** `useHannWindow` などの対称形式はフィルタ設計用です。短時間変換には `useHannPeriodicWindow` とその同族が必要です。そうでなければ重畳加算の和がフレームの継ぎ目で波打ちます。
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
<a id="13-phase-rings"></a>
|
|
685
|
+
## 13. 位相環:正確な周期
|
|
686
|
+
|
|
687
|
+
第 10 章は環を「周期」のモデルとして導入し、データ集合をあらゆる位相に対して測るのに使った。
|
|
688
|
+
本章はその完全な面を与える。位置がどのように角度になり、環境回転が何を変え何を変えないのか、
|
|
689
|
+
そして環を時間に対して、データに対して、別の環に対して読む道具である。
|
|
690
|
+
|
|
691
|
+
`SynchronousRing` は位置リストであり、その下には先頭と末尾が接続された循環双方向リンクがある。
|
|
692
|
+
同時に挿入順の*集合*でもある。同じ要素は二度現れず、拒否された書き込みはリンクもサンプルも
|
|
693
|
+
変えない。これを単なるリストではなく環にしているのは、位置の位相が現在の長さに対する余りだけ
|
|
694
|
+
で決まることである。だから環はビット単位で周期的であり、平衡も漂流しない。
|
|
695
|
+
|
|
696
|
+
### 13.1 一つの整数を正確に計算する
|
|
697
|
+
|
|
698
|
+
環は `n` 個の要素を持ち、要素数・位置周期・異なる位相の数は同じ整数である。位置 `i` の位相は
|
|
699
|
+
|
|
700
|
+
`phase + span · 2π · (((i mod n) + n) mod n) / n`
|
|
701
|
+
|
|
702
|
+
であり、余りは `number` になる前に `bigint` で還元される。`phaseAt(i)` と `phaseAt(i + n)` が
|
|
703
|
+
単に近いのではなく*ビット単位で同一*なのはそのためである。二度目の呼び出しは同じ整数に還元
|
|
704
|
+
され、同じ積を評価し、同じ倍精度値を返す。`number` の正確な整数範囲をはるかに超える索引でも
|
|
705
|
+
正しい位置に着き、負の索引は環を逆にたどる。
|
|
706
|
+
|
|
707
|
+
| 呼び出し | 返り値 | 意味 |
|
|
708
|
+
| :--- | :--- | :--- |
|
|
709
|
+
| `new SynchronousRing(source, options?)` | 環 | `source` は任意の反復可能オブジェクト、重複は捨てる |
|
|
710
|
+
| `getPhase()` / `getFrequency()` / `getSpan()` | `Angle`、`Frequency`、`number` | 三つの構築パラメータ |
|
|
711
|
+
| `size()` | `bigint` | 周期でもある `n` |
|
|
712
|
+
| `phaseAt(index)` | `Optional<Angle>` | 固有位相のみ。時間・速率・周波数は無視 |
|
|
713
|
+
| `elementAt(index)` | `Optional<RingElement>` | 要素、固有位相、環境周波数、フェーザ |
|
|
714
|
+
| `frequencyAt(index)` | `Optional<Frequency>` | 環境回転。空環では空 |
|
|
715
|
+
| `toComplex()` / `toComplex(index)` | `Complex[]` / `Optional<Complex>` | 固有フェーザ |
|
|
716
|
+
|
|
717
|
+
`RingOptions` には要素ごとの関数が二つある。`magnitude(element, index)` はフェーザの長さを
|
|
718
|
+
与え、既定は 1。`rate(element, index)` は要素自身の固有周波数を与え、既定は 0、すなわち固定
|
|
719
|
+
マーカーである。
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
import { SynchronousRing } from "semantic-typescript";
|
|
723
|
+
|
|
724
|
+
const ring = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
725
|
+
|
|
726
|
+
ring.size(); // 6n
|
|
727
|
+
ring.phaseAt(0n).get().toDegreeValue(); // 0
|
|
728
|
+
ring.phaseAt(1n).get().toDegreeValue(); // 60
|
|
729
|
+
ring.phaseAt(-1n).get().toDegreeValue(); // 300
|
|
730
|
+
ring.phaseAt(6n).get().getValue() === ring.phaseAt(0n).get().getValue(); // true
|
|
731
|
+
ring.phaseAt(10n ** 20n).get().toDegreeValue(); // 240
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
六要素の環で `phaseAt(-1n)` は `300`、`10n ** 20n` は六で割って `4` 余るので `240` と読める。
|
|
735
|
+
どちらも浮動小数点の積を蓄積しない。索引の表現が変わっても同一性が正確であり続ける理由である。
|
|
736
|
+
|
|
737
|
+
### 13.2 環境回転と或る瞬間の状態
|
|
738
|
+
|
|
739
|
+
`frequency` は環が埋め込まれた環境の**環境回転レート**であり、位置間の位相ステップではない。
|
|
740
|
+
環境はすべての要素の位相を同じ角度だけ進めるので、固有の模様を変えられず、平衡を壊せず、
|
|
741
|
+
どの `phaseAt` の結果も変えられない。既定は `0 Hz` である。
|
|
742
|
+
|
|
743
|
+
`relativeAt(index, time)` はその共通角度を引いて、`PhaseField` が採点する座標を残す。
|
|
744
|
+
`viewAt` と `viewsAt` は或る瞬間の完全な状態を要素自身の `rate` 込みで返し、微分アクセサは
|
|
745
|
+
ドリフトを露出する。
|
|
746
|
+
|
|
747
|
+
| 呼び出し | 式 | 読むもの |
|
|
748
|
+
| :--- | :--- | :--- |
|
|
749
|
+
| `relativeAt(index, time)` | `固有位相 + 自身速率 · t − 環境回転 · t` | 環境から見た位相 |
|
|
750
|
+
| `viewAt(index, time)` / `viewsAt(time)` | 同上に要素・索引・時間・速率・フェーザを加える | 完全な状態 |
|
|
751
|
+
| `jetAt(index, time)` | `t` の位相、自身速率、`slip = 自身速率 − 環境回転` | 微分状態 |
|
|
752
|
+
| `windingAt(index, time)` | `trunc(slip · t)` | 滑った整数回転、零へ切り捨て |
|
|
753
|
+
| `errorIntegralAt(index, time, origin?)` | `∫ 相対位相 dt`、単位は回転 · 秒 | どれだけ先行/遅延したか |
|
|
754
|
+
| `resultant(time?)` | `abs(Σ w · e^{iθ}) / Σ w` | 位相の集中度、環境回転の影響を受けない |
|
|
755
|
+
| `analyse(data, time?)` | 参考ごとの計数と平均 | データを各要素に対して測る |
|
|
756
|
+
| `scoreAt(index, time, field)` / `scoresAt(time, field)` | 相対位相で場を標本化 | 位置ごとの有界スコア |
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
import { SynchronousRing, useHertz } from "semantic-typescript";
|
|
760
|
+
|
|
761
|
+
const moving = new SynchronousRing(["a", "b", "c", "d", "e", "f"], {
|
|
762
|
+
frequency: useHertz(1),
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
moving.getFrequency().getHertz(); // 1
|
|
766
|
+
moving.phaseAt(1n).get().toDegreeValue(); // 60 — intrinsic
|
|
767
|
+
moving.relativeAt(1n, 0.25).get().toDegreeValue(); // 330 — 60 − 90
|
|
768
|
+
moving.viewAt(1n, 0.25).get().relative.toDegreeValue(); // 330
|
|
769
|
+
moving.jetAt(1n, 0.25).get().slip; // -1
|
|
770
|
+
moving.purity(); // 1 — rotation cannot break it
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
`1 Hz` では環境は `0.25 s` で四分の一回転進むので、位置一の相対位相は `60 − 90 = 330` 度に
|
|
774
|
+
なり、固有位相は `60` のままである。要素自身は動かないので jet の `slip` は `-1` ヘルツで
|
|
775
|
+
あり、`purity()` は依然 `1` である。すべてのフェーザを同じ角度だけ回すのは変形ではない。
|
|
776
|
+
|
|
777
|
+
### 13.3 純度と平衡
|
|
778
|
+
|
|
779
|
+
`purity()` は環のエネルギーのうち基本モードが運ぶ割合である。
|
|
780
|
+
|
|
781
|
+
`max(abs(c₁)², abs(c_{n−1})²) / (n · Σ abs(z)²)`
|
|
782
|
+
|
|
783
|
+
環はどちらの向きにも円をたどり得るので、両方の基本ビンを見る。六要素で `span: 5` の環は一歩
|
|
784
|
+
`300` 度、すなわち `−60` 度で進む。逆向きに走り、全エネルギーを `c₅` に置くので、`c₁` だけを
|
|
785
|
+
見る式はそれを不純と誤判定する。必要なのはこの二つのビンと全エネルギーだけなので、費用は
|
|
786
|
+
`O(n)` のままである。
|
|
787
|
+
|
|
788
|
+
境界の扱いも契約の一部である。二要素未満の環には基本モードがなく、内容にかかわらず
|
|
789
|
+
`purity() === 0` と `isBalanced() === false` を報告する。フェーザの長さがすべて零の環は
|
|
790
|
+
エネルギーを持たず、同じく零を報告する。`isBalanced(epsilon?)` は `1 − purity() <= epsilon`
|
|
791
|
+
で、既定の許容差は `1E-9` である。
|
|
792
|
+
|
|
793
|
+
```typescript
|
|
794
|
+
import { SynchronousRing } from "semantic-typescript";
|
|
795
|
+
|
|
796
|
+
const forward = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
797
|
+
const backward = new SynchronousRing(["a", "b", "c", "d", "e", "f"], { span: 5 });
|
|
798
|
+
const weighted = new SynchronousRing(["a", "b", "c", "d"], {
|
|
799
|
+
magnitude: element => element === "a" ? 1 : 0,
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
forward.purity(); // 1
|
|
803
|
+
backward.phaseAt(1n).get().toDegreeValue(); // 300 — it runs backwards
|
|
804
|
+
backward.purity(); // 1 — the energy sits in c₅
|
|
805
|
+
new SynchronousRing(["only"]).purity(); // 0
|
|
806
|
+
new SynchronousRing(["only"]).isBalanced(); // false
|
|
807
|
+
weighted.purity(); // 0.25
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### 13.4 合力長と対称成分
|
|
811
|
+
|
|
812
|
+
`resultant(time?)` は平均合力長であり、フェーザ和の大きさを大きさの和で割ったものである。
|
|
813
|
+
環がまだ `n` 乗根のままかを問う `purity` と違い、`resultant` は今この瞬間にフェーザがどれ
|
|
814
|
+
だけ集中しているかを問う。平衡環は `0` を報告する。すべての要素が同じ角度を共有する環なら
|
|
815
|
+
`1` だが、集合の意味論が重複要素を禁じるので、実際には `magnitude` が一つ以外を零にしたとき
|
|
816
|
+
に `1` が現れる。
|
|
817
|
+
|
|
818
|
+
`zeroSequence()` は平均フェーザ、すなわち `c₀ / n` である。`sequences()` は直接変換によって
|
|
819
|
+
`n` 個の対称成分を低次から返す。`toSpectrum()` はそれらを `Spectrum` に包み、ビン `j` は
|
|
820
|
+
環一回転あたり `j` 周期を報告するので、周波数軸は波数軸になる。
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
import { SynchronousRing } from "semantic-typescript";
|
|
824
|
+
|
|
825
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
826
|
+
const ring = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
827
|
+
|
|
828
|
+
round(ring.resultant().get()); // 0
|
|
829
|
+
round(ring.zeroSequence().get().magnitude()); // 0
|
|
830
|
+
ring.sequences().map(component => round(component.magnitude())); // [0, 6, 0, 0, 0, 0]
|
|
831
|
+
ring.toSpectrum().get().size(); // 6
|
|
832
|
+
ring.toSpectrum().get().getResolution().getHertz(); // 1
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
成分は複素数であり、一般に**共役対称ではない**。平衡環は全エネルギーをビン一に置き、逆向き
|
|
836
|
+
の環はビン `n − 1` に置く。`Spectrum` の片側補助関数は実信号を仮定するので、折り返さずに
|
|
837
|
+
ビンを直接読むこと。
|
|
838
|
+
|
|
839
|
+
### 13.5 データを各要素に対して分析する
|
|
840
|
+
|
|
841
|
+
`analyse(data, time?)` は各観測を同一性で環に照合し、各要素を参考位相として測る。環が持た
|
|
842
|
+
ない観測は推測せず `missed` として数える。位相の唯一の源が環だからである。各参考行は
|
|
843
|
+
`count`、平均相対位相 `mean`、平均合力長 `resultant` を報告する。
|
|
844
|
+
|
|
845
|
+
覚えておく価値のある恒等式が一つある。**`resultant` はどの参考でも同じである**。基準を変える
|
|
846
|
+
とすべての相対位相が定数だけ回り、回転は合力長を変えられない。参考ごとに違うのは計数と平均
|
|
847
|
+
である。
|
|
848
|
+
|
|
849
|
+
```typescript
|
|
850
|
+
import { SynchronousRing } from "semantic-typescript";
|
|
851
|
+
|
|
852
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
853
|
+
const ring = new SynchronousRing(["a", "b", "c", "d", "e"]);
|
|
854
|
+
const report = ring.analyse(["a", "c", "c", "a", "zz", "e"]);
|
|
855
|
+
|
|
856
|
+
report.matched; // 5
|
|
857
|
+
report.missed; // 1
|
|
858
|
+
report.references.map(row => row.count); // [2, 0, 2, 0, 1]
|
|
859
|
+
report.references.map(row => round(row.resultant)); // [0.1453, 0.1453, 0.1453, 0.1453, 0.1453]
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
採点は同じ硬貨の裏側である。`PhaseField` は有界な余弦級数(10.2 節)であり、`scoreAt` /
|
|
863
|
+
`scoresAt` は相対位相でそれを標本化する。だから同じ環を、整列に対して、ある調波模様に対して、
|
|
864
|
+
あるいは任意の場に対して、環そのものを変えずに採点できる。
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
import { SynchronousRing, useField } from "semantic-typescript";
|
|
868
|
+
|
|
869
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
870
|
+
const ring = new SynchronousRing(["a", "b", "c", "d", "e", "f"]);
|
|
871
|
+
const cosine = useField([1]);
|
|
872
|
+
|
|
873
|
+
round(ring.scoreAt(0n, 0, cosine).get()); // 1
|
|
874
|
+
round(ring.scoreAt(1n, 0, cosine).get()); // 0.5
|
|
875
|
+
ring.scoresAt(0, cosine).map(score => round(score)); // [1, 0.5, -0.5, -1, -0.5, 0.5]
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### 13.6 環の合成と角スペクトル
|
|
879
|
+
|
|
880
|
+
`combineRings(left, right)` は二つの環を積ではなく最小公倍数で接続する。結合環の瞬間 `i` は
|
|
881
|
+
左環の `i mod m` と右環の `i mod n` を読み、`lcm(m, n)` 瞬間で閉じる。決して同時に起こり
|
|
882
|
+
得ない組——位置が `gcd(m, n)` を法として一致しないもの——は単に存在しない。だから結果は積では
|
|
883
|
+
なく引き戻しであり、`aligns(left, right)` は与えられた組が表現可能かを報告する。
|
|
884
|
+
|
|
885
|
+
`useAngularSpectrum(ring, instants, plan)` は `(ω, n)` 平面を作る。各瞬間がフェーザのフレーム
|
|
886
|
+
を寄与し、モード `j` のフレーム `k` はそのフレームの `j` 番目の対称成分であり、
|
|
887
|
+
`cells[frame][order]` が係数である。一列を取れば単一空間モードの時系列になり、
|
|
888
|
+
`spectrum(order)` はその時間スペクトルである。
|
|
889
|
+
|
|
890
|
+
```typescript
|
|
891
|
+
import { Sample, SynchronousRing, combineRings, useAngularSpectrum } from "semantic-typescript";
|
|
892
|
+
|
|
893
|
+
const left = new SynchronousRing(["a", "b", "c", "d"]);
|
|
894
|
+
const right = new SynchronousRing(["x", "y", "z", "w", "v", "u"]);
|
|
895
|
+
const joined = combineRings(left, right);
|
|
896
|
+
|
|
897
|
+
joined.period; // 12n
|
|
898
|
+
joined.divisor; // 2n
|
|
899
|
+
joined.ring.size(); // 12n
|
|
900
|
+
joined.aligns(0n, 0n); // true
|
|
901
|
+
joined.aligns(1n, 0n); // false
|
|
902
|
+
joined.ring.get(0n).join("-"); // a-x
|
|
903
|
+
|
|
904
|
+
const angular = useAngularSpectrum(left, [0, 0.25, 0.5, 0.75], new Sample(4, 1));
|
|
905
|
+
angular.order; // 4
|
|
906
|
+
angular.frameCount; // 4
|
|
907
|
+
angular.cells[0][1].magnitude(); // 4
|
|
908
|
+
angular.spectrum(1).get().size(); // 4
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### 13.7 落とし穴
|
|
912
|
+
|
|
913
|
+
**正確さは正確さである。** `phaseAt(i)` と `phaseAt(i + n)` は同じ倍精度値であり、許容差の
|
|
914
|
+
中の二つの値ではない。要素ごとに `2π / n` を蓄積して作った環はこれを満たさない。この環は
|
|
915
|
+
まず `bigint` で索引を還元する。
|
|
916
|
+
|
|
917
|
+
**`frequency` は位相ステップではない。** 環境回転レートであり、既定は零で、すべてのフェーザ
|
|
918
|
+
を同じ角度だけ回しても固有の模様は壊れない。位置同士を相対的に進めたいなら `rate` を使い、
|
|
919
|
+
`frequency` は使わない。
|
|
920
|
+
|
|
921
|
+
**`purity()` は両方の基本ビンを見なければならない。** 逆向きの環も `n` 乗根のままである。
|
|
922
|
+
`c₁` だけでは不純と判定してしまう。
|
|
923
|
+
|
|
924
|
+
**`n < 2` は定義された境界である。** `purity()` は `0`、`isBalanced()` は `false` を返す。
|
|
925
|
+
空環の標本化メソッドは空の `Optional` を返す。
|
|
926
|
+
|
|
927
|
+
**`n` と因子を共有する span は軌道を潰す。** 六要素で `span: 2` は三つの異なる位相しか訪れ
|
|
928
|
+
ず、環はもはや平衡でない。
|
|
929
|
+
|
|
930
|
+
**`resultant` は回転不変であって時間不変ではない。** 環境回転は変えないが、要素ごとの
|
|
931
|
+
`rate` は環が位相を失うにつれて変える。
|
|
932
|
+
|
|
933
|
+
**環は集合であり、`analyse` は同一性で照合する。** 重複要素は構築時に捨てられ、環の要素と
|
|
934
|
+
構造的に等しいが同じ参照でないオブジェクトは missed と数えられる。
|
|
935
|
+
|
|
936
|
+
**`toSpectrum()` は複素スペクトルである。** ビンは共役対称でなく、`Spectrum` の片側補助関数
|
|
937
|
+
は適用できない。ビンを直接読むこと。
|
|
938
|
+
|
|
939
|
+
**`windingAt` は零へ切り捨て、`errorIntegralAt` は位相を積分する。** 半回転の滑りは `0n` と
|
|
940
|
+
報告される。誤差積分の単位は 回転 · 秒であり、速率ではない。
|
|
941
|
+
|
|
942
|
+
```typescript
|
|
943
|
+
import { SynchronousRing, useHertz } from "semantic-typescript";
|
|
944
|
+
|
|
945
|
+
const collapsed = new SynchronousRing(["a", "b", "c", "d", "e", "f"], { span: 2 });
|
|
946
|
+
collapsed.phaseAt(1n).get().toDegreeValue(); // 120
|
|
947
|
+
collapsed.phaseAt(3n).get().toDegreeValue(); // 0
|
|
948
|
+
Number(collapsed.purity().toFixed(4)); // 0
|
|
949
|
+
|
|
950
|
+
const falling = new SynchronousRing(["a", "b", "c", "d"], {
|
|
951
|
+
frequency: useHertz(2),
|
|
952
|
+
rate: () => useHertz(0.5),
|
|
953
|
+
});
|
|
954
|
+
falling.jetAt(0n, 2).get().slip; // -1.5
|
|
955
|
+
falling.windingAt(0n, 1).get(); // -1n
|
|
956
|
+
falling.windingAt(0n, 0.5).get(); // 0n
|
|
957
|
+
|
|
958
|
+
const objects = new SynchronousRing([{ key: 1 }, { key: 2 }]);
|
|
959
|
+
objects.analyse([{ key: 1 }]).missed; // 1 — identity, not structure
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
---
|
|
963
|
+
|
|
964
|
+
<a id="14-signal-analysis"></a>
|
|
965
|
+
## 14. 信号解析:窓・スペクトル・時間
|
|
966
|
+
|
|
967
|
+
第 10 章では環を位相の周期として扱った。本章はその時間側の対応物である。実数値の記録は、
|
|
968
|
+
サンプルそのものに加えて、すべてのスペクトル公式が必要とする二つの文脈を携える——ビン番号を
|
|
969
|
+
ヘルツに変えるサンプリング計画と、コヒーレントゲインと雑音帯域幅の両方を決める窓である。
|
|
970
|
+
|
|
971
|
+
値オブジェクトは `SynchronousSignalCollectable`、その待ち行列版の双子が
|
|
972
|
+
`AsynchronousSignalCollectable` である。どちらも同じ `SignalCollectable` の面を公開する。
|
|
973
|
+
二十三のメソッドがあり、それぞれが `synchronous/collectors` の
|
|
974
|
+
`useSynchronous<Method>` ファクトリ一つに厳密に委譲する。この分割は意図的である。クラスは
|
|
975
|
+
文書化されたファサード、ファクトリが累積を所有し、コンストラクタはこの長さで
|
|
976
|
+
`sum(w) / count` と `count * sum(w^2) / sum(w)^2` を一度だけ計算して保持する。
|
|
977
|
+
「コヒーレントゲインで割る」ことを覚えておかなければならない呼び出し側はいつか忘れるし、
|
|
978
|
+
そこで生じる 6.02 デシベルはもっともらしい測定値に見えてしまう。
|
|
979
|
+
|
|
980
|
+
### 14.1 信号は自前の校正を持つ
|
|
981
|
+
|
|
982
|
+
信号は、反復可能オブジェクトとサンプリングレートから、既存の `Frame` から、あるいは明示的な
|
|
983
|
+
`Sample` を伴うコンストラクタから作る。既定の窓は周期 Hann 窓である。窓を指定しない解析は
|
|
984
|
+
すべてこれを使い、どの解析も上書きを受け付ける。メタデータのアクセサは構築時に固定された
|
|
985
|
+
状態を定数時間で読むだけなので、信号を渡し歩いても文脈がずれない。
|
|
986
|
+
|
|
987
|
+
| 呼び出し | 返り値 | 意味 |
|
|
988
|
+
| :--- | :--- | :--- |
|
|
989
|
+
| `SynchronousSignalCollectable.useRate(values, rate, window?)` | 信号 | 長さ `count / rate` |
|
|
990
|
+
| `SynchronousSignalCollectable.useFrame(frame, window?)` | 信号 | その frame の計画を保つ |
|
|
991
|
+
| `new SynchronousSignalCollectable(plan, values, window?)` | 信号 | 計画のフレーム数はサンプル数と一致必須 |
|
|
992
|
+
| `getSample()` / `getValues()` / `getWindow()` | 計画・サンプル・窓 | 凍結された文脈 |
|
|
993
|
+
| `getRate()` | `number` | 毎秒のサンプル数 |
|
|
994
|
+
| `getNyquist()` / `getResolution()` | `Frequency` | `rate / 2` と `rate / count` |
|
|
995
|
+
| `getCoherentGain()` / `getNoiseBandwidth()` | `number` | この長さで再計算される |
|
|
996
|
+
| `size()` / `at(index)` / `toFrame()` / `equals(other)` / `toString()` | — | 値の意味論 |
|
|
997
|
+
|
|
998
|
+
```typescript
|
|
999
|
+
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1000
|
+
|
|
1001
|
+
const signal = SynchronousSignalCollectable.useRate(
|
|
1002
|
+
Array.from({ length: 64 }, (_unused, index) => Math.cos(2 * Math.PI * 8 * index / 64)),
|
|
1003
|
+
64,
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
signal.size(); // 64n
|
|
1007
|
+
signal.getRate(); // 64
|
|
1008
|
+
signal.getNyquist().getHertz(); // 32
|
|
1009
|
+
signal.getResolution().getHertz(); // 1
|
|
1010
|
+
signal.getCoherentGain(); // 0.5
|
|
1011
|
+
signal.getNoiseBandwidth(); // 1.5
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
`getResolution()` はビン間隔であって分解能ではない。変換が `count` 個のビンを持つので
|
|
1015
|
+
`rate / count` であり、ゼロ詰めは間隔を変えるが、窓が何を分離できるかは変えない(14.8 節)。
|
|
1016
|
+
|
|
1017
|
+
### 14.2 二十三のメソッド
|
|
1018
|
+
|
|
1019
|
+
メソッドは六つの族に分かれる。七つはスペクトルの読み、二つは時間周波数変換、四つは解析量、
|
|
1020
|
+
三つは系列統計量、五つは形状記述子、そして二つは別の信号を返す。
|
|
1021
|
+
|
|
1022
|
+
| メソッド | 返り値 | 規約 |
|
|
1023
|
+
| :--- | :--- | :--- |
|
|
1024
|
+
| `spectrum(size?, window?)` | `Spectrum` | 生のまま非正規化、複素格子を全保持 |
|
|
1025
|
+
| `oneSided(size?, window?)` | `Bin[]` | 共役半を折り返し、DC と Nyquist は二倍しない |
|
|
1026
|
+
| `amplitude(window?)` | `number[]` | `2·abs(X) / (count · CG)`、DC と Nyquist は一度だけ |
|
|
1027
|
+
| `power(window?)` | `number[]` | 内部ビンは `A² / 2`、DC と Nyquist は `A²` |
|
|
1028
|
+
| `decibel(window?)` | `number[]` | `20·log10(振幅)` |
|
|
1029
|
+
| `powerDecibel(window?)` | `number[]` | `10·log10(電力)` |
|
|
1030
|
+
| `powerSpectralDensity(frameSize?, hopSize?, window?)` | `SpectralDensity` | Welch 平均、`abs(X)² / (rate · Σw²)` |
|
|
1031
|
+
| `shortTimeFourierTransform(frameSize, hopSize, window?)` | `Spectrum[]` | 両端を延長し、フレーム中心で時刻を報告 |
|
|
1032
|
+
| `spectrogram(frameSize, hopSize, window?)` | `Spectrogram` | 同じ列に時間軸を付ける |
|
|
1033
|
+
| `analyticSignal()` | `Complex[]` | Hilbert 対 `x + i·H(x)` |
|
|
1034
|
+
| `instantaneousPhase()` | `Angle[]` | `[0, 2π)` に折り畳む |
|
|
1035
|
+
| `instantaneousFrequency(rate?, gate?)` | `number[]` | 位相の差分を展開し、包絡線で門限を掛ける |
|
|
1036
|
+
| `envelope()` | `number[]` | `abs(z[n])`、両端は循環の影響を受ける |
|
|
1037
|
+
| `zeroCrossingRate(perSecond?)` | `number` | 隣接サンプル対あたりの零交差 |
|
|
1038
|
+
| `spectralFlux(frameSize?, hopSize?, window?)` | `number[]` | フレーム間の単位ノルム振幅距離 |
|
|
1039
|
+
| `autocorrelation(maximumLag?, normalization?)` | `number[]` | 既定は係数規約、`ρ[0] = 1` |
|
|
1040
|
+
| `spectralCentroid(window?)` | `Optional<Frequency>` | 電力重み付き平均周波数 |
|
|
1041
|
+
| `spectralBandwidth(window?)` | `Optional<Frequency>` | 電力重み付き標準偏差 |
|
|
1042
|
+
| `spectralRolloff(ratio?, window?)` | `Optional<Frequency>` | 累積電力が初めて `ratio` に達するビン |
|
|
1043
|
+
| `spectralFlatness(window?)` | `number` | 電力の幾何平均 / 算術平均 |
|
|
1044
|
+
| `signalToNoiseRatio(frameSize?, hopSize?, window?, floorQuantile?)` | `Optional<number>` | 中央値の雑音床、`10·log10` |
|
|
1045
|
+
| `detrended(mode?)` | 信号 | `"none"`、`"constant"`(既定)、`"linear"` |
|
|
1046
|
+
| `windowed(window?)` | 信号 | 窓を適用し、それを採用する |
|
|
1047
|
+
|
|
1048
|
+
省略可能な引数は飾りではない。`frameSize` の既定は `min(256, size)`、ホップは半フレームの
|
|
1049
|
+
切り捨て、`instantaneousFrequency` の門限はピーク包絡線の `1E-3`、`spectralRolloff` は
|
|
1050
|
+
`0.85` である。どの既定値も、参考実装が「名前だけ与えて値は渡さない」ときに選ぶ値である。
|
|
1051
|
+
|
|
1052
|
+
### 14.3 振幅・電力・密度
|
|
1053
|
+
|
|
1054
|
+
三つの異なる問いがあるので、三つの正規化が要る。
|
|
1055
|
+
|
|
1056
|
+
`amplitude()` は「このビンの正弦波はどれだけ大きいか」に答える。内部ビンは
|
|
1057
|
+
`2·abs(X[k]) / (count · CG)`、DC と Nyquist は `abs(X[k]) / (count · CG)` である。二倍は
|
|
1058
|
+
共役半の折り返し、コヒーレントゲインは窓の除去である。どちらを省いても単位振幅の正弦波は
|
|
1059
|
+
`0.5`、すなわち −6.02 デシベルと読めてしまう。
|
|
1060
|
+
|
|
1061
|
+
`power()` は「このビンの正弦波はどれだけの平均二乗を持つか」に答える。内部ビンは `A² / 2`、
|
|
1062
|
+
DC と Nyquist は `A²` である。後者は振動しないからだ。これはピーク規約であり、パーシバル
|
|
1063
|
+
の定理を意図的に満たさない。窓が各スペクトル線を主ローブに広げるので、全ビンの和は平均二乗
|
|
1064
|
+
より等価雑音帯域幅の分だけ大きい——Hann では `1.5` である。
|
|
1065
|
+
|
|
1066
|
+
`powerSpectralDensity()` は「毎ヘルツどれだけの電力か」に答える。
|
|
1067
|
+
`abs(X)² / (rate · Σw²)` を重複するセグメントで平均する。重みの二乗和を分母にしたときだけ
|
|
1068
|
+
白色雑音の床が不偏になる。`count` で割ると単位雑音は `0.375`、`count · CG²` で割ると `1.5`
|
|
1069
|
+
と読めてしまう。
|
|
1070
|
+
|
|
1071
|
+
```typescript
|
|
1072
|
+
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1073
|
+
|
|
1074
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
1075
|
+
const tone = SynchronousSignalCollectable.useRate(
|
|
1076
|
+
Array.from({ length: 256 }, (_unused, index) => Math.cos(2 * Math.PI * 32 * index / 256)),
|
|
1077
|
+
256,
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
round(tone.amplitude()[32]); // 1
|
|
1081
|
+
round(tone.power()[32]); // 0.5
|
|
1082
|
+
round(tone.power().reduce((sum, power) => sum + power, 0)); // 0.75
|
|
1083
|
+
round(tone.powerSpectralDensity(64n, 32n).totalPower()); // 0.5
|
|
1084
|
+
tone.powerSpectralDensity(64n, 32n).getSegments(); // 7n
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
覚えておく価値があるのはこの二つの総量である。ピーク規約の和は `0.75`、密度の積分は `0.5`、
|
|
1088
|
+
その比はちょうど雑音帯域幅になる。三つのうちフレーム長を変えても意味が保たれるのは密度だけ
|
|
1089
|
+
である。
|
|
1090
|
+
|
|
1091
|
+
### 14.4 レベル:二つのデシベル規約
|
|
1092
|
+
|
|
1093
|
+
振幅と電力は同じ因子を共有しないので、同じ対数も共有しない。`amplitudeDecibel` は
|
|
1094
|
+
`20·log10` を適用し、電圧・変位・音圧に正しい。`powerDecibel` は `10·log10` を適用し、
|
|
1095
|
+
電力・エネルギー・平均二乗・密度に正しい。信号のメソッド名も同じ分割に従う。
|
|
1096
|
+
|
|
1097
|
+
```typescript
|
|
1098
|
+
import { amplitudeDecibel, powerDecibel, SynchronousSignalCollectable } from "semantic-typescript";
|
|
1099
|
+
|
|
1100
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
1101
|
+
const tone = SynchronousSignalCollectable.useRate(
|
|
1102
|
+
Array.from({ length: 256 }, (_unused, index) => Math.cos(2 * Math.PI * 32 * index / 256)),
|
|
1103
|
+
256,
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
amplitudeDecibel(0.25); // -12.0412
|
|
1107
|
+
powerDecibel(0.25); // -6.0206
|
|
1108
|
+
round(tone.decibel()[32]); // 0
|
|
1109
|
+
round(tone.powerDecibel()[32]); // -3.0103
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
同じ音の二つの読みはどちらも正しい。振幅は 1(ゼロデシベル)、平均二乗は二分の一
|
|
1113
|
+
(−3.01 デシベル)である。電力を振幅規約に通すとデシベル値が二倍になり、正しい −6.02 が
|
|
1114
|
+
−12.04 になる。
|
|
1115
|
+
|
|
1116
|
+
### 14.5 片側の折り返し
|
|
1117
|
+
|
|
1118
|
+
実信号は `X[count − k] = conj(X[k])` を満たすので、負周波数の半は独立な情報を持たない。
|
|
1119
|
+
折り返しは内部ビンをすべて二倍し、自己共役な二つのビン——DC と、偶数長では Nyquist——を
|
|
1120
|
+
変換が作ったまま残す。この二つは自分自身の鏡像であり、二倍にするとエネルギーを捏造し、
|
|
1121
|
+
ちょうど 6.02 デシベル高く報告する。
|
|
1122
|
+
|
|
1123
|
+
```typescript
|
|
1124
|
+
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1125
|
+
|
|
1126
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
1127
|
+
const edge = SynchronousSignalCollectable.useRate(
|
|
1128
|
+
Array.from({ length: 16 }, (_unused, index) => 2 + Math.cos(Math.PI * index)),
|
|
1129
|
+
16,
|
|
1130
|
+
);
|
|
1131
|
+
const bins = edge.oneSided();
|
|
1132
|
+
|
|
1133
|
+
bins.length; // 9
|
|
1134
|
+
round(bins[0].magnitude()); // 16
|
|
1135
|
+
round(bins[8].magnitude()); // 8
|
|
1136
|
+
round(edge.amplitude()[0]); // 2
|
|
1137
|
+
round(edge.amplitude()[8]); // 1
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
生の振幅が `16` と `8` なのは窓の重みの和が 8 だからである。`count · CG = 8` で割ると
|
|
1141
|
+
校正済みの振幅 `2` と `1` になる。端のビンを二倍すると `4` と `2` と読め、その誤りは
|
|
1142
|
+
決して例外を投げない。
|
|
1143
|
+
|
|
1144
|
+
### 14.6 時間周波数の眺め
|
|
1145
|
+
|
|
1146
|
+
`shortTimeFourierTransform` と `spectrogram` は参考の短時間変換と同じように記録を分割する。
|
|
1147
|
+
両端に半フレームの零を置き、末尾を零で埋めて整数個のホップに揃える。最初のフレームは最初の
|
|
1148
|
+
サンプルを中心にし、最後のフレームは最後のサンプルに届き、フレーム `m` の中心は
|
|
1149
|
+
`m · hop / rate` 秒である。完全なフレームだけを残すと最大 `frameSize − 1` サンプルを捨てる
|
|
1150
|
+
——1000 サンプル、フレーム 256、ホップ 128 では 104 サンプル、記録の約十分の一である。
|
|
1151
|
+
|
|
1152
|
+
```typescript
|
|
1153
|
+
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1154
|
+
|
|
1155
|
+
const tone = SynchronousSignalCollectable.useRate(
|
|
1156
|
+
Array.from({ length: 64 }, (_unused, index) => Math.cos(2 * Math.PI * 4 * index / 64)),
|
|
1157
|
+
64,
|
|
1158
|
+
);
|
|
1159
|
+
const spectrogram = tone.spectrogram(16n, 8n);
|
|
1160
|
+
|
|
1161
|
+
spectrogram.size(); // 9
|
|
1162
|
+
spectrogram.getFrameSize(); // 16
|
|
1163
|
+
spectrogram.getBoundary(); // "zeros"
|
|
1164
|
+
spectrogram.getTime(0); // 0
|
|
1165
|
+
spectrogram.getTime(6); // 0.75
|
|
1166
|
+
tone.shortTimeFourierTransform(16n, 8n).length; // 9
|
|
1167
|
+
tone.spectralFlux(16n, 8n).length; // 8
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
各列は `frameSize` 個のビンを持つ `Spectrum` で、同じフレームに `spectrum()` を呼んだ結果と
|
|
1171
|
+
完全に一致する。`getTime()` が報告するのはフレームの開始ではなく中心である。
|
|
1172
|
+
|
|
1173
|
+
### 14.7 窓・コヒーレントゲイン・COLA
|
|
1174
|
+
|
|
1175
|
+
どの形にも二つの形式がある。対称形式は分母が `count − 1`、周期(DFT 偶数)形式は分母が
|
|
1176
|
+
`count` で、一周期より一つ短いため、DFT が仮定する周期延長の下で連続になる。解析と重畳加算
|
|
1177
|
+
には周期形式が要り、フィルタ設計は対称形式を保つ。
|
|
1178
|
+
|
|
1179
|
+
差は測定でき、しかも決して例外にならない。50% 重複では周期 Hann 窓の和はどの位置でも 1 に
|
|
1180
|
+
なる。対称形式は波打ち、16 サンプルで `0.84` デシベル、64 で `0.21`、1024 でも `0.013`
|
|
1181
|
+
残る。クラスは保持する窓のコヒーレントゲインと等価雑音帯域幅を公開するので、補正は決して
|
|
1182
|
+
ハードコードされない。対称 Hann は 8 サンプルで `0.4375` と読み、`0.5` ではない。
|
|
1183
|
+
|
|
1184
|
+
```typescript
|
|
1185
|
+
import {
|
|
1186
|
+
coherentGain, equivalentNoiseBandwidth, useFlatTopWindow,
|
|
1187
|
+
useHannPeriodicWindow, useHannWindow,
|
|
1188
|
+
} from "semantic-typescript";
|
|
1189
|
+
|
|
1190
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
1191
|
+
|
|
1192
|
+
round(useHannWindow(1, 4)); // 0.75
|
|
1193
|
+
round(useHannPeriodicWindow(1, 4)); // 0.5
|
|
1194
|
+
coherentGain(useHannPeriodicWindow, 16); // 0.5
|
|
1195
|
+
equivalentNoiseBandwidth(useHannPeriodicWindow, 16); // 1.5
|
|
1196
|
+
useFlatTopWindow(0, 1024); // -0.000421
|
|
1197
|
+
Array.from({ length: 1024 }, (_unused, i) => useFlatTopWindow(i, 1024)).filter(w => w < 0).length; // 578
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
フラットトップの行は表の誤りではない。フラットトップ窓は定義上部分的に負であり、ここでは
|
|
1201
|
+
1024 個の重みのうち 578 個が負である。それらを零に切り詰めると主ローブが乱れ、この窓の
|
|
1202
|
+
存在理由である低いスキャロッピング損失が壊れる。ライブラリは系列をそのまま返す。
|
|
1203
|
+
|
|
1204
|
+
### 14.8 落とし穴
|
|
1205
|
+
|
|
1206
|
+
**ゼロ詰めは補間であり、決して分解しない。** 変換長をサンプル数より大きくしても、同じ
|
|
1207
|
+
DTFT をより密に標本化するだけである。ピークの*位置*は細かくなり、ビン間隔は `rate / size`
|
|
1208
|
+
になるが、二つの成分を分離できるかは窓の長さと形が決めたままである。ちょうど二つのビンの
|
|
1209
|
+
中間にある音は、詰めない格子では `82.1093` と `80.8598`、詰めた格子では自分の周波数で
|
|
1210
|
+
`128` と読める。
|
|
1211
|
+
|
|
1212
|
+
```typescript
|
|
1213
|
+
import { SynchronousSignalCollectable, useRectangularWindow } from "semantic-typescript";
|
|
1214
|
+
|
|
1215
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
1216
|
+
const tone = SynchronousSignalCollectable.useRate(
|
|
1217
|
+
Array.from({ length: 256 }, (_unused, index) => Math.cos(2 * Math.PI * 100.5 * index / 256)),
|
|
1218
|
+
256,
|
|
1219
|
+
);
|
|
1220
|
+
const flat = tone.spectrum(256n, useRectangularWindow);
|
|
1221
|
+
const padded = tone.spectrum(1024n, useRectangularWindow);
|
|
1222
|
+
|
|
1223
|
+
flat.getResolution().getHertz(); // 1
|
|
1224
|
+
padded.getResolution().getHertz(); // 0.25
|
|
1225
|
+
round(flat.magnitudeAt(100).get()); // 82.1093
|
|
1226
|
+
round(flat.magnitudeAt(101).get()); // 80.8598
|
|
1227
|
+
round(padded.magnitudeAt(402).get()); // 128
|
|
1228
|
+
padded.frequencyAt(402).get().getHertz(); // 100.5
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
`0.4` ビン離れた等振幅の二音は、どんなゼロ詰めでも融合したままである。256 点の記録と Hann
|
|
1232
|
+
窓では、8192 まで詰めても局所最大は一つしか現れない。
|
|
1233
|
+
|
|
1234
|
+
**Hilbert 変換は循環である。** `analyticSignal`、`envelope`、`instantaneousPhase` は最初と
|
|
1235
|
+
最後のサンプルを隣人として扱うので、周期が長さを割り切らない記録は両端が汚染される。記録の
|
|
1236
|
+
中ほどは信頼できるが、両端は信頼できない。
|
|
1237
|
+
|
|
1238
|
+
```typescript
|
|
1239
|
+
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1240
|
+
|
|
1241
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
1242
|
+
const edge = SynchronousSignalCollectable.useRate(
|
|
1243
|
+
Array.from({ length: 512 }, (_unused, index) => Math.cos(2 * Math.PI * 8.3 * index / 512)),
|
|
1244
|
+
512,
|
|
1245
|
+
);
|
|
1246
|
+
const envelope = edge.envelope();
|
|
1247
|
+
|
|
1248
|
+
round(envelope.slice(8, -8).reduce((sum, value) => sum + value, 0) / 496); // 1.0025
|
|
1249
|
+
round(Math.max(...envelope.slice(0, 8))); // 1.2592
|
|
1250
|
+
round(Math.min(...envelope.slice(-8))); // 0.1314
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
**消える包絡線は位相を定義しない。** 包絡線が潰れるところでは、丸め誤差の二つの角度の差が
|
|
1254
|
+
半回転になり得て、サンプリングレートの半分として読める。門限こそがこの数を意味のあるものに
|
|
1255
|
+
する。既定値はピーク包絡線の `1E-3` である。
|
|
1256
|
+
|
|
1257
|
+
```typescript
|
|
1258
|
+
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1259
|
+
|
|
1260
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
1261
|
+
const beat = SynchronousSignalCollectable.useRate(
|
|
1262
|
+
Array.from({ length: 4096 }, (_unused, index) =>
|
|
1263
|
+
Math.cos(2 * Math.PI * 100 * index / 2048) + Math.cos(2 * Math.PI * 102 * index / 2048)),
|
|
1264
|
+
2048,
|
|
1265
|
+
);
|
|
1266
|
+
|
|
1267
|
+
round(Math.max(...beat.instantaneousFrequency(2048, 0).map(Math.abs))); // 920.218
|
|
1268
|
+
round(Math.max(...beat.instantaneousFrequency().map(Math.abs))); // 101
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
**正規化されていない相関は類似度ではない。** 生の和 `r[k]` は記録長とともに増えるので、
|
|
1272
|
+
二つの長さは比較できない。既定の `"coefficient"` は `r[0]` で割り、すべての値を
|
|
1273
|
+
`[-1, 1]` に保つ。`"unbiased"` は `count − k` で割り、分散が遅れとともに増える代償を払う。
|
|
1274
|
+
|
|
1275
|
+
```typescript
|
|
1276
|
+
import { SynchronousSignalCollectable } from "semantic-typescript";
|
|
1277
|
+
|
|
1278
|
+
const round = (value: number): number => Number(value.toFixed(4));
|
|
1279
|
+
const periodic = SynchronousSignalCollectable.useRate(
|
|
1280
|
+
Array.from({ length: 1024 }, (_unused, index) => Math.cos(2 * Math.PI * 8 * index / 1024)),
|
|
1281
|
+
1024,
|
|
1282
|
+
);
|
|
1283
|
+
const rho = periodic.autocorrelation(200n);
|
|
1284
|
+
|
|
1285
|
+
round(rho[0]); // 1
|
|
1286
|
+
round(rho[128]); // 0.875
|
|
1287
|
+
round(rho[64]); // -0.9375
|
|
1288
|
+
round(periodic.autocorrelation(200n, "none")[0]); // 512
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
もう一つ警告に値する読みがある。`spectralFlux` は比較の前に各フレームを正規化するので、
|
|
1292
|
+
レベル変化だけでは通量は零になる。その先頭と末尾の値は零境界の縁の効果であり、立ち上がり
|
|
1293
|
+
ではない。
|
|
1294
|
+
|
|
1295
|
+
---
|
|
1296
|
+
|
|
1297
|
+
<a id="15-license"></a>
|
|
1298
|
+
## 15. ライセンス
|
|
1299
|
+
|
|
1300
|
+
MIT © Eloy Kim
|