Intel MacのVMwareのUbuntuが立ち上がらなくなった、「コピーしますか」とかいうポップアップ出ていてそれが関連するかどうかだけれども、ともかくも身動き取れなくなったからSSDにバックアップしていた仮想マシンバックアップをコピー戻してで修復、区画は大きめに取っていても実際のサイズでコピーが終了するようだ、つまり全領域の復元はやらないということ
![]()
ついでにVMwareのアップデートも適用、Ubuntu20系のサポートは来年5月までだからそれほど残り時間はない
admin

la vie libre
Intel MacのVMwareのUbuntuが立ち上がらなくなった、「コピーしますか」とかいうポップアップ出ていてそれが関連するかどうかだけれども、ともかくも身動き取れなくなったからSSDにバックアップしていた仮想マシンバックアップをコピー戻してで修復、区画は大きめに取っていても実際のサイズでコピーが終了するようだ、つまり全領域の復元はやらないということ
![]()
ついでにVMwareのアップデートも適用、Ubuntu20系のサポートは来年5月までだからそれほど残り時間はない
admin
正弦波の合成だけでは実用性はないので、今年の元旦の能登地震の公開されている波形データからスペクトルを求めてみた
公開先はこちらですが、トップにある輪島市のデータを使っています、形式はcsvなのでヘッダー情報除けばPythonやRustで簡単に処理できます
・ヘッダー情報
SITE CODE= 67016輪島市門前町走出 ,37.4962,137.2705,15.86,7.6,45292.67387,8.93,15.21
LAT.= 37.2871,,,,,,,
LON.= 136.7680,,,,,,,
SAMPLING RATE= 100Hz,,,,,,,
UNIT = gal(cm/s/s),,,,,,,
INITIAL TIME = 2024 01 01 16 10 10,,,,,,,
NS,EW,UD,,,,,
気象庁|強震観測データ|2024/1/1 石川県能登地方の地震
Pythonで全時間領域を対象にしてみたもの(画像はUD軸)
リンク先にある波形と比較するとほぼ類似の形状になってます
以下はRustで部分切り出し(全体で100サンプル/secで120,000フレーム、つまり120秒分のデータがありますが一番のピークの3,000~4012部分の切り出し)を、窓処理してFFTしてみたもの
<NS波形>
<NSスペクトラム>
以下はEW
以下はUD
NS/EWに比較すると周波数成分が高い方に出てきます、Pythonの全時間軸ともちろん傾向的には同じようなスペクトルになっています
X軸の20がほぼ10Hzに相当します
以下はPythonのコードになりますが、対話形式でほぼGeminiで作成させたコードがそのまま動きました、きちんと境界条件を与えてやれば人間がコードを書く手間を大幅に省力化できます、コードは読めないとダメですが
import numpy as np
import matplotlib.pyplot as plt
def fft_csv(filename, column_index, sampling_rate):
"""
CSVファイルの特定列データをFFTする関数
Args:
filename: CSVファイルのパス
column_index: FFTしたい列のインデックス (0から始まる)
sampling_rate: サンプリングレート
Returns:
freqs: 周波数
fft_result: FFTの結果
"""
# CSVファイルを読み込む
data = np.loadtxt(filename, delimiter=',', skiprows=1) # ヘッダー行をスキップ
# 指定した列のデータを取得
target_data = data[:, column_index]
# FFTを実行
fft_result = np.fft.fft(target_data)
# 周波数軸を作成
N = len(target_data)
freqs = np.fft.fftfreq(N, 1.0/sampling_rate)
return freqs, fft_result
# 使用例
if __name__ == "__main__":
filename = "noto.csv" # CSVファイル名
column_index = 2 # FFTしたい列 (3列目) 0 :NS/1 :EW/2 :UD
sampling_rate = 100 # サンプリングレート
freqs, fft_result = fft_csv(filename, column_index, sampling_rate)
# 結果をプロット (両対数プロット)
plt.loglog(freqs, np.abs(fft_result))
plt.xlabel("Frequency [Hz]")
plt.ylabel("Amplitude")
plt.title("FFT of Column {}".format(column_index+1))
plt.grid(True)
plt.show()
admin
普通は周波数スペクトルのグラフはLogスケールなのでそれでみてみる
ソースコードの変更箇所は、前回のコードから以下の一箇所だけ変更で、10を底にする普通のLogスケールに変換しています
// absolute value calc
let y: Vec<f32> = buffer.iter().map(|z| z.norm().log(10.0)*20.0).collect();
<結果のグラフ>
信号レベルからかなり低いのですが、そこそこのレベルで『ゼロ』レベルが推移していますが、これはf32であることによる丸め誤差で、f64にすると景色が変わります、実用上は検出した信号に対して100db以上のダイナミックマージンあればこれ以上の精度は不要だろうし、計算速度の点からもデメリットがあると思う
admin
実際のFFTでは無限に継続する波形というのはないので、実用的なアプリケーションでは信号の切り出しが必要です
ところでrustfftには窓機能のメソッドはないので、個別に実装が必要ですが、一番ポピュラーと思うhanning窓を適用してみました
hanning窓は(1-cos(x))/2で表現されますが、前回の正弦波の合成波形にこの窓を適用します
<窓適用後の入力波形>
<FFTの結果>
比較用の窓処理なしのスペクトル
窓をかけるとピークレベルはほぼ半分になります(対称波形のエンベロープの面積から)、また多少スペクトルの根元が広がっています、予測ではもっと広がると思ってた
P.S. 2024/10/28
スペクトルの分散の考え方は、このケースでは正弦波を窓関数でAM変調していると捉えれば、キャリア周波数の両サイドに山がいくつか見えるはずで、周波数分解能が足りないからダンゴになって見えているだけです
<コード>
正弦波の合成のところで窓処理も同時に行ってます
use plotters::prelude::*;
use rustfft::{num_complex::Complex, FftPlanner};
use std::f32::consts::PI;
use std::time::Instant;
const N: usize = 256; // FFT size
const SAMPLE_RATE: f32 = 256.0; // sampling rate
const INPUT_FREQ: f32 = 20.0; // input frequency
fn complex_vector_with_hanning(length: usize, dt: f32, frequency: f32) -> Vec<Complex<f32>> {
(0..length)
.map(|i| {
let t = i as f32 * dt;
let phase = frequency * 2.0 * PI * t;
let phase2 = 3.0 * frequency * 2.0 * PI * t;
Complex::new(
(f32::sin(phase) + f32::sin(phase2)) * 0.5 * (1.0 - f32::cos(2.0 * PI * t)),
0.0,
)
})
.collect()
}
fn re_vector(complex_numbers: Vec<Complex<f32>>) -> Vec<f32> {
complex_numbers.iter().map(|z| z.re).collect()
}
fn draw(
x: Vec<usize>,
y: Vec<f32>,
f_name: &str,
cap: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let image_width = 1080;
let image_height = 720;
let root = BitMapBackend::new(f_name, (image_width, image_height)).into_drawing_area();
root.fill(&WHITE)?;
// https://qiita.com/lo48576/items/343ca40a03c3b86b67cb
let (y_min, y_max) = y
.iter()
.fold((0.0 / 0.0, 0.0 / 0.0), |(m, n), v| (v.min(m), v.max(n)));
let caption = cap;
let font = (“sans-serif”, 20);
let mut chart = ChartBuilder::on(&root)
.caption(caption, font.into_font())
.margin(10)
.x_label_area_size(16)
.y_label_area_size(42)
.build_cartesian_2d(*x.first().unwrap()..*x.last().unwrap(), y_min..y_max)?;
chart.configure_mesh().draw()?;
let line_series = LineSeries::new(x.iter().zip(y.iter()).map(|(x, y)| (*x, *y)), &RED);
chart.draw_series(line_series)?;
Ok(())
}
fn main() {
// fft data preparation with window
let sr = 1.0 / SAMPLE_RATE;
let mut buffer = complex_vector_with_hanning(N, sr, INPUT_FREQ);
// draw input signal
let buf_re = re_vector(buffer.clone());
let n = N;
let f_input = “plot_input.png”;
let cap = “fft input”;
let x: Vec<usize> = (1..=n).collect();
let _ = draw(x, buf_re, &f_input, &cap);
// fft mode setting
let mut planner: FftPlanner<f32> = FftPlanner::new();
let fft = planner.plan_fft_forward(N);
let start_time = Instant::now();
// fft exec
fft.process(&mut buffer);
let end_time = Instant::now();
println!(“Elapsed time for FFT: {:?}”, end_time.duration_since(start_time));
// absolute value calc
let y: Vec<f32> = buffer.iter().map(|z| z.norm()).collect();
// draw fft graph output
let n = N;
let file_fft = “plot_fft_hanning.png”;
let cap = “fft hanning result”;
let x: Vec<usize> = (1..=n).collect();
let _ = draw(x, y, &file_fft, &cap);
}
admin
rustfftには逆FFTの機能もあるので、FFT結果を逆FFTして元の波形と比較してみます
<やってること>
① 二つの三角関数の合成のVec作成してreに代入、imは全部ゼロ
② 作成したComplex Vecからre部分抽出のVecを作成してグラフ化
③ FFT実行
④ FFT実行結果VecのnormのVec作成してグラフ化
⑤ FFT実行結果Vecをそのまま使用して、iFFT実行
⑥ iFFT実行結果のComplex Vecからre部分抽出のVecを作成してグラフ化
<最終的なコード>
// Computes a forward FFT
//
use plotters::prelude::*;
use rustfft::{num_complex::Complex, FftPlanner};
use std::f32::consts::PI;
use std::time::Instant;
const N: usize = 512; // FFT size
const SAMPLE_RATE: f32 = 256.0; // sampling rate
const INPUT_FREQ: f32 = 20.0; // input frequency
// prepare target data
fn complex_vector(length: usize, dt: f32, frequency: f32) -> Vec<Complex<f32>> {
(0..length)
.map(|i| {
let t = i as f32 * dt;
let phase = frequency * 2.0 * PI * t;
let phase2 = 3.0 * frequency * 2.0 * PI * t;
Complex::new(phase.sin() + phase2.sin(), 0.0)
})
.collect()
}
fn re_vector(complex_numbers: Vec<Complex<f32>>) -> Vec<f32> {
complex_numbers.iter().map(|z| z.re).collect()
}
fn draw(x: Vec<usize>, y: Vec<f32>, f_name: &str, cap: &str) -> Result<(), Box<dyn std::error::Error>> {
let image_width = 1080;
let image_height = 720;
let root = BitMapBackend::new(f_name, (image_width, image_height)).into_drawing_area();
root.fill(&WHITE)?;
// https://qiita.com/lo48576/items/343ca40a03c3b86b67cb
let (y_min, y_max) = y
.iter()
.fold((0.0 / 0.0, 0.0 / 0.0), |(m, n), v| (v.min(m), v.max(n)));
let caption = cap;
let font = ("sans-serif", 20);
let mut chart = ChartBuilder::on(&root)
.caption(caption, font.into_font())
.margin(10)
.x_label_area_size(16)
.y_label_area_size(42)
.build_cartesian_2d(*x.first().unwrap()..*x.last().unwrap(), y_min..y_max)?;
chart.configure_mesh().draw()?;
let line_series = LineSeries::new(x.iter().zip(y.iter()).map(|(x, y)| (*x, *y)), &RED);
chart.draw_series(line_series)?;
Ok(())
}
fn main() {
// fft data preparation
let sr = 1.0 / SAMPLE_RATE;
let mut buffer = complex_vector(N, sr, INPUT_FREQ);
// draw input signal
let buf_re = re_vector(buffer.clone());
let n = N;
let f_input = "plot_input.png";
let cap = "fft input";
let x: Vec<usize> = (1..=n).collect();
let _ = draw(x, buf_re, &f_input, &cap);
// fft mode setting
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(N);
let start_time = Instant::now();
// fft exec
fft.process(&mut buffer);
let end_time = Instant::now();
println!("Elapsed time: {:?}", end_time.duration_since(start_time));
// absolute value calc
let y: Vec<f32> = buffer.iter().map(|z| z.norm()).collect();
// drwa fft graph output
let n = N;
let file_fft = "plot_fft.png";
let cap = "fft result";
let x: Vec<usize> = (1..=n).collect();
let _ = draw(x, y, &file_fft, &cap);
// ifft exec
let ifft = planner.plan_fft_inverse(N);
ifft.process(&mut buffer);
// get complex:re
let y = re_vector(buffer);
// ifft graph output
let n = N;
let file_ifft = "plot_ifft.png";
let cap = "ifft result";
let x: Vec<usize> = (1..=n).collect();
let _ = draw(x, y, &file_ifft, &cap);
}
<作成されたグラフ>
②のグラフ
④のグラフ
⑥のグラフ
確かに元の波形が逆FFTで再現できています、サンプリング周波数を2のn乗分の1にしたのでノイズなく綺麗なスペクトルが現れています。
スペクトル計算ではnorm計算が必要ですが、iFFTの結果の波形はVecのre部分だけを使います
admin
FFTの実行速度をbumpy FFTとRustのクレートである、
https://docs.rs/rustfft/latest/rustfft/
と比較してみた
<条件>
Python numpy/rustfftでM1 Macとラズパイzero W
・実行条件
sampling_rate = 2000 # サンプリング周波数(Hz)
T = 1 / sampling_rate # サンプリング間隔
t = np.arange(0, 1.0, T) # 時間ベクトル
# 信号生成(50Hzと120Hzのサイン波を重ねたもの)
f1 = 100 # Hz
f2 = 300 # Hz
signal = np.sin(2*np.pi*f1*t) + 0.5*np.sin(2*np.pi*f2*t)
<実行速度>
M1 MacBook Air: np.fft.fft: 0.000027 [sec]
ラズパイzero W: np.fft.fft: 0.001830 [sec] ふーむ、およそ60倍ぐらい違うか、想定範囲だけど
<rustfftで実行>
・実行条件
const N: usize = 1024; // FFT size
const SAMPLE_RATE: f32 = 100.0; // sampling rate
const INPUT_FREQ: f32 = 20.0; // input frequency
どちらもreleaseモードでコンパイル、ほぼ条件は同じでnumpyfftとrustfftは同等の実行速度で、Cで記述されているだろうnumpyだからある意味当然の結果です
M1 MacBook Air
Elapsed time: 31.583µs
(参考値:debugモードでコンパイル)
Elapsed time: 1.878708ms
ラズパイzero W
$ ./fft
Elapsed time: 1.957008ms・Rustのコード
fftは複素数で計算していますが、データはre部分だけ作成、結果は複素数になるので絶対値を求めてVecに格納、このクレートは入力データのVecに計算結果が格納されるという変則的なクレートのように思う
// Computes a forward FFT
//
use plotters::prelude::*;
use rustfft::{FftPlanner, num_complex::Complex};
use std::f32::consts::PI;
use std::time::Instant;
const N: usize = 1024; // FFT size
const SAMPLE_RATE: f32 = 100.0; // sampling rate
const INPUT_FREQ: f32 = 20.0; // input frequency
// prepare target data
fn complex_vector(length: usize, dt: f32, frequency: f32) -> Vec<Complex<f32>> {
(0..length)
.map(|i| {
let t = i as f32 * dt;
let phase = frequency * 2.0 * PI * t;
let phase2 = 3.0 * frequency * 2.0 * PI * t;
Complex::new(phase.sin() + phase2.sin(), 0.0)
})
.collect()
}
fn draw(x: Vec<usize>, y: Vec<f32>) -> Result<(), Box<dyn std::error::error="">> {
let image_width = 1080;
let image_height = 720;
let root = BitMapBackend::new("plot.png", (image_width, image_height)).into_drawing_area();
root.fill(&WHITE)?;
// https://qiita.com/lo48576/items/343ca40a03c3b86b67cb
let (y_min, y_max) = y
.iter()
.fold((0.0 / 0.0, 0.0 / 0.0), |(m, n), v| (v.min(m), v.max(n)));
let caption = "DFT result";
let font = ("sans-serif", 20);
let mut chart = ChartBuilder::on(&root)
.caption(caption, font.into_font())
.margin(10)
.x_label_area_size(16)
.y_label_area_size(42)
.build_cartesian_2d(
*x.first().unwrap()..*x.last().unwrap(),
y_min..y_max,
)?;
chart.configure_mesh().draw()?;
let line_series = LineSeries::new(x.iter().zip(y.iter()).map(|(x, y)| (*x, *y)), &RED);
chart.draw_series(line_series)?;
Ok(())
}
fn main() {
// fft data preparation
let sr = 1.0/SAMPLE_RATE;
let mut buffer = complex_vector(N, sr, INPUT_FREQ);
// fft mode setting
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(N);
let start_time = Instant::now();
// fft exec
fft.process(&mut buffer);
let end_time = Instant::now();
println!("Elapsed time: {:?}", end_time.duration_since(start_time));
// absolute value calc
let y: Vec<f32> = buffer.iter().map(|z| z.norm()).collect();
// drwa graph
let n = N;
let x: Vec<usize> = (1..=n).collect();
let _ = draw(x, y);
}ビジュアル化した結果、x軸の512を境に対称形になっています、レベル(ピーク値)が違うのは今のところ謎
admin
Golangを 1.21にアップしたら、buildでgo.bug.st/serial関連のエラー出るので、関連部分のソースコードは触っていないのでモジュールを最新にアップデートした
% go list -m all
~~
go.bug.st/serial v1.6.2
~~
前後省略これは更新後ですが、前の版数は1.5.0
更新履歴は、
https://pkg.go.dev/go.bug.st/serial@v1.6.2?tab=versions
% go get go.bug.st/serial
で更新してやります、これでめでたくビルドは成功
admin
昨日の継続ですが、素数計算のロジックをGeminiで作成してみた
<環境>
M1 Macbook Air
————————————————————–
GeminiにRustである範囲の素数計算するコード出してといって、多少は手を入れてますが、ほぼ生成されたそのまま
use std::time::Instant;
fn is_prime(num: u64) -> bool {
if num <= 1 {
return false;
}
if num <= 3 {
return true;
}
if num % 2 == 0 || num % 3 == 0 {
return false;
}
let mut i = 5;
while i * i <= num {
if num % i == 0 || num % (i + 2) == 0 {
return false;
}
i += 6;
}
true
}
fn find_primes(start: u64, end: u64) {
let mut pri: Vec<u64> = Vec::new();
let start_time = Instant::now();
for num in start..=end {
if is_prime(num) {
pri.push(num);
}
}
let end_time = Instant::now();
println!("Elapsed time: {:?}", end_time.duration_since(start_time));
println!("number of primes: {}", pri.len());
}
fn main() {
let start = 2;
let end = 1000_000;
println!("{}から{}までの素数:", start, end);
find_primes(start, end);
} 最初に気づいたことは① 素数検出ロジックは2, 3で割り算できずかつ6の倍数の±1で割り切れないことが『標準』だということ、確かに計算量はそれだけでほぼ三分の一になるよね
次に、それをマルチスレッド化、これも自動生成
use rayon::prelude::*;
use std::time::Instant;
fn is_prime(num: u64) -> bool {
if num <= 1 {
return false;
}
if num <= 3 {
return true;
}
if num % 2 == 0 || num % 3 == 0 {
return false;
}
let mut i = 5;
while i * i <= num { if num % i == 0 || num % (i + 2) == 0 { return false; } i += 6; } true } fn find_primes_multithreaded(start: u64, end: u64) -> Vec<u64> {
(start..=end)
.into_par_iter()
.filter(|num| is_prime(*num))
.collect()
}
fn main() {
let start = 2;
let end = 1000_000;
println!("{}から{}までの素数:", start, end);
let start_time = Instant::now();
let primes = find_primes_multithreaded(start, end);
let end_time = Instant::now();
println!("Elapsed time: {:?}", end_time.duration_since(start_time));
println!("number of primes: {}", primes.len());
} ② rayonとinto_par_iter()使えば、マルチスレッド化がプロセッサの数に応じて自動でiter処理を実行してくれること
ちなみに実行時間を測ってみると、
<Geminiで生成したシングルスレッド版 : release mode>
2から1000000までの素数:
Elapsed time: 37.51125ms
rayon::tierクレートのinto_par_iter()メソッドでマルチスレッド対応のiteratorが作れるということ、
<Geminiで生成したマルチスレッド版 : release mode>
2から1000000までの素数:
Elapsed time: 7.397958ms昨日のロジックではrelease modeのバイナリで90ms程度要していたから、やはり三分の一程度高速化されていて、さらにマルチスレッド版(8CPU)でオーバーヘッドあるにしてもさらに5倍ぐらい高速化
というわけで、仕様を明確に定義できる領域であれば、生成A.I使った方が人が記述するより時間短縮して効率の良いコードが作成できるだろうということで、githubのcopilotなどのその範疇(使ったことはないけど)でしょう
逆に他の言語からのそのまま置き換え(変換)は元のコードがスマートでないと無駄な作業が発生するし、もしかしたらそれほど向いていないかもしれない
admin
Rustのドキュメントによれば、–debugオプションと–releaseオプションで作成されたバイナリの実行速度差は10倍から最大100倍といっていますが、実際のサンプルでどうなるかみてみた
<環境>
M1 Macbook Airとラズパイzero W
百万までの素数を求めてベクターに格納、比較対象はGolnagでの実行速度、
Golangのコードはこちら、
https://github.com/chateight/golang/blob/master/concpara/waitGroup.go
Rustのコードは省力化のためにGeminiで作成、ただしマルチスレッド周りでたくさんエラー出たからシングルスレッド化して動かしてみた、マルチスレッド対応のコードは多少残存しているけど、比較の目的にはそれほど影響ないだろうからそのまま
use std::sync::{Arc, Mutex};
use std::time::Instant;
fn main() {
let start_time = Instant::now();
let num = find_primes(2, 10000 * 100);
println!("{}",num.len());
let end_time = Instant::now();
println!("Elapsed time: {:?}", end_time.duration_since(start_time));
}
fn find_primes(start: i32, end: i32) -> Vec<i32>{
let counter = Arc::new(Mutex::new(start)); // Shared counter for prime candidates
let mut value = Vec::new();
let counter_clone = counter.clone();
loop {
let c = counter_clone.lock().unwrap().clone();
if c > end {
break;
}
let mut flag = true;
for j in 2..((c as f64).sqrt() as i32 + 1) {
if c % j == 0 {
flag = false;
break;
}
}
if flag {
value.push(c);
}
*counter_clone.lock().unwrap() += 1;
}
value
}結果:素数の数は重要ではないけれども、ロジック検証用
@MacBook Air M1
<Golangでの百万までの素数計算>
78498 ; 素数の数
440.783125ms ; 実行時間
<rust build>
78498
Elapsed time: 508.7235ms
<rust release>
78498
Elapsed time: 91.830167ms
@Raspberry pi zero W
<rust build>
78498
Elapsed time: 18.59699031s
<rust release>
78498
Elapsed time: 7.10051562s
–debugバイナリと–releaseバイナリの実行速度差はM1 Macだと6倍弱、ラズパイzeroだと2倍程度で予想したほどの差はないというのが実感、M1 Macとラズパイzeroの速度差は安定の数十倍
Golangはマルチスレッドにしてマルチコアを使ったつもりだけれども、Rustに比較するとアドバンテージはなさそうに見える
admin
結構以前からあるツールのようですが、DockerあるいはオープンソースのコンテナであるPodmanなどのコンテナ使ってコンテナ内にターゲットのイメージを持ってきてその中でコンパイルしてくれるツールです
<環境>
・コンパイル環境:M1 Mac
・ターゲット:Raspberry pi zero W
Dockerの場合にはroot権限でなくユーザ権限でないと使えないというし、Padmanならば最初からユーザー権限ということでPodmanをインストして使ってみました
https://podman.io/docs/installation
Podmanを使うかDockerを使うかは~/.zshrcに環境変数の指定が必要で、
https://docs.rs/crate/cross/latest
export CROSS_CONTAINER_ENGINE=podman
を指定します
Podmanを起動状態で、
% CROSS_CONTAINER_OPTS="--platform linux/amd64" cross build --target arm-unknown-linux-gnueabihf
を実行すると、imageをダウンロードしてその上でコンパイルします
% CROSS_CONTAINER_OPTS="--platform linux/amd64" cross build --target arm-unknown-linux-gnueabihf
Trying to pull ghcr.io/cross-rs/arm-unknown-linux-gnueabihf:0.2.5...
Getting image source signatures
Copying blob sha256:2c7e00e2a4a7dccfad0ec62cc82e2d3c05f3a4f1d4d0acc1733e78def0556d1e
~~~途中省略~~~
Writing manifest to image destination
Compiling hello v0.1.0 (/project)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.45sこのようにコンパイルできて、定番のHello Worldソースからビルドした実行ファイルをラズパイ zero Wに転送すると実行できました
なぜCROSS_CONTAINER_OPTS="--platform linux/amd64"必要なのかは、
https://github.com/cross-rs/cross/issues/1214
からの情報ですがM1上のクロスだよと指定が必要のようで、指定しないとtargetで使うimage見つからないよと言われます
admin