Skip to content

Commit 5169cbf

Browse files
committed
Refactor of autodiff with generalized traversal.
Added test for autodiff.
1 parent bb405b6 commit 5169cbf

File tree

6 files changed

+2750
-1121
lines changed

6 files changed

+2750
-1121
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
ad_test: @autodiff @print type = {
3+
4+
add_1: (x: double, y: double) -> (r: double) = {
5+
r = x + y;
6+
}
7+
8+
add_2: (x: double, y: double) -> (r: double) = {
9+
r = x + y + x;
10+
}
11+
12+
sub_1: (x: double, y: double) -> (r: double) = {
13+
r = x - y;
14+
}
15+
16+
sub_2: (x: double, y: double) -> (r: double) = {
17+
r = x - y - x;
18+
}
19+
20+
add_sub_2: (x: double, y: double) -> (r: double) = {
21+
r = x + y - x;
22+
}
23+
24+
mul_1: (x: double, y: double) -> (r: double) = {
25+
r = x * y;
26+
}
27+
28+
mul_2: (x: double, y: double) -> (r: double) = {
29+
r = x * y * x;
30+
}
31+
32+
div_1: (x: double, y: double) -> (r: double) = {
33+
r = x / y;
34+
}
35+
36+
div_2: (x: double, y: double) -> (r: double) = {
37+
r = x / y / y;
38+
}
39+
40+
mul_div_2: (x: double, y: double) -> (r: double) = {
41+
r = x * y / x;
42+
}
43+
}
44+
45+
write_output: (func: std::string, x: double, x_d: double, y: double, y_d: double, ret) = {
46+
std::cout << "diff((func)$) at (x = (x)$, x_d = (x_d)$, y = (y)$, y_d = (y_d)$) = (r = (ret.r)$, r_d = (ret.r_d)$)" << std::endl;
47+
}
48+
49+
main: () = {
50+
51+
x: double = 2.0;
52+
x_d: double = 1.0;
53+
y: double = 3.0;
54+
y_d: double = 2.0;
55+
56+
write_output("x + y", x, x_d, y, y_d, ad_test::add_1_diff(x, x_d, y, y_d));
57+
write_output("x + y + x", x, x_d, y, y_d, ad_test::add_2_diff(x, x_d, y, y_d));
58+
write_output("x - y", x, x_d, y, y_d, ad_test::sub_1_diff(x, x_d, y, y_d));
59+
write_output("x - y - x", x, x_d, y, y_d, ad_test::sub_2_diff(x, x_d, y, y_d));
60+
write_output("x + y - x", x, x_d, y, y_d, ad_test::add_sub_2_diff(x, x_d, y, y_d));
61+
write_output("x * y", x, x_d, y, y_d, ad_test::mul_1_diff(x, x_d, y, y_d));
62+
write_output("x * y * x", x, x_d, y, y_d, ad_test::mul_2_diff(x, x_d, y, y_d));
63+
write_output("x / y", x, x_d, y, y_d, ad_test::div_1_diff(x, x_d, y, y_d));
64+
write_output("x / y / y", x, x_d, y, y_d, ad_test::div_2_diff(x, x_d, y, y_d));
65+
write_output("x * y / x", x, x_d, y, y_d, ad_test::mul_div_2_diff(x, x_d, y, y_d));
66+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
diff(x + y) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = 5.000000, r_d = 3.000000)
2+
diff(x + y + x) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = 7.000000, r_d = 4.000000)
3+
diff(x - y) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = -1.000000, r_d = -1.000000)
4+
diff(x - y - x) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = -3.000000, r_d = -2.000000)
5+
diff(x + y - x) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = 3.000000, r_d = 2.000000)
6+
diff(x * y) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = 6.000000, r_d = 7.000000)
7+
diff(x * y * x) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = 12.000000, r_d = 20.000000)
8+
diff(x / y) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = 0.666667, r_d = -0.111111)
9+
diff(x / y / y) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = 0.222222, r_d = -0.185185)
10+
diff(x * y / x) at (x = 2.000000, x_d = 1.000000, y = 3.000000, y_d = 2.000000) = (r = 3.000000, r_d = 2.000000)
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
2+
#define CPP2_IMPORT_STD Yes
3+
4+
//=== Cpp2 type declarations ====================================================
5+
6+
7+
#include "cpp2util.h"
8+
9+
#line 1 "pure2-autodiff.cpp2"
10+
11+
#line 2 "pure2-autodiff.cpp2"
12+
class ad_test;
13+
14+
15+
//=== Cpp2 type definitions and function declarations ===========================
16+
17+
#line 1 "pure2-autodiff.cpp2"
18+
19+
#line 2 "pure2-autodiff.cpp2"
20+
class ad_test {
21+
using add_1_ret = double;
22+
23+
24+
#line 4 "pure2-autodiff.cpp2"
25+
public: [[nodiscard]] static auto add_1(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> add_1_ret;
26+
using add_2_ret = double;
27+
28+
29+
#line 8 "pure2-autodiff.cpp2"
30+
public: [[nodiscard]] static auto add_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> add_2_ret;
31+
using sub_1_ret = double;
32+
33+
34+
#line 12 "pure2-autodiff.cpp2"
35+
public: [[nodiscard]] static auto sub_1(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> sub_1_ret;
36+
using sub_2_ret = double;
37+
38+
39+
#line 16 "pure2-autodiff.cpp2"
40+
public: [[nodiscard]] static auto sub_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> sub_2_ret;
41+
using add_sub_2_ret = double;
42+
43+
44+
#line 20 "pure2-autodiff.cpp2"
45+
public: [[nodiscard]] static auto add_sub_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> add_sub_2_ret;
46+
using mul_1_ret = double;
47+
48+
49+
#line 24 "pure2-autodiff.cpp2"
50+
public: [[nodiscard]] static auto mul_1(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> mul_1_ret;
51+
using mul_2_ret = double;
52+
53+
54+
#line 28 "pure2-autodiff.cpp2"
55+
public: [[nodiscard]] static auto mul_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> mul_2_ret;
56+
using div_1_ret = double;
57+
58+
59+
#line 32 "pure2-autodiff.cpp2"
60+
public: [[nodiscard]] static auto div_1(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> div_1_ret;
61+
using div_2_ret = double;
62+
63+
64+
#line 36 "pure2-autodiff.cpp2"
65+
public: [[nodiscard]] static auto div_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> div_2_ret;
66+
using mul_div_2_ret = double;
67+
68+
69+
#line 40 "pure2-autodiff.cpp2"
70+
public: [[nodiscard]] static auto mul_div_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> mul_div_2_ret;
71+
struct add_1_diff_ret { double r; double r_d; };
72+
73+
74+
public: [[nodiscard]] static auto add_1_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> add_1_diff_ret;
75+
76+
struct add_2_diff_ret { double r; double r_d; };
77+
78+
public: [[nodiscard]] static auto add_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> add_2_diff_ret;
79+
80+
struct sub_1_diff_ret { double r; double r_d; };
81+
82+
public: [[nodiscard]] static auto sub_1_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> sub_1_diff_ret;
83+
84+
struct sub_2_diff_ret { double r; double r_d; };
85+
86+
public: [[nodiscard]] static auto sub_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> sub_2_diff_ret;
87+
88+
struct add_sub_2_diff_ret { double r; double r_d; };
89+
90+
public: [[nodiscard]] static auto add_sub_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> add_sub_2_diff_ret;
91+
92+
struct mul_1_diff_ret { double r; double r_d; };
93+
94+
public: [[nodiscard]] static auto mul_1_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> mul_1_diff_ret;
95+
96+
struct mul_2_diff_ret { double r; double r_d; };
97+
98+
public: [[nodiscard]] static auto mul_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> mul_2_diff_ret;
99+
100+
struct div_1_diff_ret { double r; double r_d; };
101+
102+
public: [[nodiscard]] static auto div_1_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> div_1_diff_ret;
103+
104+
struct div_2_diff_ret { double r; double r_d; };
105+
106+
public: [[nodiscard]] static auto div_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> div_2_diff_ret;
107+
108+
struct mul_div_2_diff_ret { double r; double r_d; };
109+
110+
public: [[nodiscard]] static auto mul_div_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> mul_div_2_diff_ret;
111+
112+
public: ad_test() = default;
113+
public: ad_test(ad_test const&) = delete; /* No 'that' constructor, suppress copy */
114+
public: auto operator=(ad_test const&) -> void = delete;
115+
116+
117+
#line 43 "pure2-autodiff.cpp2"
118+
};
119+
120+
auto write_output(cpp2::impl::in<std::string> func, cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d, auto const& ret) -> void;
121+
122+
#line 49 "pure2-autodiff.cpp2"
123+
auto main() -> int;
124+
125+
//=== Cpp2 function definitions =================================================
126+
127+
#line 1 "pure2-autodiff.cpp2"
128+
129+
#line 4 "pure2-autodiff.cpp2"
130+
[[nodiscard]] auto ad_test::add_1(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> add_1_ret{
131+
cpp2::impl::deferred_init<double> r;
132+
#line 5 "pure2-autodiff.cpp2"
133+
r.construct(x + y);
134+
return std::move(r.value()); }
135+
136+
#line 8 "pure2-autodiff.cpp2"
137+
[[nodiscard]] auto ad_test::add_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> add_2_ret{
138+
cpp2::impl::deferred_init<double> r;
139+
#line 9 "pure2-autodiff.cpp2"
140+
r.construct(x + y + x);
141+
return std::move(r.value()); }
142+
143+
#line 12 "pure2-autodiff.cpp2"
144+
[[nodiscard]] auto ad_test::sub_1(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> sub_1_ret{
145+
cpp2::impl::deferred_init<double> r;
146+
#line 13 "pure2-autodiff.cpp2"
147+
r.construct(x - y);
148+
return std::move(r.value()); }
149+
150+
#line 16 "pure2-autodiff.cpp2"
151+
[[nodiscard]] auto ad_test::sub_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> sub_2_ret{
152+
cpp2::impl::deferred_init<double> r;
153+
#line 17 "pure2-autodiff.cpp2"
154+
r.construct(x - y - x);
155+
return std::move(r.value()); }
156+
157+
#line 20 "pure2-autodiff.cpp2"
158+
[[nodiscard]] auto ad_test::add_sub_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> add_sub_2_ret{
159+
cpp2::impl::deferred_init<double> r;
160+
#line 21 "pure2-autodiff.cpp2"
161+
r.construct(x + y - x);
162+
return std::move(r.value()); }
163+
164+
#line 24 "pure2-autodiff.cpp2"
165+
[[nodiscard]] auto ad_test::mul_1(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> mul_1_ret{
166+
cpp2::impl::deferred_init<double> r;
167+
#line 25 "pure2-autodiff.cpp2"
168+
r.construct(x * y);
169+
return std::move(r.value()); }
170+
171+
#line 28 "pure2-autodiff.cpp2"
172+
[[nodiscard]] auto ad_test::mul_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> mul_2_ret{
173+
cpp2::impl::deferred_init<double> r;
174+
#line 29 "pure2-autodiff.cpp2"
175+
r.construct(x * y * x);
176+
return std::move(r.value()); }
177+
178+
#line 32 "pure2-autodiff.cpp2"
179+
[[nodiscard]] auto ad_test::div_1(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> div_1_ret{
180+
cpp2::impl::deferred_init<double> r;
181+
#line 33 "pure2-autodiff.cpp2"
182+
r.construct(x / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(x),y));
183+
return std::move(r.value()); }
184+
185+
#line 36 "pure2-autodiff.cpp2"
186+
[[nodiscard]] auto ad_test::div_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> div_2_ret{
187+
cpp2::impl::deferred_init<double> r;
188+
#line 37 "pure2-autodiff.cpp2"
189+
r.construct(x / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(x),y) / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(y),y));
190+
return std::move(r.value()); }
191+
192+
#line 40 "pure2-autodiff.cpp2"
193+
[[nodiscard]] auto ad_test::mul_div_2(cpp2::impl::in<double> x, cpp2::impl::in<double> y) -> mul_div_2_ret{
194+
cpp2::impl::deferred_init<double> r;
195+
#line 41 "pure2-autodiff.cpp2"
196+
r.construct(x * y / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(y),x));
197+
return std::move(r.value()); }
198+
199+
[[nodiscard]] auto ad_test::add_1_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> add_1_diff_ret{
200+
double r {0.0};
201+
double r_d {0.0};r_d = x_d + y_d;r = x + y;
202+
return { std::move(r), std::move(r_d) };
203+
}
204+
[[nodiscard]] auto ad_test::add_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> add_2_diff_ret{
205+
double r {0.0};
206+
double r_d {0.0};r_d = x_d + y_d + x_d;r = x + y + x;
207+
return { std::move(r), std::move(r_d) };
208+
}
209+
[[nodiscard]] auto ad_test::sub_1_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> sub_1_diff_ret{
210+
double r {0.0};
211+
double r_d {0.0};r_d = x_d - y_d;r = x - y;
212+
return { std::move(r), std::move(r_d) };
213+
}
214+
[[nodiscard]] auto ad_test::sub_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> sub_2_diff_ret{
215+
double r {0.0};
216+
double r_d {0.0};r_d = x_d - y_d - x_d;r = x - y - x;
217+
return { std::move(r), std::move(r_d) };
218+
}
219+
[[nodiscard]] auto ad_test::add_sub_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> add_sub_2_diff_ret{
220+
double r {0.0};
221+
double r_d {0.0};r_d = x_d + y_d - x_d;r = x + y - x;
222+
return { std::move(r), std::move(r_d) };
223+
}
224+
[[nodiscard]] auto ad_test::mul_1_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> mul_1_diff_ret{
225+
double r {0.0};
226+
double r_d {0.0};r_d = x * y_d + y * x_d;r = x * y;
227+
return { std::move(r), std::move(r_d) };
228+
}
229+
[[nodiscard]] auto ad_test::mul_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> mul_2_diff_ret{
230+
double r {0.0};
231+
double r_d {0.0};
232+
auto temp_1_d {x * y_d + y * x_d};
233+
auto temp_1 {x * y}; r_d = temp_1 * x_d + x * cpp2::move(temp_1_d);r = cpp2::move(temp_1) * x;
234+
return { std::move(r), std::move(r_d) };
235+
}
236+
[[nodiscard]] auto ad_test::div_1_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> div_1_diff_ret{
237+
double r {0.0};
238+
double r_d {0.0};r_d = x_d / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(x_d),y) - x * y_d / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(y_d),(y * y));r = x / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(x),y);
239+
return { std::move(r), std::move(r_d) };
240+
}
241+
[[nodiscard]] auto ad_test::div_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> div_2_diff_ret{
242+
double r {0.0};
243+
double r_d {0.0};
244+
auto temp_1_d {x_d / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(x_d),y) - x * y_d / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(y_d),(y * y))};
245+
auto temp_1 {x / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(x),y)}; r_d = cpp2::move(temp_1_d) / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(cpp2::move(temp_1_d)),y) - temp_1 * y_d / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(y_d),(y * y));r = cpp2::move(temp_1) / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(cpp2::move(temp_1)),y);
246+
return { std::move(r), std::move(r_d) };
247+
}
248+
[[nodiscard]] auto ad_test::mul_div_2_diff(cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d) -> mul_div_2_diff_ret{
249+
double r {0.0};
250+
double r_d {0.0};
251+
auto temp_1_d {x * y_d + y * x_d};
252+
auto temp_1 {x * y}; r_d = cpp2::move(temp_1_d) / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(cpp2::move(temp_1_d)),x) - temp_1 * x_d / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(x_d),(x * x));r = cpp2::move(temp_1) / CPP2_ASSERT_NOT_ZERO(CPP2_TYPEOF(cpp2::move(temp_1)),x);
253+
return { std::move(r), std::move(r_d) };
254+
}
255+
256+
#line 45 "pure2-autodiff.cpp2"
257+
auto write_output(cpp2::impl::in<std::string> func, cpp2::impl::in<double> x, cpp2::impl::in<double> x_d, cpp2::impl::in<double> y, cpp2::impl::in<double> y_d, auto const& ret) -> void{
258+
std::cout << "diff(" + cpp2::to_string(func) + ") at (x = " + cpp2::to_string(x) + ", x_d = " + cpp2::to_string(x_d) + ", y = " + cpp2::to_string(y) + ", y_d = " + cpp2::to_string(y_d) + ") = (r = " + cpp2::to_string(ret.r) + ", r_d = " + cpp2::to_string(ret.r_d) + ")" << std::endl;
259+
}
260+
261+
#line 49 "pure2-autodiff.cpp2"
262+
auto main() -> int{
263+
264+
double x {2.0};
265+
double x_d {1.0};
266+
double y {3.0};
267+
double y_d {2.0};
268+
269+
write_output("x + y", x, x_d, y, y_d, ad_test::add_1_diff(x, x_d, y, y_d));
270+
write_output("x + y + x", x, x_d, y, y_d, ad_test::add_2_diff(x, x_d, y, y_d));
271+
write_output("x - y", x, x_d, y, y_d, ad_test::sub_1_diff(x, x_d, y, y_d));
272+
write_output("x - y - x", x, x_d, y, y_d, ad_test::sub_2_diff(x, x_d, y, y_d));
273+
write_output("x + y - x", x, x_d, y, y_d, ad_test::add_sub_2_diff(x, x_d, y, y_d));
274+
write_output("x * y", x, x_d, y, y_d, ad_test::mul_1_diff(x, x_d, y, y_d));
275+
write_output("x * y * x", x, x_d, y, y_d, ad_test::mul_2_diff(x, x_d, y, y_d));
276+
write_output("x / y", x, x_d, y, y_d, ad_test::div_1_diff(x, x_d, y, y_d));
277+
write_output("x / y / y", x, x_d, y, y_d, ad_test::div_2_diff(x, x_d, y, y_d));
278+
write_output("x * y / x", x, x_d, y, y_d, ad_test::mul_div_2_diff(cpp2::move(x), cpp2::move(x_d), cpp2::move(y), cpp2::move(y_d)));
279+
}
280+

0 commit comments

Comments
 (0)