Skip to content

Commit 67220af

Browse files
authored
Merge pull request #520 from qfall/missing_crosstype_arithmetic
Missing crosstype arithmetic
2 parents 31d1bdd + c2de991 commit 67220af

File tree

12 files changed

+189
-27
lines changed

12 files changed

+189
-27
lines changed

src/integer/mat_poly_over_z/arithmetic/mul_scalar.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Z, MatPolyOverZ, MatPolyOverZ);
5858
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatPolyOverZ, Z, MatPolyOverZ);
5959
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Z, MatPolyOverZ, MatPolyOverZ);
6060

61-
implement_for_others!(Z, MatPolyOverZ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
61+
implement_for_others!(Z, MatPolyOverZ, MatPolyOverZ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
6262

6363
impl Mul<&PolyOverZ> for &MatPolyOverZ {
6464
type Output = MatPolyOverZ;

src/integer/mat_z/arithmetic/mul_scalar.rs

Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1111
use super::super::MatZ;
1212
use crate::integer::Z;
13+
use crate::integer_mod_q::{MatZq, Zq};
1314
use crate::macros::arithmetics::{
1415
arithmetic_assign_between_types, arithmetic_assign_trait_borrowed_to_owned,
1516
arithmetic_trait_borrowed_to_owned, arithmetic_trait_mixed_borrowed_owned,
@@ -60,7 +61,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Z, MatZ, MatZ);
6061
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatZ, Z, MatZ);
6162
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Z, MatZ, MatZ);
6263

63-
implement_for_others!(Z, MatZ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
64+
implement_for_others!(Z, MatZ, MatZ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
6465

6566
impl Mul<&Q> for &MatZ {
6667
type Output = MatQ;
@@ -75,7 +76,7 @@ impl Mul<&Q> for &MatZ {
7576
/// # Examples
7677
/// ```
7778
/// use qfall_math::integer::MatZ;
78-
/// use qfall_math::rational::{MatQ, Q};
79+
/// use qfall_math::rational::Q;
7980
/// use std::str::FromStr;
8081
///
8182
/// let mat_1 = MatZ::from_str("[[2, 1],[1, 2]]").unwrap();
@@ -96,6 +97,42 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Q, MatZ, MatQ);
9697
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatZ, Q, MatQ);
9798
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Q, MatZ, MatQ);
9899

100+
implement_for_others!(Q, MatZ, MatQ, Mul Scalar for f32 f64);
101+
102+
impl Mul<&Zq> for &MatZ {
103+
type Output = MatZq;
104+
/// Implements the [`Mul`] trait for a [`MatZ`] matrix with a [`Zq`] representative of a residue class.
105+
/// [`Mul`] is implemented for any combination of owned and borrowed values.
106+
///
107+
/// Parameters:
108+
/// - `scalar`: specifies the scalar by which the matrix is multiplied
109+
///
110+
/// Returns the product of `self` and `scalar` as a [`MatZq`].
111+
///
112+
/// # Examples
113+
/// ```
114+
/// use qfall_math::integer::MatZ;
115+
/// use qfall_math::integer_mod_q::Zq;
116+
/// use std::str::FromStr;
117+
///
118+
/// let mat_1 = MatZ::from_str("[[2, 1],[1, 2]]").unwrap();
119+
/// let zq = Zq::from((1,3));
120+
///
121+
/// let mat_2 = &mat_1 * &zq;
122+
/// ```
123+
fn mul(self, scalar: &Zq) -> Self::Output {
124+
let out = MatZq::from((self, scalar.get_mod()));
125+
out * scalar
126+
}
127+
}
128+
129+
arithmetic_trait_reverse!(Mul, mul, Zq, MatZ, MatZq);
130+
131+
arithmetic_trait_borrowed_to_owned!(Mul, mul, MatZ, Zq, MatZq);
132+
arithmetic_trait_borrowed_to_owned!(Mul, mul, Zq, MatZ, MatZq);
133+
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatZ, Zq, MatZq);
134+
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Zq, MatZ, MatZq);
135+
99136
impl MulAssign<&Z> for MatZ {
100137
/// Computes the scalar multiplication of `self` and `scalar` reusing
101138
/// the memory of `self`.
@@ -243,6 +280,24 @@ mod test_mul {
243280
assert_eq!(mat_3, integer_1 * mat_1);
244281
assert_eq!(mat_4, integer_2 * mat_2);
245282
}
283+
284+
/// Checks if scalar multiplication is available for any integer type
285+
#[test]
286+
fn availability() {
287+
let mat = MatZ::from_str("[[2, 1],[1, 2]]").unwrap();
288+
let integer = Z::from(3);
289+
290+
let _ = &mat * 1u8;
291+
let _ = &mat * 1u16;
292+
let _ = &mat * 1u32;
293+
let _ = &mat * 1u64;
294+
let _ = &mat * 1i8;
295+
let _ = &mat * 1i16;
296+
let _ = &mat * 1i32;
297+
let _ = &mat * 1i64;
298+
let _ = &mat * &integer;
299+
let _ = &mat * integer;
300+
}
246301
}
247302

248303
#[cfg(test)]
@@ -328,6 +383,99 @@ mod test_mul_q {
328383
assert_eq!(mat_3, rational_1 * mat_1);
329384
assert_eq!(mat_4, rational_2 * mat_2);
330385
}
386+
387+
/// Checks if scalar multiplication is available for any rational type
388+
#[test]
389+
fn availability() {
390+
let mat = MatZ::from_str("[[2, 1],[1, 2]]").unwrap();
391+
let rational = Q::from(3);
392+
393+
let _ = &mat * 1.0f32;
394+
let _ = &mat * 1.0f64;
395+
let _ = &mat * &rational;
396+
let _ = &mat * rational;
397+
}
398+
}
399+
400+
#[cfg(test)]
401+
mod test_mul_zq {
402+
use super::MatZ;
403+
use crate::integer_mod_q::{MatZq, Zq};
404+
use std::str::FromStr;
405+
406+
/// Checks if scalar multiplication works fine for both borrowed
407+
#[test]
408+
fn borrowed_correctness() {
409+
let mat_1 = MatZ::from_str("[[2, 1],[1, 2]]").unwrap();
410+
let mat_2 = mat_1.clone();
411+
let mat_3 = MatZq::from_str("[[1, 2],[2, 1]] mod 3").unwrap();
412+
let zq = Zq::from((2, 3));
413+
414+
let mat_1 = &mat_1 * &zq;
415+
let mat_2 = &zq * &mat_2;
416+
417+
assert_eq!(mat_3, mat_1);
418+
assert_eq!(mat_3, mat_2);
419+
}
420+
421+
/// Checks if scalar multiplication works fine for both owned
422+
#[test]
423+
fn owned_correctness() {
424+
let mat_1 = MatZ::from_str("[[2, 1],[1, 2]]").unwrap();
425+
let mat_2 = mat_1.clone();
426+
let mat_3 = MatZq::from_str("[[2, 1],[1, 2]] mod 3").unwrap();
427+
let zq = Zq::from((1, 3));
428+
429+
let mat_1 = mat_1 * zq.clone();
430+
let mat_2 = zq * mat_2;
431+
432+
assert_eq!(mat_3, mat_1);
433+
assert_eq!(mat_3, mat_2);
434+
}
435+
436+
/// Checks if scalar multiplication works fine for half owned/borrowed
437+
#[test]
438+
fn half_correctness() {
439+
let mat_1 = MatZ::from_str("[[2, 1],[1, 2]]").unwrap();
440+
let mat_2 = mat_1.clone();
441+
let mat_3 = mat_1.clone();
442+
let mat_4 = mat_1.clone();
443+
let mat_5 = MatZq::from_str("[[2, 1],[1, 2]] mod 3").unwrap();
444+
let zq = Zq::from((1, 3));
445+
446+
let mat_1 = mat_1 * &zq;
447+
let mat_2 = &zq * mat_2;
448+
let mat_3 = &mat_3 * zq.clone();
449+
let mat_4 = zq * &mat_4;
450+
451+
assert_eq!(mat_5, mat_1);
452+
assert_eq!(mat_5, mat_2);
453+
assert_eq!(mat_5, mat_3);
454+
assert_eq!(mat_5, mat_4);
455+
}
456+
457+
/// Checks if scalar multiplication works fine for matrices of different dimensions
458+
#[test]
459+
fn different_dimensions_correctness() {
460+
let mat_1 = MatZ::from_str("[[1],[0],[4]]").unwrap();
461+
let mat_2 = MatZ::from_str("[[2, 5, 6],[1, 3, 1]]").unwrap();
462+
let mat_3 = MatZq::from_str("[[1],[0],[1]] mod 3").unwrap();
463+
let mat_4 = MatZq::from_str("[[2, 2, 0],[1, 0, 1]] mod 3").unwrap();
464+
let integer = Zq::from((1, 3));
465+
466+
assert_eq!(mat_3, &integer * mat_1);
467+
assert_eq!(mat_4, integer * mat_2);
468+
}
469+
470+
/// Checks if scalar multiplication is available for any rational type
471+
#[test]
472+
fn availability() {
473+
let mat = MatZ::from_str("[[2, 1],[1, 2]]").unwrap();
474+
let zq = Zq::from((1, 3));
475+
476+
let _ = &mat * &zq;
477+
let _ = &mat * zq;
478+
}
331479
}
332480

333481
#[cfg(test)]

src/integer/poly_over_z/arithmetic/mul_scalar.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Z, PolyOverZ, PolyOverZ);
6363
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, PolyOverZ, Z, PolyOverZ);
6464
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Z, PolyOverZ, PolyOverZ);
6565

66-
implement_for_others!(Z, PolyOverZ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
66+
implement_for_others!(Z, PolyOverZ, PolyOverZ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
6767

6868
impl Mul<&Zq> for &PolyOverZ {
6969
type Output = PolyOverZq;
@@ -146,6 +146,8 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Q, PolyOverZ, PolyOverQ);
146146
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, PolyOverZ, Q, PolyOverQ);
147147
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Q, PolyOverZ, PolyOverQ);
148148

149+
implement_for_others!(Q, PolyOverZ, PolyOverQ, Mul Scalar for f32 f64);
150+
149151
impl MulAssign<&Z> for PolyOverZ {
150152
/// Computes the scalar multiplication of `self` and `scalar` reusing
151153
/// the memory of `self`.
@@ -375,5 +377,7 @@ mod test_mul_q {
375377
_ = q.clone() * &poly;
376378
_ = &q * poly.clone();
377379
_ = poly.clone() * &q;
380+
_ = &poly * 1.0f32;
381+
_ = &poly * 1.0f64;
378382
}
379383
}

src/integer_mod_q/mat_polynomial_ring_zq/arithmetic/mul_scalar.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Z, MatPolynomialRingZq, MatPolynom
6666
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatPolynomialRingZq, Z, MatPolynomialRingZq);
6767
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Z, MatPolynomialRingZq, MatPolynomialRingZq);
6868

69-
implement_for_others!(Z, MatPolynomialRingZq, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
69+
implement_for_others!(Z, MatPolynomialRingZq, MatPolynomialRingZq, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
7070

7171
impl Mul<&Zq> for &MatPolynomialRingZq {
7272
type Output = MatPolynomialRingZq;

src/integer_mod_q/mat_zq/arithmetic/mul_scalar.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Z, MatZq, MatZq);
6161
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatZq, Z, MatZq);
6262
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Z, MatZq, MatZq);
6363

64-
implement_for_others!(Z, MatZq, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
64+
implement_for_others!(Z, MatZq, MatZq, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
6565

6666
impl Mul<&Zq> for &MatZq {
6767
type Output = MatZq;

src/integer_mod_q/poly_over_zq/arithmetic/mul_scalar.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Z, PolyOverZq, PolyOverZq);
6565
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, PolyOverZq, Z, PolyOverZq);
6666
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Z, PolyOverZq, PolyOverZq);
6767

68-
implement_for_others!(Z, PolyOverZq, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
68+
implement_for_others!(Z, PolyOverZq, PolyOverZq, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
6969

7070
impl Mul<&Zq> for &PolyOverZq {
7171
type Output = PolyOverZq;

src/integer_mod_q/polynomial_ring_zq/arithmetic/mul_scalar.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Z, PolynomialRingZq, PolynomialRin
5959
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, PolynomialRingZq, Z, PolynomialRingZq);
6060
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Z, PolynomialRingZq, PolynomialRingZq);
6161

62-
implement_for_others!(Z, PolynomialRingZq, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
62+
implement_for_others!(Z, PolynomialRingZq, PolynomialRingZq, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
6363

6464
impl Mul<&Zq> for &PolynomialRingZq {
6565
type Output = PolynomialRingZq;

src/macros/for_others.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
/// Implements a specified trait using implicit conversions to a bridge type.
2121
///
2222
/// - ['Mul'](std::ops::Mul) with signature
23-
/// `($bridge_type, $type, Mul Scalar for $source_type)`
24-
/// - ['Div'](std::ops::Mul) with signature
25-
/// `($bridge_type, $type, Div Scalar for $source_type)`
23+
/// `($bridge_type, $type, $output_type, Mul Scalar for $source_type)`
24+
/// - ['Div'](std::ops::Div) with signature
25+
/// `($bridge_type, $type, $output_type, Div Scalar for $source_type)`
2626
/// - ['Rem'](std::ops::Rem) with signature
2727
/// `($bridge_type, $type, Rem for $source_type)`
2828
/// - ['PartialEq'](std::cmp::PartialEq) with signature
@@ -32,16 +32,16 @@
3232
///
3333
/// # Examples
3434
/// ```compile_fail
35-
/// implement_for_others!(Z, MatZ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
35+
/// implement_for_others!(Z, MatZ, MatZ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
3636
/// implement_for_others!(Z, Q, PartialEq for fmpz i8 i16 i32 i64 u8 u16 u32 u64);
3737
/// implement_for_others!(Z, Q, PartialOrd for fmpz i8 i16 i32 i64 u8 u16 u32 u64);
3838
/// implement_for_others!(Z, PolyOverZ, Rem for i8 i16 i32 i64 u8 u16 u32 u64);
3939
/// ```
4040
macro_rules! implement_for_others {
4141
// [`Mul`] trait scalar
42-
($bridge_type:ident, $type:ident, Mul Scalar for $($source_type:ident)*) => {
42+
($bridge_type:ident, $type:ident, $output_type:ident, Mul Scalar for $($source_type:ident)*) => {
4343
$(#[doc(hidden)] impl Mul<$source_type> for &$type {
44-
type Output = $type;
44+
type Output = $output_type;
4545
paste::paste! {
4646
#[doc = "Documentation can be found at [`" $type "::mul`]."]
4747
fn mul(self, scalar: $source_type) -> Self::Output {
@@ -52,7 +52,7 @@ macro_rules! implement_for_others {
5252

5353
#[doc(hidden)]
5454
impl Mul<$source_type> for $type {
55-
type Output = $type;
55+
type Output = $output_type;
5656
paste::paste! {
5757
#[doc = "Documentation can be found at [`" $type "::mul`]."]
5858
fn mul(self, scalar: $source_type) -> Self::Output {
@@ -63,7 +63,7 @@ macro_rules! implement_for_others {
6363

6464
#[doc(hidden)]
6565
impl Mul<&$type> for $source_type {
66-
type Output = $type;
66+
type Output = $output_type;
6767
paste::paste! {
6868
#[doc = "Documentation can be found at [`" $type "::mul`]."]
6969
fn mul(self, matrix: &$type) -> Self::Output {
@@ -74,7 +74,7 @@ macro_rules! implement_for_others {
7474

7575
#[doc(hidden)]
7676
impl Mul<$type> for $source_type {
77-
type Output = $type;
77+
type Output = $output_type;
7878
paste::paste! {
7979
#[doc = "Documentation can be found at [`" $type "::mul`]."]
8080
fn mul(self, matrix: $type) -> Self::Output {
@@ -85,9 +85,9 @@ macro_rules! implement_for_others {
8585
};
8686

8787
// [`Div`] trait scalar
88-
($bridge_type:ident, $type:ident, Div Scalar for $($source_type:ident)*) => {
88+
($bridge_type:ident, $type:ident, $output_type:ident, Div Scalar for $($source_type:ident)*) => {
8989
$(#[doc(hidden)] impl Div<$source_type> for &$type {
90-
type Output = $type;
90+
type Output = $output_type;
9191
paste::paste! {
9292
#[doc = "Documentation can be found at [`" $type "::div`]."]
9393
fn div(self, scalar: $source_type) -> Self::Output {
@@ -98,7 +98,7 @@ macro_rules! implement_for_others {
9898

9999
#[doc(hidden)]
100100
impl Div<$source_type> for $type {
101-
type Output = $type;
101+
type Output = $output_type;
102102
paste::paste! {
103103
#[doc = "Documentation can be found at [`" $type "::div`]."]
104104
fn div(self, scalar: $source_type) -> Self::Output {

src/rational/mat_q/arithmetic/div_scalar.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl Div<&Z> for &MatQ {
5858
arithmetic_trait_borrowed_to_owned!(Div, div, MatQ, Z, MatQ);
5959
arithmetic_trait_mixed_borrowed_owned!(Div, div, MatQ, Z, MatQ);
6060

61-
implement_for_others!(Z, MatQ, Div Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
61+
implement_for_others!(Z, MatQ, MatQ, Div Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
6262

6363
impl Div<&Q> for &MatQ {
6464
type Output = MatQ;
@@ -95,7 +95,7 @@ impl Div<&Q> for &MatQ {
9595
arithmetic_trait_borrowed_to_owned!(Div, div, MatQ, Q, MatQ);
9696
arithmetic_trait_mixed_borrowed_owned!(Div, div, MatQ, Q, MatQ);
9797

98-
implement_for_others!(Q, MatQ, Div Scalar for f32 f64);
98+
implement_for_others!(Q, MatQ, MatQ, Div Scalar for f32 f64);
9999

100100
impl DivAssign<&Q> for MatQ {
101101
/// Computes the scalar multiplication of `self` and `other` reusing

src/rational/mat_q/arithmetic/mul_scalar.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Q, MatQ, MatQ);
5858
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatQ, Q, MatQ);
5959
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Q, MatQ, MatQ);
6060

61-
implement_for_others!(Z, MatQ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
61+
implement_for_others!(Z, MatQ, MatQ, Mul Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
6262

6363
impl Mul<&Q> for &MatQ {
6464
type Output = MatQ;
@@ -97,7 +97,7 @@ arithmetic_trait_borrowed_to_owned!(Mul, mul, Z, MatQ, MatQ);
9797
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatQ, Z, MatQ);
9898
arithmetic_trait_mixed_borrowed_owned!(Mul, mul, Z, MatQ, MatQ);
9999

100-
implement_for_others!(Q, MatQ, Mul Scalar for f32 f64);
100+
implement_for_others!(Q, MatQ, MatQ, Mul Scalar for f32 f64);
101101

102102
impl MulAssign<&Q> for MatQ {
103103
/// Computes the scalar multiplication of `self` and `other` reusing
@@ -140,6 +140,7 @@ impl MulAssign<&Z> for MatQ {
140140
arithmetic_assign_trait_borrowed_to_owned!(MulAssign, mul_assign, MatQ, Q);
141141
arithmetic_assign_trait_borrowed_to_owned!(MulAssign, mul_assign, MatQ, Z);
142142
arithmetic_assign_between_types!(MulAssign, mul_assign, MatQ, Z, u64 u32 u16 u8 i64 i32 i16 i8);
143+
arithmetic_assign_between_types!(MulAssign, mul_assign, MatQ, Q, f32 f64);
143144

144145
#[cfg(test)]
145146
mod test_mul_z {
@@ -404,5 +405,7 @@ mod test_mul_assign {
404405
a *= 1_i16;
405406
a *= 1_i32;
406407
a *= 1_i64;
408+
a *= 1.0_f32;
409+
a *= 1.0_f64;
407410
}
408411
}

0 commit comments

Comments
 (0)