diff --git a/test/algorithms/overlay/multi_overlay_cases.hpp b/test/algorithms/overlay/multi_overlay_cases.hpp index fd0d80ad8f..2fac8f0db5 100644 --- a/test/algorithms/overlay/multi_overlay_cases.hpp +++ b/test/algorithms/overlay/multi_overlay_cases.hpp @@ -1594,18 +1594,24 @@ static std::string issue_1288[3] = "POLYGON((-0.5 -1.49999999, -2.0 -0.1, -1.99999999 -1.5))" }; -static std::string bug_21155501[2] = - { - "MULTIPOLYGON(((-8.3935546875 27.449790329784214,4.9658203125 18.729501999072138,11.8212890625 23.563987128451217,9.7119140625 25.48295117535531,9.8876953125 31.728167146023935,8.3056640625 32.99023555965106,8.5693359375 37.16031654673677,-1.8896484375 35.60371874069731,-0.5712890625 32.02670629333614,-8.9208984375 29.458731185355344,-8.3935546875 27.449790329784214)))", - "MULTIPOLYGON(((4.9658203125 18.729501999072138,-3.4868710311820115 24.246968623627644,8.3589904332912 33.833614418115445,8.3056640625 32.99023555965106,9.8876953125 31.728167146023935,9.7119140625 25.48295117535531,11.8212890625 23.563987128451217,4.9658203125 18.729501999072138)),((-3.88714525609152 24.508246314579743,-8.3935546875 27.449790329784214,-8.9208984375 29.458731185355344,-0.5712890625 32.02670629333614,-1.8896484375 35.60371874069731,8.5693359375 37.16031654673677,8.362166569827938 33.883846345901595,-3.88714525609152 24.508246314579743)))", - }; - static std::string issue_1299[2] = { "MULTIPOLYGON(((1.2549999979079400 0.85000000411847698, -1.2550000020920500 0.84999999897038103, -1.2549999999999999 -0.85000000102961903, 1.2549999999999999 -0.84999999999999998)))", "MULTIPOLYGON(((-0.87500000000000000 -0.84999999999999998, -0.87500000000000000 -0.070000000000000201, -1.2549999999999999 -0.070000000000000201, -1.2549999999999999 -0.84999999999999998)))" }; +static std::string issue_1350_comment[2] = +{ + "MULTIPOLYGON(((2 10,2 8,0 8,0 10,2 10)),((10 8,10 2,8 2,8 0,0 0,0 4,2 4,2 8,4 8,4 10,6 10,6 8,6 6,8 6,8 8,10 8),(8 2,8 4,4 4,4 2,8 2)))", + "MULTIPOLYGON(((2 6,2 4,2 2,2 0,0 0,0 6,2 6)),((2 10,2 8,0 8,0 10,2 10)),((6 8,6 6,2 6,2 8,6 8)),((8 4,8 2,6 2,6 4,8 4)),((10 8,10 6,8 6,8 8,10 8)),((8 10,8 8,6 8,6 10,8 10)))" +}; + +static std::string bug_21155501[2] = + { + "MULTIPOLYGON(((-8.3935546875 27.449790329784214,4.9658203125 18.729501999072138,11.8212890625 23.563987128451217,9.7119140625 25.48295117535531,9.8876953125 31.728167146023935,8.3056640625 32.99023555965106,8.5693359375 37.16031654673677,-1.8896484375 35.60371874069731,-0.5712890625 32.02670629333614,-8.9208984375 29.458731185355344,-8.3935546875 27.449790329784214)))", + "MULTIPOLYGON(((4.9658203125 18.729501999072138,-3.4868710311820115 24.246968623627644,8.3589904332912 33.833614418115445,8.3056640625 32.99023555965106,9.8876953125 31.728167146023935,9.7119140625 25.48295117535531,11.8212890625 23.563987128451217,4.9658203125 18.729501999072138)),((-3.88714525609152 24.508246314579743,-8.3935546875 27.449790329784214,-8.9208984375 29.458731185355344,-0.5712890625 32.02670629333614,-1.8896484375 35.60371874069731,8.5693359375 37.16031654673677,8.362166569827938 33.883846345901595,-3.88714525609152 24.508246314579743)))", + }; + static std::string mysql_21965285_b[2] = { "MULTIPOLYGON(((3 0, -19 -19, -7 3, -2 10, 15 0, 3 0)))", diff --git a/test/algorithms/overlay/overlay_cases.hpp b/test/algorithms/overlay/overlay_cases.hpp index 7a896eca96..166ddf0def 100644 --- a/test/algorithms/overlay/overlay_cases.hpp +++ b/test/algorithms/overlay/overlay_cases.hpp @@ -1188,7 +1188,8 @@ static std::string issue_1226[2] = "POLYGON((-0.91943242964602156508 0.55292377741135378955,-0.90478776881879174887 0.51756843862590162786,-0.91 0.48,-0.91943242964602156508 0.55292377741135378955))" }; -// Triangle, nearly a line +// Triangle, nearly a line. +// Still gives an error in difference static std::string issue_1229[2] = { "POLYGON((38436.758 22765.61,930.538 -10523.68,925.121 -10507.965,38436.758 22765.61))", diff --git a/test/algorithms/set_operations/CMakeLists.txt b/test/algorithms/set_operations/CMakeLists.txt index a27a257eb0..16a7a6cd53 100644 --- a/test/algorithms/set_operations/CMakeLists.txt +++ b/test/algorithms/set_operations/CMakeLists.txt @@ -8,3 +8,9 @@ add_subdirectory(union) add_subdirectory(intersection) add_subdirectory(difference) add_subdirectory(sym_difference) + +foreach(item IN ITEMS + set_ops_areal_areal + ) + boost_geometry_add_unit_test("algorithms" ${item}) +endforeach() \ No newline at end of file diff --git a/test/algorithms/set_operations/Jamfile b/test/algorithms/set_operations/Jamfile index 27ebad64b0..7afe10baa3 100644 --- a/test/algorithms/set_operations/Jamfile +++ b/test/algorithms/set_operations/Jamfile @@ -1,6 +1,6 @@ # Boost.Geometry (aka GGL, Generic Geometry Library) # -# Copyright (c) 2007-2014 Barend Gehrels, Amsterdam, the Netherlands. +# Copyright (c) 2007-2024 Barend Gehrels, Amsterdam, the Netherlands. # Copyright (c) 2008-2014 Bruno Lalande, Paris, France. # Copyright (c) 2009-2014 Mateusz Loskot, London, UK. # @@ -14,6 +14,11 @@ # Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) +test-suite boost-geometry-algorithms + : + [ run set_ops_areal_areal.cpp : : : : algorithms_set_ops_areal_areal ] + ; + build-project difference ; build-project intersection ; build-project sym_difference ; diff --git a/test/algorithms/set_operations/difference/difference.cpp b/test/algorithms/set_operations/difference/difference.cpp index 35f4de1523..0d7f3c7646 100644 --- a/test/algorithms/set_operations/difference/difference.cpp +++ b/test/algorithms/set_operations/difference/difference.cpp @@ -562,43 +562,6 @@ void test_all() TEST_DIFFERENCE(issue_1138, 1, 203161.751, 2, 1237551.0171, 1); - { - ut_settings settings; - settings.set_test_validity(false); - settings.validity_of_sym = false; - TEST_DIFFERENCE_WITH(issue_1226, 1, 0.238037722, 0, 0.0, 1, settings); - } - - TEST_DIFFERENCE(issue_1231, 2, 36.798659456837477, 3, 195.2986, 5); - - TEST_DIFFERENCE(issue_1244, 3, 8, 3, 2, 6); - - { - // The symmetric difference reports an invalidity since the choice of - // discarding start/touch turns. - // This might be a false negative. - // Clockwise: "method: t; operations: u/x" - // CCW: "method: m; operations: i/x" - ut_settings settings; - settings.validity_of_sym = false; - TEST_DIFFERENCE_WITH(issue_1293, 1, 1.40999, 1, 2.318951, 2, settings); - } - -#if defined(BOOST_GEOMETRY_TEST_FAILURES) - // Difference fails for this case. This was not reported for this case. - // Reported as a failing intersection, which is fixed. - // The failing difference should be investigated more thoroughly. - TEST_DIFFERENCE(issue_1295, 1, 9.999, 1, 9.999, 1); -#endif - - TEST_DIFFERENCE(issue_1326, 3, 6.7128537626409130468, 6, 0.00372806966532758478, 9); - - TEST_DIFFERENCE(issue_1342_a, 2, 5.762381026454777, 0, 0.0, 2); - TEST_DIFFERENCE(issue_1342_b, 2, 5.762381026454777, 1, 2.55e-14, 3); - - TEST_DIFFERENCE(issue_1345_a, 1, 0.059308854, 0, 0.0, 1); - TEST_DIFFERENCE(issue_1345_b, 2, 0.024048025, 0, 0.0, 2); - TEST_DIFFERENCE(mysql_21977775, 2, 160.856568913, 2, 92.3565689126, 4); TEST_DIFFERENCE(mysql_21965285, 1, 92.0, 1, 14.0, 1); TEST_DIFFERENCE(mysql_23023665_1, 1, 92.0, 1, 142.5, 2); diff --git a/test/algorithms/set_operations/difference/difference_multi.cpp b/test/algorithms/set_operations/difference/difference_multi.cpp index a219e8dad4..734ba09b2d 100644 --- a/test/algorithms/set_operations/difference/difference_multi.cpp +++ b/test/algorithms/set_operations/difference/difference_multi.cpp @@ -172,16 +172,6 @@ void test_areal() TEST_DIFFERENCE(issue_900, 0, 0.0, 2, 35, 2); - TEST_DIFFERENCE(issue_1222, 2, 32.0, 1, 4.0, 1); - { - // "method: t; operations: c/c;" still happening in the result - // for multi/multi - ut_settings settings; - settings.set_test_validity(BG_IF_TEST_FAILURES); - settings.validity_of_sym = BG_IF_TEST_FAILURES; - TEST_DIFFERENCE_WITH(0, 1, issue_1288, 2, 10.95, 0, 0.0, 2); - } - // Areas and #clips correspond with POSTGIS (except sym case) test_one("case_101_multi", case_101_multi[0], case_101_multi[1], @@ -371,8 +361,6 @@ void test_areal() TEST_DIFFERENCE(mysql_regression_1_65_2017_08_31, optional(), optional_sliver(1e-6), 3, 152.064185, count_set(3, 4)); - - TEST_DIFFERENCE(issue_1299, 1, 3.9706, 0, 0, 1); } diff --git a/test/algorithms/set_operations/intersection/intersection.cpp b/test/algorithms/set_operations/intersection/intersection.cpp index faa861425e..416cdc44eb 100644 --- a/test/algorithms/set_operations/intersection/intersection.cpp +++ b/test/algorithms/set_operations/intersection/intersection.cpp @@ -293,23 +293,8 @@ void test_areal() TEST_INTERSECTION(issue_893, 1, -1, 473001.5082956461); - TEST_INTERSECTION(issue_1226, 1, -1, 0.00036722862); TEST_INTERSECTION(issue_1229, 0, -1, 0); - TEST_INTERSECTION(issue_1231, 1, -1, 54.701340543162516); - - TEST_INTERSECTION(issue_1244, 1, -1, 7); - - TEST_INTERSECTION(issue_1293, 1, -1, 1.49123); - TEST_INTERSECTION(issue_1295, 1, -1, 4.90121); - TEST_INTERSECTION(issue_1326, 1, -1, 16.4844); - - TEST_INTERSECTION(issue_1342_a, 1, -1, 43.05575); - TEST_INTERSECTION(issue_1342_b, 1, -1, 43.05575); - - TEST_INTERSECTION(issue_1345_a, 1, -1, 0.00062682687); - TEST_INTERSECTION(issue_1345_b, 1, -1, 0.010896761); - test_one("buffer_mp1", buffer_mp1[0], buffer_mp1[1], 1, 31, 2.271707796); test_one("buffer_mp2", buffer_mp2[0], buffer_mp2[1], diff --git a/test/algorithms/set_operations/intersection/intersection_multi.cpp b/test/algorithms/set_operations/intersection/intersection_multi.cpp index 02ded2dfca..ec45ce3fd1 100644 --- a/test/algorithms/set_operations/intersection/intersection_multi.cpp +++ b/test/algorithms/set_operations/intersection/intersection_multi.cpp @@ -362,14 +362,9 @@ void test_areal() TEST_INTERSECTION(issue_888_34, 7, -1, 0.0256838); TEST_INTERSECTION(issue_888_37, 13, -1, 0.0567043); - TEST_INTERSECTION(issue_1222, 1, -1, 4.0); - TEST_INTERSECTION(issue_1288, 1, -1, 1.05); - TEST_INTERSECTION(mysql_23023665_7, 2, 11, 9.80505786783); TEST_INTERSECTION(mysql_23023665_12, 2, 0, 11.812440191387557); TEST_INTERSECTION(mysql_regression_1_65_2017_08_31, 2, -1, 29.9022122); - - TEST_INTERSECTION(issue_1299, 1, -1, 0.2964); } template diff --git a/test/algorithms/set_operations/set_ops_areal_areal.cpp b/test/algorithms/set_operations/set_ops_areal_areal.cpp new file mode 100644 index 0000000000..6a2b9a835a --- /dev/null +++ b/test/algorithms/set_operations/set_ops_areal_areal.cpp @@ -0,0 +1,218 @@ +// Boost.Geometry +// Unit Test + +// Copyright (c) 2024 Barend Gehrels, Amsterdam, the Netherlands. + +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include + +#include +#include + +#include + +// Use another alias, on purpose it is not "bg::" here +namespace bgeo = boost::geometry; + +struct ut_settings +{ + explicit ut_settings() + {} + + inline ut_settings& ignore_validity() + { + test_validity = false; + return *this; + } + + inline ut_settings& ignore_validity_diff() + { + test_validity_diff1 = false; + test_validity_diff2 = false; + test_validity_diff_sym = false; + return *this; + } + + inline ut_settings& ignore_validity_diff1() + { + test_validity_diff1 = false; + return *this; + } + + inline ut_settings& ignore_validity_diff2() + { + test_validity_diff2 = false; + return *this; + } + + inline ut_settings& ignore_validity_diff_sym() + { + test_validity_diff_sym = false; + return *this; + } + + inline ut_settings& set_epsilon(long double epsilon) + { + epsilon = epsilon; + return *this; + } + + bool test_validity{true}; + bool test_validity_diff1{true}; + bool test_validity_diff2{true}; + bool test_validity_diff_sym{true}; + + long double epsilon{1.0e-6}; +}; +template +void test_detail(std::string const& name, std::string const& wkt1, std::string const& wkt2, + ut_settings const& settings) +{ + Geometry1 geometry1; + Geometry2 geometry2; + bgeo::read_wkt(wkt1, geometry1); + bgeo::read_wkt(wkt2, geometry2); + bgeo::correct(geometry1); + bgeo::correct(geometry2); + + Multi result_intersection, result_union; + Multi result_intersection_rev, result_union_rev; + Multi diff1, diff2, sym_diff; + + bgeo::intersection(geometry1, geometry2, result_intersection); + bgeo::intersection(geometry2, geometry1, result_intersection_rev); + bgeo::union_(geometry1, geometry2, result_union); + bgeo::union_(geometry2, geometry1, result_union_rev); + bgeo::difference(geometry1, geometry2, diff1); + bgeo::difference(geometry2, geometry1, diff2); + bgeo::sym_difference(geometry1, geometry2, sym_diff); + + auto const area1 = bgeo::area(geometry1); + auto const area2 = bgeo::area(geometry2); + auto const area_union = bgeo::area(result_union); + auto const area_intersection = bgeo::area(result_intersection); + auto const area_diff1 = bgeo::area(diff1); + auto const area_diff2 = bgeo::area(diff2); + auto const area_sym_diff = bgeo::area(sym_diff); + + auto const balance = (area1 + area2) - area_union - area_intersection; + auto const balance_d1 = (area_union - area2) - area_diff1; + auto const balance_d2 = (area_union - area1) - area_diff2; + auto const balance_sym = (area_diff1 + area_diff2) - area_sym_diff; + + auto const eps = settings.epsilon; + + BOOST_CHECK_MESSAGE(area_union + eps >= area1 && area_union + eps >= area2 , + "Case: " << name << " wrong union " << area_union << " vs " << area1 << " and " << area2); + BOOST_CHECK_MESSAGE(area_intersection - eps <= area1 && area_intersection - eps <= area2, + "Case: " << name << " wrong intersection " << area_intersection << " vs " << area1 << " and " << area2); + + BOOST_CHECK_MESSAGE(bgeo::math::abs(balance) < eps, + "Case: " << name << " wrong union or intersection " << balance); + BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_d1) < eps, + "Case: " << name << " wrong difference (a-b) " << balance_d1); + BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_d2) < eps, + "Case: " << name << " wrong difference (b-a) " << balance_d2); + BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_sym) < eps, + "Case: " << name << " wrong symmetric difference " << balance_sym); + + BOOST_CHECK_MESSAGE(bgeo::math::abs(area_union - bgeo::area(result_union_rev)) < eps, + "Case: " << name << " wrong union reversed: " << area_union << " != " << bgeo::area(result_union_rev)); + BOOST_CHECK_MESSAGE(bgeo::math::abs(area_intersection - bgeo::area(result_intersection_rev)) < eps, + "Case: " << name << " wrong intersection reversed: " << area_intersection << " != " << bgeo::area(result_intersection_rev)); + + if (settings.test_validity) + { + BOOST_CHECK_MESSAGE(bgeo::is_valid(geometry1), + "Case: " << name << " geometry1 is not valid"); + BOOST_CHECK_MESSAGE(bgeo::is_valid(geometry2), + "Case: " << name << " geometry2 is not valid"); + BOOST_CHECK_MESSAGE(bgeo::is_valid(result_union), + "Case: " << name << " union is not valid"); + BOOST_CHECK_MESSAGE(bgeo::is_valid(result_intersection), + "Case: " << name << " intersection is not valid"); + if (settings.test_validity_diff1) + { + BOOST_CHECK_MESSAGE(bgeo::is_valid(diff1), + "Case: " << name << " difference (a-b) is not valid"); + } + if (settings.test_validity_diff2) + { + BOOST_CHECK_MESSAGE(bgeo::is_valid(diff2), + "Case: " << name << " difference (b-a) is not valid"); + } + if (settings.test_validity_diff_sym) + { + BOOST_CHECK_MESSAGE(bgeo::is_valid(sym_diff), + "Case: " << name << " symmetric difference is not valid"); + } + } +} + +template +void test(std::string const& name, std::string const& wkt1, std::string const& wkt2, + ut_settings const& settings) +{ + using point_t = bgeo::model::point; + using polygon_t = bgeo::model::polygon; + using mp_t = bgeo::model::multi_polygon; + + constexpr auto multipolygon_tag = "MULTIPOLYGON"; + bool const is_polygon1 = ! boost::icontains(wkt1, multipolygon_tag); + bool const is_polygon2 = ! boost::icontains(wkt2, multipolygon_tag); + + if (is_polygon1 && is_polygon2) + { + test_detail(name, wkt1, wkt2, settings); + } + else if (! is_polygon1 && ! is_polygon2) + { + test_detail(name, wkt1, wkt2, settings); + } + else if (is_polygon1) + { + test_detail(name, wkt1, wkt2, settings); + } + else + { + test_detail(name, wkt1, wkt2, settings); + } +} + +void test_all(std::string const& name, std::string const& wkt1, std::string const& wkt2, + ut_settings const& settings = ut_settings()) +{ + test(name, wkt1, wkt2, settings); +} + +#define TEST_CASE(caseid) (test_all(#caseid, caseid[0], caseid[1])) +#define TEST_CASE_WITH(caseid, index1, index2, settings) (test_all(#caseid "_" #index1 "_" #index2, caseid[index1], caseid[index2], settings)) + +int test_main(int, char* []) +{ + TEST_CASE(issue_1222); + TEST_CASE_WITH(issue_1226, 0, 1, ut_settings().ignore_validity_diff()); + TEST_CASE(issue_1231); + TEST_CASE(issue_1244); + TEST_CASE_WITH(issue_1288, 0, 1, ut_settings().ignore_validity_diff()); + TEST_CASE_WITH(issue_1288, 0, 2, ut_settings()); + TEST_CASE(issue_1293); + TEST_CASE_WITH(issue_1295, 0, 1, ut_settings().ignore_validity_diff()); + TEST_CASE(issue_1299); + + TEST_CASE(issue_1326); + TEST_CASE(issue_1342_a); + TEST_CASE(issue_1342_b); + + TEST_CASE(issue_1345_a); + TEST_CASE(issue_1345_b); + TEST_CASE_WITH(issue_1345_a, 1, 0, ut_settings()); + TEST_CASE_WITH(issue_1345_b, 1, 0, ut_settings()); + + TEST_CASE(issue_1350_comment); + + return 0; +} \ No newline at end of file diff --git a/test/algorithms/set_operations/union/union.cpp b/test/algorithms/set_operations/union/union.cpp index e42d82eb5d..44e8c6a61c 100644 --- a/test/algorithms/set_operations/union/union.cpp +++ b/test/algorithms/set_operations/union/union.cpp @@ -453,39 +453,9 @@ void test_areal() TEST_UNION(issue_1186, 1, 1, -1, 21.6189); TEST_UNION_REV(issue_1186, 1, 1, -1, 21.6189); - TEST_UNION(issue_1226, 1, 0, -1, 0.238405); - TEST_UNION_REV(issue_1226, 1, 0, -1, 0.238405); - TEST_UNION(issue_1229, 1, 0, -1, 384869.166); TEST_UNION_REV(issue_1229, 1, 0, -1, 384869.166); - TEST_UNION(issue_1231, 1, 0, -1, 286.799); - TEST_UNION_REV(issue_1231, 1, 0, -1, 286.799); - - TEST_UNION(issue_1244, 1, 1, -1, 17); - TEST_UNION_REV(issue_1244, 1, 1, -1, 17); - - TEST_UNION(issue_1293, 1, 0, -1, 5.22017); - TEST_UNION_REV(issue_1293, 1, 0, -1, 5.22017); - - TEST_UNION(issue_1295, 1, 0, -1, 17.56273); - TEST_UNION_REV(issue_1295, 1, 0, -1, 17.56273); - - TEST_UNION(issue_1326, 1, 0, -1, 23.201); - TEST_UNION_REV(issue_1326, 1, 0, -1, 23.201); - - TEST_UNION(issue_1342_a, 1, 0, -1, 48.81812749462216); - TEST_UNION_REV(issue_1342_a, 1, 0, -1, 48.81812749462216); - - TEST_UNION(issue_1342_b, 1, 0, -1, 48.81812749462214); - TEST_UNION_REV(issue_1342_b, 1, 0, -1, 48.81812749462214); - - TEST_UNION(issue_1345_a, 1, 0, -1, 0.059935681); - TEST_UNION_REV(issue_1345_a, 1, 0, -1, 0.059935681); - - TEST_UNION(issue_1345_b, 1, 0, -1, 0.034944786); - TEST_UNION_REV(issue_1345_b, 1, 0, -1, 0.034944786); - TEST_UNION(geos_1, 1, 0, -1, expectation_limits(3458.0, 3461.3203125)); TEST_UNION(geos_2, 1, 0, -1, expectation_limits(349.0625, 350.55102539)); TEST_UNION(geos_3, 1, 0, -1, 29391548.4998779); diff --git a/test/algorithms/set_operations/union/union_multi.cpp b/test/algorithms/set_operations/union/union_multi.cpp index 185622a520..fc3307d16a 100644 --- a/test/algorithms/set_operations/union/union_multi.cpp +++ b/test/algorithms/set_operations/union/union_multi.cpp @@ -430,9 +430,6 @@ void test_areal() TEST_UNION(issue_1109, 2, 0, -1, 3946.5); - TEST_UNION(issue_1222, 1, 0, -1, 40.0); - TEST_UNION(issue_1288, 1, 0, -1, 12.0); - // One or two polygons, the ideal case is 1 TEST_UNION(mail_2019_01_21_johan, count_set(1, 2), 0, -1, 0.00058896); @@ -444,8 +441,6 @@ void test_areal() 1, 9, -1, 1250.0); TEST_UNION(mysql_regression_1_65_2017_08_31, 3, 0, -1, 181.966397646608); - - TEST_UNION(issue_1299, 1, 0, -1, 4.267); } // Test cases (generic)