From 5bad6db8fbb25eb915c5ba77c402668b68e9da16 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 13:48:34 +0900 Subject: [PATCH 01/24] refactor parts for flamegraph analysis --- src/mlpg_adjust/mlpg.rs | 17 +++++++---- src/mlpg_adjust/mod.rs | 67 +++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 5b1d2fa6..39aba24a 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -114,6 +114,15 @@ impl MlpgMatrix { par } + fn calculate_gv_switch(gv_switch: &[bool], durations: &[usize], mask: &[bool]) -> Vec { + gv_switch + .iter() + .copied() + .duration(durations) + .filter_by(mask) + .collect() + } + /// Solve the equasion, and if necessary, applies GV (global variance). pub fn par( &mut self, @@ -126,12 +135,8 @@ impl MlpgMatrix { if let Some((gv_param, gv_switch)) = gv { let mtx_before = self.clone(); let par = self.solve(); - let gv_switch: Vec<_> = gv_switch - .iter() - .copied() - .duration(durations) - .filter_by(msd_flag.mask()) - .collect(); + let gv_switch: Vec<_> = + Self::calculate_gv_switch(gv_switch, durations, msd_flag.mask()); let mgv = MlpgGlobalVariance::new(mtx_before, par, &gv_switch); let MeanVari(gv_mean, gv_vari) = gv_param[vector_index]; diff --git a/src/mlpg_adjust/mod.rs b/src/mlpg_adjust/mod.rs index 017fe66c..e02e8f9d 100644 --- a/src/mlpg_adjust/mod.rs +++ b/src/mlpg_adjust/mod.rs @@ -54,35 +54,8 @@ impl<'a> MlpgAdjust<'a> { let mut pars = vec![vec![0.0; self.vector_length]; msd_flag.mask().len()]; for vector_index in 0..self.vector_length { - let parameters: Vec> = self - .windows - .iter() - .enumerate() - .map(|(window_index, window)| { - let m = self.vector_length * window_index + vector_index; - - self.stream - .iter() - .map(|(curr_stream, _)| curr_stream[m].with_ivar()) - .duration(durations) - .zip(&msd_boundaries) - .map(|(mean_ivar, (left, right))| { - let is_left_msd_boundary = *left < window.left_width(); - let is_right_msd_boundary = *right < window.right_width(); - - // If the window includes non-msd frames, set the ivar to 0.0 - if (is_left_msd_boundary || is_right_msd_boundary) && window_index != 0 - { - mean_ivar.with_0() - } else { - mean_ivar - } - }) - .filter_by(msd_flag.mask()) - .collect() - }) - .collect(); - + let parameters = + self.create_parameters(vector_index, durations, &msd_boundaries, msd_flag.mask()); let mut mtx = MlpgMatrix::calc_wuw_and_wum(self.windows, parameters); let par = mtx.par(&self.gv, vector_index, self.gv_weight, durations, &msd_flag); @@ -93,6 +66,42 @@ impl<'a> MlpgAdjust<'a> { pars } + + #[inline(never)] + fn create_parameters( + &self, + vector_index: usize, + durations: &[usize], + msd_boundaries: &[(usize, usize)], + mask: &[bool], + ) -> Vec> { + self.windows + .iter() + .enumerate() + .map(|(window_index, window)| { + let m = self.vector_length * window_index + vector_index; + + self.stream + .iter() + .map(|(curr_stream, _)| curr_stream[m].with_ivar()) + .duration(durations) + .zip(msd_boundaries) + .map(|(mean_ivar, (left, right))| { + let is_left_msd_boundary = *left < window.left_width(); + let is_right_msd_boundary = *right < window.right_width(); + + // If the window includes non-msd frames, set the ivar to 0.0 + if (is_left_msd_boundary || is_right_msd_boundary) && window_index != 0 { + mean_ivar.with_0() + } else { + mean_ivar + } + }) + .filter_by(mask) + .collect() + }) + .collect() + } } trait IterExt: Iterator { From 9ec19d893322c6913c7d736e1bbb2e966c8b7b9d Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 14:38:55 +0900 Subject: [PATCH 02/24] flatten wuw --- src/mlpg_adjust/mlpg.rs | 78 +++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 39aba24a..60def4bc 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -15,7 +15,7 @@ pub struct MlpgMatrix { win_size: usize, length: usize, width: usize, - wuw: Vec>, + wuw: Vec, wum: Vec, } @@ -25,13 +25,10 @@ impl MlpgMatrix { pub fn calc_wuw_and_wum(windows: &Windows, parameters: Vec>) -> Self { let length = parameters[0].len(); let width = windows.max_width() * 2 + 1; - let mut wum = Vec::with_capacity(length); - let mut wuw = Vec::with_capacity(length); + let mut wum = vec![0.0; length]; + let mut wuw = vec![0.0; length * width]; for t in 0..length { - wuw.push(vec![0.0; width]); - wum.push(0.0); - for (i, window) in windows.iter().enumerate() { for (index, coef) in window.iter_rev(0) { if coef == 0.0 { @@ -54,7 +51,7 @@ impl MlpgMatrix { break; } - wuw[t][j] += wu * coef; + wuw[t * width + j] += wu * coef; } } } @@ -77,37 +74,44 @@ impl MlpgMatrix { /// Perform Cholesky decomposition. fn ldl_factorization(&mut self) { - for t in 0..self.length { - for i in 1..self.width.min(t + 1) { - self.wuw[t][0] -= self.wuw[t - i][i] * self.wuw[t - i][i] * self.wuw[t - i][0]; + let Self { width, length, .. } = *self; + let wuw = &mut self.wuw[..length * width]; + for t in 0..length { + for i in 1..width.min(t + 1) { + wuw[width * t] -= + wuw[(t - i) * width + i] * wuw[(t - i) * width + i] * wuw[(t - i) * width]; } - for i in 1..self.width { - for j in 1..(self.width - i).min(t + 1) { - self.wuw[t][i] -= - self.wuw[t - j][j] * self.wuw[t - j][i + j] * self.wuw[t - j][0]; + for i in 1..width { + for j in 1..(width - i).min(t + 1) { + wuw[width * t + i] -= wuw[(t - j) * width + j] + * wuw[(t - j) * width + i + j] + * wuw[(t - j) * width]; } - self.wuw[t][i] /= self.wuw[t][0]; + wuw[width * t + i] /= wuw[width * t]; } } } /// Forward & backward substitution. fn substitutions(&self) -> Vec { + let Self { width, length, .. } = *self; + let wum = &self.wum[..length]; + let wuw = &self.wuw[..length * width]; let mut g = vec![0.0; self.length]; // forward - for t in 0..self.length { - g[t] = self.wum[t]; - for i in 1..self.width.min(t + 1) { - g[t] -= self.wuw[t - i][i] * g[t - i]; + for t in 0..length { + g[t] = wum[t]; + for i in 1..width.min(t + 1) { + g[t] -= wuw[(t - i) * width + i] * g[t - i]; } } let mut par = vec![0.0; self.length]; // backward for t in (0..self.length).rev() { - par[t] = g[t] / self.wuw[t][0]; - for i in 1..self.width.min(self.length - t) { - par[t] -= self.wuw[t][i] * par[t + i]; + par[t] = g[t] / wuw[t * width]; + for i in 1..width.min(length - t) { + par[t] -= wuw[t * width + i] * par[t + i]; } } @@ -207,17 +211,19 @@ impl<'a> MlpgGlobalVariance<'a> { .for_each(|(p, _)| *p = ratio * (*p - mean) + mean); } fn calc_hmmobj_derivative(&self) -> (f64, Vec) { - let mut g = vec![0.0; self.mtx.length]; + let MlpgMatrix { width, length, .. } = self.mtx; + let wuw = &self.mtx.wuw[..length * width]; - #[allow(clippy::needless_range_loop)] - for t in 0..self.mtx.length { - g[t] = self.mtx.wuw[t][0] * self.par[t]; - for i in 1..self.mtx.width { - if t + i < self.mtx.length { - g[t] += self.mtx.wuw[t][i] * self.par[t + i]; + let mut g = vec![0.0; length]; + + for t in 0..length { + g[t] = wuw[t * width] * self.par[t]; + for i in 1..width { + if t + i < length { + g[t] += wuw[t * width + i] * self.par[t + i]; } if t + 1 > i { - g[t] += self.mtx.wuw[t - i][i] * self.par[t - i]; + g[t] += wuw[(t - i) * width + i] * self.par[t - i]; } } } @@ -241,21 +247,23 @@ impl<'a> MlpgGlobalVariance<'a> { gv_mean: f64, gv_vari: f64, ) { - let length = self.mtx.length; + let MlpgMatrix { width, length, .. } = self.mtx; let w = 1.0 / ((self.mtx.win_size * length) as f64); let dv = -2.0 * gv_vari * (vari - gv_mean) / self.mtx.length as f64; - #[allow(clippy::needless_range_loop)] + let wum = &self.mtx.wum[..length]; + let wuw = &self.mtx.wuw[..length * width]; + for t in 0..length { - let h = -W1 * w * self.mtx.wuw[t][0] + let h = -W1 * w * wuw[t * width] - W2 * 2.0 / (length * length) as f64 * ((length - 1) as f64 * gv_vari * (vari - gv_mean) + 2.0 * gv_vari * (self.par[t] - mean) * (self.par[t] - mean)); let next_g = if self.gv_switch[t] { - 1.0 / h * (W1 * w * (-g[t] + self.mtx.wum[t]) + W2 * dv * (self.par[t] - mean)) + 1.0 / h * (W1 * w * (-g[t] + wum[t]) + W2 * dv * (self.par[t] - mean)) } else { - 1.0 / h * (W1 * w * (-g[t] + self.mtx.wum[t])) + 1.0 / h * (W1 * w * (-g[t] + wum[t])) }; self.par[t] += step * next_g; From 95d34f41d4ee856b67425baab644abdb2f6b5c1e Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 15:00:33 +0900 Subject: [PATCH 03/24] pre-index slices --- src/mlpg_adjust/mlpg.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 60def4bc..19bd9f6d 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -210,30 +210,38 @@ impl<'a> MlpgGlobalVariance<'a> { .filter(|(_, sw)| **sw) .for_each(|(p, _)| *p = ratio * (*p - mean) + mean); } + #[inline(never)] fn calc_hmmobj_derivative(&self) -> (f64, Vec) { - let MlpgMatrix { width, length, .. } = self.mtx; + let MlpgMatrix { + width, + length, + win_size, + .. + } = self.mtx; let wuw = &self.mtx.wuw[..length * width]; + let wum = &self.mtx.wum[..length]; + let par = &self.par[..length]; let mut g = vec![0.0; length]; for t in 0..length { - g[t] = wuw[t * width] * self.par[t]; + g[t] = wuw[t * width] * par[t]; for i in 1..width { - if t + i < length { - g[t] += wuw[t * width + i] * self.par[t + i]; + if i < length - t { + g[t] += wuw[t * width + i] * par[t + i]; } - if t + 1 > i { - g[t] += wuw[(t - i) * width + i] * self.par[t - i]; + if i < t + 1 { + g[t] += wuw[(t - i) * width + i] * par[t - i]; } } } - let w = 1.0 / ((self.mtx.win_size * self.mtx.length) as f64); + let w = 1.0 / ((win_size * length) as f64); let mut hmmobj = 0.0; #[allow(clippy::needless_range_loop)] - for t in 0..self.mtx.length { - hmmobj += W1 * w * self.par[t] * (self.mtx.wum[t] - 0.5 * g[t]); + for t in 0..length { + hmmobj += W1 * w * par[t] * (wum[t] - 0.5 * g[t]); } (hmmobj, g) From ba541c518bd3c1ed64a16897f20afe86c91439fc Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 15:01:13 +0900 Subject: [PATCH 04/24] separate loop --- src/mlpg_adjust/mlpg.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 19bd9f6d..d737f3ed 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -230,6 +230,11 @@ impl<'a> MlpgGlobalVariance<'a> { if i < length - t { g[t] += wuw[t * width + i] * par[t + i]; } + } + } + + for t in 0..length { + for i in 1..width { if i < t + 1 { g[t] += wuw[(t - i) * width + i] * par[t - i]; } From 5f5a6d4f0ddb3eda233b80c8c15f8cfb8bb669b5 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 15:43:51 +0900 Subject: [PATCH 05/24] offset loop variable --- src/mlpg_adjust/mlpg.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index d737f3ed..b0c71aa0 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -233,10 +233,14 @@ impl<'a> MlpgGlobalVariance<'a> { } } - for t in 0..length { + // u = t - i + // i < t + 1 -> u >= 0 + // i > 0, t < length -> u < length + // t < length -> i < length - u + for u in 0..length { for i in 1..width { - if i < t + 1 { - g[t] += wuw[(t - i) * width + i] * par[t - i]; + if i < length - u { + g[u + i] += wuw[u * width + i] * par[u]; } } } From 6e01bd60f72e5c4e9625ed74c7c4ed8e323402a4 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 15:45:40 +0900 Subject: [PATCH 06/24] rename variable --- src/mlpg_adjust/mlpg.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index b0c71aa0..bb37c68f 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -233,14 +233,10 @@ impl<'a> MlpgGlobalVariance<'a> { } } - // u = t - i - // i < t + 1 -> u >= 0 - // i > 0, t < length -> u < length - // t < length -> i < length - u - for u in 0..length { + for t in 0..length { for i in 1..width { - if i < length - u { - g[u + i] += wuw[u * width + i] * par[u]; + if i < length - t { + g[t + i] += wuw[t * width + i] * par[t]; } } } From 692907a50e11de29ec97e18fe6d62c6f39cff2ce Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 15:48:22 +0900 Subject: [PATCH 07/24] merge loops --- src/mlpg_adjust/mlpg.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index bb37c68f..c30d5470 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -225,17 +225,10 @@ impl<'a> MlpgGlobalVariance<'a> { let mut g = vec![0.0; length]; for t in 0..length { - g[t] = wuw[t * width] * par[t]; + g[t] += wuw[t * width] * par[t]; for i in 1..width { if i < length - t { g[t] += wuw[t * width + i] * par[t + i]; - } - } - } - - for t in 0..length { - for i in 1..width { - if i < length - t { g[t + i] += wuw[t * width + i] * par[t]; } } From 61cce5d9602ac701e67788c31f2e8a364c2f06d6 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 16:11:04 +0900 Subject: [PATCH 08/24] chunks_exact --- src/mlpg_adjust/mlpg.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index c30d5470..b11b7268 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -218,18 +218,20 @@ impl<'a> MlpgGlobalVariance<'a> { win_size, .. } = self.mtx; - let wuw = &self.mtx.wuw[..length * width]; + assert!(width >= 1); // required for `wuw[0]` access + let wuw = self.mtx.wuw.chunks_exact(width); let wum = &self.mtx.wum[..length]; let par = &self.par[..length]; let mut g = vec![0.0; length]; - for t in 0..length { - g[t] += wuw[t * width] * par[t]; + // .zip(0..length) to help optimizer recognize t < length + for (wuw, t) in wuw.zip(0..length) { + g[t] += wuw[0] * par[t]; for i in 1..width { - if i < length - t { - g[t] += wuw[t * width + i] * par[t + i]; - g[t + i] += wuw[t * width + i] * par[t]; + if t + i < length { + g[t] += wuw[i] * par[t + i]; + g[t + i] += wuw[i] * par[t]; } } } From 5b3d9d14199104a0aec305b25aa6a6fcc4f56936 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 16:21:33 +0900 Subject: [PATCH 09/24] remove unnecessary allow() --- src/mlpg_adjust/mlpg.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index b11b7268..46d234db 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -239,7 +239,6 @@ impl<'a> MlpgGlobalVariance<'a> { let w = 1.0 / ((win_size * length) as f64); let mut hmmobj = 0.0; - #[allow(clippy::needless_range_loop)] for t in 0..length { hmmobj += W1 * w * par[t] * (wum[t] - 0.5 * g[t]); } From 51ba4d2b7037bc0914139b7f305d859063c83f8f Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 16:36:44 +0900 Subject: [PATCH 10/24] mean_quad - mean^2 --- src/lib.rs | 2 +- src/mlpg_adjust/mlpg.rs | 27 +++++++++++---------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ac00319..1bab0053 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,7 +127,7 @@ mod tests { assert_eq!(speech.len(), 100800); approx::assert_abs_diff_eq!(speech[2000], 17.15977345625943, epsilon = 1.0e-10); - approx::assert_abs_diff_eq!(speech[30000], 2566.2058730889985, epsilon = 1.0e-10); + approx::assert_abs_diff_eq!(speech[30000], 2566.205873089253, epsilon = 1.0e-10); approx::assert_abs_diff_eq!(speech[70000], -1898.2890228814217, epsilon = 1.0e-10); approx::assert_abs_diff_eq!(speech[100799], -13.514971382534956, epsilon = 1.0e-10); } diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 46d234db..23c6c665 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -180,23 +180,18 @@ impl<'a> MlpgGlobalVariance<'a> { } fn calc_gv(&self) -> (f64, f64) { - let mean = self - .par - .iter() - .zip(self.gv_switch.iter()) - .filter(|(_, sw)| **sw) - .map(|(p, _)| *p) - .sum::() - / self.gv_length as f64; - let vari = self - .par - .iter() - .zip(self.gv_switch.iter()) - .filter(|(_, sw)| **sw) - .map(|(p, _)| (*p - mean) * (*p - mean)) - .sum::() - / self.gv_length as f64; + let mut sum = 0.0; + let mut sum_quad = 0.0; + + for (par, sw) in std::iter::zip(&self.par, self.gv_switch) { + if *sw { + sum += *par; + sum_quad += *par * *par; + } + } + let mean = sum / self.gv_length as f64; + let vari = (sum_quad / self.gv_length as f64) - (mean * mean); (mean, vari) } From 14e17f7ebfa28073726ec9b53f80431e1d451294 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 16:45:39 +0900 Subject: [PATCH 11/24] for_each -> for --- src/mlpg_adjust/mlpg.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 23c6c665..5b576c5a 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -199,11 +199,12 @@ impl<'a> MlpgGlobalVariance<'a> { fn conv_gv(&mut self, gv_mean: f64) { let (mean, vari) = self.calc_gv(); let ratio = (gv_mean / vari).sqrt(); - self.par - .iter_mut() - .zip(self.gv_switch.iter()) - .filter(|(_, sw)| **sw) - .for_each(|(p, _)| *p = ratio * (*p - mean) + mean); + + for (par, sw) in std::iter::zip(&mut self.par, self.gv_switch) { + if *sw { + *par = ratio * (*par - mean) + mean; + } + } } #[inline(never)] fn calc_hmmobj_derivative(&self) -> (f64, Vec) { From 8b7efe3a78aede0494ab1037fd1222759ba2983c Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 17:09:41 +0900 Subject: [PATCH 12/24] pre-index slices --- src/mlpg_adjust/mlpg.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 5b576c5a..bc4df871 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -243,7 +243,7 @@ impl<'a> MlpgGlobalVariance<'a> { } fn next_step( &mut self, - g: Vec, + g: &[f64], step: f64, mean: f64, vari: f64, @@ -255,21 +255,26 @@ impl<'a> MlpgGlobalVariance<'a> { let w = 1.0 / ((self.mtx.win_size * length) as f64); let dv = -2.0 * gv_vari * (vari - gv_mean) / self.mtx.length as f64; + assert!(width >= 1); // required for `wuw[0]` access + let wuw = self.mtx.wuw.chunks_exact(width); let wum = &self.mtx.wum[..length]; - let wuw = &self.mtx.wuw[..length * width]; + let par = &mut self.par[..length]; + let gv_switch = &self.gv_switch[..length]; + let g = &g[..length]; - for t in 0..length { - let h = -W1 * w * wuw[t * width] + // .zip(0..length) to help optimizer recognize t < length + for (wuw, t) in wuw.zip(0..length) { + let h = -W1 * w * wuw[0] - W2 * 2.0 / (length * length) as f64 * ((length - 1) as f64 * gv_vari * (vari - gv_mean) - + 2.0 * gv_vari * (self.par[t] - mean) * (self.par[t] - mean)); - let next_g = if self.gv_switch[t] { - 1.0 / h * (W1 * w * (-g[t] + wum[t]) + W2 * dv * (self.par[t] - mean)) + + 2.0 * gv_vari * (par[t] - mean) * (par[t] - mean)); + let next_g = if gv_switch[t] { + 1.0 / h * (W1 * w * (-g[t] + wum[t]) + W2 * dv * (par[t] - mean)) } else { 1.0 / h * (W1 * w * (-g[t] + wum[t])) }; - self.par[t] += step * next_g; + par[t] += step * next_g; } } @@ -301,7 +306,7 @@ impl<'a> MlpgGlobalVariance<'a> { } } - self.next_step(g, step, mean, vari, gv_mean, gv_vari); + self.next_step(&g, step, mean, vari, gv_mean, gv_vari); prev = obj; } From 6c4d16c219fd5fc43de2754e06bd0349a6f7125e Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 22:31:52 +0900 Subject: [PATCH 13/24] remove unnecessary inline(never) --- src/mlpg_adjust/mlpg.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index bc4df871..8d1cbf21 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -206,7 +206,6 @@ impl<'a> MlpgGlobalVariance<'a> { } } } - #[inline(never)] fn calc_hmmobj_derivative(&self) -> (f64, Vec) { let MlpgMatrix { width, From c6402d4049396303bbd46e26d956f415f6d91058 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Mon, 17 Nov 2025 22:52:52 +0900 Subject: [PATCH 14/24] refactor --- src/mlpg_adjust/mlpg.rs | 7 ++++--- src/model/voice/window.rs | 11 ++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 8d1cbf21..b6a95d4c 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -29,7 +29,7 @@ impl MlpgMatrix { let mut wuw = vec![0.0; length * width]; for t in 0..length { - for (i, window) in windows.iter().enumerate() { + for (window, parameter) in std::iter::zip(windows, ¶meters) { for (index, coef) in window.iter_rev(0) { if coef == 0.0 { continue; @@ -39,8 +39,9 @@ impl MlpgMatrix { if idx < 0 || idx >= length as isize { continue; } - let wu = coef * parameters[i][idx as usize].1; - wum[t] += wu * parameters[i][idx as usize].0; + let MeanVari(mean, vari) = parameter[idx as usize]; + let wu = coef * vari; + wum[t] += wu * mean; for (inner_index, coef) in window.iter_rev(index.index()) { if coef == 0.0 { diff --git a/src/model/voice/window.rs b/src/model/voice/window.rs index 78af8d08..bf2050db 100644 --- a/src/model/voice/window.rs +++ b/src/model/voice/window.rs @@ -11,7 +11,7 @@ impl Windows { } pub fn iter(&self) -> impl '_ + Iterator { - self.windows.iter() + self.into_iter() } pub fn size(&self) -> usize { self.windows.len() @@ -21,6 +21,15 @@ impl Windows { } } +impl<'a> IntoIterator for &'a Windows { + type Item = &'a Window; + type IntoIter = std::slice::Iter<'a, Window>; + + fn into_iter(self) -> Self::IntoIter { + self.windows.iter() + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Window { coefficients: Vec, From f3b0655fcf4650ddcc79d945a6b74b9736371f2f Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Tue, 18 Nov 2025 11:51:37 +0900 Subject: [PATCH 15/24] replace WindowIndex with isize --- src/mlpg_adjust/mlpg.rs | 8 ++++---- src/mlpg_adjust/mod.rs | 5 +++-- src/model/voice/window.rs | 31 +++++++++++++------------------ 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index b6a95d4c..a82fa5eb 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -30,12 +30,12 @@ impl MlpgMatrix { for t in 0..length { for (window, parameter) in std::iter::zip(windows, ¶meters) { - for (index, coef) in window.iter_rev(0) { + for (index, coef) in window.iter_rev(window.left_width()) { if coef == 0.0 { continue; } - let idx = (t as isize) - index.position(); + let idx = (t as isize) - index; if idx < 0 || idx >= length as isize { continue; } @@ -43,11 +43,11 @@ impl MlpgMatrix { let wu = coef * vari; wum[t] += wu * mean; - for (inner_index, coef) in window.iter_rev(index.index()) { + for (inner_index, coef) in window.iter_rev(index) { if coef == 0.0 { continue; } - let j = inner_index.index() - index.index(); + let j = (inner_index - index) as usize; if t + j >= length { break; } diff --git a/src/mlpg_adjust/mod.rs b/src/mlpg_adjust/mod.rs index e02e8f9d..fe9f8209 100644 --- a/src/mlpg_adjust/mod.rs +++ b/src/mlpg_adjust/mod.rs @@ -87,8 +87,9 @@ impl<'a> MlpgAdjust<'a> { .duration(durations) .zip(msd_boundaries) .map(|(mean_ivar, (left, right))| { - let is_left_msd_boundary = *left < window.left_width(); - let is_right_msd_boundary = *right < window.right_width(); + // TODO: migrate msd_boundaries to isize + let is_left_msd_boundary = *left < (-window.left_width()) as usize; + let is_right_msd_boundary = *right < window.right_width() as usize; // If the window includes non-msd frames, set the ivar to 0.0 if (is_left_msd_boundary || is_right_msd_boundary) && window_index != 0 { diff --git a/src/model/voice/window.rs b/src/model/voice/window.rs index bf2050db..ea50f7a7 100644 --- a/src/model/voice/window.rs +++ b/src/model/voice/window.rs @@ -40,14 +40,13 @@ impl Window { Self { coefficients } } - pub fn iter_rev(&self, start: usize) -> impl '_ + Iterator { - let width = self.width(); - self.coefficients[start..] + #[inline(always)] + pub fn iter_rev(&self, start: isize) -> impl '_ + Iterator { + self.coefficients[(start - self.left_width()) as usize..] .iter() .enumerate() .rev() - .zip(std::iter::repeat((start, width))) - .map(|((idx, coef), (start, width))| (WindowIndex::new(start + idx, width), *coef)) + .map(move |(idx, coef)| (idx as isize + start, *coef)) } #[inline] @@ -55,12 +54,12 @@ impl Window { self.coefficients.len() } #[inline] - pub fn left_width(&self) -> usize { - self.width() / 2 + pub fn left_width(&self) -> isize { + -(self.width() as isize / 2) } #[inline] - pub fn right_width(&self) -> usize { - self.width() - self.left_width() - 1 + pub fn right_width(&self) -> isize { + self.width() as isize + self.left_width() - 1 } } @@ -101,25 +100,21 @@ mod tests { fn width_3() { let window = Window::new(vec![-1.0, 0.0, 1.0]); assert_eq!(window.width(), 3); - assert_eq!(window.left_width(), 1); + assert_eq!(window.left_width(), -1); assert_eq!(window.right_width(), 1); } #[test] fn iterator() { let window = Window::new(vec![-1.0, 0.0, 1.0]); - let iterated = window.iter_rev(0).collect::>(); + let iterated = window.iter_rev(window.left_width()).collect::>(); assert_eq!(iterated[2].1, -1.0); assert_eq!(iterated[1].1, 0.0); assert_eq!(iterated[0].1, 1.0); - assert_eq!(iterated[2].0.index(), 0); - assert_eq!(iterated[1].0.index(), 1); - assert_eq!(iterated[0].0.index(), 2); - - assert_eq!(iterated[2].0.position(), -1); - assert_eq!(iterated[1].0.position(), 0); - assert_eq!(iterated[0].0.position(), 1); + assert_eq!(iterated[2].0, -1); + assert_eq!(iterated[1].0, 0); + assert_eq!(iterated[0].0, 1); } } From e32bc6fa129ac84a54096825c2739aeab83f924f Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Wed, 19 Nov 2025 01:50:20 +0900 Subject: [PATCH 16/24] swap loop --- src/mlpg_adjust/mlpg.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index a82fa5eb..cc3bce5a 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -28,8 +28,10 @@ impl MlpgMatrix { let mut wum = vec![0.0; length]; let mut wuw = vec![0.0; length * width]; - for t in 0..length { - for (window, parameter) in std::iter::zip(windows, ¶meters) { + for (window, parameter) in std::iter::zip(windows, ¶meters) { + let parameter = ¶meter[..length]; + + for t in 0..length { for (index, coef) in window.iter_rev(window.left_width()) { if coef == 0.0 { continue; From cd499a9941da767effed95e8ec25dad365ee0e85 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Wed, 19 Nov 2025 01:59:03 +0900 Subject: [PATCH 17/24] no rev --- src/lib.rs | 4 ++-- src/mlpg_adjust/mlpg.rs | 4 ++-- src/model/voice/window.rs | 13 ++++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1bab0053..507f823e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,7 @@ mod tests { assert_eq!(speech.len(), 74880); approx::assert_abs_diff_eq!(speech[2000], 2.3158134981607754e-5, epsilon = 1.0e-10); - approx::assert_abs_diff_eq!(speech[30000], 6459.375032316974, epsilon = 1.0e-10); + approx::assert_abs_diff_eq!(speech[30000], 6459.375032318177, epsilon = 1.0e-10); } // これ,名詞,代名詞,一般,*,*,*,これ,コレ,コレ,0/2,C3,-1 @@ -127,7 +127,7 @@ mod tests { assert_eq!(speech.len(), 100800); approx::assert_abs_diff_eq!(speech[2000], 17.15977345625943, epsilon = 1.0e-10); - approx::assert_abs_diff_eq!(speech[30000], 2566.205873089253, epsilon = 1.0e-10); + approx::assert_abs_diff_eq!(speech[30000], 2566.205873089126, epsilon = 1.0e-10); approx::assert_abs_diff_eq!(speech[70000], -1898.2890228814217, epsilon = 1.0e-10); approx::assert_abs_diff_eq!(speech[100799], -13.514971382534956, epsilon = 1.0e-10); } diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index cc3bce5a..bacdbb0d 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -32,7 +32,7 @@ impl MlpgMatrix { let parameter = ¶meter[..length]; for t in 0..length { - for (index, coef) in window.iter_rev(window.left_width()) { + for (index, coef) in window.iter(window.left_width()) { if coef == 0.0 { continue; } @@ -45,7 +45,7 @@ impl MlpgMatrix { let wu = coef * vari; wum[t] += wu * mean; - for (inner_index, coef) in window.iter_rev(index) { + for (inner_index, coef) in window.iter(index) { if coef == 0.0 { continue; } diff --git a/src/model/voice/window.rs b/src/model/voice/window.rs index ea50f7a7..10de0b5c 100644 --- a/src/model/voice/window.rs +++ b/src/model/voice/window.rs @@ -41,11 +41,10 @@ impl Window { } #[inline(always)] - pub fn iter_rev(&self, start: isize) -> impl '_ + Iterator { + pub fn iter(&self, start: isize) -> impl '_ + Iterator { self.coefficients[(start - self.left_width()) as usize..] .iter() .enumerate() - .rev() .map(move |(idx, coef)| (idx as isize + start, *coef)) } @@ -107,14 +106,14 @@ mod tests { #[test] fn iterator() { let window = Window::new(vec![-1.0, 0.0, 1.0]); - let iterated = window.iter_rev(window.left_width()).collect::>(); + let iterated = window.iter(window.left_width()).collect::>(); - assert_eq!(iterated[2].1, -1.0); + assert_eq!(iterated[0].1, -1.0); assert_eq!(iterated[1].1, 0.0); - assert_eq!(iterated[0].1, 1.0); + assert_eq!(iterated[2].1, 1.0); - assert_eq!(iterated[2].0, -1); + assert_eq!(iterated[0].0, -1); assert_eq!(iterated[1].0, 0); - assert_eq!(iterated[0].0, 1); + assert_eq!(iterated[2].0, 1); } } From 8d818af4865136db603a0e0acb7fd04d053b27d6 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Sun, 23 Nov 2025 14:11:16 +0900 Subject: [PATCH 18/24] rename vars --- src/mlpg_adjust/mlpg.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index bacdbb0d..ae67e690 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -42,11 +42,10 @@ impl MlpgMatrix { continue; } let MeanVari(mean, vari) = parameter[idx as usize]; - let wu = coef * vari; - wum[t] += wu * mean; + wum[t] += coef * vari * mean; - for (inner_index, coef) in window.iter(index) { - if coef == 0.0 { + for (inner_index, inner_coef) in window.iter(index) { + if inner_coef == 0.0 { continue; } let j = (inner_index - index) as usize; @@ -54,7 +53,7 @@ impl MlpgMatrix { break; } - wuw[t * width + j] += wu * coef; + wuw[t * width + j] += coef * inner_coef * vari; } } } From 41debeb49fd288ac887ee7757f9dd5e8937992a1 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Sun, 23 Nov 2025 15:23:01 +0900 Subject: [PATCH 19/24] remove unused guard --- src/mlpg_adjust/mlpg.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index ae67e690..c5fa7de3 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -49,10 +49,6 @@ impl MlpgMatrix { continue; } let j = (inner_index - index) as usize; - if t + j >= length { - break; - } - wuw[t * width + j] += coef * inner_coef * vari; } } From 55d2a5ae98d08c5c7df31a80e778b3bdf95314b0 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Sat, 29 Nov 2025 12:50:28 +0900 Subject: [PATCH 20/24] temporarily use mlsafir.yml to test performance --- .github/workflows/mlsafir.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mlsafir.yml b/.github/workflows/mlsafir.yml index 1d305e15..8b35e1b7 100644 --- a/.github/workflows/mlsafir.yml +++ b/.github/workflows/mlsafir.yml @@ -3,6 +3,7 @@ on: pull_request: paths: - src/vocoder/mlsa.rs + - src/mlpg_adjust/** jobs: collect: From 0571e5efad2787b31caea941e7c29bd9804c22da Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Sun, 5 Jul 2026 00:34:21 +0900 Subject: [PATCH 21/24] gv_by_ranges --- src/mlpg_adjust/mlpg.rs | 122 +++++++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 46 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index c5fa7de3..7ab8d683 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -2,6 +2,8 @@ //! //! For details, please refer to . +use std::ops::Range; + use crate::model::{GvParameter, MeanVari, Windows}; use super::{IterExt, mask::Mask}; @@ -116,13 +118,30 @@ impl MlpgMatrix { par } - fn calculate_gv_switch(gv_switch: &[bool], durations: &[usize], mask: &[bool]) -> Vec { - gv_switch + fn calculate_gv_by_ranges( + gv_switch: &[bool], + durations: &[usize], + mask: &[bool], + ) -> Vec<(Range, bool)> { + let mut gv_by_ranges: Vec<(Range, bool)> = Vec::new(); + + for (i, sw) in gv_switch .iter() .copied() .duration(durations) .filter_by(mask) - .collect() + .enumerate() + { + if let Some((current_range, current_sw)) = gv_by_ranges.last_mut() + && *current_sw == sw + { + current_range.end += 1; + } else { + gv_by_ranges.push((i..i + 1, sw)); + } + } + + gv_by_ranges } /// Solve the equasion, and if necessary, applies GV (global variance). @@ -137,9 +156,8 @@ impl MlpgMatrix { if let Some((gv_param, gv_switch)) = gv { let mtx_before = self.clone(); let par = self.solve(); - let gv_switch: Vec<_> = - Self::calculate_gv_switch(gv_switch, durations, msd_flag.mask()); - let mgv = MlpgGlobalVariance::new(mtx_before, par, &gv_switch); + let gv_by_ranges = Self::calculate_gv_by_ranges(gv_switch, durations, msd_flag.mask()); + let mgv = MlpgGlobalVariance::new(mtx_before, par, gv_by_ranges); let MeanVari(gv_mean, gv_vari) = gv_param[vector_index]; mgv.apply_gv(gv_mean * gv_weight, gv_vari) @@ -151,22 +169,19 @@ impl MlpgMatrix { /// MLPG global variance (GV) calculator. #[derive(Debug, Clone)] -pub struct MlpgGlobalVariance<'a> { +pub struct MlpgGlobalVariance { par: Vec, - gv_switch: &'a [bool], - gv_length: usize, + gv_by_ranges: Vec<(Range, bool)>, mtx: MlpgMatrix, } -impl<'a> MlpgGlobalVariance<'a> { +impl MlpgGlobalVariance { /// Create a new GV structure. - pub fn new(mtx: MlpgMatrix, par: Vec, gv_switch: &'a [bool]) -> Self { - let gv_length = gv_switch.iter().filter(|b| **b).count(); + pub fn new(mtx: MlpgMatrix, par: Vec, gv_by_ranges: Vec<(Range, bool)>) -> Self { Self { par, - gv_switch, - gv_length, + gv_by_ranges, mtx, } } @@ -178,18 +193,28 @@ impl<'a> MlpgGlobalVariance<'a> { } fn calc_gv(&self) -> (f64, f64) { - let mut sum = 0.0; - let mut sum_quad = 0.0; - - for (par, sw) in std::iter::zip(&self.par, self.gv_switch) { - if *sw { - sum += *par; - sum_quad += *par * *par; + let gv_length = gv_ranges(&self.gv_by_ranges) + .map(|r| r.len()) + .sum::() as f64; + let mut sums = [0.0; 4]; + let mut square_sums = [0.0; 4]; + + for range in gv_ranges(&self.gv_by_ranges) { + let (par, par_rem) = self.par[range.clone()].as_chunks::<4>(); + for p in par { + for i in 0..4 { + sums[i] += p[i]; + square_sums[i] += p[i] * p[i]; + } + } + for p in par_rem { + sums[0] += p; + square_sums[0] += p * p; } } - let mean = sum / self.gv_length as f64; - let vari = (sum_quad / self.gv_length as f64) - (mean * mean); + let mean = sums.iter().sum::() / gv_length; + let vari = (square_sums.iter().sum::() / gv_length) - (mean * mean); (mean, vari) } @@ -198,9 +223,9 @@ impl<'a> MlpgGlobalVariance<'a> { let (mean, vari) = self.calc_gv(); let ratio = (gv_mean / vari).sqrt(); - for (par, sw) in std::iter::zip(&mut self.par, self.gv_switch) { - if *sw { - *par = ratio * (*par - mean) + mean; + for range in gv_ranges(&self.gv_by_ranges) { + for p in &mut self.par[range] { + *p = ratio * *p + (1.0 - ratio) * mean; } } } @@ -253,25 +278,23 @@ impl<'a> MlpgGlobalVariance<'a> { let dv = -2.0 * gv_vari * (vari - gv_mean) / self.mtx.length as f64; assert!(width >= 1); // required for `wuw[0]` access - let wuw = self.mtx.wuw.chunks_exact(width); - let wum = &self.mtx.wum[..length]; - let par = &mut self.par[..length]; - let gv_switch = &self.gv_switch[..length]; - let g = &g[..length]; - - // .zip(0..length) to help optimizer recognize t < length - for (wuw, t) in wuw.zip(0..length) { - let h = -W1 * w * wuw[0] - - W2 * 2.0 / (length * length) as f64 - * ((length - 1) as f64 * gv_vari * (vari - gv_mean) - + 2.0 * gv_vari * (par[t] - mean) * (par[t] - mean)); - let next_g = if gv_switch[t] { - 1.0 / h * (W1 * w * (-g[t] + wum[t]) + W2 * dv * (par[t] - mean)) - } else { - 1.0 / h * (W1 * w * (-g[t] + wum[t])) - }; - - par[t] += step * next_g; + for (Range { start, end }, sw) in &self.gv_by_ranges { + let wuw = self.mtx.wuw[start * width..end * width].chunks_exact(width); + let wum = &self.mtx.wum[*start..*end]; + let par = &mut self.par[*start..*end]; + let g = &g[*start..*end]; + + for (wuw, t) in wuw.zip(0..end - start) { + let h = -W1 * w * wuw[0] + - W2 * 2.0 / (length * length) as f64 + * ((length - 1) as f64 * gv_vari * (vari - gv_mean) + + 2.0 * gv_vari * (par[t] - mean) * (par[t] - mean)); + let next_g = 1.0 / h + * (W1 * w * (-g[t] + wum[t]) + + (*sw as usize as f64) * W2 * dv * (par[t] - mean)); + + par[t] += step * next_g; + } } } @@ -281,7 +304,7 @@ impl<'a> MlpgGlobalVariance<'a> { const STEPDEC: f64 = 0.5; const STEPINC: f64 = 1.2; - if self.gv_length == 0 || GV_MAX_ITERATION == 0 { + if self.gv_by_ranges.is_empty() || GV_MAX_ITERATION == 0 { return; } @@ -309,3 +332,10 @@ impl<'a> MlpgGlobalVariance<'a> { } } } + +fn gv_ranges(gv_by_ranges: &[(Range, bool)]) -> impl Iterator> + '_ { + gv_by_ranges + .iter() + .filter(|(_, sw)| *sw) + .map(|(r, _)| r.clone()) +} From 3ebd385cc64822992539932b1c70677bb6511284 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Sun, 5 Jul 2026 11:16:45 +0900 Subject: [PATCH 22/24] tmp: change mask structure --- src/mlpg_adjust/mask.rs | 143 +++++++++++++++++++++++----------------- src/mlpg_adjust/mlpg.rs | 6 +- src/mlpg_adjust/mod.rs | 7 +- 3 files changed, 89 insertions(+), 67 deletions(-) diff --git a/src/mlpg_adjust/mask.rs b/src/mlpg_adjust/mask.rs index d6885bcb..ab404df4 100644 --- a/src/mlpg_adjust/mask.rs +++ b/src/mlpg_adjust/mask.rs @@ -2,33 +2,53 @@ //! //! The unvoiced frames are determined using multi-space probability distribution (MSD) parameter in stream. -use crate::model::StreamParameter; +use std::ops::Range; -use super::IterExt; +use crate::model::StreamParameter; /// Mask for unvoiced frames -pub struct Mask(Vec); - -impl FromIterator for Mask { - fn from_iter>(iter: I) -> Self { - Self(iter.into_iter().collect()) - } +pub struct Mask { + // matches the length of `durations` + ranged_durations: Vec>, + // contiguous ranges joined together + voiced_ranges: Vec>, } impl Mask { /// Create mask from `msd` field in stream with lengths of `durations`. pub fn create(stream: &StreamParameter, threshold: f64, durations: &[usize]) -> Self { - Self( - stream - .iter() - .map(|(_, msd)| *msd > threshold) - .duration(durations) - .collect(), - ) + let mut i = 0; + let mut ranged_durations = Vec::with_capacity(durations.len()); + let mut voiced_ranges: Vec> = Vec::new(); + for ((_, msd), duration) in stream.iter().zip(durations) { + let range = i..(i + duration); + if *msd > threshold { + if let Some(last) = voiced_ranges.last_mut() + && last.end == range.start + { + last.end = range.end; + } else { + voiced_ranges.push(range.clone()); + } + } + ranged_durations.push(range); + i += duration; + } + Self { + ranged_durations, + voiced_ranges, + } + } + pub fn len(&self) -> usize { + self.ranged_durations.last().map_or(0, |r| r.end) } /// Get the internal mask. - pub fn mask(&self) -> &[bool] { - &self.0 + pub fn mask(&self) -> Vec { + let mut out = vec![false; self.len()]; + for range in &self.voiced_ranges { + out[range.clone()].fill(true); + } + out } /// Fill back the masked region with `default` and returns an iterator of full-length sequence. pub fn fill<'a, T: 'a + Clone>( @@ -37,11 +57,9 @@ impl Mask { default: T, ) -> impl 'a + Iterator { let mut iter = masked.into_iter(); - self.0.iter().map(move |&mask| { - if mask { - iter.next().expect( - "Length of `masked` must be the same as the number of `true`'s in mask.", - ) + self.mask().into_iter().map(move |is_voiced| { + if is_voiced { + iter.next().unwrap() } else { default.clone() } @@ -49,62 +67,56 @@ impl Mask { } /// Get distances from left- and right-boundaries. pub fn boundary_distances(&self) -> Vec<(usize, usize)> { - if self.0.is_empty() { + if self.ranged_durations.is_empty() { return vec![]; } - let mut result = vec![(0, 0); self.0.len()]; - - let mut left = 0; - for (frame, mask) in self.0.iter().enumerate() { - if *mask { - result[frame].0 = frame - left; - } else { - // current position will be cut off - left = frame + 1; + let mut out = vec![(0, 0); self.len()]; + for range in &self.voiced_ranges { + for i in range.clone() { + out[i] = (i - range.start, range.end - 1 - i); } } - - let mut right = self.0.len() - 1; - for (frame, mask) in self.0.iter().enumerate().rev() { - if *mask { - result[frame].1 = right - frame; - } else { - // current position will be cut off - if frame == 0 { - break; - } - right = frame - 1; - } - } - - result + out } } #[cfg(test)] +#[allow( + clippy::single_range_in_vec_init, + reason = "intended vec of single range" +)] mod tests { use super::Mask; #[test] fn fill() { assert_eq!( - Mask(vec![false, false, true, true, false, true]) - .fill([0, 1, 2], 5) - .collect::>(), + Mask { + ranged_durations: vec![0..2, 2..4, 4..5, 5..6], + voiced_ranges: vec![2..4, 5..6] + } + .fill([0, 1, 2], 5) + .collect::>(), vec![5, 5, 0, 1, 5, 2] ); assert_eq!( - Mask(vec![false, false]).fill([0, 1], 5).collect::>(), + Mask { + ranged_durations: vec![0..2], + voiced_ranges: vec![] + } + .fill([0, 1], 5) + .collect::>(), vec![5, 5] ); } #[test] fn boundary_distances() { assert_eq!( - Mask(vec![ - true, true, true, true, true, true, true, true, true, true - ]) + Mask { + ranged_durations: vec![0..10], + voiced_ranges: vec![0..10] + } .boundary_distances(), vec![ (0, 9), @@ -120,9 +132,10 @@ mod tests { ], ); assert_eq!( - Mask(vec![ - true, true, true, false, false, true, true, true, true, true - ]) + Mask { + ranged_durations: vec![0..3, 3..5, 5..10], + voiced_ranges: vec![0..3, 5..10] + } .boundary_distances(), vec![ (0, 2), @@ -138,9 +151,10 @@ mod tests { ] ); assert_eq!( - Mask(vec![ - true, true, true, false, true, false, false, false, false, false - ]) + Mask { + ranged_durations: vec![0..3, 3..4, 4..5, 5..10], + voiced_ranges: vec![0..3, 4..5] + } .boundary_distances(), vec![ (0, 2), @@ -155,6 +169,13 @@ mod tests { (0, 0) ] ); - assert_eq!(Mask(vec![]).boundary_distances(), vec![]); + assert_eq!( + Mask { + ranged_durations: vec![], + voiced_ranges: vec![] + } + .boundary_distances(), + vec![] + ); } } diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 7ab8d683..ab0784da 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -6,7 +6,7 @@ use std::ops::Range; use crate::model::{GvParameter, MeanVari, Windows}; -use super::{IterExt, mask::Mask}; +use super::IterExt; const W1: f64 = 1.0; const W2: f64 = 1.0; @@ -151,12 +151,12 @@ impl MlpgMatrix { vector_index: usize, gv_weight: f64, durations: &[usize], - msd_flag: &Mask, + mask: &[bool], ) -> Vec { if let Some((gv_param, gv_switch)) = gv { let mtx_before = self.clone(); let par = self.solve(); - let gv_by_ranges = Self::calculate_gv_by_ranges(gv_switch, durations, msd_flag.mask()); + let gv_by_ranges = Self::calculate_gv_by_ranges(gv_switch, durations, mask); let mgv = MlpgGlobalVariance::new(mtx_before, par, gv_by_ranges); let MeanVari(gv_mean, gv_vari) = gv_param[vector_index]; diff --git a/src/mlpg_adjust/mod.rs b/src/mlpg_adjust/mod.rs index fe9f8209..10e4b00f 100644 --- a/src/mlpg_adjust/mod.rs +++ b/src/mlpg_adjust/mod.rs @@ -51,13 +51,14 @@ impl<'a> MlpgAdjust<'a> { pub fn create(&self, durations: &[usize]) -> Vec> { let msd_flag = Mask::create(&self.stream, self.msd_threshold, durations); let msd_boundaries = msd_flag.boundary_distances(); - let mut pars = vec![vec![0.0; self.vector_length]; msd_flag.mask().len()]; + let mask = msd_flag.mask(); + let mut pars = vec![vec![0.0; self.vector_length]; mask.len()]; for vector_index in 0..self.vector_length { let parameters = - self.create_parameters(vector_index, durations, &msd_boundaries, msd_flag.mask()); + self.create_parameters(vector_index, durations, &msd_boundaries, &mask); let mut mtx = MlpgMatrix::calc_wuw_and_wum(self.windows, parameters); - let par = mtx.par(&self.gv, vector_index, self.gv_weight, durations, &msd_flag); + let par = mtx.par(&self.gv, vector_index, self.gv_weight, durations, &mask); for (par, value) in pars.iter_mut().zip(msd_flag.fill(par, NODATA)) { par[vector_index] = value; From 5af0a0eeba3261bd4f7d020145ed7a0384264880 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Sun, 5 Jul 2026 13:11:03 +0900 Subject: [PATCH 23/24] better masking --- src/mlpg_adjust/mask.rs | 128 ++++++++++++++------------------------ src/mlpg_adjust/mlpg.rs | 41 ++++++------ src/mlpg_adjust/mod.rs | 82 ++++++------------------ src/model/voice/window.rs | 19 ++++-- 4 files changed, 99 insertions(+), 171 deletions(-) diff --git a/src/mlpg_adjust/mask.rs b/src/mlpg_adjust/mask.rs index ab404df4..5b4c296b 100644 --- a/src/mlpg_adjust/mask.rs +++ b/src/mlpg_adjust/mask.rs @@ -9,9 +9,9 @@ use crate::model::StreamParameter; /// Mask for unvoiced frames pub struct Mask { // matches the length of `durations` - ranged_durations: Vec>, + pub(super) ranged_durations: Vec>, // contiguous ranges joined together - voiced_ranges: Vec>, + pub(super) voiced_ranges: Vec>, } impl Mask { @@ -42,8 +42,10 @@ impl Mask { pub fn len(&self) -> usize { self.ranged_durations.last().map_or(0, |r| r.end) } - /// Get the internal mask. - pub fn mask(&self) -> Vec { + pub fn voiced_len(&self) -> usize { + self.voiced_ranges.iter().map(|r| r.len()).sum() + } + fn mask(&self) -> Vec { let mut out = vec![false; self.len()]; for range in &self.voiced_ranges { out[range.clone()].fill(true); @@ -65,19 +67,49 @@ impl Mask { } }) } - /// Get distances from left- and right-boundaries. - pub fn boundary_distances(&self) -> Vec<(usize, usize)> { - if self.ranged_durations.is_empty() { - return vec![]; +} + +#[derive(Debug)] +pub struct IntoIter<'a> { + ranged_durations: std::slice::Iter<'a, Range>, + voiced_ranges: std::slice::Iter<'a, Range>, + next_voiced_range: Option<&'a Range>, +} + +impl<'a> IntoIter<'a> { + pub fn new(mask: &'a Mask) -> Self { + let ranged_durations = mask.ranged_durations.iter(); + let mut voiced_ranges = mask.voiced_ranges.iter(); + let next_voiced_range = voiced_ranges.next(); + Self { + ranged_durations, + voiced_ranges, + next_voiced_range, } + } +} - let mut out = vec![(0, 0); self.len()]; - for range in &self.voiced_ranges { - for i in range.clone() { - out[i] = (i - range.start, range.end - 1 - i); - } +impl Iterator for IntoIter<'_> { + type Item = (Range, Option>); + + fn next(&mut self) -> Option { + let range = self.ranged_durations.next()?; + let voiced_range = self + .next_voiced_range + .filter(|r| r.start <= range.start && range.end <= r.end); + if voiced_range.is_some_and(|r| r.end == range.end) { + self.next_voiced_range = self.voiced_ranges.next(); } - out + Some((range.clone(), voiced_range.cloned())) + } +} + +impl<'a> IntoIterator for &'a Mask { + type Item = as Iterator>::Item; + type IntoIter = IntoIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self) } } @@ -110,72 +142,4 @@ mod tests { vec![5, 5] ); } - #[test] - fn boundary_distances() { - assert_eq!( - Mask { - ranged_durations: vec![0..10], - voiced_ranges: vec![0..10] - } - .boundary_distances(), - vec![ - (0, 9), - (1, 8), - (2, 7), - (3, 6), - (4, 5), - (5, 4), - (6, 3), - (7, 2), - (8, 1), - (9, 0) - ], - ); - assert_eq!( - Mask { - ranged_durations: vec![0..3, 3..5, 5..10], - voiced_ranges: vec![0..3, 5..10] - } - .boundary_distances(), - vec![ - (0, 2), - (1, 1), - (2, 0), - (0, 0), - (0, 0), - (0, 4), - (1, 3), - (2, 2), - (3, 1), - (4, 0) - ] - ); - assert_eq!( - Mask { - ranged_durations: vec![0..3, 3..4, 4..5, 5..10], - voiced_ranges: vec![0..3, 4..5] - } - .boundary_distances(), - vec![ - (0, 2), - (1, 1), - (2, 0), - (0, 0), - (0, 0), - (0, 0), - (0, 0), - (0, 0), - (0, 0), - (0, 0) - ] - ); - assert_eq!( - Mask { - ranged_durations: vec![], - voiced_ranges: vec![] - } - .boundary_distances(), - vec![] - ); - } } diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index ab0784da..122644cc 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -4,9 +4,10 @@ use std::ops::Range; -use crate::model::{GvParameter, MeanVari, Windows}; - -use super::IterExt; +use crate::{ + mlpg_adjust::mask::Mask, + model::{GvParameter, MeanVari, Windows}, +}; const W1: f64 = 1.0; const W2: f64 = 1.0; @@ -118,26 +119,25 @@ impl MlpgMatrix { par } - fn calculate_gv_by_ranges( - gv_switch: &[bool], - durations: &[usize], - mask: &[bool], - ) -> Vec<(Range, bool)> { + fn calculate_gv_by_ranges(gv_switch: &[bool], mask: &Mask) -> Vec<(Range, bool)> { let mut gv_by_ranges: Vec<(Range, bool)> = Vec::new(); - for (i, sw) in gv_switch - .iter() - .copied() - .duration(durations) - .filter_by(mask) - .enumerate() - { + let mut cum_duration = 0; + for (sw, (range, voiced_range)) in std::iter::zip(gv_switch, mask) { + // not voiced + if voiced_range.is_none() { + continue; + } + + let range = cum_duration..cum_duration + range.len(); + cum_duration += range.len(); if let Some((current_range, current_sw)) = gv_by_ranges.last_mut() - && *current_sw == sw + && *current_sw == *sw + && current_range.end == range.start { - current_range.end += 1; + current_range.end = range.end; } else { - gv_by_ranges.push((i..i + 1, sw)); + gv_by_ranges.push((range, *sw)); } } @@ -150,13 +150,12 @@ impl MlpgMatrix { gv: &Option, vector_index: usize, gv_weight: f64, - durations: &[usize], - mask: &[bool], + mask: &Mask, ) -> Vec { if let Some((gv_param, gv_switch)) = gv { let mtx_before = self.clone(); let par = self.solve(); - let gv_by_ranges = Self::calculate_gv_by_ranges(gv_switch, durations, mask); + let gv_by_ranges = Self::calculate_gv_by_ranges(gv_switch, mask); let mgv = MlpgGlobalVariance::new(mtx_before, par, gv_by_ranges); let MeanVari(gv_mean, gv_vari) = gv_param[vector_index]; diff --git a/src/mlpg_adjust/mod.rs b/src/mlpg_adjust/mod.rs index 10e4b00f..b6454361 100644 --- a/src/mlpg_adjust/mod.rs +++ b/src/mlpg_adjust/mod.rs @@ -2,8 +2,6 @@ //! //! For details on MLPG, please refer to . -use std::iter; - use crate::{ constants::NODATA, model::{GvParameter, MeanVari, ModelStream, StreamParameter, Windows}, @@ -49,18 +47,15 @@ impl<'a> MlpgAdjust<'a> { } /// Parameter generation using GV weight pub fn create(&self, durations: &[usize]) -> Vec> { - let msd_flag = Mask::create(&self.stream, self.msd_threshold, durations); - let msd_boundaries = msd_flag.boundary_distances(); - let mask = msd_flag.mask(); + let mask = Mask::create(&self.stream, self.msd_threshold, durations); let mut pars = vec![vec![0.0; self.vector_length]; mask.len()]; for vector_index in 0..self.vector_length { - let parameters = - self.create_parameters(vector_index, durations, &msd_boundaries, &mask); + let parameters = self.create_parameters(vector_index, &mask); let mut mtx = MlpgMatrix::calc_wuw_and_wum(self.windows, parameters); - let par = mtx.par(&self.gv, vector_index, self.gv_weight, durations, &mask); + let par = mtx.par(&self.gv, vector_index, self.gv_weight, &mask); - for (par, value) in pars.iter_mut().zip(msd_flag.fill(par, NODATA)) { + for (par, value) in pars.iter_mut().zip(mask.fill(par, NODATA)) { par[vector_index] = value; } } @@ -68,70 +63,31 @@ impl<'a> MlpgAdjust<'a> { pars } - #[inline(never)] - fn create_parameters( - &self, - vector_index: usize, - durations: &[usize], - msd_boundaries: &[(usize, usize)], - mask: &[bool], - ) -> Vec> { + fn create_parameters(&self, vector_index: usize, mask: &Mask) -> Vec> { self.windows .iter() .enumerate() .map(|(window_index, window)| { let m = self.vector_length * window_index + vector_index; - self.stream - .iter() - .map(|(curr_stream, _)| curr_stream[m].with_ivar()) - .duration(durations) - .zip(msd_boundaries) - .map(|(mean_ivar, (left, right))| { - // TODO: migrate msd_boundaries to isize - let is_left_msd_boundary = *left < (-window.left_width()) as usize; - let is_right_msd_boundary = *right < window.right_width() as usize; + let mut out = Vec::with_capacity(mask.voiced_len()); + for ((curr_stream, _), (range, voiced_range)) in self.stream.iter().zip(mask) { + let Some(voiced_range) = voiced_range else { + continue; + }; - // If the window includes non-msd frames, set the ivar to 0.0 - if (is_left_msd_boundary || is_right_msd_boundary) && window_index != 0 { - mean_ivar.with_0() + let mean_ivar = curr_stream[m].with_ivar(); + for i in range { + if !window.contained_in(&voiced_range, i) && window_index != 0 { + out.push(mean_ivar.with_0()); } else { - mean_ivar + out.push(mean_ivar); } - }) - .filter_by(mask) - .collect() + } + } + + out }) .collect() } } - -trait IterExt: Iterator { - fn duration<'a>( - self, - durations: impl IntoIterator + 'a, - ) -> impl Iterator; - - fn filter_by<'a>( - self, - mask: impl IntoIterator + 'a, - ) -> impl Iterator; -} - -impl> IterExt for I { - fn duration<'a>( - self, - durations: impl IntoIterator + 'a, - ) -> impl Iterator { - self.zip(durations) - .flat_map(move |(item, duration)| iter::repeat_n(item, *duration)) - } - - fn filter_by<'a>( - self, - mask: impl IntoIterator + 'a, - ) -> impl Iterator { - self.zip(mask) - .filter_map(|(item, mask)| if *mask { Some(item) } else { None }) - } -} diff --git a/src/model/voice/window.rs b/src/model/voice/window.rs index 10de0b5c..96fc7bb8 100644 --- a/src/model/voice/window.rs +++ b/src/model/voice/window.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -49,7 +51,7 @@ impl Window { } #[inline] - pub fn width(&self) -> usize { + pub(super) fn width(&self) -> usize { self.coefficients.len() } #[inline] @@ -57,8 +59,9 @@ impl Window { -(self.width() as isize / 2) } #[inline] - pub fn right_width(&self) -> isize { - self.width() as isize + self.left_width() - 1 + pub fn contained_in(&self, range: &Range, self_index: usize) -> bool { + self_index >= range.start + (self.width() / 2) + && range.end > self_index + (self.width() / 2) } } @@ -92,7 +95,10 @@ mod tests { let window = Window::new(vec![0.0]); assert_eq!(window.width(), 1); assert_eq!(window.left_width(), 0); - assert_eq!(window.right_width(), 0); + assert!(!window.contained_in(&(3..5), 2)); + assert!(window.contained_in(&(3..5), 3)); + assert!(window.contained_in(&(3..5), 4)); + assert!(!window.contained_in(&(3..5), 5)); } #[test] @@ -100,7 +106,10 @@ mod tests { let window = Window::new(vec![-1.0, 0.0, 1.0]); assert_eq!(window.width(), 3); assert_eq!(window.left_width(), -1); - assert_eq!(window.right_width(), 1); + assert!(!window.contained_in(&(3..6), 2)); + assert!(!window.contained_in(&(3..6), 3)); + assert!(window.contained_in(&(3..6), 4)); + assert!(!window.contained_in(&(3..6), 5)); } #[test] From 7122aaae5020787f3bc6d50ba3a7506b0d8313a1 Mon Sep 17 00:00:00 2001 From: cm-ayf Date: Sun, 5 Jul 2026 14:45:20 +0900 Subject: [PATCH 24/24] better calc_wuw_and_wum --- src/mlpg_adjust/mlpg.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/mlpg_adjust/mlpg.rs b/src/mlpg_adjust/mlpg.rs index 122644cc..2b50eee9 100644 --- a/src/mlpg_adjust/mlpg.rs +++ b/src/mlpg_adjust/mlpg.rs @@ -31,28 +31,27 @@ impl MlpgMatrix { let mut wum = vec![0.0; length]; let mut wuw = vec![0.0; length * width]; - for (window, parameter) in std::iter::zip(windows, ¶meters) { + use std::iter::zip; + for (window, parameter) in zip(windows, ¶meters) { let parameter = ¶meter[..length]; - for t in 0..length { - for (index, coef) in window.iter(window.left_width()) { + for (index, coef) in window.iter(window.left_width()) { + for ((wuw, wum), MeanVari(mean, vari)) in zip( + wuw.chunks_exact_mut(width) + .zip(&mut wum) + .skip(index.max(0) as usize), + parameter.iter().skip((-index).max(0) as usize), + ) { if coef == 0.0 { continue; } + *wum += coef * vari * mean; - let idx = (t as isize) - index; - if idx < 0 || idx >= length as isize { - continue; - } - let MeanVari(mean, vari) = parameter[idx as usize]; - wum[t] += coef * vari * mean; - - for (inner_index, inner_coef) in window.iter(index) { + for (wuw, (_, inner_coef)) in zip(wuw, window.iter(index)) { if inner_coef == 0.0 { continue; } - let j = (inner_index - index) as usize; - wuw[t * width + j] += coef * inner_coef * vari; + *wuw += coef * inner_coef * vari; } } }