use crate::Poly;
use cas_domain::{Integer, Rational};
use std::sync::Arc;
const PRIMES: &[u64] = &[
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73,
];
pub(crate) struct Det(u64);
impl Det {
pub(crate) fn new() -> Self {
Det(0xC0FF_EE01)
}
pub(crate) fn next(&mut self) -> u64 {
let mut x = self.0 | 1;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
}
type IPoly = Vec<i128>;
fn ip_trim(v: &mut IPoly) {
while v.last() == Some(&0) {
v.pop();
}
}
fn ip_deg(v: &IPoly) -> i32 {
v.len() as i32 - 1
}
fn ip_exact_div(a: &IPoly, b: &IPoly) -> Option<IPoly> {
if b.is_empty() {
return None;
}
if a.is_empty() {
return Some(vec![]);
}
if ip_deg(a) < ip_deg(b) {
return None;
}
let db = ip_deg(b) as usize;
let lb = *b.last()?;
let mut r = a.clone();
let mut q = vec![0i128; a.len() - b.len() + 1];
while ip_deg(&r) >= ip_deg(b) && !r.is_empty() {
let dr = ip_deg(&r) as usize;
let lr = *r.last()?;
if lr % lb != 0 {
return None;
}
let t = lr / lb;
q[dr - db] = t;
for (i, &c) in b.iter().enumerate() {
r[dr - db + i] = r[dr - db + i].checked_sub(t.checked_mul(c)?)?;
}
ip_trim(&mut r);
}
if r.is_empty() { Some(q) } else { None }
}
fn ip_primitive(f: &IPoly) -> (i128, IPoly) {
if f.is_empty() {
return (0, vec![]);
}
let mut g: i128 = 0;
for &c in f {
g = gcd_i128(g, c.abs());
}
if g == 0 {
g = 1;
}
let mut pp: IPoly = f.iter().map(|&c| c / g).collect();
if *pp.last().unwrap() < 0 {
for c in &mut pp {
*c = -*c;
}
g = -g;
}
(g, pp)
}
fn gcd_i128(mut a: i128, mut b: i128) -> i128 {
while b != 0 {
let r = a % b;
a = b;
b = r;
}
a
}
type Fp = Vec<u64>;
fn fp_trim(v: &mut Fp) {
while v.last() == Some(&0) {
v.pop();
}
}
fn fp_deg(v: &Fp) -> i32 {
v.len() as i32 - 1
}
fn fp_monic(mut v: Fp, p: u64) -> Fp {
fp_trim(&mut v);
if let Some(&l) = v.last() {
let inv = fp_pow(l, p - 2, p);
for c in &mut v {
*c = *c % p * inv % p;
}
}
v
}
fn fp_pow(mut b: u64, mut e: u64, p: u64) -> u64 {
let mut r = 1u64;
b %= p;
while e > 0 {
if e & 1 == 1 {
r = r * b % p;
}
b = b * b % p;
e >>= 1;
}
r
}
fn fp_sub(a: &Fp, b: &Fp, p: u64) -> Fp {
let mut out = vec![0u64; a.len().max(b.len())];
for (i, &x) in a.iter().enumerate() {
out[i] = x;
}
for (i, &y) in b.iter().enumerate() {
out[i] = (out[i] + p - y % p) % p;
}
fp_trim(&mut out);
out
}
fn fp_mul(a: &Fp, b: &Fp, p: u64) -> Fp {
if a.is_empty() || b.is_empty() {
return vec![];
}
let mut out = vec![0u64; a.len() + b.len() - 1];
for (i, &x) in a.iter().enumerate() {
if x == 0 {
continue;
}
for (j, &y) in b.iter().enumerate() {
out[i + j] =
((out[i + j] as u128 + x as u128 * y as u128 % p as u128) % p as u128) as u64;
}
}
fp_trim(&mut out);
out
}
fn fp_divrem(a: &Fp, b: &Fp, p: u64) -> (Fp, Fp) {
assert!(!b.is_empty(), "fp 除式为零");
let mut r = a.clone();
let mut q = vec![0u64; (a.len() as i32 - fp_deg(b)).max(0) as usize + 1];
let db = fp_deg(b) as usize;
let inv = fp_pow(*b.last().unwrap(), p - 2, p);
while !r.is_empty() && fp_deg(&r) >= db as i32 {
let dr = r.len() - 1;
let t = *r.last().unwrap() * inv % p;
q[dr - db] = t;
for (i, &c) in b.iter().enumerate() {
r[dr - db + i] = (r[dr - db + i] + p - t * c % p) % p;
}
fp_trim(&mut r);
}
fp_trim(&mut q);
(q, r)
}
fn fp_xgcd(a: &Fp, b: &Fp, p: u64) -> (Fp, Fp, Fp) {
let (mut r0, mut r1) = (a.to_vec(), b.to_vec());
let (mut s0, mut s1) = (vec![1u64], vec![]);
let (mut t0, mut t1) = (vec![], vec![1u64]);
while !r1.is_empty() {
let (q, r) = fp_divrem(&r0, &r1, p);
r0 = r1;
r1 = r;
let s2 = fp_sub(&s0, &fp_mul(&q, &s1, p), p);
s0 = s1;
s1 = s2;
let t2 = fp_sub(&t0, &fp_mul(&q, &t1, p), p);
t0 = t1;
t1 = t2;
}
if r0.is_empty() {
return (vec![], vec![], vec![]);
}
let inv = fp_pow(*r0.last().unwrap(), p - 2, p);
let mul = |v: &Fp| -> Fp { v.iter().map(|&c| c * inv % p).collect() };
(mul(&r0), mul(&s0), mul(&t0))
}
fn fp_gcd(a: &Fp, b: &Fp, p: u64) -> Fp {
fp_xgcd(a, b, p).0
}
fn fp_deriv(a: &Fp, p: u64) -> Fp {
let mut out = vec![0u64; a.len().saturating_sub(1)];
for i in 1..a.len() {
out[i - 1] = a[i] * (i as u64) % p;
}
fp_trim(&mut out);
out
}
fn fp_powmod(mut b: Fp, mut e: u128, f: &Fp, p: u64) -> Fp {
let mut r: Fp = vec![1];
fn rem(v: Fp, f: &Fp, p: u64) -> Fp {
let (_, r) = fp_divrem(&v, f, p);
r
}
b = rem(b, f, p);
while e > 0 {
if e & 1 == 1 {
r = rem(fp_mul(&r, &b, p), f, p);
}
b = rem(fp_mul(&b, &b, p), f, p);
e >>= 1;
}
r
}
fn fp_rand(n: usize, p: u64, det: &mut Det) -> Fp {
let mut v: Fp = (0..n).map(|_| det.next() % p).collect();
fp_trim(&mut v);
v
}
fn ddf(f: &Fp, p: u64) -> Vec<(Fp, u32)> {
let mut out = vec![];
let mut fcur = f.clone();
let mut h: Fp = vec![0, 1]; let mut d = 1u32;
loop {
h = fp_powmod(h, p as u128, &fcur, p); let xx: Fp = vec![0, 1];
let g = fp_gcd(&fcur, &fp_sub(&h, &xx, p), p);
if fp_deg(&g) > 0 {
let q = fp_monic(g, p);
let (quot, rem) = fp_divrem(&fcur, &q, p);
debug_assert!(rem.is_empty(), "ddf 因子必整除");
out.push((q.clone(), d));
fcur = quot;
h = {
let (_, r) = fp_divrem(&h, &fcur, p);
r
};
}
let df = fp_deg(&fcur);
if df <= 0 {
break;
}
if df <= d as i32 {
if df > 0 {
out.push((fcur.clone(), df as u32));
}
break;
}
d += 1;
}
out
}
fn edf(f: &Fp, d: u32, p: u64, det: &mut Det) -> Vec<Fp> {
let n = fp_deg(f);
if n <= d as i32 {
return vec![f.clone()];
}
loop {
let q = fp_rand(f.len(), p, det);
if q.is_empty() {
continue;
}
let e = ((p as u128).pow(d) - 1) / 2;
let t = fp_powmod(q, e, f, p);
if t.is_empty() {
continue;
}
let one: Fp = vec![1];
let g = fp_gcd(f, &fp_sub(&t, &one, p), p);
let dg = fp_deg(&g);
if dg > 0 && dg < n {
let (quot, rem) = fp_divrem(f, &g, p);
debug_assert!(rem.is_empty(), "edf 因子必整除");
let mut out = edf(&fp_monic(g, p), d, p, det);
out.extend(edf(&fp_monic(quot, p), d, p, det));
return out;
}
}
}
fn cz_factor(f_monic_sqfree: &Fp, p: u64, det: &mut Det) -> Vec<Fp> {
let mut out = vec![];
for (g, d) in ddf(f_monic_sqfree, p) {
out.extend(edf(&g, d, p, det));
}
out
}
#[allow(clippy::too_many_arguments)]
fn hensel2(
target: &[u64],
a0: &Fp,
b0: &Fp,
_s: &Fp,
t: &Fp,
p: u64,
k: u32,
pk: u64,
) -> Option<(Vec<u64>, Vec<u64>)> {
let mut a: Vec<u64> = a0.to_vec();
let mut b: Vec<u64> = b0.to_vec();
let mut step_mod = p; for _step in 1..k {
let next_mod = step_mod * p; let prod = umul(&a, &b, next_mod);
let mut err: Fp = vec![];
for i in 0..prod.len().max(target.len()) {
let ai = target.get(i).copied().unwrap_or(0) % next_mod;
let pi = prod.get(i).copied().unwrap_or(0);
let diff = (ai + next_mod - pi) % next_mod;
if diff % step_mod != 0 {
return None; }
err.push(diff / step_mod % p);
}
fp_trim(&mut err);
if !err.is_empty() {
let sig = {
let prod_e = fp_mul(&err, t, p);
let (_, r) = fp_divrem(&prod_e, a0, p);
r
};
let tau = {
let pr = fp_sub(&err, &fp_mul(&sig, b0, p), p);
let (q, r) = fp_divrem(&pr, a0, p);
if !r.is_empty() {
return None; }
q
};
for (i, &c) in sig.iter().enumerate() {
a[i] = (a[i] + step_mod % pk * c % pk) % pk;
}
for (i, &c) in tau.iter().enumerate() {
b[i] = (b[i] + step_mod % pk * c % pk) % pk;
}
}
step_mod = next_mod;
}
trim_u(&mut a);
trim_u(&mut b);
Some((a, b))
}
fn trim_u(v: &mut Vec<u64>) {
while v.last() == Some(&0) {
v.pop();
}
}
fn umul(a: &[u64], b: &[u64], m: u64) -> Vec<u64> {
if a.is_empty() || b.is_empty() {
return vec![];
}
let mut out = vec![0u64; a.len() + b.len() - 1];
for (i, &x) in a.iter().enumerate() {
if x == 0 {
continue;
}
for (j, &y) in b.iter().enumerate() {
out[i + j] = (out[i + j] as u128 + x as u128 * y as u128 % m as u128) as u64 % m;
}
}
trim_u(&mut out);
out
}
fn hensel_all(target: &[u64], facs: &[Fp], p: u64, k: u32, pk: u64) -> Vec<Vec<u64>> {
if facs.len() == 1 {
return vec![target.to_vec()];
}
let mid = facs.len() / 2;
let (left, right) = facs.split_at(mid);
let a0 = left
.iter()
.cloned()
.reduce(|x, y| fp_mul(&x, &y, p))
.unwrap();
let b0 = right
.iter()
.cloned()
.reduce(|x, y| fp_mul(&x, &y, p))
.unwrap();
let (_, s, t) = fp_xgcd(&a0, &b0, p);
let Some((al, br)) = hensel2(target, &a0, &b0, &s, &t, p, k, pk) else {
return vec![target.to_vec(); facs.len()];
};
let mut out = hensel_all(&al, left, p, k, pk);
out.extend(hensel_all(&br, right, p, k, pk));
out
}
fn zassenhaus(s: &IPoly) -> Vec<IPoly> {
let d = ip_deg(s);
if d <= 1 {
return vec![s.to_vec()];
}
let lc = *s.last().unwrap() as i64; let amax: i128 = s.iter().map(|c| c.abs()).max().unwrap();
let mut chosen: Option<(u64, Fp)> = None;
for &p in PRIMES {
if lc % p as i64 == 0 {
continue;
}
let fmod: Fp = s
.iter()
.map(|&c| (c.rem_euclid(p as i128)) as u64)
.collect();
let fmod = fp_monic(fmod, p);
let df = fp_deriv(&fmod, p);
if fp_gcd(&fmod, &df, p).len() <= 1 {
chosen = Some((p, fmod));
break;
}
}
let (p, fmod) = match chosen {
Some(x) => x,
None => {
return vec![s.to_vec()]; }
};
let mut det = Det::new();
let facs_p = cz_factor(&fmod, p, &mut det);
if facs_p.len() <= 1 {
return vec![s.to_vec()]; }
let b_bound: u128 = (2u128 << d.min(120)) * amax as u128 * lc.unsigned_abs() as u128;
let mut pk = p as u128;
let mut k = 1u32;
while pk <= 2 * b_bound {
pk *= p as u128;
k += 1;
if pk > 1 << 60 {
return vec![s.to_vec()];
}
}
let pk = pk as u64;
let lc_inv = inv_mod(lc.rem_euclid(pk as i64) as u64, pk);
let a: Vec<u64> = s
.iter()
.map(|&c| ((c.rem_euclid(pk as i128) as u64 as u128 * lc_inv as u128) % pk as u128) as u64)
.collect();
let lifted = hensel_all(&a, &facs_p, p, k, pk);
combine(s, &lifted, pk, lc)
}
fn inv_mod(a: u64, m: u64) -> u64 {
let (mut r0, mut r1) = (m as i128, a as i128 % m as i128);
let (mut s0, mut s1) = (0i128, 1i128);
while r1 != 0 {
let q = r0 / r1;
let r2 = r0 - q * r1;
r0 = r1;
r1 = r2;
let s2 = s0 - q * s1;
s0 = s1;
s1 = s2;
}
debug_assert_eq!(r0, 1, "逆元不存在");
s0.rem_euclid(m as i128) as u64
}
fn combine(f: &IPoly, lifted: &[Vec<u64>], pk: u64, lc: i64) -> Vec<IPoly> {
let r = lifted.len();
if r > 16 {
return vec![f.to_vec()]; }
let half = pk / 2;
let sym = |v: Vec<u64>| -> IPoly {
v.iter()
.map(|&c| {
if c > half {
c as i128 - pk as i128
} else {
c as i128
}
})
.collect()
};
let mut factors: Vec<IPoly> = vec![];
let mut used = vec![false; r];
let mut f_cur = f.clone();
let mut masks: Vec<u64> = (1..(1u64 << (r - 1))).collect();
masks.sort_by_key(|m| m.count_ones());
loop {
let mut found = false;
for &mask in &masks {
if (0..r - 1).any(|i| mask >> i & 1 == 1 && used[i]) {
continue;
}
if mask.count_ones() as usize > ip_deg(&f_cur).max(0) as usize {
continue;
}
let mut cand: Vec<u64> = vec![1];
for (i, g) in lifted.iter().enumerate().take(r - 1) {
if mask >> i & 1 == 1 {
cand = umul(&cand, g, pk);
}
}
let lc_u = (lc as i128).rem_euclid(pk as i128) as u64;
let k = mask.count_ones();
let mut hit = None;
let mut scaled = cand.clone();
for j in 0..=k {
if j > 0 {
scaled = umul(&scaled, &[lc_u], pk);
}
let cand_ip = ip_trim_mut(sym(scaled.clone()));
if cand_ip.is_empty() {
continue;
}
let (_, pp) = ip_primitive(&cand_ip);
if pp.is_empty() || ip_deg(&pp) == 0 || ip_deg(&pp) > ip_deg(&f_cur) {
continue;
}
if let Some(q) = ip_exact_div(&f_cur, &pp) {
hit = Some((pp, q));
break;
}
}
if let Some((pp, q)) = hit {
factors.push(pp);
f_cur = q;
for (i, u) in used.iter_mut().enumerate().take(r - 1) {
if mask >> i & 1 == 1 {
*u = true;
}
}
found = true;
break;
}
}
if !found {
break;
}
}
if !f_cur.is_empty() && ip_deg(&f_cur) > 0 {
factors.push(f_cur);
}
factors
}
fn ip_trim_mut(mut v: IPoly) -> IPoly {
ip_trim(&mut v);
v
}
pub(crate) fn squarefree_parts(f: &Poly<Rational>) -> Vec<(Poly<Rational>, u32)> {
let df = f.deriv(0);
if df.is_zero() {
return vec![(f.clone(), 1)];
}
let c = f.gcd(&df);
let mut w = match f.exact_div(&c) {
Some(v) => v,
None => return vec![(f.clone(), 1)],
};
let mut z = match df.exact_div(&c) {
Some(v) => v.sub(&w.deriv(0)),
None => return vec![(f.clone(), 1)],
};
let mut out = vec![];
let mut i = 1u32;
while !w.is_constant() {
let g = w.gcd(&z);
if !g.is_constant() {
out.push((g.clone(), i));
w = w.exact_div(&g).expect("Yun 整除");
z = z.exact_div(&g).expect("Yun 整除").sub(&w.deriv(0));
} else {
z = z.sub(&w.deriv(0));
}
i += 1;
}
out
}
impl Poly<Rational> {
pub fn factor_univariate(&self) -> (Rational, Vec<(Poly<Rational>, u32)>) {
assert_eq!(self.ring().nvars(), 1, "factor_univariate 仅支持一元");
if self.is_zero() {
return (Rational::zero(), vec![]);
}
if self.is_constant() {
return (self.terms().next().unwrap().1.clone(), vec![]);
}
let mut den_lcm = Integer::one();
for (_, c) in self.terms() {
den_lcm = den_lcm.mul(&c.den());
}
let mut num_gcd = Integer::zero();
for (_, c) in self.terms() {
let scaled = c.num().mul(&den_lcm.div_exact(&c.den()));
num_gcd = if num_gcd.is_zero() {
scaled
} else {
num_gcd.gcd(&scaled)
};
}
if num_gcd.is_zero() {
num_gcd = Integer::one();
}
let cont = Rational::from_ints(&num_gcd, &den_lcm).unwrap();
let mut dense: IPoly = {
let mut v = vec![0i128; self.degree() as usize + 1];
for (e, c) in self.terms() {
let scaled = c.mul(&Rational::from_integer(&den_lcm));
let r = scaled.div(&Rational::from_integer(&num_gcd)).unwrap();
assert!(r.den().is_one(), "本原化后必为整系数");
v[e[0] as usize] = r.num().to_i64().expect("本原化后系数落 i64") as i128;
}
v
};
let mut cont = cont;
if *dense.last().unwrap() < 0 {
for c in &mut dense {
*c = -*c;
}
cont = cont.neg();
}
let ring = self.ring().clone();
let mut factors: Vec<(Poly<Rational>, u32)> = vec![];
for (s, m) in squarefree_parts(&from_dense(&dense, &ring)) {
if s.is_constant() {
continue;
}
let dense_s = to_dense(&s);
for h in zassenhaus(&dense_s) {
factors.push((from_dense(&h, &ring), m));
}
}
factors.sort_by(|a, b| cmp_dense(&to_dense(&a.0), &to_dense(&b.0)));
(cont, factors)
}
}
fn to_dense(p: &Poly<Rational>) -> IPoly {
let mut v = vec![0i128; p.degree() as usize + 1];
for (e, c) in p.terms() {
assert!(c.den().is_one(), "to_dense 需整系数");
v[e[0] as usize] = c.num().to_i64().expect("整系数落 i64") as i128;
}
v
}
fn from_dense(v: &IPoly, ring: &Arc<crate::PolyRing>) -> Poly<Rational> {
let items: Vec<(Vec<u32>, Rational)> = v
.iter()
.enumerate()
.filter(|(_, c)| **c != 0)
.map(|(i, &c)| {
(
vec![i as u32],
Rational::from_integer(&Integer::from_i64(c as i64)),
)
})
.collect();
Poly::from_terms(ring.clone(), items)
}
fn cmp_dense(a: &IPoly, b: &IPoly) -> std::cmp::Ordering {
use std::cmp::Ordering;
match ip_deg(a).cmp(&ip_deg(b)) {
Ordering::Equal => {}
o => return o,
}
for i in (0..a.len()).rev() {
match a[i].cmp(&b[i]) {
Ordering::Equal => {}
o => return o,
}
}
std::cmp::Ordering::Equal
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{MonOrder, PolyRing};
fn ring() -> Arc<PolyRing> {
PolyRing::new(["x"], MonOrder::DegRevLex)
}
fn from_coeffs(cs: &[i64]) -> Poly<Rational> {
let items: Vec<(Vec<u32>, Rational)> = cs
.iter()
.enumerate()
.filter(|(_, c)| **c != 0)
.map(|(i, &c)| {
(
vec![i as u32],
Rational::from_ints(&Integer::from_i64(c), &Integer::from_i64(1)).unwrap(),
)
})
.collect();
Poly::from_terms(ring(), items)
}
fn check_refold(f: &Poly<Rational>) {
let (cont, facs) = f.factor_univariate();
let mut prod = Poly::constant(ring(), cont);
for (g, m) in &facs {
prod = prod.mul(&g.pow(*m));
}
assert_eq!(prod, *f, "重展开不等于原式: f={f:?} factors={facs:?}");
}
#[test]
fn 二项式与已知分解() {
let f = from_coeffs(&[-1, 0, 1]); let (c, facs) = f.factor_univariate();
assert_eq!(facs.len(), 2);
let mut prod = Poly::constant(ring(), c);
for (g, m) in &facs {
prod = prod.mul(&g.pow(*m));
}
assert_eq!(prod, f);
let f = from_coeffs(&[4, 0, 0, 0, 1]);
let (_, facs) = f.factor_univariate();
assert_eq!(facs.len(), 2, "x^4+4 应分解为两个二次因子: {facs:?}");
check_refold(&f);
let f = from_coeffs(&[1, 0, 0, 0, 1]);
let (_, facs) = f.factor_univariate();
assert_eq!(facs.len(), 1);
}
#[test]
fn x_n_minus_1_全族() {
for n in 1..=64u32 {
let mut cs = vec![0i64; n as usize + 1];
cs[0] = -1;
cs[n as usize] = 1;
let f = from_coeffs(&cs);
check_refold(&f);
}
let mut cs = vec![0i64; 13];
cs[0] = -1;
cs[12] = 1;
let (_, facs) = from_coeffs(&cs).factor_univariate();
assert_eq!(facs.len(), 6);
}
#[test]
fn 随机积重展开() {
let mut det = Det::new();
for _ in 0..600 {
let nf = 2 + det.next() % 2; let mut f = Poly::constant(ring(), Rational::one());
for _ in 0..nf {
let deg = 1 + det.next() % 6;
let cs: Vec<i64> = (0..=deg).map(|_| (det.next() % 13) as i64 - 6).collect();
let g = from_coeffs(&cs);
if g.is_zero() {
continue;
}
f = f.mul(&g);
}
let scale = Rational::from_ints(
&Integer::from_i64((det.next() % 7) as i64 - 3),
&Integer::from_i64(1 + (det.next() % 5) as i64),
)
.unwrap();
let f = f.mul(&Poly::constant(ring(), scale));
if !f.is_zero() && !f.is_constant() {
check_refold(&f);
}
}
}
#[test]
fn 重数与内容() {
let f = from_coeffs(&[-8, 24, -24, 8]);
let (c, facs) = f.factor_univariate();
assert_eq!(facs.len(), 1);
assert_eq!(facs[0].1, 3);
assert_eq!(
c,
Rational::from_ints(&Integer::from_i64(8), &Integer::from_i64(1)).unwrap()
);
check_refold(&f);
}
}
#[cfg(test)]
mod hensel_regression {
use super::*;
#[test]
fn 溢出回归_符号翻转() {
let f1: IPoly = vec![-1, 2, 5, 2, -6];
let f2: IPoly = vec![6, -3, 1, 1, 6, 3, -4];
let f3: IPoly = vec![4, -6, 0, -1, -5];
let mut f = vec![0i128; 15];
for (i, &x) in f1.iter().enumerate() {
for (j, &y) in f2.iter().enumerate() {
for (k, &z) in f3.iter().enumerate() {
f[i + j + k] += x * y * z;
}
}
}
ip_trim(&mut f);
assert_eq!(zassenhaus(&f).len(), 3, "原始版本");
let mut neg = f.clone();
for c in &mut neg {
*c = -*c;
}
assert_eq!(zassenhaus(&neg).len(), 3, "符号翻转版本(曾触发 u64 溢出)");
}
}