温室ファンの振動が別の周波数に見える
目標
温室のファンの875Hzの振動が、125Hzに見える状況を再現し、アナログフィルターと観測レートを変えて、区別できる範囲を報告します。
なぜ重要なのか
前のRC・ADC取得時間の単元と、Pythonの関数・list・dict・例外・ファイル処理、複素数・三角関数を理解してから進めてください。ラボの予想所要時間は120分です。基本の60分のセッションが終わる前に、+時間で延長してください。最大は180分で、終了するとファイルが消えます。コードとレポートを、別に保管してください。実際の機器の接続や、電圧の印加は行いません。
環境と成果物
書くファイルは、/root/aliasing/analyze.pyの1つです。Python3とngspice(バージョン42)はイメージに入っていて、外部からのダウンロードや追加の権限は必要ありません。/opt/lab/fixtures/aliasing/aliaswave.pyは、sys.pathにそのフォルダーを追加してから、importしてください。このヘルパーのcapture(cfg,新しいフォルダー)は、実際の回路を実行して、新しいフォルダー/trace.tsvのパスを返します。input.cir・config.json・solver.logも一緒に残します。親フォルダーはすでにある必要があり、既存のフォルダーは上書きしません。
cfgは、正確に3つのキーのdictです。input_hzは125・375・625・875のうちの1つ、cutoff_hzはNone・300・80のうちの1つ、dt_sは2e-6・1e-6のうちの1つです。数値のbool・文字列・NaN・無限大は拒否します。回路は、1Vのコサイン入力と、1kΩの直列抵抗、C=1/(2π1000fc)の接地コンデンサです。Noneなら、Cの代わりに10¹²Ωの負荷です。全体の90msを計算し、入力・出力を一緒に記録します。この回路には、前の単元の取得スイッチモデルは入っていません。
提供されるload_trace(path)は、最大16MiBの波形を検証して、時間・入力・出力の3列のtupleのlistを返します。ヘッダーはtime v(in) v(out)、データは2–200000行です。時間は0以上・厳密に増加で、最初の時間は1µs以下、最後の時間は、90msから1e-12秒以内でなければなりません。すべての値は有限で、電圧の絶対値は1e6V以下です。破損はValueError、OSのファイルエラーは、そのまま伝わります。resampleは、この検証を通過したrowsを受け取るので、呼び出しのたびにファイルの検査を繰り返す必要はありません。
関数の数値契約
明示した数値は、intまたはfloatだけを許容し、bool・文字列・NaN・無限大・floatの変換範囲の超過はValueErrorです。元の入力と元のファイルは変更しません。返すdictのキーは以下のとおりで、不要なフィールドは追加しません。
| 関数 | 入力範囲と戻り値 |
|---|---|
| alias_hz | 0≤freq≤1e9、0 |
| resample | 100≤fs≤8000、start≥0、countは実際のintで2–256。時刻start+i/fsの出力電圧のリスト |
| spectrum | valuesは正確にlist、偶数の長さ2–256、各電圧の絶対値≤1e6、100≤fs≤8000 |
| rc_gain | 0≤freq≤1e9、cutoff=Noneなら1、そうでなければ1e-6≤cutoff≤1e9 |
| rc_window | 1e-6≤wanted |
spectrumの各項目は、bin(0からN/2)、hz=k*fs/N、amplitude_vです。Z=(1/N)Σvalues[i]exp(−2jπki/N)、振幅は、k=0またはN/2ならabs(Z)、それ以外は2abs(Z)です。同じ値のピークは、最初のビンを選びます。
analyzeは、fs=1000または4000だけを受け付けます。既定の開始0.024秒・64標本で分析し、返すキーは、fs_hz、start_s、n、samples_v、spectrum、peak_hz、peak_amplitude_vです。peak_hzは最大振幅のビンの周波数ですが、その振幅≤1e-6VならNoneです。peak_amplitude_vは、その場合も実際の最大値を残します。ファイルの読み込みエラーを、勝手に無信号に変えません。
compare_reportsは、正常なanalyzeのレポートを受け取ります。fs_hz・start_s・nのうち1つでも違う場合、またはsamples_vの長さがnと違う場合、あるいは空の場合は、ValueErrorです。返すmax_sample_difference_vは、標本どうしの絶対差の最大、indistinguishableは、その値≤1e-4Vのboolです。振幅スペクトルだけを比べることはしません。
rc_gainは、1/sqrt(1+(freq/cutoff)2)です。rc_windowの戻り値は、min_cutoff_hz=wanted/sqrt(1/min_gain2−1)、max_cutoff_hz=nuisance/sqrt(1/max_gain**2−1)、feasible=(下限≤上限)です。feasible=Falseも正常な結果であり、要求を変更してTrueにしてはいけません。
assess_filterは、正常なanalyzeのレポート2つを受け取り、それぞれのpeak_amplitude_vが0以上の有限の数値であることを確認します。返すキーは、wanted_amplitude_v、nuisance_amplitude_v、passband_ok(最初の振幅≥.9)、stopband_ok(2つ目≤.1)、all_pass(2つの条件のAND)です。この判定は、1V入力・整数周期の観測という、今回の実験の契約です。
総合レポートとCLIの契約
campaign(inputs,runner)のinputsは、正確にlistで、[125,875]または[375,625]だけを受け付けます。要素は、上の数値のルールに従います。順序は、前が望む入力で、後ろが妨害です。入力全体を先に検証してから、runner(cfg)を呼び出します。runnerは波形のパスを返し、例外は呼び出し側に伝えます。元の入力は保存します。
実行の順序は、cutoffがNone→300→80、各cutoffで入力の組の順序、各入力でdtが2e-6→1e-6です。合計12回で、波形ごとにanalyze(path,1000)とanalyze(path,4000)を実行します。6つの条件それぞれのtrialsの項目は、次のとおりです。
- input_hz、cutoff_hz: 実際の回路条件
- coarse、fine: それぞれ基本・精密の波形のレポートを、low(1kHz)・high(4kHz)の2つのキーに保存したdict
- difference_v: coarseとfineを、同じ観測レートどうしでcompare_reportsした差の最大値
- converged: difference_v≤1e-4V
- model_consistent: 4つの分析すべてで、peak_hzがalias_hz(input_hz,fs_hz)と等しく、振幅がrc_gain(input_hz,cutoff_hz)から1e-4V以内のときだけTrue
filter_resultsは、cutoffの順に3つです。各項目のキーは、cutoff_hz、assessment(2つの入力のcoarse.lowのassess_filter)、fine_assessment(2つの入力のfine.lowのassess_filter)、valid(2つの入力とも収束していて、モデルが一致)、eligible(validで、2つのassessmentのall_passもすべてTrue)です。
最上位の戻り値のキーは、schema=1、trials、filter_results、single_rc_window=rc_window(*inputs)、collision(フィルターなしの2つの入力のcoarse.lowをcompare_reports)、scope='ideal-rc-and-uniform-sampling-only'です。単一RCの候補がなければ、eligibleをFalseのまま残します。エラーを空のtrialsで隠しません。
main(argv)は、入力の組のJSONファイルと、まだ存在しない出力フォルダーのパスの、2つの引数を受け取ります。JSONと入力の組を先に検証してから、出力フォルダーを作ります。親は、すでにある必要があります。runnerの呼び出し順に、その下のrun-00..run-11の新しいフォルダーで、提供されるaliaswave.captureを呼び出します。campaignの結果を、stdoutにJSON1行で出力して、0を返します。引数・設定・ファイル・実行のExceptionは、{"error":"analysis_failed"}の1行と2を返し、tracebackは出力しません。すでに作った新しい証拠は、失敗しても消しません。importのときは実行せず、直接実行のときだけ、mainの戻り値で終了します。
提供される入力の例は、/opt/lab/fixtures/aliasing/inputs.jsonです。最終ステップで直接実行するには、新しい出力名を選びます。レポートのリダイレクトも、既存の資料を上書きしないように注意してください。
python3 /root/aliasing/analyze.py /opt/lab/fixtures/aliasing/inputs.json /root/aliasing/trial-01 > /root/aliasing/report-01.json
ステップ
- 速い信号が得る別の名前: /root/aliasing/analyze.pyに、alias_hz(freq,fs)を書きます。f mod fsと、最初のナイキスト区間の対称で、折り返された周波数を返します。複数の区間・DC・境界を処理し、以下の数値契約に従います。
- 行番号の代わりに観測時刻を使う: 同じファイルに、resample(rows,fs,start=.024,count=64)を追加します。検証済みの波形から、start+i/fsの出力電圧を線形補間したlistを返します。端点は含み、範囲外の観測はValueErrorで拒否します。
- 振幅が2倍になるビンを区別する: spectrum(values,fs)を追加します。1/Nで正規化したDFTで、k=0..N/2のbin・hz・amplitude_vを入れたdictのリストを作ります。内部のビンだけ2倍で、DCとナイキストは1倍です。
- 2つの観測レートで、同じ波形を読み直す: analyze(path,fs)を追加します。提供されるload_traceでファイルを読み、観測・DFTをつないで、以下のレポートの契約を返します。fsは1000または4000で、実際の標本時刻も変わらなければなりません。
- 同じスペクトルと、同じ標本を区別する: compare_reports(a,b)を追加します。観測の時計の3つのフィールドと標本数を確認してから、最大の標本差と、100µV以内かどうかを返します。異なる時計や、空の標本は、ValueErrorです。
- 不可能なフィルター要求を計算する: rc_gain(freq,cutoff)と、rc_window(wanted,nuisance,min_gain=.9,max_gain=.1)を追加します。元の入力周波数でゲインを計算し、2つの帯域の要求のfcの許容区間と、共通部分が存在するかどうかを返します。
- 望む信号も守れているかを問う: assess_filter(wanted,nuisance)を追加します。2つのanalyzeレポートの最大振幅で、望む信号≥0.9V、妨害≤0.1Vをそれぞれ判定し、2つの条件のANDを返します。
- 回路・時計・収束を証拠としてまとめる: campaign(inputs,runner)、main(argv)、CLIのエントリーポイントを追加します。以下の呼び出し順に12回路を実行し、24の分析をつなぎます。収束・モデル・要求を区別し、実際の波形とJSONレポートを保存します。
参考
採点は、ステップごとの関数と、元の資料の保存を確認します。最後には、一時フォルダーで、実際のngspiceと別のCLIも実行し、保存した波形とレポートを突き合わせます。検査専用のフォルダーだけを整理し、学習者のファイルは変更しません。ヘルパーの入力の制約は、実験の範囲を明確にするためのものであり、すべてのADC・フィルターのモデルではありません。実際のボードのノイズ・ジッター・量子化・温度別の信頼性は、検証しません。
速い信号が得る別の名前
/root/aliasing/analyze.pyに、alias_hz(freq,fs)を書きます。f mod fsと、最初のナイキスト区間の対称で、折り返された周波数を返します。複数の区間・DC・境界を処理し、以下の数値契約に従います。
観測レートを1回引くことと、余りを求めることは、違います。boolは、Pythonでは整数のように動作しますが、設定の数値としては受け取りません。
行番号の代わりに観測時刻を使う
同じファイルに、resample(rows,fs,start=.024,count=64)を追加します。検証済みの波形から、start+i/fsの出力電圧を線形補間したlistを返します。端点は含み、範囲外の観測はValueErrorで拒否します。
rowsの列は、時間・入力・出力の順です。ちょうど一致する行と、2つの行の間にある場合を、分けて考えてください。
振幅が2倍になるビンを区別する
spectrum(values,fs)を追加します。1/Nで正規化したDFTで、k=0..N/2のbin・hz・amplitude_vを入れたdictのリストを作ります。内部のビンだけ2倍で、DCとナイキストは1倍です。
一定の電圧と、交互に符号が変わる標本で、端点の倍率を確認できます。この値は、電力密度ではなく、振幅です。
2つの観測レートで、同じ波形を読み直す
analyze(path,fs)を追加します。提供されるload_traceでファイルを読み、観測・DFTをつないで、以下のレポートの契約を返します。fsは1000または4000で、実際の標本時刻も変わらなければなりません。
分析結果のpeak_hzは、観測された名前です。元の入力周波数が分かったとは表現せず、無信号には周波数を作りません。
同じスペクトルと、同じ標本を区別する
compare_reports(a,b)を追加します。観測の時計の3つのフィールドと標本数を確認してから、最大の標本差と、100µV以内かどうかを返します。異なる時計や、空の標本は、ValueErrorです。
大きさのスペクトルが同じでも、位相が違うことがあります。同じ時間点で、電圧を比べなければなりません。
不可能なフィルター要求を計算する
rc_gain(freq,cutoff)と、rc_window(wanted,nuisance,min_gain=.9,max_gain=.1)を追加します。元の入力周波数でゲインを計算し、2つの帯域の要求のfcの許容区間と、共通部分が存在するかどうかを返します。
RCフィルターは、標本化の前にあります。折り返された周波数でゲインを計算せず、下限が上限を超える結果も、そのまま保存してください。
望む信号も守れているかを問う
assess_filter(wanted,nuisance)を追加します。2つのanalyzeレポートの最大振幅で、望む信号≥0.9V、妨害≤0.1Vをそれぞれ判定し、2つの条件のANDを返します。
妨害を減らしても、望む信号まで失えば、設計要求を満たせません。観測値と判定フィールドを、一緒に残します。
回路・時計・収束を証拠としてまとめる
campaign(inputs,runner)、main(argv)、CLIのエントリーポイントを追加します。以下の呼び出し順に12回路を実行し、24の分析をつなぎます。収束・モデル・要求を区別し、実際の波形とJSONレポートを保存します。
空の候補は、正常な設計の結論かもしれません。実行の例外を空の成功リストに変えたり、既存の証拠フォルダーを上書きしたりしません。